大数据技术之高频面试题|剑谱
  GPYyDLfgzzIb 2023年11月02日 65 0

大数据技术之高频面试题

版本:V8.0.15

目录

第1章 项目涉及技术 12

1.1 Linux&Shell 12

1.1.1 Linux常用高级命令 12

1.1.2 Shell常用工具及写过的脚本 13

1.1.3 Shell中提交了一个脚本,进程号已经不知道了,但是需要kill掉这个进程,怎么操作? 13

1.1.4 Shell中单引号和双引号区别 13

1.2 Hadoop 14

1.2.1 Hadoop常用端口号 14

1.2.2 Hadoop配置文件以及简单的Hadoop集群搭建 14

1.2.3 HDFS读流程和写流程 15

1.2.4 HDFS小文件处理 15

1.2.5 HDFS的NameNode内存 16

1.2.6 NameNode心跳并发配置 16

1.2.7 纠删码原理 16

1.2.8 异构存储(冷热数据分离) 17

1.2.9 Shuffle及优化 17

1.2.10 Yarn工作机制 20

1.2.11 Yarn调度器 20

1.2.12 项目经验之基准测试 21

1.2.13 Hadoop宕机 22

1.2.14 Hadoop解决数据倾斜方法 22

1.3 Zookeeper 22

1.3.1 常用命令 22

1.3.2 Paxos算法(扩展) 23

1.3.3 讲一讲什么是CAP法则?Zookeeper符合了这个法则的哪两个?(扩展) 23

1.3.4 选举机制 23

1.3.5 Follower和Leader状态同步 25

1.4 Flume 25

1.4.1 Flume组成,Put事务,Take事务 25

1.4.2 Flume拦截器 27

1.4.3 Flume Channel选择器 27

1.4.4 Flume监控器 27

1.4.5 Flume采集数据会丢失吗?(防止数据丢失的机制) 28

1.5 Kafka 28

1.5.1 Kafka架构 28

1.5.2 Kafka的机器数量 29

1.5.3 副本数设定 29

1.5.4 Kafka压测 29

1.5.5 Kafka日志保存时间 29

1.5.6 Kafka中数据量计算 29

1.5.7 Kafka的硬盘大小 30

1.5.8 Kafka监控 30

1.5.9 Kakfa分区数 30

1.5.10 多少个Topic 30

1.5.11 Kafka的ISR副本同步队列 30

1.5.12 Kafka分区分配策略 31

1.5.13 Kafka挂掉 31

1.5.14 Kafka丢不丢数据 31

1.5.15 Kafka数据重复 32

1.5.16 Kafka消息数据积压,Kafka消费能力不足怎么处理? 33

1.5.17 Kafka参数优化 33

1.5.18 Kafka高效读写数据 33

1.5.19 Kafka单条日志传输大小 34

1.5.20 Kafka过期数据清理 34

1.5.21 Kafka可以按照时间消费数据 34

1.5.22 Kafka消费者角度考虑是拉取数据还是推送数据 34

1.5.23 Kafka中的数据是有序的吗 34

1.5.24 Kafka的LeaderEpoch哪个版本引入的? 35

1.5.25 Kafka生产者调优配置 37

1.6 Hive 39

1.6.1 Hive的架构及HQL转换为MR流程 39

1.6.2 Hive和数据库比较 40

1.6.3 内部表和外部表 41

1.6.4 4个By区别 41

1.6.5 系统函数 41

1.6.6 自定义UDF、UDTF函数 42

1.6.7 窗口函数 42

1.6.8 Hive优化 43

1.6.9 Hive解决数据倾斜方法 44

1.6.10 Hive里边字段的分隔符用的什么?为什么用\t?有遇到过字段里边有\t的情况吗,怎么处理的? 46

1.6.11 Tez引擎优点? 48

1.6.12 MySQL元数据备份 48

1.6.13 Union与Union all区别 49

1.7 Sqoop 49

1.7.1 Sqoop参数 49

1.7.2 Sqoop导入导出Null存储一致性问题 49

1.7.3 Sqoop数据导出一致性问题 50

1.7.4 Sqoop底层运行的任务是什么 50

1.7.5 Sqoop一天导入多少数据 50

1.7.6 Sqoop数据导出的时候一次执行多长时间 50

1.7.7 Sqoop在导入数据的时候数据倾斜 50

1.7.8 Sqoop数据导出Parquet(项目中遇到的问题) 51

1.8 Azkaban 51

1.8.1 每天集群运行多少指标? 51

1.8.2 任务挂了怎么办? 51

1.9 HBase 51

1.9.1 HBase存储结构 51

1.9.2 RowKey设计原则 52

1.9.3 RowKey如何设计 52

1.9.4 Phoenix二级索引(讲原理) 53

1.10 Scala 54

1.10.1 开发环境 54

1.10.2 变量和数据类型 54

1.10.3 流程控制 54

1.10.4 函数式编程 54

1.10.5 面向对象 54

1.10.6 集合 54

1.10.7 模式匹配 54

1.10.8 异常 54

1.10.9 隐式转换 54

1.10.10 泛型 54

1.11 Spark Core & SQL 55

1.11.1 Spark解决什么问题 55

1.11.2 Spark为什么会有自己的资源调度器 55

1.11.3 Spark运行模式 55

1.11.4 Spark常用端口号 55

1.11.5 简述Spark的架构与作业提交流程(画图讲解,注明各个部分的作用)(重点) 56

1.11.6 Spark任务使用什么进行提交,JavaEE界面还是脚本 56

1.11.7 Spark提交作业参数(重点) 56

1.11.8 RDD五大属性 57

1.11.9 Spark的transformation算子(不少于8个)(重点) 57

1.11.10 Spark的action算子(不少于6个)(重点) 58

1.11.11 map和mapPartitions区别 59

1.11.12 Repartition和Coalesce区别 59

1.11.13 reduceByKey与groupByKey的区别 59

1.11.14 reduceByKey、foldByKey、aggregateByKey、combineByKey区别 59

1.11.15 Kryo序列化 60

1.11.16 Spark中的血缘(笔试重点) 60

1.11.17 Spark任务的划分 60

1.11.18 cache缓存级别 60

1.11.19 释放缓存和缓存 60

1.11.20 缓存和检查点区别 61

1.11.21 Spark分区 61

1.11.22 Spark累加器 61

1.11.23 Spark广播变量 62

1.11.24 SparkSQL中RDD、DataFrame、DataSet三者的转换 (笔试重点) 62

1.11.25 请列举会引起Shuffle过程的Spark算子,并简述功能。 62

1.11.26 当Spark涉及到数据库的操作时,如何减少Spark运行中的数据库连接数? 63

1.11.27 如何使用Spark实现TopN的获取(描述思路或使用伪代码)(重点) 63

1.11.28 京东:调优之前与调优之后性能的详细对比(例如调整map个数,map个数之前多少、之后多少,有什么提升) 63

1.11.29 Spark Shuffle默认并行度 63

1.11.30 控制Spark reduce缓存 调优shuffle 63

1.11.31 Spark内核源码(重点) 64

1.12 Spark Streaming 67

1.12.1 Spark Streaming第一次运行不丢失数据 67

1.12.2 Spark Streaming精准一次消费 67

1.12.3 Spark Streaming控制每秒消费数据的速度 68

1.12.4 Spark Streaming背压机制 68

1.12.5 Spark Streaming一个stage耗时 68

1.12.6 Spark Streaming优雅关闭 68

1.12.7 Spark Streaming默认分区个数 68

1.12.8 SparkStreaming有哪几种方式消费Kafka中的数据,它们之间的区别是什么? 68

1.12.9 简述SparkStreaming窗口函数的原理(重点) 70

1.13 数据倾斜 70

1.13.1 数据倾斜表现 70

1.13.2 数据倾斜产生原因 71

1.13.3 解决数据倾斜思路 72

1.13.4 定位导致数据倾斜代码 73

1.13.5 查看导致数据倾斜的key分布情况 75

1.13.6 Spark 数据倾斜的解决方案 75

1.13.7 Spark数据倾斜处理小结 93

1.14 Flink 93

1.14.1 简单介绍一下 Flink 93

1.14.2 Flink跟Spark Streaming的区别 94

1.14.3 Flink集群有哪些角色?各自有什么作用? 95

1.14.4 Flink的编程模型是什么? 95

1.14.5 公司怎么提交的实时任务,有多少Job Manager? 有多少TaskManager? 96

1.14.6 Flink的并行度了解吗?Flink的并行度设置是怎样的? 96

1.14.7 Flink的keyby怎么实现的分区?分区、分组的区别是什么? 96

1.14.8 Flink的interval join的实现原理?join不上的怎么办? 97

1.14.9 介绍一下Flink的状态编程、状态机制? 97

1.14.10 Flink的三种时间语义 97

1.14.11 Flink 中的Watermark机制 97

1.14.12 Watermark是数据吗?怎么生成的?怎么传递的? 98

1.14.13 Watermark的生成方式? 98

1.14.14 说说Flink中的窗口(分类、生命周期、触发、划分) 98

1.14.15 Exactly-Once的保证 99

1.14.16 Flink分布式快照的原理是什么 99

1.14.17 Checkpoint的参数怎么设置的? 100

1.14.18 介绍一下Flink的CEP机制 100

1.14.19 Flink CEP 编程中当状态没有到达的时候会将数据保存在哪里? 100

1.14.20 Flink SQL的工作机制? 101

1.14.21 FlinkSQL怎么对SQL语句进行优化的? 101

1.14.22 Flink提交流程、组件通讯、调度机制、任务执行、内存模型(重点) 102

1.14.23 Flink优化、背压、数据倾斜(重点) 104

1.14.24 Flink常见的维表Join方案 104

第2章 项目架构 105

2.1 提高自信 105

2.2 数仓概念 106

2.3 系统数据流程设计 106

2.4 框架版本选型 106

2.5 服务器选型 108

2.6 集群规模 109

2.7 人员配置参考 110

2.7.1 整体架构 110

2.7.2 你们部门的职级等级,晋升规则 110

2.7.3 人员配置参考 110

第3章 数仓分层 111

3.1 数据仓库建模(绝对重点) 111

3.1.1 建模工具是什么? 111

3.1.2 ODS层 111

3.1.3 DWD层 112

3.1.4 DWS层 113

3.1.5 DWT层 114

3.1.6 ADS层 115

3.2 ODS层做了哪些事? 115

3.3 DWD层做了哪些事? 115

3.3.1 数据清洗 115

3.3.2 清洗的手段 115

3.3.3 清洗掉多少数据算合理 116

3.3.4 脱敏 116

3.3.5 维度退化 116

3.3.6 压缩LZO 116

3.3.7 列式存储parquet 116

3.4 DWS层做了哪些事? 116

3.4.1 DWS层有3-5张宽表(处理100-200个指标 70%以上的需求) 116

3.4.2 哪个宽表最宽?大概有多少个字段? 116

3.4.3 具体用户行为宽表字段名称 116

3.4.4 那张表数据量最多 117

3.5 ADS层分析过哪些指标 117

3.5.1 分析过的指标(一分钟至少说出30个指标) 117

3.5.2 留转G复活指标 118

3.5.3 哪个商品卖的好? 119

3.6 ADS层手写指标 119

3.6.1 如何分析用户活跃? 119

3.6.2 如何分析用户新增?vivo 119

3.6.3 如何分析用户1天留存? 119

3.6.4 如何分析沉默用户? 119

3.6.5 如何分析本周回流用户? 119

3.6.6 如何分析流失用户? 120

3.6.7 如何分析最近连续3周活跃用户数? 120

3.6.8 如何分析最近七天内连续三天活跃用户数? 120

3.7 分析过最难的指标 120

3.7.1 最近连续3周活跃用户 120

3.7.2 最近7天连续3天活跃用户数 121

3.7.3 运费分摊 121

3.7.4 城市备注 121

第4章 生产经验—业务 121

4.1 电商常识 121

4.1.1 SKU和SPU 121

4.1.2 订单表跟订单详情表区别? 121

4.2 埋点行为数据基本格式(基本字段) 122

4.2.1 页面 122

4.2.2 事件 123

4.2.3 曝光 124

4.2.4 启动 124

4.2.5 错误 125

4.2.6 埋点数据日志格式 125

4.3 电商业务流程 127

4.4 维度表和事实表(重点) 128

4.4.1 维度表 128

4.4.2 事实表 128

4.5 同步策略(重点) 129

4.6 关系型数据库范式理论(ER建模) 129

4.7 数据模型 129

4.8 拉链表(重点) 129

4.9 即席查询数据仓库 130

4.10 数据仓库每天跑多少张表,大概什么时候运行,运行多久? 130

4.11 活动的话,数据量会增加多少?怎么解决? 131

4.12 并发峰值多少?大概哪个时间点? 131

4.13 数仓中使用的哪种文件存储格式 131

4.14 哪张表最费时间,有没有优化 131

4.14 哪张表数据量最大,是多少 131

4.15 用什么工具做权限管理 131

4.16 数仓当中数据多久删除一次 131

第5章 生产经验--测试上线相关 132

5.1 测试相关 132

5.1.1 公司有多少台测试服务器? 132

5.1.2 测试环境什么样? 132

5.1.3 测试数据哪来的? 132

5.1.4 如何保证写的sql正确性(重点) 132

5.1.5 测试之后如何上线? 132

5.2 项目实际工作流程 132

5.3 项目中实现一个需求大概多长时间 134

5.4 项目在3年内迭代次数,每一个项目具体是如何迭代的。公司版本迭代多久一次,迭代到哪个版本 134

5.5 项目开发中每天做什么事 134

5.6 实时项目数据计算 135

5.6.1 跑实时任务,怎么分配内存和CPU资源 135

5.6.2 跑实时任务,每天数据量多少? 135

第6章 生产经验—技术 135

6.1 可视化报表工具 135

6.2 集群监控工具 135

6.3 项目中遇到的问题怎么解决的(重点*****) 135

6.4 Linux+Shell+Hadoop+ZK+Flume+kafka+Hive+Sqoop+Azkaban那些事 137

第7章 生产经验—热点问题 137

7.1 元数据管理(Atlas血缘系统) 137

7.2 数据质量监控(Griffin) 137

7.2.1 监控原则 137

7.2.2 数据质量实现 139

7.3 权限管理(Ranger) 139

7.4 数据治理 139

7.5 数据中台 139

7.5.1 什么是中台? 140

7.5.2 传统项目痛点 140

7.5.3 各家中台 141

7.5.4 中台具体划分 142

7.5.5 中台使用场景 143

7.6 数据湖 143

7.7 埋点 144

7.8 电商运营经验 145

7.8.1 电商8类基本指标 145

7.8.2 直播指标 148

第8章 实时数仓项目 152

8.1 数据采集到ods层做了哪些事 152

8.1.1 前端埋点的行为数据为什么又采集一份? 152

8.1.2为什么选择kafka? 153

8.1.3为什么用maxwell?历史数据同步怎么保证一致性? 153

8.1.4 kafka保存多久?如果需要以前的数据怎么办? 153

8.2 ods层 153

8.3 dwd+dim层 153

8.3.1 存储位置,为什么维度表存Hbase? 153

8.3.2 埋点行为数据分流 153

8.3.3 业务数据动态分流 154

8.4 dwm层 154

8.4.1 为什么要加一个dwm层? 154

8.4.2 事实表与事实表join 154

8.4.3 事实表与维度表join 155

8.4.4怎么保证缓存一致性 156

8.5 dws层 156

8.5.1 为什么选择ClickHouse 156

8.5.2 轻度聚合 156

8.6 ads层 156

8.6.1 实现方案 156

8.6.2 怎么保证ClickHouse的一致性? 157

8.7 监控 157

第8章 手写代码 157

8.1 基本算法 157

8.1.1 冒泡排序 157

8.1.2 二分查找 158

8.1.3 快排 160

8.1.4 归并 161

8.1.5 二叉树之Scala实现 162

8.2 开发代码 167

8.2.1 手写Spark-WordCount 167

8.3 手写HQL 167

8.3.1 手写HQL 第1题 167

8.3.2 手写HQL 第2题 168

8.3.3 手写HQL 第3题 170

8.3.4 手写HQL 第4题 171

8.3.5 手写HQL 第5题 172

8.3.6 手写HQL 第6题 176

8.3.7 手写HQL 第7题 177

8.3.8 手写SQL 第8题 179

8.3.9 手写HQL 第9题 179

8.3.10 手写HQL 第10题 181

8.3.11 手写HQL 第11题 184

连续问题 189

分组问题 192

间隔连续问题 194

打折日期交叉问题 199

同时在线问题 202

第9章 JavaSE 203

9.1 HashMap底层源码,数据结构 203

9.2 Java自带哪几种线程池? 206

9.3 HashMap和HashTable区别 207

9.4 TreeSet和HashSet区别 208

9.5 String buffer和String build区别 208

9.6 Final、Finally、Finalize 208

9.7 ==和Equals区别 209

第10章 Redis 209

10.1 缓存穿透、缓存雪崩、缓存击穿 209

10.2 哨兵模式 210

10.3 数据类型 210

10.4 持久化 210

11.5 悲观锁 211

11.6 乐观锁 211

第11章 MySql 211

11.1 MyISAM与InnoDB的区别 211

11.2 索引优化 211

11.3 b-tree和b+tree的区别 212

11.4 redis是单线程的,为什么那么快 212

11.5 MySQL的事务 212

第12章 JVM 214

12.1 JVM内存分哪几个区,每个区的作用是什么? 214

12.2 Java类加载过程? 215

12.3 java中垃圾收集的方法有哪些? 216

12.4 如何判断一个对象是否存活?(或者GC对象的判定方法) 216

12.5 什么是类加载器,类加载器有哪些? 217

12.6 简述Java内存分配与回收策略以及Minor GC和Major GC(full GC) 217

第13章 JUC 218

13.1 Synchronized与Lock的区别 218

13.2 Runnable和Callable的区别 218

13.3 什么是分布式锁 218

13.4 什么是分布式事务 218

第14章 面试说明 218

14.1 面试过程最关键的是什么? 218

14.2 面试时该怎么说? 218

14.3 面试技巧 219

14.3.1 六个常见问题 219

14.3.2 两个注意事项 220

14.3.3 自我介绍(控制在4分半以内,不超过5分钟) 220

第15章 LeetCode题目精选 220

15.1 两数之和 220

15.1.1 问题描述 220

15.1.2 参考答案 221

15.2 爬楼梯 221

15.2.1 问题描述 221

15.2.2 参考答案 222

15.3 翻转二叉树 222

15.3.1 问题描述 223

15.3.2 参考答案 223

15.4 反转链表 224

15.4.1 问题描述 224

15.4.2 参考答案 224

15.5 LRU缓存机制 225

15.5.1 问题描述 225

15.5.2 参考答案 225

15.6 最长回文子串 227

15.6.1 问题描述 227

15.6.2 参考答案 227

15.7 有效的括号 228

15.7.1 问题描述 228

15.7.2 参考答案 229

15.8 数组中的第K个最大元素 231

15.8.1 问题描述 231

15.8.2 参考答案 232

15.9 实现 Trie (前缀树) 234

15.9.1 问题描述 234

15.9.2 参考答案 234

15.10 编辑距离 236

15.10.1 问题描述 236

15.10.2 参考答案 237

















1 项目涉及技术

1.1 Linux&Shell

1.1.1 Linux常用高级命令


序号

命令

命令解释

1

top

查看内存

2

df -h

查看磁盘存储情况

3

iotop

查看磁盘IO读写(yum install iotop安装)

4

iotop -o

直接查看比较高的磁盘读写程序

5

netstat -tunlp | grep 端口号

查看端口占用情况

6

uptime

查看报告系统运行时长及平均负载

7

ps -ef

查看进程

1.1.2 Shell常用工具及写过的脚本

1)awk、sed、cut、sort

2)用Shell写过哪些脚本

(1)集群启动,分发脚本

#!/bin/bash

case $1 in 
"start")
	for i in hadoop102 hadoop103 hadoop104
	do
		ssh $i "绝对路径"
	done 
;;
"stop")

;;
esac

(2)数仓与MySQL的导入导出

MySQL HDFS hive

sqoop (4个map)

除了sqoop之外还可以用:DataX、hadoop、java

驱动

主机名 端口号

用户名

密码


路径

删除

同步策略: 全量 特殊 新增 新增和变化

query "select id , name from user where 创建时间=今天 or 操作时间= 今天"

压缩

列式存储

(3)数仓层级内部的导入:ods->dwd->dws->dwt->ads

①#!/bin/bash

②定义变量 APP=gmall

③获取时间

传入 按照传入时间

不传 T+1

④sql="

先按照当前天 写sql => 遇到时间 $do_date 遇到表 {$APP}.

自定义函数 UDF UDTF {$APP}.

"

⑤执行sql

1.1.3 Shell中提交了一个脚本,进程号已经不知道了,但是需要kill掉这个进程,怎么操作?

ssh $i "ps -ef | grep file-flume-kafka | grep -v grep |awk '{print \$2}' | xargs kill"

1.1.4 Shell中单引号和双引号区别

1)在/home/atguigu/bin创建一个test.sh文件

[atguigu@hadoop102 bin]$ vim test.sh

在文件中添加如下内容

#!/bin/bash

do_date=$1


echo '$do_date'

echo "$do_date"

echo "'$do_date'"

echo '"$do_date"'

echo `date`

2)查看执行结果

[atguigu@hadoop102 bin]$ test.sh 2019-02-10

$do_date

2019-02-10

'2019-02-10'

"$do_date"

2019年 05月 02日 星期四 21:02:08 CST

3)总结:

(1)单引号不取变量值

(2)双引号取变量值

(3)反引号`,执行引号中命令

(4)双引号内部嵌套单引号,取出变量值

(5)单引号内部嵌套双引号,不取出变量值

1.2 Hadoop

1.2.1 Hadoop常用端口号


hadoop2.x

Hadoop3.x

访问HDFS端口

50070

9870

访问MR执行情况端口

8088

8088

历史服务器

19888

19888

客户端访问集群端口

9000

8020

1.2.2 Hadoop配置文件

配置文件:

hadoop2.x core-site.xml、hdfs-site.xml、mapred-site.xml、yarn-site.xml slaves

hadoop3.x core-site.xml、hdfs-site.xml、mapred-site.xml、yarn-site.xml workers

1.2.3 HDFS读流程和写流程



1.2.4 HDFS小文件处理

1)会有什么影响

(1)存储层面:

1个文件块,占用namenode多大内存150字节

128G能存储多少文件块? 128 g* 1024m*1024kb*1024byte/150字节 = 9.1亿文件块

(2)计算层面:

每个小文件都会起到一个MapTask,1个MapTask默认内存1G。浪费资源。

2)怎么解决

(1)采用har归档方式,将小文件归档

(2)采用CombineTextInputFormat

(3)有小文件场景开启JVM重用;如果没有小文件,不要开启JVM重用,因为会一直占用使用到的task卡槽,直到任务完成才释放。

JVM重用可以使得JVM实例在同一个job中重新使用N次,N的值可以在Hadoop的mapred-site.xml文件中进行配置。通常在10-20之间

<property>

<name>mapreduce.job.jvm.numtasks</name>

<value>10</value>

<description>How many tasks to run per jvm,if set to -1 ,there is no limit</description>

</property>

1.2.5 HDFS的NameNode内存

1)Hadoop2.x系列,配置NameNode默认2000m

2)Hadoop3.x系列,配置NameNode内存是动态分配的

NameNode内存最小值1G,每增加100万个block,增加1G内存。

1.2.6 NameNode心跳并发配置

大数据技术之高频面试题|剑谱_数据

企业经验:dfs.namenode.handler.count=,比如集群规模(DataNode台数)为3台时,此参数设置为21。可通过简单的python代码计算该值,代码如下。

1.2.7 纠删码原理

CPU资源换取存储空间。

大数据技术之高频面试题|剑谱_Shell_02

1.2.8 异构存储(冷热数据分离)

期望经常使用的数据存储在固态硬盘或者内存镜像硬盘;不经常使用的历史数据存储在老旧的破旧硬盘。

大数据技术之高频面试题|剑谱_数据倾斜_03


1.2.9 Shuffle及优化

1、Shuffle过程



1.2.10 Yarn工作机制



1.2.11 Yarn调度器

1)Hadoop调度器重要分为三类:

FIFO 、Capacity Scheduler(容量调度器)和Fair Sceduler(公平调度器)。

Apache默认的资源调度器是容量调度器;

CDH默认的资源调度器是公平调度器。

2)区别:

FIFO调度器:支持单队列 、先进先出 生产环境不会用。

容量调度器:支持多队列。队列资源分配,优先选择资源占用率最低的队列分配资源;作业资源分配,按照作业的优先级和提交时间顺序分配资源;容器资源分配,本地原则(同一节点/同一机架/不同节点不同机架)

公平调度器:支持多队列,保证每个任务公平享有队列资源。资源不够时可以按照缺额分配。

3)在生产环境下怎么选择?

大厂:如果对并发度要求比较高,选择公平,要求服务器性能必须OK;

中小公司,集群服务器资源不太充裕选择容量。

4)在生产环境怎么创建队列?

(1)调度器默认就1个default队列,不能满足生产要求。

(2)按照框架:hive /spark/ flink 每个框架的任务放入指定的队列(企业用的不是特别多)

(3)按照部门:业务部门1、业务部门2

(4)按照业务模块:登录注册、购物车、下单

5)创建多队列的好处?

(1)因为担心员工不小心,写递归死循环代码,把所有资源全部耗尽。

(2)实现任务的降级使用,特殊时期保证重要的任务队列资源充足。

业务部门1(重要)=》业务部门2(比较重要)=》下单(一般)=》购物车(一般)=》登录注册(次要)

1.2.12 项目经验之基准测试

硬盘的读写速度网络带宽影响集群吞吐量的两个核心因素。

1.2.13 Hadoop宕机

1)如果MR造成系统宕机。此时要控制Yarn同时运行的任务数,和每个任务申请的最大内存。调整参数:yarn.scheduler.maximum-allocation-mb(单个任务可申请的最多物理内存量,默认是8192MB)

2)如果写入文件过快造成NameNode宕机。那么调高Kafka的存储大小,控制从Kafka到HDFS的写入速度。例如,可以调整Flume每批次拉取数据量的大小参数batchsize。

1.2.14 Hadoop解决数据倾斜方法

1)提前在map进行combine,减少传输的数据量

在Mapper加上combiner相当于提前进行reduce,即把一个Mapper中的相同key进行了聚合,减少shuffle过程中传输的数据量,以及Reducer端的计算量。

如果导致数据倾斜的key大量分布在不同的mapper的时候,这种方法就不是很有效了。

2)导致数据倾斜的key 大量分布在不同的mapper

(1)局部聚合加全局聚合。

第一次在map阶段对那些导致了数据倾斜的key 加上1到n的随机前缀,这样本来相同的key 也会被分到多个Reducer中进行局部聚合,数量就会大大降低。

第二次mapreduce,去掉key的随机前缀,进行全局聚合。

思想:二次mr,第一次将key随机散列到不同reducer进行处理达到负载均衡目的。第二次再根据去掉key的随机前缀,按原key进行reduce处理。

这个方法进行两次mapreduce,性能稍差。

(2)增加Reducer,提升并行度JobConf.setNumReduceTasks(int)

(3)实现自定义分区

根据数据分布情况,自定义散列函数,将key均匀分配到不同Reducer

1.3 Zookeeper

1.3.1 常用命令

ls、get、create、delete

1.3.2 Paxos算法和ZAB协议(扩展)

注意:暂时先不用看。如果后期准备面今日头条,需要认真准备,其他公司几乎都不问。

关注尚硅谷教育公众号回复大数据。 找zookeeper视频。

1.3.3 讲一讲什么是CAP法则?Zookeeper符合了这个法则的哪两个?(扩展)


1.3.4 选举机制

半数机制:2n+1,安装奇数台

10台服务器:3台

20台服务器:5台

100台服务器:11台

台数多,好处:提高可靠性;坏处:影响通信延时



1.3.5 Follower和Leader状态同步


1.4 Flume

1.4.1 Flume组成,Put事务,Take事务

1)taildir source

(1)断点续传、多目录

(2)哪个Flume版本产生的?Apache1.7、CDH1.6

(3)没有断点续传功能时怎么做的? 自定义

(4)taildir挂了怎么办?

不会丢数:断点续传

重复数据:

(5)怎么处理重复数据?

不处理:生产环境通常不处理,出现重复的概率比较低。处理会影响传输效率。

处理

自身:在taildirsource里面增加自定义事务,影响效率

找兄弟:下一级处理(hive dwd sparkstreaming flink布隆)、去重手段(groupby、开窗取窗口第一条、redis)

(6)taildir source 是否支持递归遍历文件夹读取文件?

不支持。 自定义 递归遍历文件夹 + 读取文件

2)file channel /memory channel/kafka channel

(1)File Channel

数据存储于磁盘,优势:可靠性高;劣势:传输速度低

默认容量:100万event

注意:FileChannel可以通过配置dataDirs指向多个路径,每个路径对应不同的硬盘,增大Flume吞吐量。

(2)Memory Channel

数据存储于内存,优势:传输速度快;劣势:可靠性差

默认容量:100个event

(3)Kafka Channel

数据存储于Kafka,基于磁盘;

优势:可靠性高;

传输速度快 Kafka Channel 大于Memory Channel + Kafka Sink 原因省去了Sink阶段

(4)Kafka Channel哪个版本产生的?

Flume1.6 版本产生=》并没有火;因为有bug

event(header body ) ture 和false 控制是否包含header信息,很遗憾,都不起作用。增加了额外清洗的工作量。

Flume1.7解决了这个问题,开始火了。

(5)生产环境如何选择

如果下一级是Kafka,优先选择Kafka Channel

如果是金融、对钱要求准确的公司,选择File Channel

如果就是普通的日志,通常可以选择Memory Channel

每天丢几百万数据 pb级 亿万富翁,掉1块钱会捡?

3)HDFS sink

(1)时间(半个小时) or 大小128m、event个数(0禁止)

具体参数:hdfs.rollInterval=1800,hdfs.rollSize=134217728,hdfs.rollCount =0

4)事务

Source到Channel是Put事务

Channel到Sink是Take事务

1.4.2 Flume拦截器

1)拦截器注意事项

(1)ETL拦截器:主要是用来判断json是否完整。没有做复杂的清洗操作主要是防止过多的降低传输速率。

(2)时间戳拦截器:主要是解决零点漂移问题

2)自定义拦截器步骤

(1)实现 Interceptor

(2)重写四个方法

  • initialize 初始化
  • public Event intercept(Event event) 处理单个Event
  • public List<Event> intercept(List<Event> events) 处理多个Event,在这个方法中调用Event intercept(Event event)
  • close方法

(3)静态内部类,实现Interceptor.Builder

3)拦截器可以不用吗?

ETL拦截器可以不用;需要在下一级Hive的dwd层和SparkSteaming里面处理

时间戳拦截器建议使用。 如果不用需要采用延迟15-20分钟处理数据的方式,比较麻烦。

1.4.3 Flume Channel选择器

Replicating:默认选择器。功能:将数据发往下一级所有通道

Multiplexing:选择性发往指定通道。

1.4.4 Flume监控器

1)采用Ganglia监控器,监控到Flume尝试提交的次数远远大于最终成功的次数,说明Flume运行比较差。主要是内存不够导致的。

2)解决办法?

(1)自身:flume默认内存2000m。考虑增加flume内存,在flume-env.sh配置文件中修改flume内存为 4-6g

-Xmx与-Xms最好设置一致,减少内存抖动带来的性能影响,如果设置不一致容易导致频繁fullgc。

(2)找朋友:增加服务器台数

搞活动 618 =》增加服务器=》用完在退出

日志服务器配置:8-16g内存、磁盘8T

1.4.5 Flume采集数据会丢失吗?(防止数据丢失的机制)

如果是FileChannel不会,Channel存储可以存储在File中,数据传输自身有事务。

如果是MemoryChannel有可能丢。

1.5 Kafka

1.5.1 Kafka架构

生产者、Broker、消费者、Zookeeper;

注意:Zookeeper中保存Broker id和消费者offsets等信息,但是没有生产者信息。




1.5.2 Kafka的机器数量

Kafka机器数量 = 2 *(峰值生产速度 * 副本数 / 100)+ 1

1.5.3 副本数设定

一般我们设置成2个或3个,很多企业设置为2个。

副本的优势:提高可靠性;副本劣势:增加了网络IO传输

1.5.4 Kafka压测

Kafka官方自带压力测试脚本(kafka-consumer-perf-test.sh、kafka-producer-perf-test.sh)。Kafka压测时,可以查看到哪个地方出现了瓶颈(CPU,内存,网络IO)。一般都是网络IO达到瓶颈。

1.5.5 Kafka日志保存时间

默认保存7天;生产环境建议3天

1.5.6 Kafka中数据量计算

每天总数据量100g,每天产生1亿条日志,10000万/24/60/60=1150条/每秒钟

平均每秒钟:1150条

低谷每秒钟:50条

高峰每秒钟:1150条 *(2-20倍)= 2300条 - 23000条

每条日志大小:0.5k - 2k(取1k)

每秒多少数据量:2.0M - 20MB

1.5.7 Kafka的硬盘大小

每天的数据量100g * 2个副本 * 3天 / 70%

1.5.8 Kafka监控

公司自己开发的监控器;

开源的监控器:KafkaManager、KafkaMonitor、KafkaEagle

1.5.9 Kakfa分区数

1)创建一个只有1个分区的topic

2)测试这个topic的producer吞吐量和consumer吞吐量。

3)假设他们的值分别是Tp和Tc,单位可以是MB/s。

4)然后假设总的目标吞吐量是Tt,那么分区数=Tt / min(Tp,Tc)

例如:producer吞吐量 = 20m/s;consumer吞吐量 = 50m/s,期望吞吐量100m/s;

分区数 = 100 / 20 = 5分区

分区数一般设置为:3-10个

1.5.10 多少个Topic

通常情况:多少个日志类型就多少个Topic。也有对日志类型进行合并的。

1.5.11 Kafka的ISR副本同步队列

ISR(In-Sync Replicas),副本同步队列。ISR中包括Leader和Follower。如果Leader进程挂掉,会在ISR队列中选择一个服务作为新的Leader。有replica.lag.max.messages(延迟条数)和replica.lag.time.max.ms(延迟时间)两个参数决定一台服务是否可以加入ISR副本队列,在0.10版本移除了replica.lag.max.messages参数,防止服务频繁的进去队列。

任意一个维度超过阈值都会把Follower剔除出ISR,存入OSR(Outof-Sync Replicas)列表,新加入的Follower也会先存放在OSR中。

1.5.12 Kafka分区分配策略

在 Kafka内部存在两种默认的分区分配策略:Range和 RoundRobin。

Range是默认策略。Range是对每个Topic而言的(即一个Topic一个Topic分),首先对同一个Topic里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序。然后用Partitions分区的个数除以消费者线程的总数来决定每个消费者线程消费几个分区。如果除不尽,那么前面几个消费者线程将会多消费一个分区。

例如:我们有10个分区,两个消费者(C1,C2),3个消费者线程,10 / 3 = 3而且除不尽。

C1-0 将消费 0, 1, 2, 3 分区

C2-0 将消费 4, 5, 6 分区

C2-1 将消费 7, 8, 9 分区

第一步:将所有主题分区组成TopicAndPartition列表,然后对TopicAndPartition列表按照hashCode进行排序,最后按照轮询的方式发给每一个消费线程。

1.5.13 Kafka挂掉

1)Flume记录

2)日志有记录

3)短期没事

1.5.14 Kafka丢不丢数据

1)producer角度

  • Ack = 0,相当于异步发送,消息发送完毕即offset增加,继续生产。
  • Ack = 1,leader收到leader replica 对一个消息的接受ack才增加offset,然后继续生产。
  • Ack = -1,leader收到所有replica 对一个消息的接受ack才增加offset,然后继续生产。

ack在生产者指定,不同生产者可以不同。

ack设为-1,需要ISR里的所有follower应答,想要真正不丢数据,需要配合参数:

  • min.insync.replicas: ack为-1时生效,ISR里应答的最小follower数量。

默认为1(leader本身也算一个!),所以当ISR里除了leader本身,没有其他的follower,即使ack设为-1,相当于1的效果,不能保证不丢数据。

需要将min.insync.replicas设置大于等于2,才能保证有其他副本同步到数据。

  • retries = Integer.MAX_VALUE,无限重试。如果上述两个条件不满足,写入一直失败,就会无限次重试,保证数据必须成功的发送给两个副本,如果做不到,就不停的重试,除非是面向金融级的场景,面向企业大客户,或者是广告计费,跟钱的计算相关的场景下,才会通过严格配置保证数据绝对不丢失

kafka-topics.sh --bootstrap-server hadoop1:9092 --create --topic testisr2 --replication-factor 3 --partitions 4 --config min.insync.replicas=2

完全不丢结论:ack=-1 + min.insync.replicas>=2 +无限重试

2)broker角度

副本数大于1

min.insync.replicas大于1

3)consumer角度

手动提交offset,flink结合checkpoint

1.5.15 Kafka数据重复

重复指的是发生重试造成的重复

幂等性 + ack-1 + 事务

Kafka数据重复,可以在下一级:SparkStreaming、redis、Flink或者Hive中dwd层去重,去重的手段:分组、按照id开窗只取第一个值;

了解:

Kafka幂等性原理(单分区单会话):producer重试引起的乱序和重复

1、重复问题的解决:

1)Kafka增加了pid和seq。Producer中每个RecordBatch都有一个单调递增的seq; Broker上每个topic的partition也会维护pid-seq的映射,并且每Commit都会更新lastSeq。

2)recordBatch到来时,broker会先检查RecordBatch再保存数据:

如果batch中 baseSeq(第一条消息的seq)比Broker维护的序号(lastSeq)大1,则保存数据,否则不保存。

2、乱序问题的解决

假设我们有5个请求,batch1、batch2、batch3、batch4、batch5;

如果只有batch2 ack failed,3、4、5都保存了,那2将会随下次batch重发而造成重复。

可以设置max.in.flight.requests.per.connection=1(客户端在单个连接上能够发送的未响应请求的个数)来解决乱序,但降低了系统吞吐。

新版本kafka设置enable.idempotence=true后能够动态调整max-in-flight-request。正常情况下max.in.flight.requests.per.connection大于1。当重试请求到来时,batch 会根据 seq重新添加到队列的合适位置,并把max.in.flight.requests.per.connection设为1,这样它前面的 batch序号都比它小,只有前面的都发完了,它才能发。

1.5.16 Kafka消息数据积压,Kafka消费能力不足怎么处理?

1)如果是Kafka消费能力不足,则可以考虑增加Topic的分区数,并且同时提升消费组的消费者数量,消费者数 = 分区数。(两者缺一不可)

2)如果是下游的数据处理不及时:提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间 < 生产速度),使处理的数据小于生产的数据,也会造成数据积压。

1.5.17 Kafka参数优化

1)Broker参数配置(server.properties)

1、日志保留策略配置

# 保留三天,也可以更短 (log.cleaner.delete.retention.ms)

log.retention.hours=72


2、Replica相关配置

default.replication.factor:1 默认副本1个


3、网络通信延时

replica.socket.timeout.ms:30000 #当集群之间网络不稳定时,调大该参数

replica.lag.time.max.ms= 600000# 如果网络不好,或者kafka集群压力较大,会出现副本丢失,然后会频繁复制副本,导致集群压力更大,此时可以调大该参数

2)Producer优化(producer.properties)

compression.type:none gzip snappy lz4

#默认发送不进行压缩,推荐配置一种适合的压缩算法,可以大幅度的减缓网络压力和Broker的存储压力。

3)Kafka内存调整(kafka-server-start.sh

默认内存1个G,生产环境尽量不要超过6个G。

export KAFKA_HEAP_OPTS="-Xms4g -Xmx4g"

1.5.18 Kafka高效读写数据

1)Kafka本身是分布式集群,同时采用分区技术,并发度高。

2)顺序写磁盘

Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到600M/s,而随机写只有100K/s。

3)零复制技术

大数据技术之高频面试题|剑谱_Shell_04

1.5.19 Kafka单条日志传输大小

Kafka对于消息体的大小默认为单条最大值是1M但是在我们应用场景中,常常会出现一条消息大于1M,如果不对Kafka进行配置。则会出现生产者无法将消息推送到Kafka或消费者无法去消费Kafka里面的数据,这时我们就要对Kafka进行以下配置:server.properties

replica.fetch.max.bytes: 1048576 broker可复制的消息的最大字节数, 默认为1M

message.max.bytes: 1000012 kafka 会接收单个消息size的最大限制, 默认为1M左右

注意:message.max.bytes必须小于等于replica.fetch.max.bytes,否则就会导致replica之间数据同步失败。

1.5.20 Kafka过期数据清理

保证数据没有被引用(没人消费他)

日志清理的策略只有delete和compact两种

log.cleanup.policy = delete启用删除策略

log.cleanup.policy = compact启用压缩策略

https://www.jianshu.com/p/fa6adeae8eb5

1.5.21 Kafka可以按照时间消费数据

Map<TopicPartition, OffsetAndTimestamp> startOffsetMap = KafkaUtil.fetchOffsetsWithTimestamp(topic, sTime, kafkaProp);

1.5.22 Kafka消费者角度考虑是拉取数据还是推送数据

拉取数据

1.5.23 Kafka中的数据是有序的吗

单分区内有序;多分区,分区与分区间无序;

扩展:

kafka producer发送消息的时候,可以指定key:

大数据技术之高频面试题|剑谱_数据倾斜_05

这个key的作用是为消息选择存储分区,key可以为空,当指定key且不为空的时候,Kafka是根据key的hash值与分区数取模来决定数据存储到那个分区。

大数据技术之高频面试题|剑谱_Shell_06

有序解决方案:同一张表的数据 放到 同一个 分区

=> ProducerRecord里传入key,会根据key取hash算出分区号

=> key使用表名,如果有库名,拼接上库名

1.5.24 Kafka的LeaderEpoch哪个版本引入的?

Kafka 0.11版本以后采用的。

1.5.25 Kafka生产者调优配置


Properties props = new Properties();


props.put("bootstrap.servers",

"hadoop1:9092,hadoop2:9092,hadoop3:9092");

props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");

props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");


//如果要想保证数据不丢失,得如下设置:

// min.insync.replicas = 2

// acks = -1

// retries = Integer.MAX_VALUE

props.put("acks", "-1");

//如果消息发送失败,就会重试,这里的3次代表重试的次数

props.put("retries", 3);

//重试的时间间隔

props.put("retry.backoff.ms",5000);


//设置是否开启压缩,默认是none不压缩

//如果要压缩的话,建议设置lz4,经过实际检验,效果还是不错的

props.put("compression.type","lz4");

//发送一次消息的批次大小,如果批次太小,会导致网络请求频繁,

//建议设置大一些,默认16384Byte(16k),建议调大,这里用32k

props.put("batch.size", 32384);

//批次达到时间就发送。默认是0,意思是消息必须立即被发送,建议100ms

props.put("linger.ms", 100);

//设置的缓冲区大小,默认33554432(32M),一般不用动

//验证何时该调整缓冲区的大小:

//用一般Java获取结束时间和开始时间: System.currentTime()

//当结束时间减去开始时间大于设置的linger.ms(100ms),此时Sender线程处理速度慢,需要调大缓冲区大小

props.put("buffer.memory", 33554432);


//发送消息的最大大小,默认是1048576(1M),上限可以调大到10M

props.put("max.request.size",10485760);

//保证一个消息发送成功,再发另外一个消息,保证单分区有序

props.put("max.in.flight.requests.per.connection",1);

//最大阻塞时间,RecordAccumulator缓存不足时或者没有可用的元数据时,KafkaProducer的send()方法调用要么被阻塞,要么抛出异常,此参数的默认值为60000,即60s

props.put("max.block.ms", 3000);


// 创建一个Producer实例

KafkaProducer<String, String> producer =

new KafkaProducer<String, String>(props);

// 有序性考虑,可以指定生产者的key

ProducerRecord<String, String> record =

new ProducerRecord<>("mytopic", "mykey", "myvalue");



//可以计算开始时间

long startTime=System.currentTime();

//发送消息的模式有两种,一种是异步的,一种是同步的,我们在实际生产中一般是使用异步的发送方式

producer.send(record, new Callback() {

@Override

public void onCompletion(RecordMetadata metadata, Exception exception) {

if(exception == null) {

// 消息发送成功

System.out.println("消息发送成功");

} else {

// 消息发送失败,需要重新发送

}

}


});

//计算结束时间

long endTime=System.currentTime();

if(endTime - startTime > 100){//说明内存被压满了

//说明有问题,考虑调大buffer.memory

}


// 这是同步发送的模式

//producer.send(record).get();


producer.close();

1.6 Hive

1.6.1 Hive的架构及HQL转换为MR流程

Hive元数据默认存储在derby数据库,不支持多客户端访问,所以将元数据存储在MySQl,支持多客户端访问。

大数据技术之高频面试题|剑谱_数据_07

大数据技术之高频面试题|剑谱_数据倾斜_08

大数据技术之高频面试题|剑谱_数据倾斜_09

1.6.2 Hive和数据库比较

Hive 和数据库除了拥有类似的查询语言,再无类似之处。

1)数据存储位置

Hive 存储在 HDFS 。数据库将数据保存在块设备或者本地文件系统中。

2)数据更新

Hive中不建议对数据的改写。而数据库中的数据通常是需要经常进行修改的,

3)执行延迟

Hive 执行延迟较高。数据库的执行延迟较低。当然,这个是有条件的,即数据规模较小,当数据规模大到超过数据库的处理能力的时候,Hive的并行计算显然能体现出优势。

4)数据规模

Hive支持很大规模的数据计算;数据库可以支持的数据规模较小。

1.6.3 内部表和外部表

元数据、原始数据

1)删除数据时:

内部表:元数据、原始数据,全删除

外部表:元数据 只删除

2)在公司生产环境下,什么时候创建内部表,什么时候创建外部表?

在公司中绝大多数场景都是外部表。

自己使用的临时表,才会创建内部表;

1.6.4 4个By区别

1)Order By:全局排序,只有一个Reducer;

2)Sort By:分区内有序;

3)Distrbute By:类似MR中Partition,进行分区,结合sort by使用。

4) Cluster By:当Distribute by和Sorts by字段相同时,可以使用Cluster by方式。Cluster by除了具有Distribute by的功能外还兼具Sort by的功能。但是排序只能是升序排序,不能指定排序规则为ASC或者DESC。

在生产环境中Order By用的比较少,容易导致OOM。

在生产环境中Sort By + Distrbute By用的多。

1.6.5 系统函数

1)date_add、date_sub函数(加减日期)

2)next_day函数(周指标相关)

3)date_format函数(根据格式整理日期)

4)last_day函数(求当月最后一天日期)

5)CONCAT、CONCAT_WS、COLLECT_SET

6)EXPLODE

7)collect_set函数

8)get_json_object解析json函数

9)NVL(表达式1,表达式2)

如果表达式1为空值,NVL返回值为表达式2的值,否则返回表达式1的值。

1.6.6 自定义UDF、UDTF函数

1)在项目中是否自定义过UDF、UDTF函数,以及用他们处理了什么问题,及自定义步骤?

(1)用UDF函数解析公共字段;用UDTF函数解析事件字段。

(2)自定义UDF:继承UDF,重写evaluate方法

(3)自定义UDTF:继承自GenericUDTF,重写3个方法:initialize(自定义输出的列名和类型),process(将结果返回forward(result)),close

2)为什么要自定义UDF/UDTF?

因为自定义函数,可以自己埋点Log打印日志,出错或者数据异常,方便调试。

引入第三方jar包时,也需要。

1.6.7 窗口函数

1)Rank

(1)RANK() 排序相同时会重复,总数不会变

(2)DENSE_RANK() 排序相同时会重复,总数会减少

(3)ROW_NUMBER() 会根据顺序计算

2) OVER():指定分析函数工作的数据窗口大小,这个数据窗口大小可能会随着行的变而变化

(1)CURRENT ROW:当前行

(2)n PRECEDING:往前n行数据

(3) n FOLLOWING:往后n行数据

(4)UNBOUNDED:起点,UNBOUNDED PRECEDING 表示从前面的起点, UNBOUNDED FOLLOWING表示到后面的终点

(5) LAG(col,n):往前第n行数据

(6)LEAD(col,n):往后第n行数据

(7) NTILE(n):把有序分区中的行分发到指定数据的组中,各个组有编号,编号从1开始,对于每一行,NTILE返回此行所属的组的编号。注意:n必须为int类型。

3)手写:分组TopN、行转列、列转行

1.6.8 Hive优化


1)MapJoin

如果不指定MapJoin或者不符合MapJoin的条件,那么Hive解析器会将Join操作转换成Common Join,即:在Reduce阶段完成join。容易发生数据倾斜。可以用MapJoin把小表全部加载到内存在map端进行join,避免reducer处理。

2)行列过滤

列处理:在SELECT中,只拿需要的列,如果有,尽量使用分区过滤,少用SELECT *。

行处理:在分区剪裁中,当使用外关联时,如果将副表的过滤条件写在Where后面,那么就会先全表关联,之后再过滤。

3)列式存储

4)采用分区技术

5)合理设置Map数

mapred.min.split.size: 指的是数据的最小分割单元大小;min的默认值是1B

mapred.max.split.size: 指的是数据的最大分割单元大小;max的默认值是256MB

通过调整max可以起到调整map数的作用,减小max可以增加map数,增大max可以减少map数。

需要提醒的是,直接调整mapred.map.tasks这个参数是没有效果的。

https://www.cnblogs.com/swordfall/p/11037539.html

6)合理设置Reduce数

Reduce个数并不是越多越好

(1)过多的启动和初始化Reduce也会消耗时间和资源;

(2)另外,有多少个Reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;

在设置Reduce个数的时候也需要考虑这两个原则:处理大数据量利用合适的Reduce数;使单个Reduce任务处理数据量大小要合适;

7)小文件如何产生的?

(1)动态分区插入数据,产生大量的小文件,从而导致map数量剧增;

(2)reduce数量越多,小文件也越多(reduce的个数和输出文件是对应的);

(3)数据源本身就包含大量的小文件。

8)小文件解决方案

(1)在Map执行前合并小文件,减少Map数:CombineHiveInputFormat具有对小文件进行合并的功能(系统默认的格式)。HiveInputFormat没有对小文件合并功能。

(2)merge

// 输出合并小文件

SET hive.merge.mapfiles = true; -- 默认true,在map-only任务结束时合并小文件

SET hive.merge.mapredfiles = true; -- 默认false,在map-reduce任务结束时合并小文件

SET hive.merge.size.per.task = 268435456; -- 默认256M

SET hive.merge.smallfiles.avgsize = 16777216; -- 当输出文件的平均大小小于16m该值时,启动一个独立的map-reduce任务进行文件merge

(3)开启JVM重用

set mapreduce.job.jvm.numtasks=10

9)开启map端combiner(不影响最终业务逻辑)

set hive.map.aggr=true;

10)压缩(选择快的)

设置map端输出、中间结果压缩。(不完全是解决数据倾斜的问题,但是减少了IO读写和网络传输,能提高很多效率)

set hive.exec.compress.intermediate=true --启用中间数据压缩

set mapreduce.map.output.compress=true --启用最终数据压缩

set mapreduce.map.outout.compress.codec=…; --设置压缩方式

11)采用tez引擎或者spark引擎

1.6.9 Hive解决数据倾斜方法

1)数据倾斜长啥样?

大数据技术之高频面试题|剑谱_数据_10

大数据技术之高频面试题|剑谱_数据倾斜_11

2)怎么产生的数据倾斜?

(1)不同数据类型关联产生数据倾斜

情形:比如用户表中user_id字段为int,log表中user_id字段string类型。当按照user_id进行两个表的Join操作时。

解决方式:把数字类型转换成字符串类型

select * from users a

left outer join logs b

on a.usr_id = cast(b.user_id as string)

bug记录:https://www.jianshu.com/p/2181e00d74dc

(2)控制空值分布

在生产环境经常会用大量空值数据进入到一个reduce中去,导致数据倾斜。

解决办法:

自定义分区,将为空的key转变为字符串加随机数或纯随机数,将因空值而造成倾斜的数据分不到多个Reducer。

注意:对于异常值如果不需要的话,最好是提前在where条件里过滤掉,这样可以使计算量大大减少

3) 单表 -- group by id

(1) 按照id分组计算count值

-> 单个Key

-> 多个Key

(2) 单个Key

加随机数,双重聚合

配置参数,双重聚合 set hive.groupby.skewindata = true;

过滤出这个Key单独处理

(3) 多个Key

增加Reducer个数,一定程度上解决问题

自定义分区器

加随机数,双重聚合

配置参数,双重聚合 set hive.groupby.skewindata = true;

4) JOIN ON 关联字段

(1) 大表JOIN小表 mapJoin 避免了Reducer

(2) 大表JOIN大表 A表加随机数 B表扩容 聚合

A concat(name,'_',随机数[1,2])

B

concat(name,'_',1)

union all

concat(name,'_',2)


name a1 name b1

name a2 name b2


name_1 a1 name_1 b1

name_2 a2 name_2 b2


name_1 a1 name_1 b1

name_2 b1

name_2 a2 name_1 b2

name_2 b2

1.6.10 Hive里边字段的分隔符用的什么?为什么用\t?有遇到过字段里边有\t的情况吗,怎么处理的?

hive 默认的字段分隔符为ascii码的控制符\001(^A),建表的时候用fields terminated by '\001'。注意:如果采用\t或者\001等为分隔符,需要要求前端埋点和javaEE后台传递过来的数据必须不能出现该分隔符,通过代码规范约束。一旦传输过来的数据含有分隔符,需要在前一级数据中转义或者替换(ETL)。

可以设置参数(导入HDFS同样有效):

--hive-drop-import-delims 导入到hive时删除 \n, \r, \001

--hive-delims-replacement 导入到hive时用自定义的字符替换掉 \n, \r, \001

  • 字段包含分隔符存在的问题:

大数据技术之高频面试题|剑谱_数据_12

  • 添加参数的效果:

大数据技术之高频面试题|剑谱_Shell_13

  • 在Hive表里的体现:

大数据技术之高频面试题|剑谱_数据倾斜_14

1.6.11 Tez引擎优点?

Tez可以将多个有依赖的作业转换为一个作业,这样只需写一次HDFS,且中间节点较少,从而大大提升作业的计算性能。

Mr/tez/spark区别:

Mr引擎:多job串联,基于磁盘,落盘的地方比较多。虽然慢,但一定能跑出结果。一般处理,周、月、年指标。

Spark引擎:虽然在Shuffle过程中也落盘,但是并不是所有算子都需要Shuffle,尤其是多算子过程,中间过程不落盘 DAG有向无环图。 兼顾了可靠性和效率。一般处理天指标。

Tez引擎:完全基于内存。 注意:如果数据量特别大,慎重使用。容易OOM。一般用于快速出结果,数据量比较小的场景。

1.6.12 MySQL元数据备份

1)MySQL之元数据备份(项目中遇到的问题)

元数据备份(重点,如数据损坏,可能整个集群无法运行,至少要保证每日零点之后备份到其它服务器两个复本)

Keepalived或者用mycat

2)MySQL utf8超过字节数问题

MySQL的utf8编码最多存储3个字节,当数据中存在表情号、特色符号时会占用超过3个字节数的字节,那么会出现错误 Incorrect string value: '\xF0\x9F\x91\x91\xE5\xB0...'

解决办法:将utf8修改为utf8mb4

首先修改库的基字符集和数据库排序规则

大数据技术之高频面试题|剑谱_数据_15

再使用 SHOW VARIABLES LIKE '%char%'; 命令查看参数

大数据技术之高频面试题|剑谱_Shell_16

确保这几个参数的value值为utf8mb4 如果不是使用set命令修改

如:set character_set_server = utf8mb4;

1.6.13 Union与Union all区别

1)union会将联合的结果集去重,效率较union all差

2)union all不会对结果集去重,所以效率高

1.7 Sqoop

1.7.1 Sqoop参数

/opt/module/sqoop/bin/sqoop import \

--connect \

--username \

--password \

--target-dir \

--delete-target-dir \

--num-mappers \

--fields-terminated-by   \

--query   "$2" ' and $CONDITIONS;'

1.7.2 Sqoop导入导出Null存储一致性问题

Hive中的Null在底层是以“\N”来存储,而MySQL中的Null在底层就是Null,为了保证数据两端的一致性。在导出数据时采用--input-null-string和--input-null-non-string两个参数。导入数据时采用--null-string和--null-non-string。

1.7.3 Sqoop数据导出一致性问题

场景1:如Sqoop在导出到Mysql时,使用4个Map任务,过程中有2个任务失败,那此时MySQL中存储了另外两个Map任务导入的数据,此时老板正好看到了这个报表数据。而开发工程师发现任务失败后,会调试问题并最终将全部数据正确的导入MySQL,那后面老板再次看报表数据,发现本次看到的数据与之前的不一致,这在生产环境是不允许的。

官网:http://sqoop.apache.org/docs/1.4.6/SqoopUserGuide.html

Since Sqoop breaks down export process into multiple transactions, it is possible that a failed export job may result in partial data being committed to the database. This can further lead to subsequent jobs failing due to insert collisions in some cases, or lead to duplicated data in others. You can overcome this problem by specifying a staging table via the --staging-table option which acts as an auxiliary table that is used to stage exported data. The staged data is finally moved to the destination table in a single transaction.

–staging-table方式

sqoop export --connect jdbc:mysql://192.168.137.10:3306/user_behavior --username root --password 123456 --table app_cource_study_report --columns watch_video_cnt,complete_video_cnt,dt --fields-terminated-by "\t" --export-dir "/user/hive/warehouse/tmp.db/app_cource_study_analysis_${day}" --staging-table app_cource_study_report_tmp --clear-staging-table --input-null-string '\N'

1.7.4 Sqoop底层运行的任务是什么

只有Map阶段,没有Reduce阶段的任务。默认是4个MapTask。

1.7.5 Sqoop一天导入多少数据

100万日活=》10万订单,1人10条,每天1g左右业务数据

Sqoop每天将1G的数据量导入到数仓。

1.7.6 Sqoop数据导出的时候一次执行多长时间

每天晚上00:10开始执行,Sqoop任务一般情况20-30分钟的都有。取决于数据量(11.11,6.18等活动在1个小时左右)。

1.7.7 Sqoop在导入数据的时候数据倾斜

Sqoop参数撇嘴: split-by:按照自增主键来切分表的工作单元。

num-mappers:启动N个map来并行导入数据,默认4个;

1.7.8 Sqoop数据导出Parquet(项目中遇到的问题)

Ads层数据用Sqoop往MySql中导入数据的时候,如果用了orc(Parquet)不能导入,需转化成text格式

(1)创建临时表,把Parquet中表数据导入到临时表,把临时表导出到目标表用于可视化

(2)ads层建表的时候就不要建Parquet表

1.8 Azkaban

1.8.1 每天集群运行多少指标?

每天跑100多个指标,有活动时跑200个左右。

1.8.2 任务挂了怎么办?

1)运行成功或者失败都会发邮件、发钉钉、集成自动打电话(项目中遇到的问题)

2)最主要的解决方案就是重新跑。

3)报警网站http://www.onealert.com/

1.9 HBase

1.9.1 HBase存储结构


1.9.2 RowKey设计原则

1)rowkey长度原则

2)rowkey散列原则

3)rowkey唯一原则

1.9.3 RowKey如何设计

使用场景:

电信案例:查询某个人(手机号)某年[某月某日](时间)的通话详情。

1) 预分区

(1) 评估未来半年到一年的数据增长,不让其自动分区(10G)

(2) 确定分区键

00| 01| 02| ...

000| 001| ...

2) 设计RowKey

(1) 确定分区号 (散列性)

00_ 01_ 02_...


手机号%分区数 不够散列

(手机号+年月日)%分区数 按照月份、年进行查询 不方便

(手机号+年月)%分区数

(2) 拼接字段 (唯一性、长度)

XX_手机号_时间戳

XX_手机号_年月日 时分秒

XX_时间戳_手机号

XX_年月日 时分秒_手机号

(3) 校验

13412341234 2021-09-07

XX_手机号_年月日 时分秒

startRow:05_13412341234_2021-09-07

stopRow :05_13412341234_2021-09-08

05_13412341234_2021-09-07|

XX_年月日 时分秒_手机号

startRow:05_2021-09-07 00:00:00_13412341234

stopRow :05_2021-09-08 00:00:00_13412341234


13412341234 2021-09 2021-11

XX_手机号_年月日 时分秒

startRow:05_13412341234_2021-09

stopRow :05_13412341234_2021-09|

05_13412341234_2021-10


startRow:03_13412341234_2021-10

stopRow :03_13412341234_2021-11


startRow:04_13412341234_2021-11

stopRow :04_13412341234_2021-12

1.9.4 Phoenix二级索引(讲原理)


1) 一级索引

RowKey

2) 原理

协处理器(HBase) coprocessor

3) 种类及用法

(1) 全局:另外创建一张表专门存储索引

读多写少 索引RowKey zs_1001

(2) 本地:将索引数据直接写入原表(原Region)

写多读少 索引RowKey 没有预分区 __zs_1001

预分区 分区键_zs_1001

1.10 Scala

1.10.1 开发环境

要求掌握必要的Scala开发环境搭建技能。

1.10.2 变量和数据类型

掌握var和val的区别

掌握数值类型(Byte、Short、Int、Long、Float、Double、Char)之间的转换关系

1.10.3 流程控制

掌握if-else、for、while等必要的流程控制结构,掌握如何实现break、continue的功能。

1.10.4 函数式编程

函数可以作为参数进行传递(spark 移动数据不如移动逻辑)

_

掌握高阶函数、匿名函数、函数柯里化、函数参数以及函数至简原则。

1.10.5 面向对象

掌握Scala与Java继承方面的区别、单例对象(伴生对象)、特质的用法及功能。

1.10.6 集合

map flatmap

掌握常用集合的使用、集合常用的计算函数。

1.10.7 模式匹配

switch case 样例类

掌握模式匹配的用法

1.10.8 异常

掌握异常常用操作即可

1.10.9 隐式转换

掌握隐式方法、隐式参数、隐式类,以及隐式解析机制

1.10.10 泛型

掌握泛型语法

1.11 Spark Core & SQL

1.11.1 Spark解决什么问题

回顾:Hadoop主要解决,海量数据的存储和海量数据的分析计算。

Spark主要解决海量数据的分析计算。

1.11.2 Spark为什么会有自己的资源调度器

Hadoop的Yarn框架比Spark框架诞生的晚,所以Spark自己也设计了一套资源调度框架。

1.11.3 Spark运行模式

1)Local:运行在一台机器上。 测试用。

2)Standalone:是Spark自身的一个调度系统。 对集群性能要求非常高时用。国内很少使用。

3)Yarn:采用Hadoop的资源调度器。 国内大量使用。

4)Mesos:国内很少使用。

1.11.4 Spark常用端口号

1)4040 spark-shell任务端口

2)7077 内部通讯端口。 类比Hadoop的8020/9000

3)8080 查看任务执行情况端口。 类比Hadoop的8088

4)18080 历史服务器。类比Hadoop的19888

注意:由于Spark只负责计算,所有并没有Hadoop中存储数据的端口50070

1.11.5 简述Spark的架构与作业提交流程(画图讲解,注明各个部分的作用)(重点)


1.11.6 Spark任务使用什么进行提交,JavaEE界面还是脚本

Shell脚本。

1.11.7 Spark提交作业参数(重点)

参考答案:

https://blog.csdn.net/gamer_gyt/article/details/79135118

1)在提交任务时的几个重要参数

executor-cores —— 每个executor使用的内核数,默认为1,官方建议2-5个,我们企业是4个

num-executors —— 启动executors的数量,默认为2

executor-memory —— executor内存大小,默认1G

driver-cores —— driver使用内核数,默认为1

driver-memory —— driver内存大小,默认512M

2)边给一个提交任务的样式

spark-submit \

--master local[5] \

--driver-cores 2 \

--driver-memory 8g \

--executor-cores 4 \

--num-executors 10 \

--executor-memory 8g \

--class PackageName.ClassName XXXX.jar \

--name "Spark Job Name" \

InputPath \

OutputPath

1.11.8 RDD五大属性


1.11.9 Spark的transformation算子(不少于8个)(重点)

1)单Value

(1)map

(2)mapPartitions

(3)mapPartitionsWithIndex

(4)flatMap

(5)glom

(6)groupBy

(7)filter

(8)sample

(9)distinct

(10)coalesce

(11)repartition

(12)sortBy

(13)pipe

2)双vlaue

(1)intersection

(2)union

(3)subtract

(4)zip

3)Key-Value

(1)partitionBy

(2)reduceByKey

(3)groupByKey

(4)aggregateByKey

(5)foldByKey

(6)combineByKey

(7)sortByKey

(8)mapValues

(9)join

(10)cogroup

1.11.10 Spark的action算子(不少于6个)(重点)

(1)reduce

(2)collect

(3)count

(4)first

(5)take

(6)takeOrdered

(7)aggregate

(8)fold

(9)countByKey

(10)save

(11)foreach

1.11.11 map和mapPartitions区别

1)map:每次处理一条数据

2)mapPartitions:每次处理一个分区数据

1.11.12 Repartition和Coalesce区别

1)关系:

两者都是用来改变RDD的partition数量的,repartition底层调用的就是coalesce方法:coalesce(numPartitions, shuffle = true)

2)区别:

repartition一定会发生shuffle,coalesce根据传入的参数来判断是否发生shuffle

一般情况下增大rdd的partition数量使用repartition,减少partition数量时使用coalesce

1.11.13 reduceByKey与groupByKey的区别

reduceByKey:具有预聚合操作

groupByKey:没有预聚合

在不影响业务逻辑的前提下,优先采用reduceByKey。

1.11.14 reduceByKey、foldByKey、aggregateByKey、combineByKey区别

ReduceByKey 没有初始值 分区内和分区间逻辑相同

foldByKey 有初始值 分区内和分区间逻辑相同

aggregateByKey 有初始值 分区内和分区间逻辑可以不同

combineByKey 初始值可以变化结构 分区内和分区间逻辑不同

1.11.15 Kryo序列化

Kryo序列化比Java序列化更快更紧凑,但Spark默认的序列化是Java序列化并不是Spark序列化,因为Spark并不支持所有序列化类型,而且每次使用都必须进行注册。注册只针对于RDD。在DataFrames和DataSet当中自动实现了Kryo序列化。

1.11.16 Spark中的血缘(笔试重点)

宽依赖和窄依赖。有Shuffle的是宽依赖。

1.11.17 Spark任务的划分

(1)Application:初始化一个SparkContext即生成一个Application;

(2)Job:一个Action算子就会生成一个Job;

(3)Stage:Stage等于宽依赖的个数加1;

(4)Task:一个Stage阶段中,最后一个RDD的分区个数就是Task的个数。


1.11.18 cache缓存级别

DataFrame的cache默认采用 MEMORY_AND_DISK

RDD 的cache默认方式采用MEMORY_ONLY

1.11.19 释放缓存和缓存

缓存:(1)dataFrame.cache (2)sparkSession.catalog.cacheTable(“tableName”)

释放缓存:(1)dataFrame.unpersist (2)sparkSession.catalog.uncacheTable(“tableName”)

1.11.20 缓存和检查点区别

1)Cache缓存只是将数据保存起来,不切断血缘依赖。Checkpoint检查点切断血缘依赖。

2)Cache缓存的数据通常存储在磁盘、内存等地方,可靠性低。Checkpoint的数据通常存储在HDFS等容错、高可用的文件系统,可靠性高。

3)建议对checkpoint()的RDD使用Cache缓存,这样checkpoint的job只需从Cache缓存中读取数据即可,否则需要再从头计算一次RDD。

1.11.21 Spark分区

1)默认采用Hash分区

缺点:可能导致每个分区中数据量的不均匀,极端情况下会导致某些分区拥有RDD的全部数据。

2)Ranger分区

要求RDD中的KEY类型必须可以排序。

3)自定义分区

根据需求,自定义分区。

1.11.22 Spark累加器


1.11.23 Spark广播变量


1.11.24 SparkSQL中RDD、DataFrame、DataSet三者的转换 (笔试重点)


1.11.25 请列举会引起Shuffle过程的Spark算子,并简述功能。

reduceBykey:

groupByKey:

…ByKey:


1.11.26 当Spark涉及到数据库的操作时,如何减少Spark运行中的数据库连接数?

使用foreachPartition代替foreach,在foreachPartition内获取数据库的连接。

1.11.27 如何使用Spark实现TopN的获取(描述思路或使用伪代码)(重点)

方法1:

(1)按照key对数据进行聚合(groupByKey)

(2)将value转换为数组,利用scala的sortBy或者sortWith进行排序(mapValues)数据量太大,会OOM。

方法2:

(1)取出所有的key

(2)对key进行迭代,每次取出一个key利用spark的排序算子进行排序

方法3:

(1)自定义分区器,按照key进行分区,使不同的key进到不同的分区

(2)对每个分区运用spark的排序算子进行排序

1.11.28 京东:调优之前与调优之后性能的详细对比(例如调整map个数,map个数之前多少、之后多少,有什么提升)

这里举个例子。比如我们有几百个文件,会有几百个map出现,读取之后进行join操作,会非常的慢。这个时候我们可以进行coalesce操作,比如240个map,我们合成60个map,也就是窄依赖。这样再shuffle,过程产生的文件数会大大减少。提高join的时间性能。

1.11.29 Spark Shuffle默认并行度

参数spark.sql.shuffle.partitions 决定 默认并行度200

1.11.30 控制Spark reduce缓存 调优shuffle

spark.reducer.maxSizeInFilght 此参数为reduce task能够拉取多少数据量的一个参数默认48MB,当集群资源足够时,增大此参数可减少reduce拉取数据量的次数,从而达到优化shuffle的效果,一般调大为96MB,,资源够大可继续往上调。


spark.shuffle.file.buffer 此参数为每个shuffle文件输出流的内存缓冲区大小,调大此参数可以减少在创建shuffle文件时进行磁盘搜索和系统调用的次数,默认参数为32k 一般调大为64k。

1.11.31 Spark内核源码(重点)









1.12 Spark Streaming

1.12.1 Spark Streaming第一次运行不丢失数据

kafka参数 auto.offset.reset 参数设置成earliest 从最初始偏移量开始消费数据

1.12.2 Spark Streaming精准一次消费

  1. 手动维护偏移量
  2. 处理完业务数据后,再进行提交偏移量操作

极端情况下,如在提交偏移量时断网或停电会造成spark程序第二次启动时重复消费问题,所以在涉及到金额或精确性非常高的场景会使用事物保证精准一次消费

1.12.3 Spark Streaming控制每秒消费数据的速度

通过spark.streaming.kafka.maxRatePerPartition参数来设置Spark Streaming从kafka分区每秒拉取的条数

1.12.4 Spark Streaming背压机制

把spark.streaming.backpressure.enabled 参数设置为ture,开启背压机制后Spark Streaming会根据延迟动态去kafka消费数据,上限由spark.streaming.kafka.maxRatePerPartition参数控制,所以两个参数一般会一起使用。

1.12.5 Spark Streaming一个stage耗时

Spark Streaming stage耗时由最慢的task决定,所以数据倾斜时某个task运行慢会导致整个Spark Streaming都运行非常慢。

1.12.6 Spark Streaming优雅关闭

把spark.streaming.stopGracefullyOnShutdown参数设置成ture,Spark会在JVM关闭时正常关闭StreamingContext,而不是立马关闭

Kill 命令:yarn application -kill 后面跟 applicationid

1.12.7 Spark Streaming默认分区个数

Spark Streaming默认分区个数与所对接的kafka topic分区个数一致,Spark Streaming里一般不会使用repartition算子增大分区,因为repartition会进行shuffle增加耗时。

1.12.8 SparkStreaming有哪几种方式消费Kafka中的数据,它们之间的区别是什么?

一、基于Receiver的方式

这种方式使用Receiver来获取数据。Receiver是使用Kafka的高层次Consumer API来实现的。receiver从Kafka中获取的数据都是存储在Spark Executor的内存中的(如果突然数据暴增,大量batch堆积,很容易出现内存溢出的问题),然后Spark Streaming启动的job会去处理那些数据。

然而,在默认的配置下,这种方式可能会因为底层的失败而丢失数据。如果要启用高可靠机制,让数据零丢失,就必须启用Spark Streaming的预写日志机制(Write Ahead Log,WAL)。该机制会同步地将接收到的Kafka数据写入分布式文件系统(比如HDFS)上的预写日志中。所以,即使底层节点出现了失败,也可以使用预写日志中的数据进行恢复。

二、基于Direct的方式

这种新的不基于Receiver的直接方式,是在Spark 1.3中引入的,从而能够确保更加健壮的机制。替代掉使用Receiver来接收数据后,这种方式会周期性地查询Kafka,来获得每个topic+partition的最新的offset,从而定义每个batch的offset的范围。当处理数据的job启动时,就会使用Kafka的简单consumer api来获取Kafka指定offset范围的数据。

优点如下: 

简化并行读取:如果要读取多个partition,不需要创建多个输入DStream然后对它们进行union操作。Spark会创建跟Kafka partition一样多的RDD partition,并且会并行从Kafka中读取数据。所以在Kafka partition和RDD partition之间,有一个一对一的映射关系。

高性能:如果要保证零数据丢失,在基于receiver的方式中,需要开启WAL机制。这种方式其实效率低下,因为数据实际上被复制了两份,Kafka自己本身就有高可靠的机制,会对数据复制一份,而这里又会复制一份到WAL中。而基于direct的方式,不依赖Receiver,不需要开启WAL机制,只要Kafka中作了数据的复制,那么就可以通过Kafka的副本进行恢复。

一次且仅一次的事务机制

三、对比:

基于receiver的方式,是使用Kafka的高阶API来在ZooKeeper中保存消费过的offset的。这是消费Kafka数据的传统方式。这种方式配合着WAL机制可以保证数据零丢失的高可靠性,但是却无法保证数据被处理一次且仅一次,可能会处理两次。因为Spark和ZooKeeper之间可能是不同步的。

基于direct的方式,使用kafka的简单api,Spark Streaming自己就负责追踪消费的offset,并保存在checkpoint中。Spark自己一定是同步的,因此可以保证数据是消费一次且仅消费一次。

在实际生产环境中大都用Direct方式

1.12.9 简述SparkStreaming窗口函数的原理(重点)

窗口函数就是在原来定义的SparkStreaming计算批次大小的基础上再次进行封装,每次计算多个批次的数据,同时还需要传递一个滑动步长的参数,用来设置当次计算任务完成之后下一次从什么地方开始计算。

图中time1就是SparkStreaming计算批次大小,虚线框以及实线大框就是窗口的大小,必须为批次的整数倍。虚线框到大实线框的距离(相隔多少批次),就是滑动步长。

1.13 数据倾斜

公司一:总用户量1000万,5台64G内存的服务器。

公司二:总用户量10亿,1000台64G内存的服务器。

1.公司一的数据分析师在做join的时候发生了数据倾斜,会导致有几百万用户的相关数据集中到了一台服务器上,几百万的用户数据,说大也不大,正常字段量的数据的话64G还是能轻松处理掉的。

2.公司二的数据分析师在做join的时候也发生了数据倾斜,可能会有1个亿的用户相关数据集中到了一台机器上了(相信我,这很常见)。这时候一台机器就很难搞定了,最后会很难算出结果。

1.13.1 数据倾斜表现

1)hadoop中的数据倾斜表现:

  • 有一个多几个Reduce卡住,卡在99.99%,一直不能结束。
  • 各种container报错OOM
  • 异常的Reducer读写的数据量极大,至少远远超过其它正常的Reducer
  • 伴随着数据倾斜,会出现任务被kill等各种诡异的表现。

2)hive中数据倾斜

一般都发生在Sql中group by和join on上,而且和数据逻辑绑定比较深。

3)Spark中的数据倾斜

Spark中的数据倾斜,包括Spark Streaming和Spark Sql,表现主要有下面几种:

  • Executor lost,OOM,Shuffle过程出错;
  • Driver OOM;
  • 单个Executor执行时间特别久,整体任务卡在某个阶段不能结束;
  • 正常运行的任务突然失败;

1.13.2 数据倾斜产生原因

我们以Spark和Hive的使用场景为例。

他们在做数据运算的时候会涉及到,count distinct、group by、join on等操作,这些都会触发Shuffle动作。一旦触发Shuffle,所有相同key的值就会被拉到一个或几个Reducer节点上,容易发生单点计算问题,导致数据倾斜。

一般来说,数据倾斜原因有以下几方面:

1)key分布不均匀;

大数据技术之高频面试题|剑谱_数据_17

2)建表时考虑不周

我们举一个例子,就说数据默认值的设计吧,假设我们有两张表:

user(用户信息表):userid,register_ip

ip(IP表):ip,register_user_cnt

这可能是两个不同的人开发的数据表。如果我们的数据规范不太完善的话,会出现一种情况:

user表中的register_ip字段,如果获取不到这个信息,我们默认为null;

但是在ip表中,我们在统计这个值的时候,为了方便,我们把获取不到ip的用户,统一认为他们的ip为0。

两边其实都没有错的,但是一旦我们做关联了,这个任务会在做关联的阶段,也就是sql的on的阶段卡死。

3)业务数据激增

比如订单场景,我们在某一天在北京和上海两个城市多了强力的推广,结果可能是这两个城市的订单量增长了10000%,其余城市的数据量不变。

然后我们要统计不同城市的订单情况,这样,一做group操作,可能直接就数据倾斜了。

1.13.3 解决数据倾斜思路

很多数据倾斜的问题,都可以用和平台无关的方式解决,比如更好的数据预处理异常值的过滤等。因此,解决数据倾斜的重点在于对数据设计和业务的理解,这两个搞清楚了,数据倾斜就解决了大部分了。

1)业务逻辑

我们从业务逻辑的层面上来优化数据倾斜,比如上面的两个城市做推广活动导致那两个城市数据量激增的例子,我们可以单独对这两个城市来做count,单独做时可用两次MR,第一次打散计算,第二次再最终聚合计算。完成后和其它城市做整合。

2)程序层面

比如说在Hive中,经常遇到count(distinct)操作,这样会导致最终只有一个Reduce任务。

我们可以先group by,再在外面包一层count,就可以了。比如计算按用户名去重后的总用户量:

(1)优化前 只有一个reduce,先去重再count负担比较大:

select name,count(distinct name)from user;

(2)优化后

// 设置该任务的每个job的reducer个数为3个。Hive默认-1,自动推断。

set mapred.reduce.tasks=3;

// 启动两个job,一个负责子查询(可以有多个reduce),另一个负责count(1):

select count(1) from (select name from user group by name) tmp;

3)调参方面

Hadoop和Spark都自带了很多的参数和机制来调节数据倾斜,合理利用它们就能解决大部分问题。

4)从业务和数据上解决数据倾斜

很多数据倾斜都是在数据的使用上造成的。我们举几个场景,并分别给出它们的解决方案。

  • 有损的方法:找到异常数据,比如ip为0的数据,过滤掉
  • 无损的方法:对分布不均匀的数据,单独计算
  • 先对key做一层hash,先将数据随机打散让它的并行度变大,再汇集
  • 数据预处理

1.13.4 定位导致数据倾斜代码

Spark数据倾斜只会发生在shuffle过程中。

这里给大家罗列一些常用的并且可能会触发shuffle操作的算子:distinct、groupByKey、reduceByKey、aggregateByKey、join、cogroup、repartition等。

出现数据倾斜时,可能就是你的代码中使用了这些算子中的某一个所导致的。

1.13.4.1 某个task执行特别慢的情况

首先要看的,就是数据倾斜发生在第几个stage中:

如果是用yarn-client模式提交,那么在提交的机器本地是直接可以看到log,可以在log中找到当前运行到了第几个stage;

如果是用yarn-cluster模式提交,则可以通过Spark Web UI来查看当前运行到了第几个stage。

此外,无论是使用yarn-client模式还是yarn-cluster模式,我们都可以在Spark Web UI上深入看一下当前这个stage各个task分配的数据量,从而进一步确定是不是task分配的数据不均匀导致了数据倾斜。

看task运行时间和数据量

task运行时间

比如下图中,倒数第三列显示了每个task的运行时间。明显可以看到,有的task运行特别快,只需要几秒钟就可以运行完;而有的task运行特别慢,需要几分钟才能运行完,此时单从运行时间上看就已经能够确定发生数据倾斜了。

task数据量

此外,倒数第一列显示了每个task处理的数据量,明显可以看到,运行时间特别短的task只需要处理几百KB的数据即可,而运行时间特别长的task需要处理几千KB的数据,处理的数据量差了10倍。此时更加能够确定是发生了数据倾斜。

推断倾斜代码

知道数据倾斜发生在哪一个stage之后,接着我们就需要根据stage划分原理,推算出来发生倾斜的那个stage对应代码中的哪一部分,这部分代码中肯定会有一个shuffle类算子。

精准推算stage与代码的对应关系,需要对Spark的源码有深入的理解,这里我们可以介绍一个相对简单实用的推算方法:只要看到Spark代码中出现了一个shuffle类算子或者是Spark SQL的SQL语句中出现了会导致shuffle的语句(比如group by语句),那么就可以判定,以那个地方为界限划分出了前后两个stage。

这里我们就以如下单词计数来举例。

val conf = new SparkConf()val sc = new SparkContext(conf)val lines = sc.textFile("hdfs://...")val words = lines.flatMap(_.split(" "))val pairs = words.map((_, 1))val wordCounts = pairs.reduceByKey(_ + _)wordCounts.collect().foreach(println(_))

在整个代码中只有一个reduceByKey是会发生shuffle的算子,也就是说这个算子为界限划分出了前后两个stage:

stage0,主要是执行从textFile到map操作,以及shuffle write操作(对pairs RDD中的数据进行分区操作,每个task处理的数据中,相同的key会写入同一个磁盘文件内)。

stage1,主要是执行从reduceByKey到collect操作,以及stage1的各个task一开始运行,就会首先执行shuffle read操作(会从stage0的各个task所在节点拉取属于自己处理的那些key,然后对同一个key进行全局性的聚合或join等操作,在这里就是对key的value值进行累加)

stage1在执行完reduceByKey算子之后,就计算出了最终的wordCounts RDD,然后会执行collect算子,将所有数据拉取到Driver上,供我们遍历和打印输出。

123456789

通过对单词计数程序的分析,希望能够让大家了解最基本的stage划分的原理,以及stage划分后shuffle操作是如何在两个stage的边界处执行的。然后我们就知道如何快速定位出发生数据倾斜的stage对应代码的哪一个部分了。

比如我们在Spark Web UI或者本地log中发现,stage1的某几个task执行得特别慢,判定stage1出现了数据倾斜,那么就可以回到代码中,定位出stage1主要包括了reduceByKey这个shuffle类算子,此时基本就可以确定是是该算子导致了数据倾斜问题。

此时,如果某个单词出现了100万次,其他单词才出现10次,那么stage1的某个task就要处理100万数据,整个stage的速度就会被这个task拖慢。

1.13.4.2 某个task莫名其妙内存溢出的情况

这种情况下去定位出问题的代码就比较容易了。我们建议直接看yarn-client模式下本地log的异常栈,或者是通过YARN查看yarn-cluster模式下的log中的异常栈。一般来说,通过异常栈信息就可以定位到你的代码中哪一行发生了内存溢出。然后在那行代码附近找找,一般也会有shuffle类算子,此时很可能就是这个算子导致了数据倾斜。

但是大家要注意的是,不能单纯靠偶然的内存溢出就判定发生了数据倾斜。因为自己编写的代码的bug,以及偶然出现的数据异常,也可能会导致内存溢出。因此还是要按照上面所讲的方法,通过Spark Web UI查看报错的那个stage的各个task的运行时间以及分配的数据量,才能确定是否是由于数据倾斜才导致了这次内存溢出。

1.13.5 查看导致数据倾斜的key分布情况

先对pairs采样10%的样本数据,然后使用countByKey算子统计出每个key出现的次数,最后在客户端遍历和打印样本数据中各个key的出现次数。

val sampledPairs = pairs.sample(false, 0.1)

val sampledWordCounts = sampledPairs.countByKey()

sampledWordCounts.foreach(println(_))

1.13.6 Spark 数据倾斜的解决方案

1.13.6.1 使用Hive ETL预处理数据
1.13.6.1.1 适用场景

导致数据倾斜的是Hive表。如果该Hive表中的数据本身很不均匀(比如某个key对应了100万数据,其他key才对应了10条数据),而且业务场景需要频繁使用Spark对Hive表执行某个分析操作,那么比较适合使用这种技术方案。

1.13.6.1.2 实现思路

此时可以评估一下,是否可以通过Hive来进行数据预处理(即通过Hive ETL预先对数据按照key进行聚合,或者是预先和其他表进行join),然后在Spark作业中针对的数据源就不是原来的Hive表了,而是预处理后的Hive表。此时由于数据已经预先进行过聚合或join操作了,那么在Spark作业中也就不需要使用原先的shuffle类算子执行这类操作了。

1.13.6.1.3 方案实现原理

这种方案从根源上解决了数据倾斜,因为彻底避免了在Spark中执行shuffle类算子,那么肯定就不会有数据倾斜的问题了。但是这里也要提醒一下大家,这种方式属于治标不治本。因为毕竟数据本身就存在分布不均匀的问题,所以Hive ETL中进行group by或者join等shuffle操作时,还是会出现数据倾斜,导致Hive ETL的速度很慢。我们只是把数据倾斜的发生提前到了Hive ETL中,避免Spark程序发生数据倾斜而已。

1.13.6.1.4 方案优缺点

优点:实现起来简单便捷,效果还非常好,完全规避掉了数据倾斜,Spark作业的性能会大幅度提升。

缺点:治标不治本,Hive ETL中还是会发生数据倾斜。

1.13.6.1.5 方案实践经验

在一些Java系统与Spark结合使用的项目中,会出现Java代码频繁调用Spark作业的场景,而且对Spark作业的执行性能要求很高,就比较适合使用这种方案。将数据倾斜提前到上游的Hive ETL,每天仅执行一次,只有那一次是比较慢的,而之后每次Java调用Spark作业时,执行速度都会很快,能够提供更好的用户体验。

1.13.6.1.6 项目实践经验

在美团·点评的交互式用户行为分析系统中使用了这种方案,该系统主要是允许用户通过Java Web系统提交数据分析统计任务,后端通过Java提交Spark作业进行数据分析统计。要求Spark作业速度必须要快,尽量在10分钟以内,否则速度太慢,用户体验会很差。所以我们将有些Spark作业的shuffle操作提前到了Hive ETL中,从而让Spark直接使用预处理的Hive中间表,尽可能地减少Spark的shuffle操作,大幅度提升了性能,将部分作业的性能提升了6倍以上。

1.13.6.2 过滤少数导致倾斜的key
1.13.6.2.1 方案适用场景

如果发现导致倾斜的key就少数几个,而且对计算本身的影响并不大的话,那么很适合使用这种方案。比如99%的key就对应10条数据,但是只有一个key对应了100万数据,从而导致了数据倾斜。

1.13.6.2.2 方案实现思路

如果我们判断那少数几个数据量特别多的key,对作业的执行和计算结果不是特别重要的话,那么干脆就直接过滤掉那少数几个key。

比如,在Spark SQL中可以使用where子句过滤掉这些key或者在Spark Core中对RDD执行filter算子过滤掉这些key。

如果需要每次作业执行时,动态判定哪些key的数据量最多然后再进行过滤,那么可以使用sample算子对RDD进行采样,然后计算出每个key的数量,取数据量最多的key过滤掉即可。

1.13.6.2.3 方案实现原理

将导致数据倾斜的key给过滤掉之后,这些key就不会参与计算了,自然不可能产生数据倾斜。

1.13.6.2.4 方案优缺点

优点:实现简单,而且效果也很好,可以完全规避掉数据倾斜。

缺点:适用场景不多,大多数情况下,导致倾斜的key还是很多的,并不是只有少数几个。

1.13.6.2.5 方案实践经验

在项目中我们也采用过这种方案解决数据倾斜。有一次发现某一天Spark作业在运行的时候突然OOM了,追查之后发现,是Hive表中的某一个key在那天数据异常,导致数据量暴增。因此就采取每次执行前先进行采样,计算出样本中数据量最大的几个key之后,直接在程序中将那些key给过滤掉。

1.13.6.3 提高shuffle操作的并行度
1.13.6.3.1 方案适用场景

如果我们必须要对数据倾斜迎难而上,那么建议优先使用这种方案,因为这是处理数据倾斜最简单的一种方案。

1.13.6.3.2 方案实现思路

在对RDD执行shuffle算子时,给shuffle算子传入一个参数,比如reduceByKey(1000),该参数就设置了这个shuffle算子执行时shuffle read task的数量,即spark.sql.shuffle.partitions,该参数代表了shuffle read task的并行度,默认是200,对于很多场景来说都有点过小。

1.13.6.3.3 方案实现原理

增加shuffle read task的数量,可以让原本分配给一个task的多个key分配给多个task,从而让每个task处理比原来更少的数据。举例来说,如果原本有5个key,每个key对应10条数据,这5个key都是分配给一个task的,那么这个task就要处理50条数据。

而增加了shuffle read task以后,每个task就分配到一个key,即每个task就处理10条数据,那么自然每个task的执行时间都会变短了。具体原理如下图所示。

大数据技术之高频面试题|剑谱_数据倾斜_18

1.13.6.3.4 方案优缺点

优点:实现起来比较简单,可以有效缓解和减轻数据倾斜的影响。

缺点:只是缓解了数据倾斜而已,没有彻底根除问题,根据实践经验来看,其效果有限。

1.13.6.3.5 方案实践经验

该方案通常无法彻底解决数据倾斜,因为如果出现一些极端情况,比如某个key对应的数据量有100万,那么无论你的task数量增加到多少,这个对应着100万数据的key肯定还是会分配到一个task中去处理,因此注定还是会发生数据倾斜的。所以这种方案只能说是在发现数据倾斜时尝试使用的第一种手段,尝试去用最简单的方法缓解数据倾斜而已,或者是和其他方案结合起来使用。

1.13.6.4 两阶段聚合(局部聚合+全局聚合)
1.13.6.4.1 方案适用场景

对RDD执行reduceByKey等聚合类shuffle算子或者在Spark SQL中使用group by语句进行分组聚合时,比较适用这种方案。

1.13.6.4.2 方案实现思路

这个方案的核心实现思路就是进行两阶段聚合:

第一次是局部聚合,先给每个key都打上一个随机数,比如10以内的随机数,此时原先一样的key就变成不一样的了,比如(hello, 1) (hello, 1) (hello, 1) (hello, 1),就会变成(1_hello, 1) (1_hello, 1) (2_hello, 1) (2_hello, 1)。

接着对打上随机数后的数据,执行reduceByKey等聚合操作,进行局部聚合,那么局部聚合结果,就会变成了(1_hello, 2) (2_hello, 2)。

然后将各个key的前缀给去掉,就会变成(hello,2)(hello,2),再次进行全局聚合操作,就可以得到最终结果了,比如(hello, 4)。

示例代码如下:

// 第一步,给RDD中的每个key都打上一个随机前缀。JavaPairRDD<String, Long> randomPrefixRdd = rdd.mapToPair( new PairFunction<Tuple2<Long,Long>, String, Long>() { private static final long serialVersionUID = 1L; @Override public Tuple2<String, Long> call(Tuple2<Long, Long> tuple) throws Exception { Random random = new Random(); int prefix = random.nextInt(10); return new Tuple2<String, Long>(prefix + "_" + tuple._1, tuple._2); } }); // 第二步,对打上随机前缀的key进行局部聚合。JavaPairRDD<String, Long> localAggrRdd = randomPrefixRdd.reduceByKey( new Function2<Long, Long, Long>() { private static final long serialVersionUID = 1L; @Override public Long call(Long v1, Long v2) throws Exception { return v1 + v2; } }); // 第三步,去除RDD中每个key的随机前缀。JavaPairRDD<Long, Long> removedRandomPrefixRdd = localAggrRdd.mapToPair( new PairFunction<Tuple2<String,Long>, Long, Long>() { private static final long serialVersionUID = 1L; @Override public Tuple2<Long, Long> call(Tuple2<String, Long> tuple) throws Exception { long originalKey = Long.valueOf(tuple._1.split("_")[1]); return new Tuple2<Long, Long>(originalKey, tuple._2); } }); // 第四步,对去除了随机前缀的RDD进行全局聚合。JavaPairRDD<Long, Long> globalAggrRdd = removedRandomPrefixRdd.reduceByKey( new Function2<Long, Long, Long>() { private static final long serialVersionUID = 1L; @Override public Long call(Long v1, Long v2) throws Exception { return v1 + v2; } });

1.13.6.4.3 方案实现原理

将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被一个task处理的数据分散到多个task上去做局部聚合,进而解决单个task处理数据量过多的问题。接着去除掉随机前缀,再次进行全局聚合,就可以得到最终的结果。具体原理见下图。

大数据技术之高频面试题|剑谱_Shell_19

1.13.6.4.4 方案优缺点

优点对于聚合类的shuffle操作导致的数据倾斜,效果是非常不错的。通常都可以解决掉数据倾斜,或者至少是大幅度缓解数据倾斜,将Spark作业的性能提升数倍以上。

缺点仅仅适用于聚合类的shuffle操作,适用范围相对较窄。如果是join类的shuffle操作,还得用其他的解决方案。

1.13.6.5 将reduce join转为map join
1.13.6.5.1 方案适用场景

在对RDD使用join类操作,或者是在Spark SQL中使用join语句时,而且join操作中的一个RDD或表的数据量比较小(比如几百M或者一两G),比较适用此方案。

1.13.6.5.2 方案实现思路

不使用join算子进行连接操作,而使用Broadcast变量与map类算子实现join操作,进而完全规避掉shuffle类的操作,彻底避免数据倾斜的发生和出现。将较小RDD中的数据直接通过collect算子拉取到Driver端的内存中来,然后对其创建一个Broadcast变量,广播给其他Executor节点;

接着对另外一个RDD执行map类算子,在算子函数内,从Broadcast变量中获取较小RDD的全量数据,与当前RDD的每一条数据按照连接key进行比对,如果连接key相同的话,那么就将两个RDD的数据用你需要的方式连接起来。

示例如下:

// 首先将数据量比较小的RDD的数据,collect到Driver中来。List<Tuple2<Long, Row>> rdd1Data = rdd1.collect()// 然后使用Spark的广播功能,将小RDD的数据转换成广播变量,这样每个Executor就只有一份RDD的数据。// 可以尽可能节省内存空间,并且减少网络传输性能开销。final Broadcast<List<Tuple2<Long, Row>>> rdd1DataBroadcast = sc.broadcast(rdd1Data); // 对另外一个RDD执行map类操作,而不再是join类操作。JavaPairRDD<String, Tuple2<String, Row>> joinedRdd = rdd2.mapToPair( new PairFunction<Tuple2<Long,String>, String, Tuple2<String, Row>>() { private static final long serialVersionUID = 1L; @Override public Tuple2<String, Tuple2<String, Row>> call(Tuple2<Long, String> tuple) throws Exception { // 在算子函数中,通过广播变量,获取到本地Executor中的rdd1数据。 List<Tuple2<Long, Row>> rdd1Data = rdd1DataBroadcast.value(); // 可以将rdd1的数据转换为一个Map,便于后面进行join操作。 Map<Long, Row> rdd1DataMap = new HashMap<Long, Row>(); for(Tuple2<Long, Row> data : rdd1Data) { rdd1DataMap.put(data._1, data._2); } // 获取当前RDD数据的key以及value。 String key = tuple._1; String value = tuple._2; // 从rdd1数据Map中,根据key获取到可以join到的数据。 Row rdd1Value = rdd1DataMap.get(key); return new Tuple2<String, String>(key, new Tuple2<String, Row>(value, rdd1Value)); } }); // 这里得提示一下。// 上面的做法,仅仅适用于rdd1中的key没有重复,全部是唯一的场景。// 如果rdd1中有多个相同的key,那么就得用flatMap类的操作,在进行join的时候不能用map,而是得遍历rdd1所有数据进行join。// rdd2中每条数据都可能会返回多条join后的数据。

1.13.6.5.3 方案实现原理

普通的join是会走shuffle过程的,而一旦shuffle,就相当于会将相同key的数据拉取到一个shuffle read task中再进行join,此时就是reduce join。

但是如果一个RDD是比较小的,则可以采用广播小RDD全量数据+map算子来实现与join同样的效果,也就是map join,此时就不会发生shuffle操作,也就不会发生数据倾斜。具体原理如下图所示。

大数据技术之高频面试题|剑谱_数据_20

1.13.6.5.4 方案优缺点

优点:对join操作导致的数据倾斜,效果非常好,因为根本就不会发生shuffle,也就根本不会发生数据倾斜。

缺点:适用场景较少,因为这个方案只适用于一个大表和一个小表的情况。毕竟我们需要将小表进行广播,此时会比较消耗内存资源,driver和每个Executor内存中都会驻留一份小RDD的全量数据。如果我们广播出去的RDD数据比较大,比如10G以上,那么就可能发生内存溢出了。因此并不适合两个都是大表的情况。

1.13.6.6 采样倾斜key并分拆join操作
1.13.6.6.1 方案适用场景

两个RDD/Hive表进行join的时候,如果数据量都比较大,无法采用“解决方案五”,那么此时可以看一下两个RDD/Hive表中的key分布情况。

如果出现数据倾斜,是因为其中某一个RDD/Hive表中的少数几个key的数据量过大,而另一个RDD/Hive表中的所有key都分布比较均匀,那么采用这个解决方案是比较合适的。

1.13.6.6.2 方案实现思路

对包含少数几个数据量过大的key的那个RDD,通过sample算子采样出一份样本来,然后统计一下每个key的数量,计算出来数据量最大的是哪几个key。

然后将这几个key对应的数据从原来的RDD中拆分出来,形成一个单独的RDD,并给每个key都打上n以内的随机数作为前缀;

而不会导致倾斜的大部分key形成另外一个RDD。

接着将需要join的另一个RDD,也过滤出来那几个倾斜key对应的数据并形成一个单独的RDD,将每条数据膨胀成n条数据,这n条数据都按顺序附加一个0~n的前缀;

不会导致倾斜的大部分key也形成另外一个RDD。

再将附加了随机前缀的独立RDD与另一个膨胀n倍的独立RDD进行join,此时就可以将原先相同的key打散成n份,分散到多个task中去进行join了。

而另外两个普通的RDD就照常join即可。

最后将两次join的结果使用union算子合并起来即可,就是最终的join结果。

示例如下:

// 首先从包含了少数几个导致数据倾斜key的rdd1中,采样10%的样本数据。JavaPairRDD<Long, String> sampledRDD = rdd1.sample(false, 0.1); // 对样本数据RDD统计出每个key的出现次数,并按出现次数降序排序。// 对降序排序后的数据,取出top 1或者top 100的数据,也就是key最多的前n个数据。// 具体取出多少个数据量最多的key,由大家自己决定,我们这里就取1个作为示范。// 每行数据变为<key,1>JavaPairRDD<Long, Long> mappedSampledRDD = sampledRDD.mapToPair( new PairFunction<Tuple2<Long,String>, Long, Long>() { private static final long serialVersionUID = 1L; @Override public Tuple2<Long, Long> call(Tuple2<Long, String> tuple) throws Exception { return new Tuple2<Long, Long>(tuple._1, 1L); } }); // 按key累加行数JavaPairRDD<Long, Long> countedSampledRDD = mappedSampledRDD.reduceByKey( new Function2<Long, Long, Long>() { private static final long serialVersionUID = 1L; @Override public Long call(Long v1, Long v2) throws Exception { return v1 + v2; } }); // 反转key和value,变为<value,key>JavaPairRDD<Long, Long> reversedSampledRDD = countedSampledRDD.mapToPair( new PairFunction<Tuple2<Long,Long>, Long, Long>() { private static final long serialVersionUID = 1L; @Override public Tuple2<Long, Long> call(Tuple2<Long, Long> tuple) throws Exception { return new Tuple2<Long, Long>(tuple._2, tuple._1); } });// 以行数排序key,取最多行数的keyfinal Long skewedUserid = reversedSampledRDD.sortByKey(false).take(1).get(0)._2; // 从rdd1中分拆出导致数据倾斜的key,形成独立的RDD。JavaPairRDD<Long, String> skewedRDD = rdd1.filter( new Function<Tuple2<Long,String>, Boolean>() { private static final long serialVersionUID = 1L; @Override public Boolean call(Tuple2<Long, String> tuple) throws Exception { return tuple._1.equals(skewedUserid); } }); // 从rdd1中分拆出不导致数据倾斜的普通key,形成独立的RDD。JavaPairRDD<Long, String> commonRDD = rdd1.filter( new Function<Tuple2<Long,String>, Boolean>() { private static final long serialVersionUID = 1L; @Override public Boolean call(Tuple2<Long, String> tuple) throws Exception { return !tuple._1.equals(skewedUserid); } }); // rdd2,就是那个所有key的分布相对较为均匀的rdd。// 这里将rdd2中,前面获取到的key对应的数据,过滤出来,分拆成单独的rdd,并对rdd中的数据使用flatMap算子都扩容100倍。// 对扩容的每条数据,都打上0~100的前缀。JavaPairRDD<String, Row> skewedRdd2 = rdd2.filter( new Function<Tuple2<Long,Row>, Boolean>() { private static final long serialVersionUID = 1L; @Override public Boolean call(Tuple2<Long, Row> tuple) throws Exception { return tuple._1.equals(skewedUserid); } }).flatMapToPair(new PairFlatMapFunction<Tuple2<Long,Row>, String, Row>() { private static final long serialVersionUID = 1L; @Override public Iterable<Tuple2<String, Row>> call( Tuple2<Long, Row> tuple) throws Exception { Random random = new Random(); List<Tuple2<String, Row>> list = new ArrayList<Tuple2<String, Row>>(); for(int i = 0; i < 100; i++) { list.add(new Tuple2<String, Row>(i + "_" + tuple._1, tuple._2)); } return list; } }); // 将rdd1中分拆出来的导致倾斜的key的独立rdd,每条数据都打上100以内的随机前缀。// 然后将这个rdd1中分拆出来的独立rdd,与上面rdd2中分拆出来的独立rdd,进行join。JavaPairRDD<Long, Tuple2<String, Row>> joinedRDD1 = skewedRDD.mapToPair( new PairFunction<Tuple2<Long,String>, String, String>() { private static final long serialVersionUID = 1L; @Override public Tuple2<String, String> call(Tuple2<Long, String> tuple) throws Exception { Random random = new Random(); int prefix = random.nextInt(100); return new Tuple2<String, String>(prefix + "_" + tuple._1, tuple._2); } }) .join(skewedUserid2infoRDD) .mapToPair(new PairFunction<Tuple2<String,Tuple2<String,Row>>, Long, Tuple2<String, Row>>() { private static final long serialVersionUID = 1L; @Override public Tuple2<Long, Tuple2<String, Row>> call( Tuple2<String, Tuple2<String, Row>> tuple) throws Exception { long key = Long.valueOf(tuple._1.split("_")[1]); return new Tuple2<Long, Tuple2<String, Row>>(key, tuple._2); } }); // 将rdd1中分拆出来的包含普通key的独立rdd,直接与rdd2进行join。JavaPairRDD<Long, Tuple2<String, Row>> joinedRDD2 = commonRDD.join(rdd2); // 将倾斜key join后的结果与普通key join后的结果,uinon起来。// 就是最终的join结果。JavaPairRDD<Long, Tuple2<String, Row>> joinedRDD = joinedRDD1.union(joinedRDD2);

1.13.6.6.3 方案实现原理

对于join导致的数据倾斜,如果只是某几个key导致了倾斜,可以将少数几个key分拆成独立RDD,并附加随机前缀打散成n份去进行join,此时这几个key对应的数据就不会集中在少数几个task上,而是分散到多个task进行join了。具体原理见下图。

大数据技术之高频面试题|剑谱_Shell_21

1.13.6.6.4 方案优缺点

优点:对于join导致的数据倾斜,如果只是某几个key导致了倾斜,采用该方式可以用最有效的方式打散key进行join。而且只需要针对少数倾斜key对应的数据进行扩容n倍,不需要对全量数据进行扩容。避免了占用过多内存。

缺点:如果导致倾斜的key特别多的话,比如成千上万个key都导致数据倾斜,那么这种方式也不适合。

1.13.6.7 使用随机前缀和扩容RDD进行join
1.13.6.7.1 方案适用场景

如果在进行join操作时,RDD中有大量的key导致数据倾斜,那么进行分拆key也没什么意义,此时就只能使用最后一种方案来解决问题了。

1.13.6.7.2 方案实现思路

该方案的实现思路基本和“解决方案六”类似,首先查看RDD/Hive表中的数据分布情况,找到那个造成数据倾斜的RDD/Hive表,比如有多个key都对应了超过1万条数据。

然后将该RDD的每条数据都打上一个n以内的随机前缀。

同时对另外一个正常的RDD进行扩容,将每条数据都扩容成n条数据,扩容出来的每条数据都依次打上一个0~n的前缀。

最后将两个处理后的RDD进行join即可。

示例代码如下:

// 首先将其中一个key分布相对较为均匀的RDD膨胀100倍。JavaPairRDD<String, Row> expandedRDD = rdd1.flatMapToPair( new PairFlatMapFunction<Tuple2<Long,Row>, String, Row>() { private static final long serialVersionUID = 1L; @Override public Iterable<Tuple2<String, Row>> call(Tuple2<Long, Row> tuple) throws Exception { List<Tuple2<String, Row>> list = new ArrayList<Tuple2<String, Row>>(); for(int i = 0; i < 100; i++) { list.add(new Tuple2<String, Row>(0 + "_" + tuple._1, tuple._2)); } return list; } }); // 其次,将另一个有数据倾斜key的RDD,每条数据都打上100以内的随机前缀。JavaPairRDD<String, String> mappedRDD = rdd2.mapToPair( new PairFunction<Tuple2<Long,String>, String, String>() { private static final long serialVersionUID = 1L; @Override public Tuple2<String, String> call(Tuple2<Long, String> tuple) throws Exception { Random random = new Random(); int prefix = random.nextInt(100); return new Tuple2<String, String>(prefix + "_" + tuple._1, tuple._2); } }); // 将两个处理后的RDD进行join即可。JavaPairRDD<String, Tuple2<String, Row>> joinedRDD = mappedRDD.join(expandedRDD);

1.13.6.7.3 方案实现原理

将原先一样的key通过附加随机前缀变成不一样的key,然后就可以将这些处理后的“不同key”分散到多个task中去处理,而不是让一个task处理大量的相同key。

该方案与“解决方案六”的不同之处就在于,上一种方案是尽量只对少数倾斜key对应的数据进行特殊处理,由于处理过程需要扩容RDD,因此上一种方案扩容RDD后对内存的占用并不大;

而这一种方案是针对有大量倾斜key的情况,没法将部分key拆分出来进行单独处理,因此只能对整个RDD进行数据扩容,对内存资源要求很高。

1.13.6.7.4 方案优缺点

优点:对join类型的数据倾斜基本都可以处理,而且效果也相对比较显著,性能提升效果非常不错。

缺点:该方案更多的是缓解数据倾斜,而不是彻底避免数据倾斜。而且需要对整个RDD进行扩容,对内存资源要求很高。

1.13.6.7.5 方案实践经验

曾经开发一个数据需求的时候,发现一个join导致了数据倾斜。优化之前,作业的执行时间大约是60分钟左右;使用该方案优化之后,执行时间缩短到10分钟左右,性能提升了6倍。

1.13.6.8 多种方案组合使用

在实践中发现,很多情况下,如果只是处理较为简单的数据倾斜场景,那么使用上述方案中的某一种基本就可以解决。但是如果要处理一个较为复杂的数据倾斜场景,那么可能需要将多种方案组合起来使用。

比如说,我们针对出现了多个数据倾斜环节的Spark作业,可以先运用解决方案一HiveETL预处理和过滤少数导致倾斜的k,预处理一部分数据,并过滤一部分数据来缓解;

其次可以对某些shuffle操作提升并行度,优化其性能;

最后还可以针对不同的聚合或join操作,选择一种方案来优化其性能。

大家需要对这些方案的思路和原理都透彻理解之后,在实践中根据各种不同的情况,灵活运用多种方案,来解决自己的数据倾斜问题。

1.13.7 Spark数据倾斜处理小结

大数据技术之高频面试题|剑谱_数据倾斜_22

1.14 Flink

1.14.1 简单介绍一下 Flink

Flink 是一个框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算。并且 Flink 提供了数据分布、容错机制以及资源管理等核心功能。Flink提供了诸多高抽象层的API以便用户编写分布式任务:

DataSet API, 对静态数据进行批处理操作,将静态数据抽象成分布式的数据集,用户可以方便地使用Flink提供的各种操作符对分布式数据集进行处理,支持Java、Scala和Python。

DataStream API,对数据流进行流处理操作,将流式的数据抽象成分布式的数据流,用户可以方便地对分布式数据流进行各种操作,支持Java和Scala。

Table API,对结构化数据进行查询操作,将结构化数据抽象成关系表,并通过类SQL的DSL对关系表进行各种查询操作,支持Java和Scala。

此外,Flink 还针对特定的应用领域提供了领域库,例如: Flink ML,Flink 的机器学习库,提供了机器学习Pipelines API并实现了多种机器学习算法。 Gelly,Flink 的图计算库,提供了图计算的相关API及多种图计算算法实现。

1.14.2 Flink跟Spark Streaming的区别

这个问题是一个非常宏观的问题,因为两个框架的不同点非常之多。但是在面试时有非常重要的一点一定要回答出来:Flink 是标准的实时处理引擎,基于事件驱动。而 Spark Streaming 是微批(Micro-Batch)的模型。

下面我们就分几个方面介绍两个框架的主要区别:

1)架构模型Spark Streaming 在运行时的主要角色包括:Master、Worker、Driver、Executor,Flink 在运行时主要包含:Jobmanager、Taskmanager和Slot。

2)任务调度Spark Streaming 连续不断的生成微小的数据批次,构建有向无环图DAG,Spark Streaming 会依次创建 DStreamGraph、JobGenerator、JobScheduler。Flink 根据用户提交的代码生成 StreamGraph,经过优化生成 JobGraph,然后提交给 JobManager进行处理,JobManager 会根据 JobGraph 生成 ExecutionGraph,ExecutionGraph 是 Flink 调度最核心的数据结构,JobManager 根据 ExecutionGraph 对 Job 进行调度。

3)时间机制Spark Streaming 支持的时间机制有限,只支持处理时间。 Flink 支持了流处理程序在时间上的三个定义:处理时间、事件时间、注入时间。同时也支持 watermark 机制来处理滞后数据。

4)容错机制对于 Spark Streaming 任务,我们可以设置 checkpoint,然后假如发生故障并重启,我们可以从上次 checkpoint 之处恢复,但是这个行为只能使得数据不丢失,可能会重复处理,不能做到恰好一次处理语义。Flink 则使用两阶段提交协议来解决这个问题。

1.14.3 Flink集群有哪些角色?各自有什么作用?

大数据技术之高频面试题|剑谱_Shell_23

Flink程序在运行时主要有TaskManager,JobManager,Client三种角色。

JobManager扮演着集群中的管理者Master的角色,它是整个集群的协调者,负责接收Flink Job,协调检查点,Failover 故障恢复等,同时管理Flink集群中从节点TaskManager。

TaskManager是实际负责执行计算的Worker,在其上执行Flink Job的一组Task,每个TaskManager负责管理其所在节点上的资源信息,如内存、磁盘、网络,在启动的时候将资源的状态向JobManager汇报。

Client是Flink程序提交的客户端,当用户提交一个Flink程序时,会首先创建一个Client,该Client首先会对用户提交的Flink程序进行预处理,并提交到Flink集群中处理,所以Client需要从用户提交的Flink程序配置中获取JobManager的地址,并建立到JobManager的连接,将Flink Job提交给JobManager。

1.14.4 Flink的编程模型是什么?

Environment -> Source -> Transform -> Sink

分层API

1.14.5 公司怎么提交的实时任务,有多少Job Manager? 有多少TaskManager?

1)我们使用yarn per-job模式提交任务

2)集群默认只有一个 Job Manager。但为了防止单点故障,我们配置了高可用。对于standlone模式,我们公司一般配置一个主 Job Manager,两个备用 Job Manager,然后结合 ZooKeeper 的使用,来达到高可用;对于yarn模式,yarn在Job Mananger故障会自动进行重启,所以只需要一个,我们配置的最大重启次数是10次。

3)基于yarn,动态申请TaskManager的数量

1.14.6 Flink的并行度了解吗?Flink的并行度设置是怎样的?

Flink中的任务被分为多个并行任务来执行,其中每个并行的实例处理一部分数据。这些并行实例的数量被称为并行度。我们在实际生产环境中可以从四个不同层面设置并行度:

操作算子层面(Operator Level)

执行环境层面(Execution Environment Level)

客户端层面(Client Level)

系统层面(System Level)

需要注意的优先级:算子层面>环境层面>客户端层面>系统层面。

并行度的设置:一般设为kafka的分区数,达到1:1

遵循2的n次方:比如2、4、8、16…..

1.14.7 Flink的keyby怎么实现的分区?分区、分组的区别是什么?

Keyby实现原理:

对指定的key调用自身的hashCode方法=》hash1

调用murmruhash算法,进行第二次hash =》键组ID

通过一个公式,计算出当前数据应该去往哪个下游分区:

键组id * 下游算子并行度 / 最大并行度(默认128)

分区:算子的一个并行实例可以理解成一个分区,是物理上的资源

分组:数据根据key进行区分,是一个逻辑上的划分

一个分区可以有多个分组,同一个分组的数据肯定在同一个分区

1.14.8 Flink的interval join的实现原理?join不上的怎么办?

底层调用的是keyby+connect ,处理逻辑:

1)判断是否迟到(迟到就不处理了)

2)每条流都存了一个Map类型的状态(key是时间戳,value是List存数据)

3)任一条流,来了一条数据,遍历对方的map状态,能匹配上就发往join方法

4)超过有效时间范围,会删除对应Map中的数据(不是clear,是remove)

Interval join不会处理join不上的数据,如果需要没join上的数据,可以用 coGroup+connect算子实现,或者直接使用flinksql里的left join或right join语法。

1.14.9 介绍一下Flink的状态编程、状态机制?

算子状态:作用范围是算子,算子的多个并行实例各自维护一个状态

键控状态:每个分组维护一个状态

状态后端:两件事=》 本地状态存哪里、checkpoint存哪里

本地状态 Checkpoint

内存 TaskManager的内存 JobManager内存

文件 TaskManager的内存 HDFS

RocksDB RocksDB HDFS

1.14.10 Flink的三种时间语义

Event Time:是事件创建的时间。它通常由事件中的时间戳描述,例如采集的日志数据中,每一条日志都会记录自己的生成时间,Flink通过时间戳分配器访问事件时间戳。

Ingestion Time:是数据进入Flink的时间。

Processing Time:是每一个执行基于时间操作的算子的本地系统时间,与机器相关,默认的时间属性就是Processing Time。

1.14.11 Flink 中的Watermark机制

1)Watermark 是一种衡量 Event Time 进展的机制,可以设定延迟触发

2)Watermark 是用于处理乱序事件的,而正确的处理乱序事件,通常用Watermark 机制结合 window 来实现;

3)基于事件时间,用来触发窗口、定时器等

4)watermark主要属性就是时间戳,可以理解一个特殊的数据,插入到流里面

5)watermark是单调不减的

6)数据流中的 Watermark 用于表示 timestamp 小于 Watermark 的数据,都已经到达了,如果后续还有timestamp 小于 Watermark 的数据到达,称为迟到数据

1.14.12 Watermark是数据吗?怎么生成的?怎么传递的?

Watermark是一条携带时间戳的特殊数据,从代码指定生成的位置,插入到流里面。

一对多:广播

多对一:取最小

多对多:拆分来看,其实就是上面两种的结合

1.14.13 Watermark的生成方式?

间歇性:来一条数据,更新一次watermark

周期性:固定周期更新watermark

官方提供的api是基于周期的,默认200ms,因为间歇性会给系统带来压力。

Watermark=当前最大事件时间-乱序时间-1ms

1.14.14 说说Flink中的窗口(分类、生命周期、触发、划分)

1)窗口分类: Keyed Window和Non-keyed Window

基于时间:滚动、滑动、会话

基于数量:滚动、滑动

2)Window口的4个相关重要组件:

  • assigner(分配器):如何将元素分配给窗口
  • function(计算函数):为窗口定义的计算。其实是一个计算函数,完成窗口内容的计算。
  • triger(触发器):在什么条件下触发窗口的计算
  • evictor(退出器):定义从窗口中移除数据

3)窗口的划分:如,基于事件时间的滚动窗口

start=按照数据的事件时间向下取窗口长度的整数倍

end=start+size

比如开了一个10s的滚动窗口,第一条数据是857s,那么它属于[850s,860s)

4)窗口的创建:当属于某个窗口的第一个元素到达,Flink就会创建一个窗口,

5)窗口的销毁:当时间超过其结束时间+用户指定的允许延迟时间(Flink保证只删除基于时间的窗口,而不能删除其他类型的窗口,例如全局窗口)。

6)窗口为什么左闭右开:属于窗口的最大时间戳=end-1ms

7)窗口什么时候触发:如基于事件时间的窗口 watermark>=end-1ms

1.14.15 Exactly-Once的保证

一般说的是端到端一致性,要考虑source和sink:

Source:可重发

Flink内部:Checkpoint机制(介绍Chandy-Lamport算法、barrier对齐)

Sink:幂等性 或 事务性 写入

我们使用的Source和Sink主要是Kafka:

作为source可以重发,由Flink维护offset,作为状态存储

作为sink官方的实现类是基于两阶段提交,能保证写入的Exactly-Once

如果下级存储不支持事务:

具体实现是幂等写入,需要下级存储具有幂等性写入特性。

比如结合HBase的rowkey的唯一性、数据的多版本,实现幂等

1.14.16 Flink分布式快照的原理是什么

Flink的容错机制的核心部分是制作分布式数据流和操作算子状态的一致性快照。 这些快照充当一致性checkpoint,系统可以在发生故障时回滚。 Flink用于制作这些快照的机制在“分布式数据流的轻量级异步快照”中进行了描述。 它受到分布式快照的标准Chandy-Lamport算法的启发,专门针对Flink的执行模型而定制。


大数据技术之高频面试题|剑谱_数据_24

barriers在数据流源处被注入并行数据流中。快照n的barriers被插入的位置(我们称之为Sn)是快照所包含的数据在数据源中最大位置。

例如,在Apache Kafka中,此位置将是分区中最后一条记录的偏移量。 将该位置Sn报告给checkpoint协调器(Flink的JobManager)。

然后barriers向下游流动。当一个中间操作算子从其所有输入流中收到快照n的barriers时,它会为快照n发出barriers进入其所有输出流中。

一旦sink操作算子(流式DAG的末端)从其所有输入流接收到barriers n,它就向checkpoint协调器确认快照n完成。

在所有sink确认快照后,意味快照着已完成。一旦完成快照n,job将永远不再向数据源请求Sn之前的记录,因为此时这些记录(及其后续记录)将已经通过整个数据流拓扑,也即是已经被处理结束。

1.14.17 Checkpoint的参数怎么设置的?

1)间隔、语义: 1min~10min,3min,语义默认精准一次

因为一些异常原因可能导致某些barrier无法向下游传递,造成job失败,对于一些时效性要求高、精准性要求不是特别严格的指标,可以设置为至少一次。

2)超时 : 参考间隔, 0.5~2倍之间, 建议0.5倍

3)最小等待间隔:上一次ck结束 到 下一次ck开始 之间的时间间隔,设置间隔的0.5倍

4)设置保存ck:Retain

5)失败次数:5

6)Task重启策略(Failover):

固定延迟重启策略: 重试几次、每次间隔多久

失败率重启策略: 重试次数、重试区间、重试间隔

1.14.18 介绍一下Flink的CEP机制

CEP全称为Complex Event Processing,复杂事件处理

Flink CEP是在 Flink 中实现的复杂事件处理(CEP)库

CEP 允许在无休止的事件流中检测事件模式,让我们有机会掌握数据中重要的部分

一个或多个由简单事件构成的事件流通过一定的规则匹配,然后输出用户想得到的数据 —— 满足规则的复杂事件

1.14.19 Flink CEP 编程中当状态没有到达的时候会将数据保存在哪里?

在流式处理中,CEP 当然是要支持 EventTime 的,那么相对应的也要支持数据的迟到现象,也就是watermark的处理逻辑。CEP对未匹配成功的事件序列的处理,和迟到数据是类似的。在 Flink CEP的处理逻辑中,状态没有满足的和迟到的数据,都会存储在一个Map数据结构中,也就是说,如果我们限定判断事件序列的时长为5分钟,那么内存中就会存储5分钟的数据,这在我看来,也是对内存的极大损伤之一。

1.14.20 Flink SQL的工作机制?

通过Calcite对编写的 Sql 进行解析、验证、优化等操作。

大数据技术之高频面试题|剑谱_数据_25

Blink Planner与Calcite进行对接,对接流程如下:

大数据技术之高频面试题|剑谱_数据_26

1)在Table/SQL 编写完成后,通过Calcite 中的parse、validate、rel阶段,以及Blink额外添加的convert阶段,将其先转为Operation

2)通过Blink Planner 的translateToRel、optimize、translateToExecNodeGraph和translateToPlan四个阶段,将Operation转换成DataStream API的 Transformation

3)再经过StreamJraph -> JobGraph -> ExecutionGraph等一系列流程,SQL最终被提交到集群。

1.14.21 FlinkSQL怎么对SQL语句进行优化的?

会使用两个优化器:RBO(基于规则的优化器) 和 CBO(基于代价的优化器)

1)RBO(基于规则的优化器)会将原有表达式裁剪掉,遍历一系列规则(Rule),只要满足条件就转换,生成最终的执行计划。一些常见的规则包括分区裁剪(Partition Prune)、列裁剪、谓词下推(Predicate Pushdown)、投影下推(Projection Pushdown)、聚合下推、limit下推、sort下推、常量折叠(Constant Folding)、子查询内联转join等。

2)CBO(基于代价的优化器)会将原有表达式保留,基于统计信息和代价模型,尝试探索生成等价关系表达式,最终取代价最小的执行计划。CBO的实现有两种模型,Volcano模型,Cascades模型。这两种模型思想很是相似,不同点在于Cascades模型一边遍历SQL逻辑树,一边优化,从而进一步裁剪掉一些执行计划。

1.14.22 Flink提交流程、组件通讯、调度机制、任务执行、内存模型(重点)

0.1 Flink提交流程(Yarn-Per-Job)

0.2 Flink通讯过程

0.3 Task调度

大数据技术之高频面试题|剑谱_数据_27

0.4 内存模型



1.14.23 Flink优化、背压、数据倾斜(重点)


1.14.24 Flink常见的维表Join方案

1)预加载: open()方法,查询维表,存储下来 ==》 定时查询

2)热存储: 存在外部系统redis、hbase等

缓存

异步查询: 异步io功能

3)广播维表

4)Temporal join:外部存储,connector创建

第2章 项目架构

2.1 提高自信

云上数据仓库解决方案:https://www.aliyun.com/solution/datavexpo/datawarehouse

大数据技术之高频面试题|剑谱_Shell_28

大数据技术之高频面试题|剑谱_数据_29

2.2 数仓概念

数据仓库的输入数据源和输出系统分别是什么?

输入系统:埋点产生的用户行为数据、JavaEE后台产生的业务数据、个别公司有爬虫数据。

输出系统:报表系统、用户画像系统、推荐系统

2.3 系统数据流程设计


2.4 框架版本选型

1)Apache:运维麻烦,组件间兼容性需要自己调研。(一般大厂使用,技术实力雄厚,有专业的运维人员)

2)CDH6.3.2:国内使用最多的版本,但 CM不开源,但其实对中、小公司使用来说没有影响(建议使用)10000美金一个节点 CDP7.0

3)HDP:开源,可以进行二次开发,但是没有CDH稳定,国内使用较少

4)云服务选择

(1)阿里云的EMR、MaxCompute、DataWorks

(2)亚马逊云EMR

(3)腾讯云EMR

(4)华为云EMR

Apache框架版本

产品

旧版本

新版本

版本新增

Hadoop

2.7.2

3.1.3

HDFS的web端口号由50070变为9870,

客户端访问端口号9820/8020/9000

Zookeeper

3.4.10

3.5.7

Mysql

5.6.24

5.7.16

①原生json支持:不需要遍历所有字符串、通过虚拟列的功能对json的数据进行索引

②多源复制:多主一从

③InnoDB优化:为innoDB_buffer_pool_size、

innoDB_log_file_size、innoDB_flush_method提供了更加合适的默认值

Hive

1.2.1

3.1.2

(没有查到,查到的都是说不再支持mr引擎)

Flume

1.7.0

1.9.0

Kafka

0.11-0.2

_2.11-2.4.1、2.8.0、3.0.0

①kafka 0.9版本之前,offset保存在Zookeeper中;从0.9版本开始,consumer自己维护了一个offset

②允许使用者从最近的副本中获取

Kafka Eagle

1.3.7

1.4.5

Azkaban

2.5.0

3.84.4

集成了给用户打电话的功能

Spark

2.1.1

3.0.0

不支持scala 2.11.x,升级为2.12.x

Hbase

1.3.1

2.0.5

Phoenix

4.14.1

5.0.0

支持Apache HBase 2.0

Redis

3.2.5

3.2.5

Canal

1.1.2

1.1.2

ElasticSearch+Kibana

6.3.1

6.3.1

Azkaban、hive、kylin需要重新编译


2.5 服务器选型

服务器使用物理机还是云主机?

1)机器成本考虑:

(1)物理机:以128G内存,20核物理CPU,40线程,8THDD和2TSSD硬盘,单台报价4W出头,惠普品牌。一般物理机寿命5年左右。

(2)云主机,以阿里云为例,差不多相同配置,每年5W

2)运维成本考虑:

(1)物理机:需要有专业的运维人员(1万*13个月)、电费(商业用户)、安装空调、场地

(2)云主机:很多运维工作都由阿里云已经完成,运维相对较轻松

3)企业选择

(1)金融有钱公司选择阿里云(上海)

(2)中小公司、为了融资上市,选择阿里云,拉到融资后买物理机。

(3)有长期打算,资金比较足,选择物理机。

2.6 集群规模


20核物理CPU 40线程 * 7 = 280线程

内存128g * 7台 = 896g (计算任务内存700g,其他安装框架需要内存)

128m =》1g内存

=》

87g数据 、700g内存

根据数据规模大家集群(在企业,干了三年 通常服务器集群 5-20台之间)

1

2

3

4

5

6

7

8

9

10

nn

nn

dn

dn

dn

dn

dn

dn

dn

dn



rm

rm

nm

nm

nm

nm

nm

nm



nm

nm














zk

zk

zk








kafka

kafka

kafka








Flume

Flume

flume



Hbase

Hbase

Hbase






hive

hive









mysql

mysql









spark

spark









Azkaban

Azkaban




ES

ES




1)消耗内存的分开;

2)kafka 、zk 、flume 传输数据比较紧密的放在一起;

3)客户端尽量放在一到两台服务器上,方便外部访问;

4)有依赖关系的尽量放到同一台服务器(例如:Hive和Azkaban Executor)

2.7 人员配置参考

2.7.1 整体架构

属于研发部/技术部/数据部,我们属于大数据组,其他还有后端项目组,前端组、测试组、UI组等。其他的还有产品部、运营部、人事部、财务部、行政部等。

大数据开发工程师=>大数据组组长=》项目经理=>部门经理=》技术总监CTO

2.7.2 你们部门的职级等级,晋升规则

职级就分初级,中级,高级。晋升规则不一定,看公司效益和职位空缺。

京东:T1、T2应届生;T3 14k左右 T4 18K左右 T5 24k-28k左右

阿里:p5、p6、p7、p8

字节:

大数据技术之高频面试题|剑谱_Shell_30

2.7.3 人员配置参考

小型公司(1-3人左右):组长1人,剩余组员无明确分工,并且可能兼顾javaEE和前端。

中小型公司(3~6人左右):组长1人,离线2人左右,实时1人左右(离线一般多于实时),组长兼顾和JavaEE、前端。

中型公司(5~10人左右):组长1人,离线3~5人左右(离线处理、数仓),实时2人左右,组长和技术大牛兼顾和javaEE、前端。

中大型公司(10~20人左右):组长1人,离线5~10人(离线处理、数仓),实时5人左右,JavaEE1人左右(负责对接JavaEE业务),前端1人(有或者没有人单独负责前端)。(发展比较良好的中大型公司可能大数据部门已经细化拆分,分成多个大数据组,分别负责不同业务)

上面只是参考配置,因为公司之间差异很大,例如ofo大数据部门只有5个人左右,因此根据所选公司规模确定一个合理范围,在面试前必须将这个人员配置考虑清楚,回答时要非常确定。

IOS多少人 安卓多少人 前端多少人 JavaEE多少人 测试多少人

(IOS、安卓) 1-2个人 前端3个人; JavaEE一般是大数据的1-1.5倍,测试:有的有,有的没有。1个左右。 产品经理1个、产品助理1-2个,运营1-3个

公司划分:

0-50 小公司

50-500 中等

500-1000 大公司

1000以上 大厂 领军的存在

第3章 数仓分层

3.1 数据仓库建模(绝对重点)

3.1.1 建模工具是什么?

PowerDesigner/SQLYog/EZDML

3.1.2 ODS层

(1)保持数据原貌不做任何修改,起到备份数据的作用。

(2)数据采用压缩,减少磁盘存储空间(例如:原始数据100G,可以压缩到10G左右)

(3)创建分区表,防止后续的全表扫描

3.1.3 DWD层

DWD层需构建维度模型,一般采用星型模型,呈现的状态一般为星座模型。

维度建模一般按照以下四个步骤:

选择业务过程→声明粒度→确认维度→确认事实

(1)选择业务过程

在业务系统中,如果业务表过多,挑选我们感兴趣的业务线,比如下单业务,支付业务,退款业务,物流业务,一条业务线对应一张事实表。如果小公司业务表比较少,建议选择所有业务线。

(2)声明粒度

数据粒度指数据仓库的数据中保存数据的细化程度或综合程度的级别。

声明粒度意味着精确定义事实表中的一行数据表示什么,应该尽可能选择最小粒度,以此来应各种各样的需求。

典型的粒度声明如下:

订单当中的每个商品项作为下单事实表中的一行,粒度为每次

每周的订单次数作为一行,粒度为每周。

每月的订单次数作为一行,粒度为每月。

如果在DWD层粒度就是每周或者每月,那么后续就没有办法统计细粒度的指标了。所有建议采用最小粒度。

(3)确定维度

维度的主要作用是描述业务是事实,主要表示的是“谁,何处,何时”等信息。例如:时间维度、用户维度、地区维度等常见维度。

(4)确定事实

此处的“事实”一词,指的是业务中的度量值,例如订单金额、下单次数等。

在DWD层,以业务过程为建模驱动,基于每个具体业务过程的特点,构建最细粒度的明细层事实表。事实表可做适当的宽表化处理。

通过以上步骤,结合本数仓的业务事实,得出业务总线矩阵表如下表所示。业务总线矩阵的原则,主要是根据维度表和事实表之间的关系,如果两者有关联则使用√标记。

表 业务总线矩阵表


时间

用户

地区

商品

优惠券

活动

编码

度量值

订单




件数/金额

订单详情





件数/金额

支付






次数/金额

加购





件数/金额

收藏





个数

评价





个数

退款





件数/金额

优惠券领用





个数


根据维度建模中的星型模型思想,将维度进行退化。例如下图所示:地区表和省份表退化为地区维度表,商品表、品类表、spu表、商品三级分类、商品二级分类、商品一级分类表退化为商品维度表,活动信息表和活动规则表退化为活动维度表。

大数据技术之高频面试题|剑谱_Shell_31

至此,数仓的维度建模已经完毕,DWS、DWT和ADS和维度建模已经没有关系了。

DWS和DWT都是建宽表,宽表都是按照主题去建。主题相当于观察问题的角度。对应着维度表。

3.1.4 DWS层

DWS层统计各个主题对象的当天行为,服务于DWT层的主题宽表。如图所示,DWS层的宽表字段,是站在不同维度的视角去看事实表,重点关注事实表的度量值,通过与之关联的事实表,获得不同的事实表的度量值。

大数据技术之高频面试题|剑谱_数据倾斜_32

3.1.5 DWT层

以分析的主题对象为建模驱动,基于上层的应用和产品的指标需求,构建主题对象的全量宽表

DWT层主题宽表都记录什么字段?

如图所示,每个维度关联的不同事实表度量值以及首次、末次时间、累积至今的度量值、累积某个时间段的度量值。

大数据技术之高频面试题|剑谱_数据_33

3.1.6 ADS层

分别对设备主题、会员主题、商品主题和营销主题进行指标分析,其中营销主题是用户主题和商品主题的跨主题分析案例

3.2 ODS层做了哪些事?

1)保持数据原貌,不做任何修改

2)压缩采用LZO,压缩比是100g数据压缩完10g左右。

3)创建分区表

3.3 DWD层做了哪些事?

3.3.1 数据清洗

(1)空值去除

(2)过滤核心字段无意义的数据,比如订单表中订单id为null,支付表中支付id为空

(3)将用户行为宽表和业务表进行数据一致性处理

select case when a is null then b else a end as JZR,

...

from A

3.3.2 清洗的手段

HQL、MR、SparkSQL、Kettle、Python(项目中采用sql进行清除)

3.3.3 清洗掉多少数据算合理

1万条数据清洗掉1条。

3.3.4 脱敏

对手机号、身份证号等敏感数据脱敏

3.3.5 维度退化

对业务数据传过来的表进行维度退化和降维。(商品一级二级三级、省市县、年月日)

商品表、spu表、品类表、商品一级分类、二级分类、三级分类=》商品表

省份表、地区表=》地区表

3.3.6 压缩LZO

3.3.7 列式存储parquet

3.4 DWS层做了哪些事?

3.4.1 DWS层有6张宽表(处理100-200个指标 70%以上的需求)

具体宽表名称:用户行为宽表,商品宽表,访客宽表、活动宽表、优惠卷、地区表等。

3.4.2 哪个宽表最宽?大概有多少个字段?

最宽的是用户行为宽表。大概有60-100个字段

3.4.3 具体用户行为宽表字段名称

评论、打赏、收藏、关注--商品、关注--人、点赞、分享、好价爆料、文章发布、活跃、签到、补签卡、幸运屋、礼品、金币、电商点击、gmv

CREATE TABLE `app_usr_interact`(

`stat_dt` date COMMENT '互动日期',

`user_id` string COMMENT '用户id',

`nickname` string COMMENT '用户昵称',

`register_date` string COMMENT '注册日期',

`register_from` string COMMENT '注册来源',

`remark` string COMMENT '细分渠道',

`province` string COMMENT '注册省份',

`pl_cnt` bigint COMMENT '评论次数',

`ds_cnt` bigint COMMENT '打赏次数',

`sc_add` bigint COMMENT '添加收藏',

`sc_cancel` bigint COMMENT '取消收藏',

`gzg_add` bigint COMMENT '关注商品',

`gzg_cancel` bigint COMMENT '取消关注商品',

`gzp_add` bigint COMMENT '关注人',

`gzp_cancel` bigint COMMENT '取消关注人',

`buzhi_cnt` bigint COMMENT '点不值次数',

`zhi_cnt` bigint COMMENT '点值次数',

`zan_cnt` bigint COMMENT '点赞次数',

`share_cnts` bigint COMMENT '分享次数',

`bl_cnt` bigint COMMENT '爆料数',

`fb_cnt` bigint COMMENT '好价发布数',

`online_cnt` bigint COMMENT '活跃次数',

`checkin_cnt` bigint COMMENT '签到次数',

`fix_checkin` bigint COMMENT '补签次数',

`house_point` bigint COMMENT '幸运屋金币抽奖次数',

`house_gold` bigint COMMENT '幸运屋积分抽奖次数',

`pack_cnt` bigint COMMENT '礼品兑换次数',

`gold_add` bigint COMMENT '获取金币',

`gold_cancel` bigint COMMENT '支出金币',

`surplus_gold` bigint COMMENT '剩余金币',

`event` bigint COMMENT '电商点击次数',

`gmv_amount` bigint COMMENT 'gmv',

`gmv_sales` bigint COMMENT '订单数')

PARTITIONED BY ( `dt` string)

3.4.4 哪张表数据量最多

用户行为宽表:

dws =》 6张

100g =》 40g / 6 = 6.7 g 2* 3倍 = 15g =》 1500万条

3.5 ADS层分析过哪些指标

3.5.1 分析过的指标(一分钟至少说出30个指标)

日活、月活、周活、留存、留存率、新增(日、周、年)、转化率、流失、回流、七天内连续3天登录(点赞、收藏、评价、购买、加购、下单、活动)、连续3周(月)登录、GMV、复购率、复购率排行、点赞、评论、收藏、领优惠卷人数、使用优惠卷人数、沉默、值不值得买、退款人数、退款率 topn 热门商品

产品经理最关心的:留转G复活

大数据技术之高频面试题|剑谱_Shell_34

3.5.2 留转G复活指标

(1)活跃

日活:100万 ;周活是日活的1.1-1.3倍;月活:是日活的2-3倍 300万

总注册的用户多少?1000万-3000万之间

渠道来源:app 公众号 抖音 百度 36 头条 地推

(2)GMV

GMV:每天 10万订单 (50 – 100元) 500万-1000万

10%-20% 100万-200万(人员:程序员、人事、行政、财务、房租、收电费)

(3)复购率

某日常商品复购;(手纸、面膜、牙膏)10%-20%

电脑、显示器、手表 1%

(4)转化率

商品详情 =》 加购物车 =》下单 =》 支付

5%-10% 60-70% 90%-95%

(5)留存率

1/2/3、周留存、月留存

搞活动: 10-20%

3.5.3 哪个商品卖的好?

面膜、手纸,每天销售5000个

3.6 ADS层手写指标

3.6.1 如何分析用户活跃?

在启动日志中统计不同设备id出现次数。去重

3.6.2 如何分析用户新增?vivo

用活跃用户表 left join 用户新增表,用户新增表中mid为空的即为用户新增。

3.6.3 如何分析用户1天留存?

留存用户=前一天新增 join 今天活跃

用户留存率=留存用户/前一天新增

3.6.4 如何分析沉默用户?

(登录时间为7天前,且只出现过一次)

按照设备id对日活表分组,登录次数为1,且是在一周前登录。

3.6.5 如何分析本周回流用户?

本周活跃left join本周新增 left join上周活跃,且本周新增id和上周活跃id都为null

3.6.6 如何分析流失用户?

(登录时间为7天前)

按照设备id对日活表分组,且七天内没有登录过。

3.6.7 如何分析最近连续3周活跃用户数?

按照设备id对周活进行分组,统计次数大于3次。

3.6.8 如何分析最近七天内连续三天活跃用户数?

1)查询出最近7天的活跃用户,并对用户活跃日期进行排名

2)计算用户活跃日期及排名之间的差值

3)对同用户及差值分组,统计差值个数

4)将差值相同个数大于等于3的数据取出,然后去重(去的是什么重???),即为连续3天及以上活跃的用户

7天连续收藏、点赞、购买、加购、付款、浏览、商品点击、退货

1个月连续7天

连续两周:

3.7 分析过最难的指标

3.7.1 最近连续3周活跃用户


3.7.2 最近7天连续3天活跃用户数


3.7.3 运费分摊

3.7.4 城市备注

第4章 生产经验—业务

4.1 电商常识

4.1.1 SKU和SPU

SKU:一台银色、128G内存的、支持联通网络的iPhoneX

SPU:iPhoneX

Tm_id:品牌Id苹果,包括IPHONE,耳机,MAC等

4.1.2 订单表跟订单详情表区别?

订单表的订单状态会变化,订单详情表不会,因为没有订单状态。

订单表记录user_id,订单id订单编号,订单的总金额order_status,支付方式,订单状态等。

订单详情表记录user_id,商品sku_id ,具体的商品信息(商品名称sku_name,价格order_price,数量sku_num)

4.2 埋点行为数据基本格式(基本字段)

我们要收集和分析的数据主要包括页面数据事件数据、曝光数据、启动数据和错误数据

4.2.1 页面

页面数据主要记录一个页面的用户访问情况,包括访问时间、停留时间、页面路径等信息。

大数据技术之高频面试题|剑谱_数据_35

所有页面id如下

home("首页"),

category("分类页"),

discovery("发现页"),

top_n("热门排行"),

favor("收藏页"),

search("搜索页"),

good_list("商品列表页"),

good_detail("商品详情"),

good_spec("商品规格"),

comment("评价"),

comment_done("评价完成"),

comment_list("评价列表"),

cart("购物车"),

trade("下单结算"),

payment("支付页面"),

payment_done("支付完成"),

orders_all("全部订单"),

orders_unpaid("订单待支付"),

orders_undelivered("订单待发货"),

orders_unreceipted("订单待收货"),

orders_wait_comment("订单待评价"),

mine("我的"),

activity("活动"),

login("登录"),

register("注册");

所有页面对象类型如下:

sku_id("商品skuId"),

keyword("搜索关键词"),

sku_ids("多个商品skuId"),

activity_id("活动id"),

coupon_id("购物券id");

所有来源类型如下:

promotion("商品推广"),

recommend("算法推荐商品"),

query("查询结果商品"),

activity("促销活动");

4.2.2 事件

事件数据主要记录应用内一个具体操作行为,包括操作类型、操作对象、操作对象描述等信息。

大数据技术之高频面试题|剑谱_数据倾斜_36

所有动作类型如下:

favor_add("添加收藏"),

favor_canel("取消收藏"),

cart_add("添加购物车"),

cart_remove("删除购物车"),

cart_add_num("增加购物车商品数量"),

cart_minus_num("减少购物车商品数量"),

trade_add_address("增加收货地址"),

get_coupon("领取优惠券");

注:对于下单、支付等业务数据,可从业务数据库获取。

所有动作目标类型如下:

sku_id("商品"),

coupon_id("购物券");

4.2.3 曝光

曝光数据主要记录页面所曝光的内容,包括曝光对象,曝光类型等信息。

大数据技术之高频面试题|剑谱_数据_37

所有曝光类型如下:

promotion("商品推广"),

recommend("算法推荐商品"),

query("查询结果商品"),

activity("促销活动");

所有曝光对象类型如下:

sku_id("商品skuId"),

activity_id("活动id");

4.2.4 启动

启动数据记录应用的启动信息。

大数据技术之高频面试题|剑谱_数据_38

所有启动入口类型如下:

icon("图标"),

notification("通知"),

install("安装后启动");

4.2.5 错误

错误数据记录应用使用过程中的错误信息,包括错误编号及错误信息。

4.2.6 埋点数据日志格式

我们的日志结构大致可分为两类,一是普通页面埋点日志,二是启动日志。

普通页面日志结构如下,每条日志包含了,当前页面的页面信息,所有事件(动作)、所有曝光信息以及错误信息。除此之外,还包含了一系列公共信息,包括设备信息,地理位置,应用信息等,即下边的common字段。

{

"common": { -- 公共信息

"ar": "230000", -- 地区编码

"ba": "iPhone", -- 手机品牌

"ch": "Appstore", -- 渠道

"md": "iPhone 8", -- 手机型号

"mid": "YXfhjAYH6As2z9Iq", -- 设备id

"os": "iOS 13.2.9", -- 操作系统

"uid": "485", -- 会员id

"vc": "v2.1.134" -- app版本号

},

"actions": [ --动作(事件)

{

"action_id": "favor_add", --动作id

"item": "3", --目标id

"item_type": "sku_id", --目标类型

"ts": 1585744376605 --动作时间戳

}

],

"displays": [

{

"displayType": "query", -- 曝光类型

"item": "3", -- 曝光对象id

"item_type": "sku_id", -- 曝光对象类型

"order": 1 --出现顺序

},

{

"displayType": "promotion",

"item": "6",

"item_type": "sku_id",

"order": 2

},

{

"displayType": "promotion",

"item": "9",

"item_type": "sku_id",

"order": 3

},

{

"displayType": "recommend",

"item": "6",

"item_type": "sku_id",

"order": 4

},

{

"displayType": "query ",

"item": "6",

"item_type": "sku_id",

"order": 5

}

],

"page": { --页面信息

"during_time": 7648, -- 持续时间毫秒

"item": "3", -- 目标id

"item_type": "sku_id", -- 目标类型

"last_page_id": "login", -- 上页类型

"page_id": "good_detail", -- 页面ID

"sourceType": "promotion" -- 来源类型

},

"err":{ --错误

"error_code": "1234", --错误码

"msg": "***********" --错误信息

},

"ts": 1585744374423 --跳入时间戳

}

启动日志结构相对简单,主要包含公共信息,启动信息和错误信息。

{

"common": {

"ar": "370000",

"ba": "Honor",

"ch": "wandoujia",

"md": "Honor 20s",

"mid": "eQF5boERMJFOujcp",

"os": "Android 11.0",

"uid": "76",

"vc": "v2.1.134"

},

"start": {

"entry": "icon", --icon手机图标 notice 通知 install 安装后启动

"loading_time": 18803, --启动加载时间

"open_ad_id": 7, --广告页ID

"open_ad_ms": 3449, -- 广告总共播放时间

"open_ad_skip_ms": 1989 -- 用户跳过广告时点

},

"err":{ --错误

"error_code": "1234", --错误码

"msg": "***********" --错误信息

},

"ts": 1585744304000

}

4.3 电商业务流程

1)记住表与表之间的关系

2)每个表记住2-3个字段

大数据技术之高频面试题|剑谱_数据_39

4.4 维度表和事实表(重点)

4.4.1 维度表

维度表:一般是对事实的描述信息。每一张维表对应现实世界中的一个对象或者概念。 例如:用户、商品、日期、地区等。

4.4.2 事实表

事实表中的每行数据代表一个业务事件(下单、支付、退款、评价等)。“事实”这个术语表示的是业务事件的度量值(可统计次数、个数、件数、金额等),例如,订单事件中的下单金额。

每一个事实表的行包括:具有可加性的数值型的度量值、与维表相连接的外键、通常具有两个和两个以上的外键、外键之间表示维表之间多对多的关系。

1)事务型事实表

每个事务或事件为单位,例如一个销售订单记录,一笔支付记录等,作为事实表里的一行数据。一旦事务被提交,事实表数据被插入,数据就不再进行更改,其更新方式为增量更新。

2)周期型快照事实表

周期型快照事实表中不会保留所有数据只保留固定时间间隔的数据,例如每天或者每月的销售额,或每月的账户余额等。

3)累积型快照事实表

累计快照事实表用于跟踪业务事实的变化。例如,数据仓库中可能需要累积或者存储订单从下订单开始,到订单商品被打包、运输、和签收的各个业务阶段的时间点数据来跟踪订单声明周期的进展情况。当这个业务过程进行时,事实表的记录也要不断更新。

订单id

用户id

下单时间

打包时间

发货时间

签收时间

订单金额



3-8

3-8

3-9

3-10


4.5 同步策略(重点)

大数据技术之高频面试题|剑谱_数据倾斜_40

4.6 关系型数据库范式理论(ER建模)

1NF:属性不可再分割(例如不能存在5台电脑的属性,坏处:表都没法用)

2NF:不能存在部分函数依赖(例如主键(学号+课名)-->成绩,姓名,但学号--》姓名,所以姓名部分依赖于主键(学号+课名),所以要去除,坏处:数据冗余)

3NF:不能存在传递函数依赖(学号--》宿舍种类--》价钱,坏处:数据冗余和增删异常)

MySQL关系模型:关系模型主要应用与OLTP系统中,为了保证数据的一致性以及避免冗余,所以大部分业务系统的表都是遵循第三范式的。

Hive 维度模型:维度模型主要应用于OLAP系统中,因为关系模型虽然冗余少,但是在大规模数据,跨表分析统计查询过程中,会造成多表关联,这会大大降低执行效率。所以HIVE把相关各种表整理成两种:事实表和维度表两种。所有维度表围绕着事实表进行解释。

4.7 数据模型

雪花模型、星型模型和星座模型

(在维度建模的基础上又分为三种模型:星型模型、雪花模型、星座模型)

星型模型(一级维度表),雪花(多级维度),星座模型(星型模型+多个事实表)

4.8 拉链表(重点)

拉链表处理的业务场景:主要处理缓慢变化维的业务场景。(用户表、订单表)


4.9 即席查询数据仓库

大数据技术之高频面试题|剑谱_数据_41

Kylin: T+1

Impala: CDH

Presto: Apache版本框架

4.10 数据仓库每天跑多少张表,大概什么时候运行,运行多久?

基本一个项目建一个库,表格个数为初始的原始数据表格加上统计结果表格的总数。(一般70-100张表格)

用户行为5张;业务数据33张表 =》ods34 =》dwd=>32张=》dws 6张宽表=>dwt6张宽表=>ads=》30张 =》108张

每天0:10开始运行。=》sqoop 20-30分钟:1点:=》 5-6个小时运行完指标

所有离线数据报表控制在8小时之内

大数据实时处理部分控制在5分钟之内。(分钟级别、秒级别)

如果是实时推荐系统,需要秒级响应

4.11 活动的话,数据量会增加多少?怎么解决?

日活增加50%,GMV增加多少20%。(留转G复活)情人节,促销手纸。

集群资源都留有预量。11.11,6.18,数据量过大,提前动态增加服务器。

4.12 并发峰值多少?大概哪个时间点?

高峰期晚上7-12点。Kafka里面20m/s 2万/s 并发峰值在1-2万人

4.13 数仓中使用的哪种文件存储格式

常用的包括:textFile,ORC,Parquet,一般企业里使用ORC或者Parquet,因为是列式存储,且压缩比非常高,所以相比于textFile,查询速度快,占用硬盘空间少

4.14 哪张表最费时间,有没有优化

用户行为宽表,数据量过大。数据倾斜的相关优化手段。(hadoop、hive、spark)

哪两张表和哪两张表发生数据倾斜:

11.11 数据量大 按照地区统计销售额

订单表(10万* 50倍 * 1k =500万 *1k=5g ) 和 订单详情(50万 50倍 * 1k=2500万*1k=25g) 用户、商品 、地区(县)

数据倾斜时,执行多久 2-5小时

采取办法:解决数据倾斜 将key打散 自定义分区

采用解决数据倾斜办法:执行40分钟搞定


4.14 哪张表数据量最大,是多少

用户行为数据:100g(1亿条)/5 = 2千万 * 2-3倍 动作、曝光、页面、故障、启动

业务数据:10万人下单,详情(50-100万条) -》加购-》下单-》支付-》物流

4.15 用什么工具做权限管理

Ranger或Sentry (用户认证kerberos(张三、李四、王五)=>表级别权限(张三、李四)、字段级别权限(李四))

4.16 数仓当中数据多久删除一次

1)部分公司永久不删

2)有一年、两年“删除”一次的,这里面说的删除是,先将超时数据压缩下载到单独安装的磁盘上。然后删除集群上数据。 很少有公司不备份数据,直接删除的。

第5章 生产经验--测试上线相关

5.1 测试相关

5.1.1 公司有多少台测试服务器?

测试服务器一般三台

5.1.2 测试环境什么样?

有钱的公司和生产环境电脑配置一样。

一般公司测试环境的配置是生产的一半。

5.1.3 测试数据哪来的?

一部分自己写Java程序自己造(更灵活),一部分从生产环境上取一部分(更真实)。

5.1.4 如何保证写的sql正确性(重点)

先在mysql的业务库里面把结果计算出来;在给你在ads层计算的结果进行比较;


需要造一些特定的测试数据,测试。

从生产环境抓取一部分数据,数据有多少你是知道的,运算完毕应该符合你的预期。

离线数据和实时数据分析的结果比较。(日活1万 实时10100),倾向取离线。

5.1.5 测试之后如何上线?

大公司:上线的时候,将脚本打包,提交git。先发邮件抄送经理和总监,运维。运维负责上线。

小公司:跟项目经理说一下,项目经理技术把关,项目经理通过了就可以上线了。风险意识。

所谓的上线就是编写脚本,并在azkaban中进行作业调度。

5.2 项目实际工作流程

以下是活跃用户需求的整体开发流程。

产品经理负责收集需求:需求来源与客户反馈、老板的意见

第1步:确定指标的业务口径

由产品经理主导,找到提出该指标的运营负责人沟通。首先要问清楚指标是怎么定义的,比如活跃用户是指启动过APP的用户。设备id 还是用户id。

邮件/需求文档-》不要口头

第2步:需求评审

由产品经理主导设计原型,对于活跃主题,我们最终要展示的是最近n天的活跃用户数变化趋势 ,效果如下图所示。此处大数据开发工程师、后端开发工程师、前端开发工程师一同参与,一起说明整个功能的价值和详细的操作流程,确保大家理解的一致。

大数据技术之高频面试题|剑谱_数据_42

第3步:大数据开发

大数据开发工程师,通过数据同步的工具如Flume、Sqoop等将数据同步到ODS层,然后就是一层一层的通过SQL计算到DWD、DWS层,最后形成可为应用直接服务的数据填充到ADS层。

第4步:后端开发

后端工程师负责,为大数据工程师提供业务数据接口;

同时还负责读取ADS层分析后,写入MySQL中的数据。

第5步:前端开发

前端工程师负责,前端埋点。

对分析后的结果数据进行可视化展示。

第6步:联调

此时大数据开发工程师、前端开发工程师、后端开发工程师都要参与进来。此时会要求大数据开发工程师基于历史的数据执行计算任务,大数据开发工程师承担数据准确性的校验。前后端解决用户操作的相关BUG保证不出现低级的问题完成自测。

第7步:测试

测试工程师对整个大数据系统进行测试。测试的手段包括,边界值、等价类等。

提交测试异常的软件有:禅道、bugzila(测试人员记录测试问题1.0,输入是什么,结果是什么,跟预期不一样->需要开发人员解释,是一个bug,下一个版本解决1.1->测试人员再测试。测试1.1ok->测试经理关闭bug)

1周开发写代码 =》2周测试时间

第8步:上线

运维工程师会配合我们的前后端开发工程师更新最新的版本到服务器。此时产品经理要找到该指标的负责人长期跟进指标的准确性。重要的指标还要每过一个周期内部再次验证,从而保证数据的准确性。

5.3 项目中实现一个需求大概多长时间

刚入职第一个需求大概需要7天左右。

对业务熟悉后,平均一天一个需求。

影响时间的因素:测试服务器购买获取环境准备、对业务熟悉、开会讨论需求、表的权限申请、测试等。新员工培训(公司规章制度、代码规范)

5.4 项目在3年内迭代次数,每一个项目具体是如何迭代的。公司版本迭代多久一次,迭代到哪个版本

瀑布式开发、敏捷开发

差不多一个月会迭代一次。每月都有节日(元旦、春节、情人节、3.8妇女节、端午节、618、国庆、中秋、1111/6.1/5.1、生日、周末)新产品、新区域

就产品或我们提出优化需求,然后评估时间。每周我们都会开会做下周计划和本周总结。(日报、周报、月报、季度报、年报)需求1周的时间,周三一定完成。周四周五(帮同事写代码、自己学习工作额外的技术)

5.1.2

5是大版本号:必须是重大升级

1:一般是核心模块变动

2:一般版本变化

5.5 项目开发中每天做什么事

1)新需求(活动、优化、新产品、新市场)。 60%

2)故障分析:数仓的任何步骤出现问题,需要查看问题,比如日活,月活下降或快速上升等。20%

3)新技术的预言(比如湖仓一体 数据湖 doris 实时数据质量监控)10%

4)其临时任务 10%

5)晨会-》10做操-》讨论中午吃什么-》12点出去吃1点-》睡到2点-》3点茶歇水果-》晚上吃啥-》吃加班餐-》开会-》晚上6点吃饭-》7点开始干活-10点-》11点

5.6 实时项目数据计算

5.6.1 跑实时任务,怎么分配内存和CPU资源

128m数据对应1g内存

1个Kafka分区对应1个CPU

5.6.2 跑实时任务,每天数据量多少?

用户行为:实时任务用到了用户行为多少张表(20g) 100g/5张表

业务数据:实时任务用到了业务数据多少张表(34m) 1g/30张表

活动、风控、销售、流量

第6章 生产经验—技术

6.1 可视化报表工具

Echarts(百度开源)、Kibana(开源)、Tableau(功能强大的收费软件)、Superset(功能一般免费)、QuickBI(阿里云收费的离线)、DataV(阿里云收费的实时)suga(百度,收费)

6.2 集群监控工具

Zabbix+ Grafana Prometheus&Grafana监控 睿象云

6.3 项目中遇到的问题怎么解决的(重点*****)

Shell 中Flume停止脚本


Hadoop宕机

Hadoop解决数据倾斜方法

集群资源分配参数(项目中遇到的问题)

HDFS小文件处理

Hadoop优化


Flume挂掉

Flume优化


Kafka挂掉

Kafka丢失

Kafka数据重复

Kafka消息数据积压

Kafka优化

Kafka单条日志传输大小


自定义UDF、UDTF函数

Hive优化

Hive解决数据倾斜方法

7天内连续3次活跃

1 7 30指标

分摊

备注


Sqoop空值、一致性、数据倾斜


Azkaban任务挂了怎么办?

Azkaban故障报警


Spark数据倾斜

Spark优化

SparkStreaming精确一次性消费


Flink 数据倾斜

Flink水位线

Flink反压

Flink处理函数

Flink SQL

Flink多流join


6.4 Linux+Shell+Hadoop+ZK+Flume+kafka+Hive+Sqoop+Azkaban那些事

大数据技术之高频面试题|剑谱_Shell_43

第7章 生产经验—热点问题

7.1 元数据管理(Atlas血缘系统)

insert into table ads_user

select id, name from dwt_user

依赖关系能够做到:表级别和字段级别

用处:作业执行失败,评估他的影响范围。 主要用于表比较多的公司。

版本问题:

0.84版本:2019-06-21

2.0版本:2019-05-13

框架版本:

Apache 0.84 2.0 2.1

CDH 2.0

7.2 数据质量监控(Griffin)

7.2.1 监控原则

1)单表数据量监控

一张表的记录数在一个已知的范围内,或者上下浮动不会超过某个阈值

  • SQL结果:var 数据量 = select count(*)from 表 where 时间等过滤条件
  • 报警触发条件设置:如果数据量不在[数值下限, 数值上限], 则触发报警
  • 同比增加:如果((本周的数据量 - 上周的数据量)/上周的数据量*100)不在 [比例下线,比例上限],则触发报警
  • 环比增加:如果((今天的数据量 - 昨天的数据量)/昨天的数据量*100)不在 [比例下线,比例上限],则触发报警
  • 报警触发条件设置一定要有。如果没有配置的阈值,不能做监控

日活、周活、月活、留存(日周月)、转化率(日、周、月)GMV(日、周、月)

复购率(日周月) 30%

2)单表空值检测

某个字段为空的记录数在一个范围内,或者占总量的百分比在某个阈值范围内

  • 目标字段:选择要监控的字段,不能选“无”
  • SQL结果:var 异常数据量 = select count(*) from 表 where 目标字段 is null
  • 单次检测:如果(异常数据量)不在[数值下限, 数值上限],则触发报警

3)单表重复值检测

一个或多个字段是否满足某些规则

  • 目标字段:第一步先正常统计条数;select count(*) form 表;
  • 第二步,去重统计;select count(*) from 表 group by 某个字段
  • 第一步的值和第二步不的值做减法,看是否在上下线阀值之内
  • 单次检测:如果(异常数据量)不在[数值下限, 数值上限], 则触发报警

4)单表值域检测

一个或多个字段没有重复记录

  • 目标字段:选择要监控的字段,支持多选
  • 检测规则:填写“目标字段”要满足的条件。其中$1表示第一个目标字段,$2表示第二个目标字段,以此类推。上图中的“检测规则”经过渲染后变为“delivery_fee = delivery_fee_base+delivery_fee_extra”
  • 阈值配置与“空值检测”相同

5)跨表数据量对比

主要针对同步流程,监控两张表的数据量是否一致

  • SQL结果:count(本表) - count(关联表)
  • 阈值配置与“空值检测”相同

7.2.2 数据质量实现


7.3 权限管理(Ranger)


7.4 数据治理

包括:数据质量管理、元数据管理、权限管理(ranger sentry)、数仓

CDH cloudmanager-》sentry; HDP ambari=>ranger

2019年下半年 国家出了一本白皮书,要求给政府做的数仓项目,要具备如下功能:

数据治理是一个复杂的系统工程,涉及到企业和单位多个领域,既要做好顶层设计,又要解决好统一标准、统一流程、统一管理体系等问题,同时也要解决好数据采集、数据清洗、数据对接和应用集成等相关问题。

数据治理实施要点主要包含数据规划、制定数据标准、整理数据、搭建数据管理工具、构建运维体系及推广贯标六大部分,其中数据规划是纲领、制定数据标准是基础、整理数据是过程、搭建数据管理工具是技术手段、构建运维体系是前提,推广贯标是持续保障。

大数据技术之高频面试题|剑谱_数据倾斜_44

7.5 数据中

7.5.1 什么是中台?

在传统IT企业,项目的物理结构是什么样的呢?无论项目内部的如何复杂,都可分为“前台”和“后台”这两部分。

什么是前台?

首先,这里所说的“前台”和“前端”并不是一回事。所谓前台即包括各种和用户直接交互的界面,比如web页面,手机app;也包括服务端各种实时响应用户请求的业务逻辑,比如商品查询、订单系统等等。

什么是后台?

后台并不直接面向用户,而是面向运营人员的配置管理系统,比如商品管理、物流管理、结算管理。后台为前台提供了一些简单的配置。

大数据技术之高频面试题|剑谱_Shell_45

7.5.2 传统项目痛点

痛点:重复造轮子。

大数据技术之高频面试题|剑谱_数据_46

7.5.3 各家中台

1)SuperCell公司

大数据技术之高频面试题|剑谱_Shell_47

2)阿里巴巴提出了“大中台,小前台”的战略

大数据技术之高频面试题|剑谱_数据倾斜_48

3)华为提出了“平台炮火支撑精兵作战”的战略

大数据技术之高频面试题|剑谱_数据倾斜_49

7.5.4 中台具体划分

1)业务中台

大数据技术之高频面试题|剑谱_Shell_50

2)技术中台

大数据技术之高频面试题|剑谱_数据倾斜_51

3)数据中台

大数据技术之高频面试题|剑谱_数据_52

4)算法中台

大数据技术之高频面试题|剑谱_数据倾斜_53

7.5.5 中台使用场景

1)从0到1的阶段,没有必要搭建中台。

从0到1的创业型公司,首要目的是生存下去,以最快的速度打造出产品,证明自身的市场价值。

这个时候,让项目野蛮生长才是最好的选择。如果不慌不忙地先去搭建中台,恐怕中台还没搭建好,公司早就饿死了。

2)从1到N的阶段,适合搭建中台。

当企业有了一定规模,产品得到了市场的认可,这时候公司的首要目的不再是活下去,而是活的更好。

这个时候,趁着项目复杂度还不是特别高,可以考虑把各项目的通用部分下沉,组建中台,以方便后续新项目的尝试和旧项目的迭代。

3)从N到N+1的阶段,搭建中台势在必行。

当企业已经有了很大的规模,各种产品、服务、部门错综复杂,这时候做架构调整会比较痛苦。

但是长痛不如短痛,为了项目的长期发展,还是需要尽早调整架构,实现平台化,以免日后越来越难以维护。

7.6 数据湖

数据湖(Data Lake)是一个存储企业的各种各样原始数据的大型仓库,其中的数据可供存取、处理、分析及传输。

hudi、iceberg、Data Lake

目前,Hadoop是最常用的部署数据湖的技术,所以很多人会觉得数据湖就是Hadoop集群。数据湖是一个概念,而Hadoop是用于实现这个概念的技术。

大数据技术之高频面试题|剑谱_Shell_54


数据仓库

数据湖

主要处理历史的、结构化的数据,而且这些数据必须与数据仓库事先定义的模型吻合。

能处理所有类型的数据,如结构化数据,非结构化数据,半结构化数据等,数据的类型依赖于数据源系统的原始数据格式。非结构化数据(语音、图片、视频等)

数据仓库分析的指标都是产品经理提前规定好的。按需分析数据。(日活、新增、留存、转化率)

根据海量的数据,挖掘出规律,反应给运营部门。

从海量的数据中找寻规律。拥有非常强的计算能力用于处理数据。

数据挖掘

7.7 埋点

免费的埋点:上课演示。

百度统计、友盟统计

目前主流的埋点方式,有代码埋点(前端/后端)、可视化埋点全埋点三种。

代码埋点是通过调用埋点SDK函数,在需要埋点的业务逻辑功能位置调用接口,上报埋点数据。例如,我们对页面中的某个按钮埋点后,当这个按钮被点击时,可以在这个按钮对应的 OnClick 函数里面调用SDK提供的数据发送接口,来发送数据。

可视化埋点只需要研发人员集成采集 SDK,不需要写埋点代码,业务人员就可以通过访问分析平台的“圈选”功能,来“圈”出需要对用户行为进行捕捉的控件,并对该事件进行命名。圈选完毕后,这些配置会同步到各个用户的终端上,由采集 SDK 按照圈选的配置自动进行用户行为数据的采集和发送。

全埋点是通过在产品中嵌入SDK,前端自动采集页面上的全部用户行为事件,上报埋点数据,相当于做了一个统一的埋点。然后再通过界面配置哪些数据需要在系统里面进行分析。

7.8 电商运营经验

7.8.1 电商8类基本指标

大数据技术之高频面试题|剑谱_数据_55

大数据技术之高频面试题|剑谱_Shell_56

大数据技术之高频面试题|剑谱_Shell_57

大数据技术之高频面试题|剑谱_Shell_58

大数据技术之高频面试题|剑谱_数据_59

大数据技术之高频面试题|剑谱_数据倾斜_60

大数据技术之高频面试题|剑谱_Shell_61

8)市场竞争指标:主要分析市场份额以及网站排名,进一步进行调整

大数据技术之高频面试题|剑谱_数据_62

7.8.2 直播指标

大数据技术之高频面试题|剑谱_数据倾斜_63

大数据技术之高频面试题|剑谱_Shell_64

大数据技术之高频面试题|剑谱_数据_65

大数据技术之高频面试题|剑谱_Shell_66

大数据技术之高频面试题|剑谱_数据_67

大数据技术之高频面试题|剑谱_Shell_68

第8章 实时数仓项目

8.1 数据采集到ods层做了哪些事

8.1.1 前端埋点的行为数据为什么又采集一份?

时效性

kafka保存3天,磁盘够:原来1T,现在2T,没压力

8.1.2为什么选择kafka?

实时写、实时读

=》 消息队列适合,其他数据库受不了

8.1.3为什么用maxwell?历史数据同步怎么保证一致性?

flinkcdc在20年7月才发布

canal与maxwell区别:

maxwell支持同步历史数据

maxwell支持断点还原(存在元数据库)

数据格式更轻量

保证至少一次,不丢

8.1.4 kafka保存多久?如果需要以前的数据怎么办?

跟离线项目保持一致:3天

我们的项目不需要,如果需要的话可以去数据库或Hive现查,ClickHouse也有历史的宽表数据

8.2 ods层

1)存储原始数据

2个topic :

埋点的行为数据 ods_base_log

业务数据 ods_base_db

2)业务数据的有序性: maxwell配置,指定生产者分区的key为 table

8.3 dwd+dim层

8.3.1 存储位置,为什么维度表存Hbase?

事实表存Kafka、维度表存Hbase

基于热存储加载维表的join方案:

随机查

长远考虑

适合实时读写

8.3.2 埋点行为数据分流

1)修复新老访客(选择性):以前是前端试别新老访客,不够准确

2)分流:侧输出流

分了3个topic: 启动、页面、曝光

8.3.3 业务数据动态分流

1)动态分流: 将事实表写入kafka的dwd层,将维度表写入hbase。为了避免因表的变化而重启Flink任务,在mysql存一张表来动态配置。

动态实现:使用广播状态

=》 读取一张配置表 ===》 维护这张配置表

source来源 sink写到哪 操作类型 字段 主键 扩展

=》实时获取配置表的变化 ==》CDC工具

=》 FlinkCDC

=》 使用了sql的方式,去同步这张配置表

=》sql的数据格式比较方便

2)怎么写HBase:借助phoenix

没有做维度退化

维表数据量小、变化频率慢

3)Hbase的rowkey怎么设计的?有没有数据热点问题?

最大的维表:用户维表

=》百万日活,2000万注册用户为例,1条平均1k:2000万*1k=约20G

使用Phoenix创建的盐表,避免数据热点问题

https://developer.aliyun.com/article/532313

8.4 dwm层

8.4.1 为什么要加一个dwm层?

DWM层主要服务DWS,因为部分需求直接从DWD层到DWS层中间会有一定的计算量,而且这部分计算的结果很有可能被多个DWS层主题复用,所以部分DWD层会形成一层DWM,我们这里主要涉及业务

  • 访问UV计算
  • 跳出明细计算
  • 订单宽表
  • 支付宽表

8.4.2 事实表与事实表join

1)事实表与事实表的双流join,使用了interval join

2)Join不上的数据怎么办?

在flink中的流join大体分为两种,一种是基于时间窗口的join(Time Windowed Join),比如join、coGroup等。另一种是基于状态缓存的join(Temporal Table Join),比如intervalJoin。

这里选用intervalJoin,因为相比较窗口join,intervalJoin使用更简单,而且避免了应匹配的数据处于不同窗口的问题。intervalJoin目前只有一个问题,就是还不支持left join。

但是我们这里是订单主表与订单从表之间的关联不需要left join,所以intervalJoin是较好的选择。

8.4.3 事实表与维度表join

维度关联采用了热存储加载的join方案,实际上就是在流中查询存储在hbase中的数据表。但是即使通过主键的方式查询,hbase速度的查询也是不及流之间的join。外部数据源的查询常常是流式计算的性能瓶颈,所以在这个基础上还有进行一定的优化。

1)使用了旁路缓存

旁路缓存模式是一种非常常见的按需分配缓存的模式。如图,任何请求优先访问缓存,缓存命中,直接获得数据返回请求。如果未命中则,查询数据库,同时把结果写入缓存以备后续请求使用。

大数据技术之高频面试题|剑谱_数据倾斜_69

2)异步IO

Flink 在1.2中引入了Async I/O,在异步模式下,将IO操作异步化,单个并行可以连续发送多个请求,哪个请求先返回就先处理,从而在连续的请求间不需要阻塞式等待,大大提高了流处理效率。

Async I/O 是阿里巴巴贡献给社区的一个呼声非常高的特性,解决与外部系统交互时网络延迟成为了系统瓶颈的问题。

大数据技术之高频面试题|剑谱_Shell_70

异步查询实际上是把维表的查询操作托管给单独的线程池完成,这样不会因为某一个查询造成阻塞,单个并行可以连续发送多个请求,提高并发效率。

这种方式特别针对涉及网络IO的操作,减少因为请求等待带来的消耗。

8.4.4怎么保证缓存一致性

当我们获取到维表更新的数据,也就是拿到维度表操作类型为update时:

1)更新Hbase的同时,删除redis里对应的之前缓存的数据

2)redis设置了过期时间:24小时

8.5 dws层

8.5.1 为什么选择ClickHouse

1)适合大宽表、数据量多、聚合统计分析 =》 快

2)宽表已经不再需要join,很合适

8.5.2 轻度聚合

1)DWS层要应对很多实时查询,如果是完全的明细那么查询的压力是非常大的。将更多的实时数据以主题的方式组合起来便于管理,同时也能减少维度查询的次数。

2)开一个小窗口,5s的滚动窗口

3)同时减轻了写ClickHouse的压力,减少后续聚合的时间

4)几张表? 表名、字段

访客、商品、地区、关键词

8.6 ads层

8.6.1 实现方案

为可视化大屏服务,提供一个数据接口用来查询ClickHouse中的数据。

大数据技术之高频面试题|剑谱_Shell_71

8.6.2 怎么保证ClickHouse的一致性?

ReplacingMergeTree只能保证最终一致性,查询时的sql语法加上去重逻辑


8.7 监控

Flink和ClickHouse都使用了Prometheus + Grafana

第9章 手写代码

9.1 基本算法

9.1.1 冒泡排序

/**

* 冒泡排序 时间复杂度 O(n^2) 空间复杂度O(1)

*/

public class BubbleSort {


public static void bubbleSort(int[] data) {


System.out.println("开始排序");

int arrayLength = data.length;


for (int i = 0; i < arrayLength - 1; i++) {


boolean flag = false;


for (int j = 0; j < arrayLength - 1 - i; j++) {

if(data[j] > data[j + 1]){

int temp = data[j + 1];

data[j + 1] = data[j];

data[j] = temp;

flag = true;

}

}


System.out.println(java.util.Arrays.toString(data));


if (!flag)

break;

}

}


public static void main(String[] args) {


int[] data = { 9, -16, 21, 23, -30, -49, 21, 30, 30 };


System.out.println("排序之前:\n" + java.util.Arrays.toString(data));


bubbleSort(data);


System.out.println("排序之后:\n" + java.util.Arrays.toString(data));

}

}

9.1.2 二分查找


图4-二分查找核心思路

实现代码:

/**

 * 二分查找 时间复杂度O(log2n);空间复杂度O(1)

 */

 

def binarySearch(arr:Array[Int],left:Int,right:Int,findVal:Int): Int={

  if(left>right){//递归退出条件,找不到,返回-1

    -1

  }


  val midIndex = (left+right)/2


  if (findVal < arr(midIndex)){//向左递归查找

    binarySearch(arr,left,midIndex-1,findVal)

  }else if(findVal > arr(midIndex)){//向右递归查找

    binarySearch(arr,midIndex+1,right,findVal)

  }else{//查找到,返回下标

    midIndex

  }

}

拓展需求:当一个有序数组中,有多个相同的数值时,如何将所有的数值都查找到。

代码实现如下:

/*

{1,8, 10, 89, 1000, 1000,1234} 当一个有序数组中,有多个相同的数值时,如何将所有的数值都查找到,比如这里的 1000.

//分析

1. 返回的结果是一个可变数组 ArrayBuffer

2. 在找到结果时,向左边扫描,向右边扫描 [条件]

3. 找到结果后,就加入到ArrayBuffer

*/

def binarySearch2(arr: Array[Int], l: Int, r: Int,

findVal: Int): ArrayBuffer[Int] = {


//找不到条件?

if (l > r) {

return ArrayBuffer()

}


val midIndex = (l + r) / 2

val midVal = arr(midIndex)

if (midVal > findVal) {

//向左进行递归查找

binarySearch2(arr, l, midIndex - 1, findVal)

} else if (midVal < findVal) { //向右进行递归查找

binarySearch2(arr, midIndex + 1, r, findVal)

} else {

println("midIndex=" + midIndex)

//定义一个可变数组

val resArr = ArrayBuffer[Int]()

//向左边扫描

var temp = midIndex - 1

breakable {

while (true) {

if (temp < 0 || arr(temp) != findVal) {

break()

}

if (arr(temp) == findVal) {

resArr.append(temp)

}

temp -= 1

}

}

//将中间这个索引加入

resArr.append(midIndex)

//向右边扫描

temp = midIndex + 1

breakable {

while (true) {

if (temp > arr.length - 1 || arr(temp) != findVal) {

break()

}

if (arr(temp) == findVal) {

resArr.append(temp)

}

temp += 1

}

}

return resArr

}

9.1.3 快排

图1-快速排序核心思想

代码实现:

/**

* 快排

* 时间复杂度:平均时间复杂度为O(nlogn)

* 空间复杂度:O(logn),因为递归栈空间的使用问题

*/

def quickSort(list: List[Int]): List[Int] = list match {

case Nil => Nil

case List() => List()

case head :: tail =>

val (left, right) = tail.partition(_ < head)

quickSort(left) ::: head :: quickSort(right)

}

9.1.4 归并


图2-归并排序核心思想

核心思想:不断的将大的数组分成两个小数组,直到不能拆分为止,即形成了单个值。此时使用合并的排序思想对已经有序的数组进行合并,合并为一个大的数据,不断重复此过程,直到最终所有数据合并到一个数组为止。

图3-归并排序“治”流程

代码实现:

/**

* 快排

* 时间复杂度:O(nlogn)

* 空间复杂度:O(n)

*/

def merge(left: List[Int], right: List[Int]): List[Int] = (left, right) match {

case (Nil, _) => right

case (_, Nil) => left

case (x :: xTail, y :: yTail) =>

if (x <= y) x :: merge(xTail, right)

else y :: merge(left, yTail)

}

9.1.5 二叉树之Scala实现

1)二叉树概念


2)二叉树的特点

(1)树执行查找、删除、插入的时间复杂度都是O(logN)

(2)遍历二叉树的方法包括前序、中序、后序

(3)非平衡树指的是根的左右两边的子节点的数量不一致

(4)在非空二叉树中,第i层的结点总数不超过 , i>=1;

(5)深度为h的二叉树最多有个结点(h>=1),最少有h个结点;

(6)对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;

3) 二叉树的Scala代码实现

定义节点以及前序、中序、后序遍历

class TreeNode(treeNo:Int){


val no = treeNo

var left:TreeNode = null

var right:TreeNode = null


//后序遍历

def postOrder():Unit={

//向左递归输出左子树

if(this.left != null){

this.left.postOrder

}

//向右递归输出右子树

if (this.right != null) {

this.right.postOrder

}


//输出当前节点值

printf("节点信息 no=%d \n",no)

}


//中序遍历

def infixOrder():Unit={

//向左递归输出左子树

if(this.left != null){

this.left.infixOrder()

}


//输出当前节点值

printf("节点信息 no=%d \n",no)


//向右递归输出右子树

if (this.right != null) {

this.right.infixOrder()

}

}


//前序遍历

def preOrder():Unit={

//输出当前节点值

printf("节点信息 no=%d \n",no)


//向左递归输出左子树

if(this.left != null){

this.left.postOrder()

}


//向右递归输出右子树

if (this.right != null) {

this.right.preOrder()

}

}


//后序遍历查找

def postOrderSearch(no:Int): TreeNode = {

//向左递归输出左子树

var resNode:TreeNode = null

if (this.left != null) {

resNode = this.left.postOrderSearch(no)

}

if (resNode != null) {

return resNode

}

if (this.right != null) {

resNode = this.right.postOrderSearch(no)

}

if (resNode != null) {

return resNode

}

println("ttt~~")

if (this.no == no) {

return this

}

resNode

}


//中序遍历查找

def infixOrderSearch(no:Int): TreeNode = {



var resNode : TreeNode = null

//先向左递归查找

if (this.left != null) {

resNode = this.left.infixOrderSearch(no)

}

if (resNode != null) {

return resNode

}

println("yyy~~")

if (no == this.no) {

return this

}

//向右递归查找

if (this.right != null) {

resNode = this.right.infixOrderSearch(no)

}

return resNode


}


//前序查找

def preOrderSearch(no:Int): TreeNode = {

if (no == this.no) {

return this

}

//向左递归查找

var resNode : TreeNode = null

if (this.left != null) {

resNode = this.left.preOrderSearch(no)

}

if (resNode != null){

return resNode

}

//向右边递归查找

if (this.right != null) {

resNode = this.right.preOrderSearch(no)

}


return resNode

}


//删除节点

//删除节点规则

//1如果删除的节点是叶子节点,则删除该节点

//2如果删除的节点是非叶子节点,则删除该子树


def delNode(no:Int): Unit = {

//首先比较当前节点的左子节点是否为要删除的节点

if (this.left != null && this.left.no == no) {

this.left = null

return

}

//比较当前节点的右子节点是否为要删除的节点

if (this.right != null && this.right.no == no) {

this.right = null

return

}

//向左递归删除

if (this.left != null) {

this.left.delNode(no)

}

//向右递归删除

if (this.right != null) {

this.right.delNode(no)

}

}

}


定义二叉树,前序、中序、后序遍历,前序、中序、后序查找,删除节点

class BinaryTree{

var root:TreeNode = null


//后序遍历

def postOrder(): Unit = {

if (root != null){

root.postOrder()

}else {

println("当前二叉树为空,不能遍历")

}

}

//中序遍历

def infixOrder(): Unit = {

if (root != null){

root.infixOrder()

}else {

println("当前二叉树为空,不能遍历")

}

}

//前序遍历

def preOrder(): Unit = {

if (root != null){

root.preOrder()

}else {

println("当前二叉树为空,不能遍历")

}

}


//后序遍历查找

def postOrderSearch(no:Int): TreeNode = {

if (root != null) {

root.postOrderSearch(no)

}else{

null

}

}


//中序遍历查找

def infixOrderSeacher(no:Int): TreeNode = {

if (root != null) {

return root.infixOrderSearch(no)

}else {

return null

}

}


//前序查找

def preOrderSearch(no:Int): TreeNode = {


if (root != null) {

return root.preOrderSearch(no)

}else{

//println("当前二叉树为空,不能查找")

return null

}

}

//删除节点

def delNode(no:Int): Unit = {

if (root != null) {

//先处理一下root是不是要删除的

if (root.no == no){

root = null

}else {

root.delNode(no)

}

}

}

9.2 开发代码

9.2.1 手写Spark-WordCount

val conf: SparkConf =

new SparkConf().setMaster("local[*]").setAppName("WordCount")


val sc = new SparkContext(conf)


sc.textFile("/input")

.flatMap(_.split(" "))

.map((_, 1))

.reduceByKey(_ + _)

.saveAsTextFile("/output")


sc.stop()


9.3 手写HQL

9.3.1 手写HQL 第1题

表结构:uid,subject_id,score

求:找出所有科目成绩都大于某一学科平均成绩的学生

数据集如下

1001 01 90

1001 02 90

1001 03 90

1002 01 85

1002 02 85

1002 03 70

1003 01 70

1003 02 70

1003 03 85

1)建表语句

create table score(

uid string,

subject_id string,

score int)

row format delimited fields terminated by '\t';

2)求出每个学科平均成绩

select

uid,

score,

avg(score) over(partition by subject_id) avg_score

from

score;t1

3)根据是否大于平均成绩记录flag,大于则记为0否则记为1

select

uid,

if(score>avg_score,0,1) flag

from

t1;t2

4)根据学生id进行分组统计flag的和,和为0则是所有学科都大于平均成绩

select

uid

from

t2

group by

uid

having

sum(flag)=0;

5)最终SQL

select

uid

from

(select

uid,

if(score>avg_score,0,1) flag

from

(select

uid,

score,

avg(score) over(partition by subject_id) avg_score

from

score)t1)t2

group by

uid

having

sum(flag)=0;

9.3.2 手写HQL 第2题

我们有如下的用户访问数据

userId

visitDate

visitCount

u01

2017/1/21

5

u02

2017/1/23

6

u03

2017/1/22

8

u04

2017/1/20

3

u01

2017/1/23

6

u01

2017/2/21

8

U02

2017/1/23

6

U01

2017/2/22

4

要求使用SQL统计出每个用户的累积访问次数,如下表所示:

用户id

月份

小计

累积

u01

2017-01

11

11

u01

2017-02

12

23

u02

2017-01

12

12

u03

2017-01

8

8

u04

2017-01

3

3

数据集

u01 2017/1/21 5

u02 2017/1/23 6

u03 2017/1/22 8

u04 2017/1/20 3

u01 2017/1/23 6

u01 2017/2/21 8

u02 2017/1/23 6

u01 2017/2/22 4

1)创建表

create table action

(userId string,

visitDate string,

visitCount int)

row format delimited fields terminated by "\t";

2)修改数据格式

select

userId,

date_format(regexp_replace(visitDate,'/','-'),'yyyy-MM') mn,

visitCount

from

action;t1

3)计算每人单月访问量

select

userId,

mn,

sum(visitCount) mn_count

from

t1

group by

userId,mn;t2

4)按月累计访问量

select

userId,

mn,

mn_count,

sum(mn_count) over(partition by userId order by mn)

from t2;

5)最终SQL

select

userId,

mn,

mn_count,

sum(mn_count) over(partition by userId order by mn)

from

( select

userId,

mn,

sum(visitCount) mn_count

from

(select

userId,

date_format(regexp_replace(visitDate,'/','-'),'yyyy-MM') mn,

visitCount

from

action)t1

group by userId,mn)t2;

9.3.3 手写HQL 第3题

有50W个京东店铺,每个顾客访客访问任何一个店铺的任何一个商品时都会产生一条访问日志,访问日志存储的表名为Visit,访客的用户id为user_id,被访问的店铺名称为shop,请统计:

1)每个店铺的UV(访客数)

2)每个店铺访问次数top3的访客信息。输出店铺名称、访客id、访问次数

数据集

u1 a

u2 b

u1 b

u1 a

u3 c

u4 b

u1 a

u2 c

u5 b

u4 b

u6 c

u2 c

u1 b

u2 a

u2 a

u3 a

u5 a

u5 a

u5 a

1)建表

create table visit(user_id string,shop string) row format delimited fields terminated by '\t';

2)每个店铺的UV(访客数)

select shop,count(distinct user_id) from visit group by shop;

3)每个店铺访问次数top3的访客信息。输出店铺名称、访客id、访问次数

(1)查询每个店铺被每个用户访问次数

select shop,user_id,count(*) ct

from visit

group by shop,user_id;t1

(2)计算每个店铺被用户访问次数排名

select shop,user_id,ct,rank() over(partition by shop order by ct) rk

from t1;t2

(3)取每个店铺排名前3的

select shop,user_id,ct

from t2

where rk<=3;

(4)最终SQL

select

shop,

user_id,

ct

from

(select

shop,

user_id,

ct,

rank() over(partition by shop order by ct) rk

from

(select

shop,

user_id,

count(*) ct

from visit

group by

shop,

user_id)t1

)t2

where rk<=3;

9.3.4 手写HQL 第4题

已知一个表STG.ORDER,有如下字段:Date,Order_id,User_id,amount。请给出sql进行统计:数据样例:2017-01-01,10029028,1000003251,33.57。

1)给出 2017年每个月的订单数、用户数、总成交金额。

2)给出2017年11月的新客数(指在11月才有第一笔订单)

建表

create table order_tab(dt string,order_id string,user_id string,amount decimal(10,2)) row format delimited fields terminated by '\t';

1)给出 2017年每个月的订单数、用户数、总成交金额。

select

date_format(dt,'yyyy-MM'),

count(order_id),

count(distinct user_id),

sum(amount)

from

order_tab

where

date_format(dt,'yyyy')='2017'

group by

date_format(dt,'yyyy-MM');

2)给出2017年11月的新客数(指在11月才有第一笔订单)

select

count(user_id)

from

order_tab

group by

user_id

having

date_format(min(dt),'yyyy-MM')='2017-11';

9.3.5 手写HQL 第5题

有日志如下,请写出代码求得所有用户和活跃用户的总数及平均年龄。(活跃用户指连续两天都有访问记录的用户)日期 用户 年龄

数据集

2019-02-11,test_1,23

2019-02-11,test_2,19

2019-02-11,test_3,39

2019-02-11,test_1,23

2019-02-11,test_3,39

2019-02-11,test_1,23

2019-02-12,test_2,19

2019-02-13,test_1,23

2019-02-15,test_2,19

2019-02-16,test_2,19

1)建表

create table user_age(dt string,user_id string,age int)row format delimited fields terminated by ',';

2)按照日期以及用户分组,按照日期排序并给出排名

select

dt,

user_id,

min(age) age,

rank() over(partition by user_id order by dt) rk

from

user_age

group by

dt,user_id;t1

3)计算日期及排名的差值

select

user_id,

age,

date_sub(dt,rk) flag

from

t1;t2

4)过滤出差值大于等于2的,即为连续两天活跃的用户

select

user_id,

min(age) age

from

t2

group by

user_id,flag

having

count(*)>=2;t3

5)对数据进行去重处理(一个用户可以在两个不同的时间点连续登录),例如:a用户在1月10号1月11号以及1月20号和1月21号4天登录。

select

user_id,

min(age) age

from

t3

group by

user_id;t4

6)计算活跃用户(两天连续有访问)的人数以及平均年龄

select

count(*) ct,

cast(sum(age)/count(*) as decimal(10,2))

from t4;

7)对全量数据集进行按照用户去重

select

user_id,

min(age) age

from

user_age

group by

user_id;t5

8)计算所有用户的数量以及平均年龄

select

count(*) user_count,

cast((sum(age)/count(*)) as decimal(10,1))

from

t5;

9)将第5步以及第7步两个数据集进行union all操作

select

0 user_total_count,

0 user_total_avg_age,

count(*) twice_count,

cast(sum(age)/count(*) as decimal(10,2)) twice_count_avg_age

from

(

select

user_id,

min(age) age

from

(select

user_id,

min(age) age

from

(

select

user_id,

age,

date_sub(dt,rk) flag

from

(

select

dt,

user_id,

min(age) age,

rank() over(partition by user_id order by dt) rk

from

user_age

group by

dt,user_id

)t1

)t2

group by

user_id,flag

having

count(*)>=2)t3

group by

user_id

)t4


union all


select

count(*) user_total_count,

cast((sum(age)/count(*)) as decimal(10,1)),

0 twice_count,

0 twice_count_avg_age

from

(

select

user_id,

min(age) age

from

user_age

group by

user_id

)t5;t6

10)求和并拼接为最终SQL

select

sum(user_total_count),

sum(user_total_avg_age),

sum(twice_count),

sum(twice_count_avg_age)

from

(select

0 user_total_count,

0 user_total_avg_age,

count(*) twice_count,

cast(sum(age)/count(*) as decimal(10,2)) twice_count_avg_age

from

(

select

user_id,

min(age) age

from

(select

user_id,

min(age) age

from

(

select

user_id,

age,

date_sub(dt,rk) flag

from

(

select

dt,

user_id,

min(age) age,

rank() over(partition by user_id order by dt) rk

from

user_age

group by

dt,user_id

)t1

)t2

group by

user_id,flag

having

count(*)>=2)t3

group by

user_id

)t4


union all


select

count(*) user_total_count,

cast((sum(age)/count(*)) as decimal(10,1)),

0 twice_count,

0 twice_count_avg_age

from

(

select

user_id,

min(age) age

from

user_age

group by

user_id

)t5)t6;

9.3.6 手写HQL 第6题

请用sql写出所有用户中在今年10月份第一次购买商品的金额,表ordertable字段(购买用户:userid,金额:money,购买时间:paymenttime(格式:2017-10-01),订单id:orderid)

1)建表

create table ordertable(

userid string,

money int,

paymenttime string,

orderid string)

row format delimited fields terminated by '\t';

2)查询出

select

userid,

min(paymenttime) paymenttime

from

ordertable

where

date_format(paymenttime,'yyyy-MM')='2017-10'

group by

userid;t1


select

t1.userid,

t1.paymenttime,

od.money

from

t1

join

ordertable od

on

t1.userid=od.userid

and

t1.paymenttime=od.paymenttime;


select

t1.userid,

t1.paymenttime,

od.money

from

(select

userid,

min(paymenttime) paymenttime

from

ordertable

where

date_format(paymenttime,'yyyy-MM')='2017-10'

group by

userid)t1

join

ordertable od

on

t1.userid=od.userid

and

t1.paymenttime=od.paymenttime;

9.3.7 手写HQL 第7题

有一个线上服务器访问日志格式如下(用sql答题)

时间 接口 ip地址

2016-11-09 11:22:05 /api/user/login 110.23.5.33

2016-11-09 11:23:10 /api/user/detail 57.3.2.16

.....

2016-11-09 23:59:40 /api/user/login 200.6.5.166

求11月9号下午14点(14-15点),访问api/user/login接口的top10的ip地址

数据集

2016-11-09 14:22:05 /api/user/login 110.23.5.33

2016-11-09 11:23:10 /api/user/detail 57.3.2.16

2016-11-09 14:59:40 /api/user/login 200.6.5.166

2016-11-09 14:22:05 /api/user/login 110.23.5.34

2016-11-09 14:22:05 /api/user/login 110.23.5.34

2016-11-09 14:22:05 /api/user/login 110.23.5.34

2016-11-09 11:23:10 /api/user/detail 57.3.2.16

2016-11-09 23:59:40 /api/user/login 200.6.5.166

2016-11-09 14:22:05 /api/user/login 110.23.5.34

2016-11-09 11:23:10 /api/user/detail 57.3.2.16

2016-11-09 23:59:40 /api/user/login 200.6.5.166

2016-11-09 14:22:05 /api/user/login 110.23.5.35

2016-11-09 14:23:10 /api/user/detail 57.3.2.16

2016-11-09 23:59:40 /api/user/login 200.6.5.166

2016-11-09 14:59:40 /api/user/login 200.6.5.166

2016-11-09 14:59:40 /api/user/login 200.6.5.166

1)建表

create table ip(

time string,

interface string,

ip string)

row format delimited fields terminated by '\t';

2)最终SQL

select

ip,

interface,

count(*) ct

from

ip

where

date_format(time,'yyyy-MM-dd HH')>='2016-11-09 14'

and

date_format(time,'yyyy-MM-dd HH')<='2016-11-09 15'

and

interface='/api/user/login'

group by

ip,interface

order by

ct desc

limit 2;t1

9.3.8 手写SQL 第8题

有一个账号表如下,请写出SQL语句,查询各自区组的money排名前十的账号(分组取前10)

1)建表(MySQL)

CREATE TABLE `account`

( `dist_id` int(11)DEFAULT NULL COMMENT '区组id',

`account` varchar(100)DEFAULT NULL COMMENT '账号',

`gold` int(11)DEFAULT 0 COMMENT '金币');

2)最终SQL

select

*

from

account as a

where

(select

count(distinct(a1.gold))

from

account as a1

where

a1.dist_id=a.dist_id

and

a1.gold>a.gold)<3;

9.3.9 手写HQL 第9题

1)有三张表分别为会员表(member)销售表(sale)退货表(regoods)

(1)会员表有字段memberid(会员id,主键)credits(积分);

(2)销售表有字段memberid(会员id,外键)购买金额(MNAccount);

(3)退货表中有字段memberid(会员id,外键)退货金额(RMNAccount)。

2)业务说明

(1)销售表中的销售记录可以是会员购买,也可以是非会员购买。(即销售表中的memberid可以为空);

(2)销售表中的一个会员可以有多条购买记录;

(3)退货表中的退货记录可以是会员,也可是非会员;

(4)一个会员可以有一条或多条退货记录。

查询需求:分组查出销售表中所有会员购买金额,同时分组查出退货表中所有会员的退货金额,把会员id相同的购买金额-退款金额得到的结果更新到表会员表中对应会员的积分字段(credits)

数据集

sale

1001 50.3

1002 56.5

1003 235

1001 23.6

1005 56.2

25.6

33.5


regoods

1001 20.1

1002 23.6

1001 10.1

23.5

10.2

1005 0.8

1)建表

create table member(memberid string,credits double) row format delimited fields terminated by '\t';


create table sale(memberid string,MNAccount double) row format delimited fields terminated by '\t';


create table regoods(memberid string,RMNAccount double) row format delimited fields terminated by '\t';

2)最终SQL

insert into table member

select

t1.memberid,

MNAccount-RMNAccount

from

(select

memberid,

sum(MNAccount) MNAccount

from

sale

where

memberid!=''

group by

memberid

)t1

join

(select

memberid,

sum(RMNAccount) RMNAccount

from

regoods

where

memberid!=''

group by

memberid

)t2

on

t1.memberid=t2.memberid;

9.3.10 手写HQL 第10题

1.用一条SQL语句查询出每门课都大于80分的学生姓名

name   kecheng   fenshu

张三    语文    81

张三    数学    75

李四    语文    76

李四    数学    90

王五    语文    81

王五    数学    100

王五    英语    90

A: select distinct name from table where name not in (select distinct name from table where fenshu<=80)

B:select name from table group by name having min(fenshu)>80

2. 学生表 如下:自动编号   学号  姓名 课程编号 课程名称 分数1     2005001 张三  0001   数学   692     2005002 李四  0001   数学   893     2005001 张三  0001   数学   69删除除了自动编号不同, 其他都相同的学生冗余信息A: delete tablename where 自动编号 not in(select min(自动编号) from tablename group by学号, 姓名, 课程编号, 课程名称, 分数)

3.一个叫team的表,里面只有一个字段name,一共有4条纪录,分别是a,b,c,d,对应四个球队,现在四个球队进行比赛,用一条sql语句显示所有可能的比赛组合.

答:select a.name, b.namefrom team a, team bwhere a.name < b.name

4.面试题:怎么把这样一个year   month amount1991   1     1.11991   2     1.21991   3     1.31991   4     1.41992   1     2.11992   2     2.21992   3     2.31992   4     2.4查成这样一个结果year m1  m2  m3 m41991 1.1 1.2 1.3 1.41992 2.1 2.2 2.3 2.4 答案select year, (select amount from aaa m where mnotallow=1 and m.year=aaa.year) as m1,(select amount from aaa m where mnotallow=2 and m.year=aaa.year) as m2,(select amount from aaa m where mnotallow=3 and m.year=aaa.year) as m3,(select amount from aaa m where mnotallow=4 and m.year=aaa.year) as m4from aaa group by year

*********************************************************************
5.说明:复制表(只复制结构,源表名:a新表名:b) SQL: select * into b from a where 1<>1 (where1=1,拷贝表结构和数据内容)ORACLE:create table b

As

Select * from a where 1=2


[<>(不等于)(SQL Server Compact)

比较两个表达式。 当使用此运算符比较非空表达式时,如果左操作数不等于右操作数,则结果为 TRUE。 否则,结果为 FALSE。]

6.

原表:courseid coursename score-------------------------------------1 java 702 oracle 903 xml 404 jsp 305 servlet 80-------------------------------------为了便于阅读,查询此表后的结果显式如下(及格分数为60):courseid coursename score mark---------------------------------------------------1 java 70 pass2 oracle 90 pass3 xml 40 fail4 jsp 30 fail5 servlet 80 pass---------------------------------------------------写出此查询语句select courseid, coursename ,score ,if(score>=60, "pass","fail") as mark from course

7.表名:购物信息

购物人 商品名称 数量

A 甲 2

B 乙 4

C 丙 1

A 丁 2

B 丙 5

……


给出所有购入商品为两种或两种以上的购物人记录


答:select * from 购物信息 where 购物人 in (select 购物人 from 购物信息 group by 购物人 having count(*) >= 2);

8.

info 表

date result

2005-05-09 win

2005-05-09 lose

2005-05-09 lose

2005-05-09 lose

2005-05-10 win

2005-05-10 lose

2005-05-10 lose

如果要生成下列结果, 该如何写sql语句?

   win lose

2005-05-09 2 2

2005-05-10 1 2

答案:

(1) select date, sum(case when result = "win" then 1 else 0 end) as "win", sum(case when result = "lose" then 1 else 0 end) as "lose" from info group by date;

(2) select a.date, a.result as win, b.result as lose

  from

  (select date, count(result) as result from info where result = "win" group by date) as a

  join

  (select date, count(result) as result from info where result = "lose" group by date) as b

on a.date = b.date;

9.3.11 手写HQL 第11题

有一个订单表order。已知字段有:order_id(订单ID), user_id(用户ID),amount(金额), pay_datetime(付费时间),channel_id(渠道ID),dt(分区字段)。

1. 在Hive中创建这个表。

2. 查询dt=‘2018-09-01‘里每个渠道的订单数,下单人数(去重),总金额。

3. 查询dt=‘2018-09-01‘里每个渠道的金额最大3笔订单。

4. 有一天发现订单数据重复,请分析原因

create external table order(

order_id int,

user_id int,

amount double,

pay_datatime timestamp,

channel_id int

)partitioned by(dt string)

row format delimited fields terminated by '\t';

select

count(order_id),

count(distinct(user_id))

sum(amount)

from

order

where dt="2019-09-01"

select

order_id

channel_id

channel_id_amount

from(

select

order_id

channel_id,

amount,

max(amount) over(partition by channel_id)

min(amount) over(partition by channel_id)

row_number()

over(

partition by channel_id

order by amount desc

)rank

from

order

where dt="2019-09-01"

)t

where t.rank<4

订单属于业务数据,在关系型数据库中不会存在数据重复

hive建表时也不会导致数据重复,

我推测是在数据迁移时,迁移失败导致重复迁移数据冗余了

t_order订单表

order_id,//订单id

item_id, //商品id

create_time,//下单时间

amount//下单金额

t_item商品表

item_id,//商品id

item_name,//商品名称

category//品类

t_item商品表

item_id,//商品id

item_name,//名称

category_1,//一级品类

category_2,//二级品类

1. 最近一个月,销售数量最多的10个商品

select

item_id,

count(order_id)a

from

t_order

where

dataediff(create_time,current_date)<=30

group by

item_id

order by a desc;

2. 最近一个月,每个种类里销售数量最多的10个商品

#一个订单对应一个商品 一个商品对应一个品类

with(

select

order_id,

item_id,

item_name,

category

from

t_order

join

t_item

on

t_order.item_id = t_item.item_id

) t

select

order_id,

item_id,

item_name,

category,

count(item_id)over(

partition by category

)item_count

from

t

group by category

order by item_count desc

limit 10;

计算平台的每一个用户发过多少日记、获得多少点赞数

with t3 as(

select * from

t1 left join t2

on t1.log_id = t2.log_id

)

select

uid,//用户Id

count(log_id)over(partition by uid)log_cnt,//

count(like_uid)over(partition by log_id)liked_cnt//获得多少点赞数

from

t3

处理产品版本号

1、需求A:找出T1表中最大的版本号

思路:列转行 切割版本号 一列变三列

主版本号 子版本号 阶段版本号

with t2 as(//转换

select

v_id v1,//版本号

v_id v2 //主

from

t1

lateral view explode(v2) tmp as v2

)

select //第一层 找出第一个

v1,

max(v2)

from

t2

——————————————————————————————————————————————————————————————

1、需求A:找出T1表中最大的版本号

select

v_id,//版本号

max(split(v_id,".")[0]) v1,//主版本不会为空

max(if(split(v_id,".")[1]="",0,split(v_id,".")[1]))v2,//取出子版本并判断是否为空,并给默认值

max(if(split(v_id,".")[2]="",0,split(v_id,".")[2]))v3//取出阶段版本并判断是否为空,并给默认值

from

t1

2、需求B:计算出如下格式的所有版本号排序,要求对于相同的版本号,顺序号并列:

select

v_id,

rank() over(partition by v_id order by v_id)seq

from

t1

9.3.12 连续问题

如下数据为蚂蚁森林中用户领取的减少碳排放量

id dt lowcarbon

1001 2021-12-12 123

1002 2021-12-12 45

1001 2021-12-13 43

1001 2021-12-13 45

1001 2021-12-13 23

1002 2021-12-14 45

1001 2021-12-14 230

1002 2021-12-15 45

1001 2021-12-15 23

… …

找出连续3天及以上减少碳排放量在100以上的用户

1)按照用户ID及时间字段分组,计算每个用户单日减少的碳排放量

select

id,

dt,

sum(lowcarbon) lowcarbon

from test1

group by id,dt

having lowcarbon>100;t1

1001 2021-12-12 123

1001 2021-12-13 111

1001 2021-12-14 230


等差数列法:两个等差数列如果等差相同,则相同位置的数据相减等到的结果相同

2)按照用户分组,同时按照时间排序,计算每条数据的Rank值

select

id,

dt,

lowcarbon,

rank() over(partition by id order by dt) rk

from t1;t2


3)将每行数据中的日期减去Rank值

select

id,

dt,

lowcarbon,

date_sub(dt,rk) flag

from t2;t3


4)按照用户及Flag分组,求每个组有多少条数据,并找出大于等于3条的数据

select

id,

flag,

count(*) ct

from t3

group by id,flag

having ct>=3;


5)最终HQL

select

id,

flag,

count(*) ct

from

(select

id,

dt,

lowcarbon,

date_sub(dt,rk) flag

from

(select

id,

dt,

lowcarbon,

rank() over(partition by id order by dt) rk

from

(select

id,

dt,

sum(lowcarbon) lowcarbon

from test1

group by id,dt

having lowcarbon>100)t1)t2)t3

group by id,flag

having ct>=3;

9.3.13 分组问题

如下为电商公司用户访问时间数据

id ts(秒)

1001 17523641234

1001 17523641256

1002 17523641278

1001 17523641334

1002 17523641434

1001 17523641534

1001 17523641544

1002 17523641634

1001 17523641638

1001 17523641654

某个用户连续的访问记录如果时间间隔小于60秒,则分为同一个组,结果为:

id ts(秒) group

1001 17523641234 1

1001 17523641256 1

1001 17523641334 2

1001 17523641534 3

1001 17523641544 3

1001 17523641638 4

1001 17523641654 4

1002 17523641278 1

1002 17523641434 2

1002 17523641634 3

1)将上一行时间数据下移

lead:领导

lag:延迟

select

id,

ts,

lag(ts,1,0) over(partition by id order by ts) lagts

from

test2;t1

1001 17523641234 0

1001 17523641256 17523641234

1001 17523641334 17523641256

1001 17523641534 17523641334

1001 17523641544 17523641534

1001 17523641638 17523641544

1001 17523641654 17523641638

1002 17523641278 0

1002 17523641434 17523641278

1002 17523641634 17523641434


2)将当前行时间数据减去上一行时间数据

select

id,

ts,

ts-lagts tsdiff

from

t1;t2


select

id,

ts,

ts-lagts tsdiff

from

(select

id,

ts,

lag(ts,1,0) over(partition by id order by ts) lagts

from

test2)t1;t2

1001 17523641234 17523641234

1001 17523641256 22

1001 17523641334 78

1001 17523641534 200

1001 17523641544 10

1001 17523641638 94

1001 17523641654 16

1002 17523641278 17523641278

1002 17523641434 156

1002 17523641634 200


3)计算每个用户范围内从第一行到当前行tsdiff大于等于60的总个数(分组号)

select

id,

ts,

sum(if(tsdiff>=60,1,0)) over(partition by id order by ts) groupid

from

t2;


4)最终HQL

select

id,

ts,

sum(if(tsdiff>=60,1,0)) over(partition by id order by ts) groupid

from

(select

id,

ts,

ts-lagts tsdiff

from

(select

id,

ts,

lag(ts,1,0) over(partition by id order by ts) lagts

from

test2)t1)t2;

9.3.14 间隔连续问题

某游戏公司记录的用户每日登录数据

id dt

1001 2021-12-12

1002 2021-12-12

1001 2021-12-13

1001 2021-12-14

1001 2021-12-16

1002 2021-12-16

1001 2021-12-19

1002 2021-12-17

1001 2021-12-20

计算每个用户最大的连续登录天数,可以间隔一天。解释:如果一个用户在1,3,5,6登录游戏,则视为连续6天登录。

思路一:等差数列

1001 2021-12-12 1

1001 2021-12-13 2

1001 2021-12-14 3

1001 2021-12-16 4

1001 2021-12-19 5

1001 2021-12-20 6


1001 2021-12-12 1 2021-12-11

1001 2021-12-13 2 2021-12-11

1001 2021-12-14 3 2021-12-11

1001 2021-12-16 4 2021-12-12

1001 2021-12-19 5 2021-12-14

1001 2021-12-20 6 2021-12-14


1001 2021-12-11 3

1001 2021-12-12 1

1001 2021-12-14 1


1001 2021-12-11 3 1

1001 2021-12-12 1 2

1001 2021-12-14 1 3


1001 2021-12-11 3 1 2021-12-10

1001 2021-12-12 1 2 2021-12-10

1001 2021-12-14 1 3 2021-12-11


思路二:分组

1001 2021-12-12

1001 2021-12-13

1001 2021-12-14

1001 2021-12-16

1001 2021-12-19

1001 2021-12-20


1)将上一行时间数据下移

1001 2021-12-12 1970-01-01

1001 2021-12-13 2021-12-12

1001 2021-12-14 2021-12-13

1001 2021-12-16 2021-12-14

1001 2021-12-19 2021-12-16

1001 2021-12-20 2021-12-19

select

id,

dt,

lag(dt,1,'1970-01-01') over(partition by id order by dt) lagdt

from

test3;t1


2)将当前行时间减去上一行时间数据(datediff(dt1,dt2))

1001 2021-12-12 564564

1001 2021-12-13 1

1001 2021-12-14 1

1001 2021-12-16 2

1001 2021-12-19 3

1001 2021-12-20 1

select

id,

dt,

datediff(dt,lagdt) flag

from

t1;t2


3)按照用户分组,同时按照时间排序,计算从第一行到当前行大于2的数据的总条数(sum(if(flag>2,1,0)))

1001 2021-12-12 1

1001 2021-12-13 1

1001 2021-12-14 1

1001 2021-12-16 1

1001 2021-12-19 2

1001 2021-12-20 2

select

id,

dt,

sum(if(flag>2,1,0)) over(partition by id order by dt) flag

from

t2;t3


4)按照用户和flag分组,求最大时间减去最小时间并加上1

select

id,

flag,

datediff(max(dt),min(dt)) days

from

t3

group by id,flag;t4


5)取连续登录天数的最大值

select

id,

max(days)+1

from

t4

group by id;


6)最终HQL

select

id,

max(days)+1

from

(select

id,

flag,

datediff(max(dt),min(dt)) days

from

(select

id,

dt,

sum(if(flag>2,1,0)) over(partition by id order by dt) flag

from

(select

id,

dt,

datediff(dt,lagdt) flag

from

(select

id,

dt,

lag(dt,1,'1970-01-01') over(partition by id order by dt) lagdt

from

test3)t1)t2)t3

group by id,flag)t4

group by id;

9.3.15 打折日期交叉问题

如下为平台商品促销数据:字段为品牌,打折开始日期,打折结束日期

brand stt edt

oppo 2021-06-05 2021-06-09

oppo 2021-06-11 2021-06-21

vivo 2021-06-05 2021-06-15

vivo 2021-06-09 2021-06-21

redmi 2021-06-05 2021-06-21

redmi 2021-06-09 2021-06-15

redmi 2021-06-17 2021-06-26

huawei 2021-06-05 2021-06-26

huawei 2021-06-09 2021-06-15

huawei 2021-06-17 2021-06-21

计算每个品牌总的打折销售天数,注意其中的交叉日期,比如vivo品牌,第一次活动时间为2021-06-05到2021-06-15,第二次活动时间为2021-06-09到2021-06-21其中9号到15号为重复天数,只统计一次,即vivo总打折天数为2021-06-05到2021-06-21共计17天。

1)将当前行以前的数据中最大的edt放置当前行

select

id,

stt,

edt,

max(edt) over(partition by id order by stt rows between UNBOUNDED PRECEDING and 1 PRECEDING) maxEdt

from test4;t1

redmi 2021-06-05 2021-06-21 null

redmi 2021-06-09 2021-06-15 2021-06-21

redmi 2021-06-17 2021-06-26 2021-06-21


2)比较开始时间与移动下来的数据,如果开始时间大,则不需要操作,

反之则需要将移动下来的数据加一替换当前行的开始时间

如果是第一行数据,maxEDT为null,则不需要操作

select

id,

if(maxEdt is null,stt,if(stt>maxEdt,stt,date_add(maxEdt,1))) stt,

edt

from t1;t2

redmi 2021-06-05 2021-06-21

redmi 2021-06-22 2021-06-15

redmi 2021-06-22 2021-06-26


3)将每行数据中的结束日期减去开始日期

select

id,

datediff(edt,stt) days

from

t2;t3

redmi 16

redmi -4

redmi 4


4)按照品牌分组,计算每条数据加一的总和

select

id,

sum(if(days>=0,days+1,0)) days

from

t3

group by id;

redmi 22


5)最终HQL

select

id,

sum(if(days>=0,days+1,0)) days

from

(select

id,

datediff(edt,stt) days

from

(select

id,

if(maxEdt is null,stt,if(stt>maxEdt,stt,date_add(maxEdt,1))) stt,

edt

from

(select

id,

stt,

edt,

max(edt) over(partition by id order by stt rows between UNBOUNDED PRECEDING and 1 PRECEDING) maxEdt

from test4)t1)t2)t3

group by id;

9.3.16 同时在线问题

如下为某直播平台主播开播及关播时间,根据该数据计算出平台最高峰同时在线的主播人数。

id stt edt

1001 2021-06-14 12:12:12 2021-06-14 18:12:12

1003 2021-06-14 13:12:12 2021-06-14 16:12:12

1004 2021-06-14 13:15:12 2021-06-14 20:12:12

1002 2021-06-14 15:12:12 2021-06-14 16:12:12

1005 2021-06-14 15:18:12 2021-06-14 20:12:12

1001 2021-06-14 20:12:12 2021-06-14 23:12:12

1006 2021-06-14 21:12:12 2021-06-14 23:15:12

1007 2021-06-14 22:12:12 2021-06-14 23:10:12

… …

1)对数据分类,在开始数据后添加正1,表示有主播上线,同时在关播数据后添加-1,表示有主播下线

select id,stt dt,1 p from test5

union

select id,edt dt,-1 p from test5;t1


1001 2021-06-14 12:12:12 1

1001 2021-06-14 18:12:12 -1

1001 2021-06-14 20:12:12 1

1001 2021-06-14 23:12:12 -1

1002 2021-06-14 15:12:12 1

1002 2021-06-14 16:12:12 -1

1003 2021-06-14 13:12:12 1

1003 2021-06-14 16:12:12 -1

1004 2021-06-14 13:15:12 1

1004 2021-06-14 20:12:12 -1

1005 2021-06-14 15:18:12 1

1005 2021-06-14 20:12:12 -1

1006 2021-06-14 21:12:12 1

1006 2021-06-14 23:15:12 -1

1007 2021-06-14 22:12:12 1

1007 2021-06-14 23:10:12 -1


2)按照时间排序,计算累加人数

select

id,

dt,

sum(p) over(order by dt) sum_p

from

(select id,stt dt,1 p from test5

union

select id,edt dt,-1 p from test5)t1;t2


3)找出同时在线人数最大值

select

max(sum_p)

from

(select

id,

dt,

sum(p) over(order by dt) sum_p

from

(select id,stt dt,1 p from test5

union

select id,edt dt,-1 p from test5)t1)t2;

第10章 JavaSE

10.1 HhashMap底层源码,数据结构

hashMap的底层结构在jdk1.7中由数组+链表实现,在jdk1.8中由数组+链表+红黑树实现,以数组+链表的结构为例。

大数据技术之高频面试题|剑谱_Shell_72

大数据技术之高频面试题|剑谱_数据倾斜_73




JDK1.8之前Put方法:

大数据技术之高频面试题|剑谱_Shell_74












JDK1.8之后Put方法:

大数据技术之高频面试题|剑谱_数据倾斜_75


10.2 Java自带哪几种线程池?

1)newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这种类型的线程池特点是:

工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。

如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。

在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

2)newFixedThreadPool

创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

3)newSingleThreadExecutor

创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

4)newScheduleThreadPool

创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。延迟3秒执行。

10.3 HashMap和HashTable区别

  1. 线程安全性不同

HashMap是线程不安全的,HashTable是线程安全的,其中的方法是Synchronize的,在多线程并发的情况下,可以直接使用HashTabl,但是使用HashMap时必须自己增加同步处理。

  1. 是否提供contains方法

HashMap只有containsValue和containsKey方法;HashTable有contains、containsKey和containsValue三个方法,其中contains和containsValue方法功能相同。

  1. key和value是否允许null值

Hashtable中,key和value都不允许出现null值。HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。

  1. 数组初始化和扩容机制

 HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。

 Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。

10.4 TreeSet和HashSet区别

HashSet是采用hash表来实现的。其中的元素没有按顺序排列,add()、remove()以及contains()等方法都是复杂度为O(1)的方法。

TreeSet是采用树结构实现(红黑树算法)。元素是按顺序进行排列,但是add()、remove()以及contains()等方法都是复杂度为O(log (n))的方法。它还提供了一些方法来处理排序的set,如first(),last(),headSet(),tailSet()等等。

10.5 String buffer和String build区别

1、StringBuffer与StringBuilder中的方法和功能完全是等价的。

2、只是StringBuffer中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而StringBuilder没有这个修饰,可以被认为是线程不安全的。 

3、在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全而StringBuffer则每次都需要判断锁,效率相对更低

10.6 Final、Finally、Finalize

final:修饰符(关键字)有三种用法:修饰类、变量和方法。修饰类时,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。修饰变量时,该变量使用中不被改变,必须在声明时给定初值,在引用中只能读取不可修改,即为常量。修饰方法时,也同样只能使用,不能在子类中被重写。

finally:通常放在try…catch的后面构造最终执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。

finalize:Object类中定义的方法,Java中允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。

10.7 ==和Equals区别

 == : 如果比较的是基本数据类型,那么比较的是变量的值

如果比较的是引用数据类型,那么比较的是地址值(两个对象是否指向同一块内存)

 equals:如果没重写equals方法比较的是两个对象的地址值。

 如果重写了equals方法后我们往往比较的是对象中的属性的内容


equals方法是从Object类中继承的,默认的实现就是使用==

大数据技术之高频面试题|剑谱_Shell_76

第11章 Redis

11.1 缓存穿透、缓存雪崩、缓存击穿

1)缓存穿透是指查询一个一定不存在的数据。由于缓存命不中时会去查询数据库,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

解决方案:

  1. 是将空对象也缓存起来,并给它设置一个很短的过期时间,最长不超过5分钟

② 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力

2)如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,就会造成缓存雪崩。

解决方案:

尽量让失效的时间点不分布在同一个时间点

3)缓存击穿,是指一个key非常热点,在不停的扛着大并发,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

解决方案:

可以设置key永不过期

11.2 哨兵模式

主从复制中反客为主的自动版,如果主机Down掉,哨兵会从从机中选择一台作为主机,并将它设置为其他从机的主机,而且如果原来的主机再次启动的话也会成为从机。

10.3 数据类型

string

字符串

list

可以重复的集合

set

不可以重复的集合

hash

类似于Map<String,String>

zset(sorted set)

带分数的set

11.4 持久化

1)RDB持久化:

  1. 在指定的时间间隔内持久化
  2. 服务shutdown会自动持久化

③ 输入bgsave也会持久化

2)AOF : 以日志形式记录每个更新操作

Redis重新启动时读取这个文件,重新执行新建、修改数据的命令恢复数据。

保存策略:

推荐(并且也是默认)的措施为每秒持久化一次,这种策略可以兼顾速度和安全性。

缺点:

1 比起RDB占用更多的磁盘空间

2 恢复备份速度要慢

3 每次读写都同步的话,有一定的性能压力

4 存在个别Bug,造成恢复不能

选择策略:

官方推荐:

如果对数据不敏感,可以选单独用RDB;不建议单独用AOF,因为可能出现Bug;如果只是做纯内存缓存,可以都不用

11.5 悲观锁

执行操作前假设当前的操作肯定(或有很大几率)会被打断(悲观)。基于这个假设,我们在做操作前就会把相关资源锁定,不允许自己执行期间有其他操作干扰。

11.6 乐观锁

执行操作前假设当前操作不会被打断(乐观)。基于这个假设,我们在做操作前不会锁定资源,万一发生了其他操作的干扰,那么本次操作将被放弃。Redis使用的就是乐观锁。

第12章 MySql

12.1 MyISAM与InnoDB的区别

对比项

MyISAM

InnoDB

外键

不支持

支持

事务

不支持

支持

行表锁

表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作

行锁,操作时只锁某一行,不对其它行有影响,

适合高并发的操作

缓存

只缓存索引,不缓存真实数据

不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响


12.2 索引优化

数据结构:B+Tree

一般来说能够达到range就可以算是优化了 idx name_deptId

口诀(两个法则加6种索引失效的情况)

全值匹配我最爱,最左前缀要遵守;

带头大哥不能死,中间兄弟不能断;

索引列上少计算,范围之后全失效;

LIKE百分写最右,覆盖索引不写*;

不等空值还有OR,索引影响要注意;

VAR引号不可丢,SQL优化有诀窍。

12.3 b-tree和b+tree的区别

1) B-树的关键字、索引和记录是放在一起的, B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。

2) 在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。

12.4 redis是单线程的,为什么那么快

1)完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。

2)数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的

3)采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗

4)使用多路I/O复用模型,非阻塞IO

5)使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求

12.5 MySQL的事务

一、事务的基本要素(ACID)

1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位

2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。

3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。

4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。


二、事务的并发问题

1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致

3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表


三、MySQL事务隔离级别

事务隔离级别 脏读 不可重复读 幻读

读未提交(read-uncommitted) 是 是 是

不可重复读(read-committed) 否 是 是

可重复读(repeatable-read) 否 否 是

串行化(serializable) 否 否 否


第13章 JVM

13.1 JVM内存分哪几个区,每个区的作用是什么?

大数据技术之高频面试题|剑谱_数据_77

java虚拟机主要分为以下几个区:

  1. 方法区
  2. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型的卸载
  3. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。
  4. 该区域是被线程共享的。
  5. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。
  6. 虚拟机栈:
  7. 虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。
  8. 虚拟机栈是线程私有的,它的生命周期与线程相同。
  9. 局部变量表里存储的是基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定
  10. 操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式
  11. 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。
  12. 本地方法栈
    本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。

java堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。

  1. 程序计数器:

内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM情况的区域。


13.2 Java类加载过程?

Java类加载需要经历一下几个过程:

  1. 加载

加载时类加载的第一个过程,在这个阶段,将完成一下三件事情:

  1. 通过一个类的全限定名获取该类的二进制流。
  2. 将该二进制流中的静态存储结构转化为方法去运行时数据结构。 
  3. 在内存中生成该类的Class对象,作为该类的数据访问入口。
  4. 验证

验证的目的是为了确保Class文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下四钟验证:

  1. 文件格式验证:验证字节流是否符合Class文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.
  2. 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。
  3. 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。
  4. 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。
  5. 准备

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

  1. 解析

该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。

  1. 初始化

初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。


13.3 java中垃圾收集的方法有哪些?

1)引用计数法   应用于:微软的COM/ActionScrip3/Python等

a) 如果对象没有被引用,就会被回收,缺点:需要维护一个引用计算器

2)复制算法 年轻代中使用的是Minor GC,这种GC算法采用的是复制算法(Copying)

a) 效率高,缺点:需要内存容量大,比较耗内存

b) 使用在占空间比较小、刷新次数多的新生区

3)标记清除 老年代一般是由标记清除或者是标记清除与标记整理的混合实现

a) 效率比较低,会差生碎片。

4)标记压缩 老年代一般是由标记清除或者是标记清除与标记整理的混合实现

a) 效率低速度慢,需要移动对象,但不会产生碎片。

5)标记清除压缩标记清除-标记压缩的集合,多次GC后才Compact

a) 使用于占空间大刷新次数少的养老区,是3 4的集合体

13.4 如何判断一个对象是否存活?(或者GC对象的判定方法)

判断一个对象是否存活有两种方法:

  1. 引用计数法
  2. 可达性算法(引用链法)

13.5 什么是类加载器,类加载器有哪些?

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。

主要有一下四种类加载器:

  1. 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
  2. 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  3. 系统类加载器(system class loader)也叫应用类加载器:它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
  4. 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。


13.6 简述Java内存分配与回收策略以及Minor GC和Major GC(full GC)

内存分配:

  1. 栈区:栈分为java虚拟机栈和本地方法栈
  2. 堆区:堆被所有线程共享区域,在虚拟机启动时创建,唯一目的存放对象实例。堆区是gc的主要区域,通常情况下分为两个区块年轻代和年老代。更细一点年轻代又分为Eden区,主要放新创建对象,From survivor 和 To survivor 保存gc后幸存下的对象,默认情况下各自占比 8:1:1。
  3. 方法区:被所有线程共享区域,用于存放已被虚拟机加载的类信息,常量,静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代(permanment generation)
  4. 程序计数器:当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令,比如循环,分支,跳转,异常处理,线程恢复等都是依赖计数器来完成。线程私有的。


回收策略以及Minor GC和Major GC:

  1. 对象优先在堆的Eden区分配。
  2. 大对象直接进入老年代。
  3. 长期存活的对象将直接进入老年代。

当Eden区没有足够的空间进行分配时,虚拟机会执行一次Minor GC.Minor GC通常发生在新生代的Eden区,在这个区的对象生存期短,往往发生GC的频率较高,回收速度比较快;Full Gc/Major GC 发生在老年代,一般情况下,触发老年代GC的时候不会触发Minor GC,但是通过配置,可以在Full GC之前进行一次Minor GC这样可以加快老年代的回收速度。

第14章 JUC

14.1 Synchronized与Lock的区别

1)Synchronized能实现的功能Lock都可以实现,而且Lock比Synchronized更好用,更灵活。

2)Synchronized可以自动上锁和解锁;Lock需要手动上锁和解锁

14.2 Runnable和Callable的区别

1)Runnable接口中的方法没有返回值;Callable接口中的方法有返回值

2)Runnable接口中的方法没有抛出异常;Callable接口中的方法抛出了异常

3)Runnable接口中的落地方法是call方法;Callable接口中的落地方法是run方法

14.3 什么是分布式锁

当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。分布式锁可以将标记存在内存,只是该内存不是某个进程分配的内存而是公共内存,如 Redis,通过set (key,value,nx,px,timeout)方法添加分布式锁。

14.4 什么是分布式事务

分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。

第15章 面试说明

15.1 面试过程最关键的是什么?

1)大大方方的聊,放松

2)体现优势,避免劣势

15.2 面试时该怎么说?

1)语言表达清楚

(1)思维逻辑清晰,表达流畅

(2)一二三层次表达

2)所述内容不犯错

(1)不说前东家或者自己的坏话

(2)往自己擅长的方面说

(3)实质,对考官来说,内容听过,就是自我肯定;没听过,那就是个学习的过程。

15.3 面试技巧

15.3.1 六个常见问题

1)你的优点是什么?

大胆的说出自己各个方面的优势和特长

2)你的缺点是什么?

不要谈自己真实问题;用“缺点”衬托自己的优点

3)你的离职原因是什么?

  • 不说前东家坏话,哪怕被伤过
  • 合情合理合法
  • 不要说超过1个以上的原因

4)您对薪资的期望是多少?

  • 非终面不深谈薪资
  • 只说区间,不说具体数字
  • 底线是不低于当前薪资
  • 非要具体数字,区间取中间值,或者当前薪资的+20%

5)您还有什么想问的问题?

  • 这是体现个人眼界和层次的问题
  • 问题本身不在于面试官想得到什么样的答案,而在于你跟别的应聘者的对比
  • 标准答案:

公司希望我入职后的3-6个月内,给公司解决什么样的问题

公司(或者对这个部门)未来的战略规划是什么样子的?

以你现在对我的了解,您觉得我需要多长时间融入公司?

6)您最快多长时间能入职?

一周左右,如果公司需要,可以适当提前。

15.3.2 两个注意事项

1)职业化的语言

2)职业化的形象

15.3.3 自我介绍(控制在4分半以内,不超过5分钟)

1)个人基本信息

2)工作履历

时间、公司名称、任职岗位、主要工作内容、工作业绩、离职原因

3)深度沟通(也叫压力面试)

刨根问底下沉式追问(注意是下沉式,而不是发散式的)

基本技巧:往自己熟悉的方向说

第16章 LeetCode题目精选

https://labuladong.gitbook.io/algo/di-ling-zhang-bi-du-xi-lie/xue-xi-shu-ju-jie-gou-he-suan-fa-de-gao-xiao-fang-fa

16.1 两数之和

问题链接:https://leetcode-cn.com/problems/two-sum/

16.1.1 问题描述

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

```

给定 nums = [2, 7, 11, 15], target = 9


因为 nums[0] + nums[1] = 2 + 7 = 9

所以返回 [0, 1]

```

16.1.2 参考答案

```java

class Solution {

public int[] twoSum(int[] nums, int target) {

Map<Integer, Integer> map = new HashMap<>();

for (int i = 0; i < nums.length; i++) {

int complement = target - nums[i];

if (map.containsKey(complement)) {

return new int[] { map.get(complement), i };

}

map.put(nums[i], i);

}

throw new IllegalArgumentException("No two sum solution");

}

}

```

16.2 爬楼梯

问题链接:https://leetcode-cn.com/problems/climbing-stairs/

16.2.1 问题描述

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

```

输入: 2

输出: 2

解释: 有两种方法可以爬到楼顶。

1. 1 阶 + 1 阶

2. 2 阶

```

示例 2:

```

输入: 3

输出: 3

解释: 有三种方法可以爬到楼顶。

1. 1 阶 + 1 阶 + 1 阶

2. 1 阶 + 2 阶

3. 2 阶 + 1 阶

```

16.2.2 参考答案

```java

public class Solution {

public int climbStairs(int n) {

if (n == 1) {

return 1;

}

int[] dp = new int[n + 1];

dp[1] = 1;

dp[2] = 2;

for (int i = 3; i <= n; i++) {

dp[i] = dp[i - 1] + dp[i - 2];

}

return dp[n];

}

}

```

16.3 翻转二叉树

链接:https://leetcode-cn.com/problems/invert-binary-tree/

16.3.1 问题描述

翻转一棵二叉树。

示例:

输入:

```

4

/ \

2 7

/ \ / \

1 3 6 9

```

输出:


```

4

/ \

7 2

/ \ / \

9 6 3 1

```

16.3.2 参考答案

```java

public TreeNode invertTree(TreeNode root) {

if (root == null) {

return null;

}

TreeNode right = invertTree(root.right);

TreeNode left = invertTree(root.left);

root.left = right;

root.right = left;

return root;

}

```

16.4 反转链表

链接:https://leetcode-cn.com/problems/reverse-linked-list/

16.4.1 问题描述

反转一个单链表。

示例:

```

输入: 1->2->3->4->5->NULL

输出: 5->4->3->2->1->NULL

```

16.4.2 参考答案

```java

public ListNode reverseList(ListNode head) {

ListNode prev = null;

ListNode curr = head;

while (curr != null) {

ListNode nextTemp = curr.next;

curr.next = prev;

prev = curr;

curr = nextTemp;

}

return prev;

}

```

16.5 LRU缓存机制

链接:https://leetcode-cn.com/problems/lru-cache/

16.5.1 问题描述

运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。

写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

进阶:

你是否可以在 O(1) 时间复杂度内完成这两种操作?

示例:

```

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );


cache.put(1, 1);

cache.put(2, 2);

cache.get(1); // 返回 1

cache.put(3, 3); // 该操作会使得密钥 2 作废

cache.get(2); // 返回 -1 (未找到)

cache.put(4, 4); // 该操作会使得密钥 1 作废

cache.get(1); // 返回 -1 (未找到)

cache.get(3); // 返回 3

cache.get(4); // 返回 4

```

16.5.2 参考答案

```java

class LRUCache extends LinkedHashMap<Integer, Integer>{

private int capacity;


public LRUCache(int capacity) {

super(capacity, 0.75F, true);

this.capacity = capacity;

}


public int get(int key) {

return super.getOrDefault(key, -1);

}


public void put(int key, int value) {

super.put(key, value);

}


@Override

protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {

return size() > capacity;

}

}


/**

* LRUCache 对象会以如下语句构造和调用:

* LRUCache obj = new LRUCache(capacity);

* int param_1 = obj.get(key);

* obj.put(key,value);

*/

```

16.6 最长回文子串

链接:https://leetcode-cn.com/problems/longest-palindromic-substring/

16.6.1 问题描述

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:


```

输入: "babad"

输出: "bab"

注意: "aba" 也是一个有效答案。

```

示例 2:


```

输入: "cbbd"

输出: "bb"

```

16.6.2 参考答案

```java

public String longestPalindrome(String s) {

if (s == null || s.length() < 1) return "";

int start = 0, end = 0;

for (int i = 0; i < s.length(); i++) {

int len1 = expandAroundCenter(s, i, i);

int len2 = expandAroundCenter(s, i, i + 1);

int len = Math.max(len1, len2);

if (len > end - start) {

start = i - (len - 1) / 2;

end = i + len / 2;

}

}

return s.substring(start, end + 1);

}


private int expandAroundCenter(String s, int left, int right) {

int L = left, R = right;

while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {

L--;

R++;

}

return R - L - 1;

}

```

16.7 有效的括号

链接:https://leetcode-cn.com/problems/valid-parentheses/

16.7.1 问题描述

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

有效字符串需满足:

1. 左括号必须用相同类型的右括号闭合。

2. 左括号必须以正确的顺序闭合。

注意空字符串可被认为是有效字符串。

示例 1:


```

输入: "()"

输出: true

```


示例 2:

```

输入: "()[]{}"

输出: true

```


示例 3:

```

输入: "(]"

输出: false

```


示例 4:


```

输入: "([)]"

输出: false

```

示例 5:

```

输入: "{[]}"

输出: true

```

16.7.2 参考答案

```java

class Solution {


// Hash table that takes care of the mappings.

private HashMap<Character, Character> mappings;


// Initialize hash map with mappings. This simply makes the code easier to read.

public Solution() {

this.mappings = new HashMap<Character, Character>();

this.mappings.put(')', '(');

this.mappings.put('}', '{');

this.mappings.put(']', '[');

}


public boolean isValid(String s) {


// Initialize a stack to be used in the algorithm.

Stack<Character> stack = new Stack<Character>();


for (int i = 0; i < s.length(); i++) {

char c = s.charAt(i);


// If the current character is a closing bracket.

if (this.mappings.containsKey(c)) {


// Get the top element of the stack. If the stack is empty, set a dummy value of '#'

char topElement = stack.empty() ? '#' : stack.pop();


// If the mapping for this bracket doesn't match the stack's top element, return false.

if (topElement != this.mappings.get(c)) {

return false;

}

} else {

// If it was an opening bracket, push to the stack.

stack.push(c);

}

}


// If the stack still contains elements, then it is an invalid expression.

return stack.isEmpty();

}

}

```

16.8 数组中的第K个最大元素

链接:https://leetcode-cn.com/problems/kth-largest-element-in-an-array/

16.8.1 问题描述

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

```

输入: [3,2,1,5,6,4] 和 k = 2

输出: 5

```

示例 2:

```

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4

输出: 4

```

说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

16.8.2 参考答案

```java

import java.util.Random;

class Solution {

int [] nums;


public void swap(int a, int b) {

int tmp = this.nums[a];

this.nums[a] = this.nums[b];

this.nums[b] = tmp;

}


public int partition(int left, int right, int pivot_index) {

int pivot = this.nums[pivot_index];

// 1. move pivot to end

swap(pivot_index, right);

int store_index = left;


// 2. move all smaller elements to the left

for (int i = left; i <= right; i++) {

if (this.nums[i] < pivot) {

swap(store_index, i);

store_index++;

}

}


// 3. move pivot to its final place

swap(store_index, right);


return store_index;

}


public int quickselect(int left, int right, int k_smallest) {

/*

Returns the k-th smallest element of list within left..right.

*/


if (left == right) // If the list contains only one element,

return this.nums[left]; // return that element


// select a random pivot_index

Random random_num = new Random();

int pivot_index = left + random_num.nextInt(right - left);


pivot_index = partition(left, right, pivot_index);


// the pivot is on (N - k)th smallest position

if (k_smallest == pivot_index)

return this.nums[k_smallest];

// go left side

else if (k_smallest < pivot_index)

return quickselect(left, pivot_index - 1, k_smallest);

// go right side

return quickselect(pivot_index + 1, right, k_smallest);

}


public int findKthLargest(int[] nums, int k) {

this.nums = nums;

int size = nums.length;

// kth largest is (N - k)th smallest

return quickselect(0, size - 1, size - k);

}

}

```

16.9 实现 Trie (前缀树)

16.9.1 问题描述

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。

示例:


```

Trie trie = new Trie();


trie.insert("apple");

trie.search("apple"); // 返回 true

trie.search("app"); // 返回 false

trie.startsWith("app"); // 返回 true

trie.insert("app");

trie.search("app"); // 返回 true

```

说明:

- 你可以假设所有的输入都是由小写字母 a-z 构成的。

- 保证所有输入均为非空字符串。

16.9.2 参考答案

```java

class Trie {

private TrieNode root;


public Trie() {

root = new TrieNode();

}


// Inserts a word into the trie.

public void insert(String word) {

TrieNode node = root;

for (int i = 0; i < word.length(); i++) {

char currentChar = word.charAt(i);

if (!node.containsKey(currentChar)) {

node.put(currentChar, new TrieNode());

}

node = node.get(currentChar);

}

node.setEnd();

}


// search a prefix or whole key in trie and

// returns the node where search ends

private TrieNode searchPrefix(String word) {

TrieNode node = root;

for (int i = 0; i < word.length(); i++) {

char curLetter = word.charAt(i);

if (node.containsKey(curLetter)) {

node = node.get(curLetter);

} else {

return null;

}

}

return node;

}


// Returns if the word is in the trie.

public boolean search(String word) {

TrieNode node = searchPrefix(word);

return node != null && node.isEnd();

}

}

```

16.10 编辑距离

链接:https://leetcode-cn.com/problems/edit-distance/

16.10.1 问题描述

给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。


你可以对一个单词进行如下三种操作:

1. 插入一个字符

2. 删除一个字符

3. 替换一个字符


示例 1:


```

输入: word1 = "horse", word2 = "ros"

输出: 3

解释:

horse -> rorse (将 'h' 替换为 'r')

rorse -> rose (删除 'r')

rose -> ros (删除 'e')

```


示例 2:


```

输入: word1 = "intention", word2 = "execution"

输出: 5

解释:

intention -> inention (删除 't')

inention -> enention (将 'i' 替换为 'e')

enention -> exention (将 'n' 替换为 'x')

exention -> exection (将 'n' 替换为 'c')

exection -> execution (插入 'u')

```

16.10.2 参考答案

```java

class Solution {

public int minDistance(String word1, String word2) {

int n = word1.length();

int m = word2.length();


// if one of the strings is empty

if (n * m == 0)

return n + m;


// array to store the convertion history

int [][] d = new int[n + 1][m + 1];


// init boundaries

for (int i = 0; i < n + 1; i++) {

d[i][0] = i;

}

for (int j = 0; j < m + 1; j++) {

d[0][j] = j;

}


// DP compute

for (int i = 1; i < n + 1; i++) {

for (int j = 1; j < m + 1; j++) {

int left = d[i - 1][j] + 1;

int down = d[i][j - 1] + 1;

int left_down = d[i - 1][j - 1];

if (word1.charAt(i - 1) != word2.charAt(j - 1))

left_down += 1;

d[i][j] = Math.min(left, Math.min(down, left_down));


}

}

return d[n][m];

}

}

```

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
GPYyDLfgzzIb
最新推荐 更多

2024-05-31