Download as pdf or txt
Download as pdf or txt
You are on page 1of 362

专治薪资上不去,技术转型问题

因为够牛,所以够狂!
黑马大数据V10.0阶段高频核心面试题

版本 增加模块日志 修改时间

V8.0 增加Spark高级与Flink高级 20200630

V8.1 增加Hive部分SQL经典10题 20200703

V8.2 增加Spark内存管理部分 20200714

V8.3 增加Spark高阶部分面试题 20200718

V8.4 增加MySQL部分面试题 20200801

V8.5 将Spark划分为不同模块 20200805

V8.7 增加教育项目(数据仓库业务) 20200827

V8.10 增加算法题目 20201210

V8.11 核心高频面试题 20210406

V8.12 进一步对题目进行筛选 20210410

V9.0 增加Kafka高版本和Hbase高版本特性 20210420

V8.14 增加Python基础高频面试题 20210830

V9.0 增加Spark关键面试题及企业真题 20210930

适用范围:大数据各个阶段学习完之后提前准备阶段重点面试题

注意:如何讲解项目中的技术点?

(1)讲清楚知识点是什么

(2)讲清楚知识点原理是什么

(3)讲清楚知识点如何使用

讲清楚有没有优化及应用场景使用技巧:以计算组件为核心复习,主要复习Hive,Spark及Flink计算组件。

博学谷教学实施中心
www.boxuegu.com
黑马大数据V10.0阶段高频核心面试题........................................................................................................0

1. V10.0面试软技能-面试基础指导..................................................................................................... 2

2. V10.0Python基础高频面试题.......................................................................................................... 9

3. V10.0Linux&Shell基础..................................................................................................................... 19

4. V10.0Hadoop高频面试题............................................................................................................... 21

5. V10.0ZK高频面试题........................................................................................................................ 52

6. V10.0Hive核心基础面试题(必记必看).......................................................................................... 59

7. V10.0HiveSQL业务强化练习(必记必看)..................................................................................... 109

8. V10.0Flume高频面试题................................................................................................................ 136

9. V10.0Spark核心基础Spark base(必记必看)............................................................................... 140

10. V10.0Spark核心基础Spark Core(必记必看)............................................................................ 147

11. V10.0Spark核心基础Spark SQL................................................................................................ 160

12. V10.0Spark核心基础Spark机器学习和图计算.........................................................................164

13. V10.0Spark面试进阶................................................................................................................... 164

14. V10.0大数据数据倾斜专题......................................................................................................... 190

15. V10.0Java基础高频面试题......................................................................................................... 214

16. V10.0Hbase高频面试题.............................................................................................................. 221

17. V10.0Kafka高频面试题............................................................................................................... 243

18. V10.0Flink必备基础(必记必看).................................................................................................. 252

19. V10.0Flink进阶中级.................................................................................................................... 259

20. V10.0Flink进阶高级.................................................................................................................... 264

21. V10.0数据结构和算法核心基础................................................................................................ 270

22. V10.0大数据数据挖掘基础........................................................................................................ 290

23. V10.0核心大厂算法面试题总结................................................................................................ 296

24. V10.0LeetCode题目精选........................................................................................................... 304

25. 小试牛刀-阿里巴巴P7面试题攻关........................................................................................... 320

26. 小试牛刀-百度T3面试题攻关................................................................................................... 345

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 1 页
1. V10.0面试软技能-面试基础指导

1.1 简历怎么写

面试时候技能点写法:

建议:不建议“熟悉Flink技术,熟悉Spark技术栈,熟悉Hive技术”,建议:熟悉运用FLink结合Kafka的Connector
完成流式数据处理,熟悉应用FLinkSQL完成业务数据的ETL等过程

建议:技能点中结合机器学习(熟悉机器学习的常用算法,诸如:分类、回归、聚类算法)

熟悉决策树DescitionTree算法,能够使用决策树算法完成用户行为分析业务建模

面试之前建议:

1-需要大家讲简短的自我介绍(不要讲以前Java转到大数据)

我叫什么

我来自于什么

我之前在哪里工作

我目前来到贵公司希望应聘什么职位

(看到贵公司工作氛围比较好,符合我的工作环境预期)

2-项目讲解都要书面化

项目名称:基于大数据或数据仓库的XXXX的用户画像

项目描述:

数据来源是什么+数据的字段有哪些(画像系统中数据来源order\goods\users\logs)

通过XXXX大数据技术,完成XXXX的任务(通过Spark大数据平台完成了用户五级标签的任务,包括4级业务场景
5级具体的属性等)

达成业务目标(通过对用户行为分析使用用户画像的指标体系为推荐系统提供数据支持)

省略:数据量(必须是能够经得起推敲)和集群(集群规划,10台-20台)

项目架构(一定要准备):

Lambda架构---架构图

Strom的作者提出的实时和离线融合的架构

实时架构---kafka+sparkstreaming+sparkmllib+....

离线架构---SparkSQL+SparkMllib

每一个数据流向的字段说明

数据采集--字段--存储---清洗---分析---建模

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 2 页
数据仓库:源数据层的表有多少,表中有哪些字段,如何进一步抽取

3-项目职责(简历中写)

不要出现太多的参与,只允许有1项参与

多状态少动作--多写数据分析的指标任务,少写数据采集,数据导入导出

日常任务:

1-数据的采集和导入---占有少部分

2-业务数据的数据分析---写SQL完成业务数据统计---Hive&&SparkSQL

3-配合多部门的数据支持

比如:数据开发中,如果算法部门需要算法数据的支持,Hve提取字段

1.2 面试前针对项目撰写完成项目文档:

建议大家准备完成自我介绍:

自我介绍

我叫XXXX 毕业于XXXX XXX专业之前从事于大数据数仓开发 目前在XXX信息科技有限公司任职,来咱们公司也是


想看看机会。

项目介绍

那我给您介绍一下最近的这个项目.

这里应该写出是在什么时间什么地点基于什么场景做的这个项目,在开始介绍下面

我们的数据源有两个,一个数据源是后端业务交互数据,它包括后台业务交互流程中产生的登录,订单,用户,商品,支付
等相关数据,保存在MySQL中,然后通过Sqoop 抽取到HDFS .另一个数据源是App前端埋点,搜集用户行为数据,它包
括了用户在使用客户端的过程中产生的数据 比如页面浏览\点击\停留\评论\点赞\收藏等. 这些数据经过服务器生
成logFile文件 然后使用Flume将数据采集到HDFS,然后对数据进行质量评估和检查,检查数据是否符合本次分析的
需求,在具有相关性的前提下检查数据的数量和质量问题,没有问题的情况下再通过预处理 把数据处理成成格式统
一,规整,干净,清晰的结构化数据 (使用MapReduce 涉及到多属性数据传递 采用javabean封装携带数据 实现
Hadoop的序列化机制 Writable 重写对象的toString方法 以\001 作为分隔符 便于后续入库分析操作 把通用的
业务逻辑进行代码提取 抽成一个公共的方法 )

预处理完成后 将数据入库 我们数据仓库分为四个层次 从低到高依次是ods层 dw层 dm层 ads层

ods层作为临时存储层,不用于直接开展数据分析,存储原始的结构化数据,这层存储了用户信息表 商品信息表 店铺
信息表 商品分类表 订单表 订单详情表 支付流水表等

dw层是将ods层的数据进行ETL处理后存储 这层也需要存储商品订单拉链表 (ETL操作会有:空值去除 过滤核心字


段无意义的数据,比如订单表中订单id为null 或者支付表中支付id为空 对手机号,身份证号等敏感数据脱敏 对业务
数据传过来的表进行维度退化和降维等)(可能提问的问题:如果被退化的维度,还有其他业务表使用,退化后处理起
来就麻烦些。还有如果要删除数据,对应的维度可能也会被永久删除。城市的三级分类(省、市、县)等)将用户行
为宽表和业务表进行数据一致性处理)

dm层需要进行汇总计算 根据领导需求,依据区域维度,商品分类维度进行汇总,公司高管拥有查询所有数据的权限,
省份不选择时,看到的为全国范围内的数据,如果指定省份(本次开发的系统为大区)时,看到的为指定省份数据,

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 3 页
如果指定某个城市,则看到的是指定城市的数据,同理商品分类维度类似推算。

ads层创建数据指标分析表 然后进行数据报表展示

在这里应该 准备几个技术上遇到的难题,以及给出解决办法(可以将最后的遇到的问题在这里讲,根据情况做调整)

技术点

拉链表:我负责的是订单拉链表 创建拉链表需要增加有效开始日期和有效结束日期 首先初始日期,从订单变化表导


入数据,且让有效开始时间=当前日期,有效结束日期=9999-99-99 然后创建一张拉链临时表 字段跟拉链表完全
一致 新的拉链表中应该增加订单变化表的全部数据 并且更新旧的拉链表 用旧的拉链表左关联订单变化表 关联
字段是订单id,where过滤出end_date只等于9999-99-99的数据,如果旧的拉链表中的end_date不等于9999-99-99,
说明已经是终态了,不需要再更新,还有通过id is not null过滤 如果没关联上,说明数据状态没变,让end_date还
等于旧的end_date,如果关联上了,说明数据状态变了,让end_date等于当前日期-1,然后把查询结果插入到拉链临
时表中 最后把拉链临时表覆盖到旧的拉链表中.

数仓运用到内部表\外部表\分区表\分桶表

内部表的结构化数据文件位置都位于Hive指定默认的文件路径下,当drop表时,Hive中表的信息连同Hdfs的数据一起
被删除.

外部表的结构化数据文件位置可以位于HDFS任意路径下,但是需要location指定清楚,当drop表时,只会删除Hive中
定义的表的信息,HDFS文件不会被删除

分区表:在涉及查询操作的时候 如果表对应的文件有多个 需要进行全表扫描 性能不高 需要使用分区表进行优


化,分区表是一种优化表 不是必须创建的表 优化了查询时候全表扫描的数据量,分区的字段不能是表中已经存在的
字段。分区字段是一个虚拟的字段 最终也会显示查询结果上,分区表最终显示格式就是在表的文件夹下面以子文件
夹的形式存在 我们一般会根据地理位置和时间进行分区

分桶表优化join查询的时候笛卡尔积的数量,分桶的字段必须是表中已经存在的字段,分桶的功能默认没有开启的 需
要手动设置开启,分桶的最终形式来看 就是原来完整的文件被按照规则分成了若干个部分

Hive调优

 使用MapJoin,把小表全部加载到内存在map端进行join,避免reducer处理。因为在在Reduce阶段完成join。
容易发生数据倾斜。

 使用分桶技术 分区技术

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

项目中遇到的问题

 Flume上传文件到HDFS时出现大量小文件

解决办法 调整hdfs.rollInterval(肉intvol 滚动间隔)、hdfs.rollSize(滚动大小)、hdfs.rollCount(滚动次数)三个参数值

 复杂字段使用自定义UDF和UDTF解析和调试

自定义UDF:继承UDF,重写evaluate方法 解析公共字段

自定义UDTF:继承自GenericUDTF,重写3个方法:initialize(一内饰laiz)(指定返回值的名称和类型),process(处
理字段一进多出),最后close方法调用

 Sqoop中导入导出Null存储一致性的问题

Hive中的Null在底层是以“\N”来存储,而MySQL中的Null在底层就是Null,为了保证数据两端的一致性。在导出数

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 4 页
据时采用--input-null-string和--input-null-non-string两个参数。导入数据时采用--null-string和--null-non-string。

1.3 面试前

(1)Boss上搜索对应职位与HR及技术进行简单沟通,寻求符合匹配项的职位

(2)沟通中根据自己做过的项目简述核心要点,更详细部分可以进一步约面试详聊

(3)切记:不要针对一个公司准备一个简历,要使用我们的简历和项目寻求合适公司

(4)语言表达清楚:思维逻辑清晰,表达流畅

(5)所述内容不犯错

(1)不说之前公司或者自己的坏话

(2)往自己擅长的方面说

(3)如果对于熟悉的内容多聊聊业务+技术;没听过,可以说后续很快能够学会。

(6)学会查看岗位职责,包含大数据岗位核心技术,比如Hadoop,Spark,Flink等

1.4 面试中

1.4.1 投递简历当天没有收到面试邀约

 当天投递,需要等待3天左右时间,需要查看简历

 建议大家去Boss直聘跟Boss聊天进一步预约面试

1.4.2 讲解项目

 清晰讲解项目业务场景描述+项目架构+自己负责部分+优化及坑

 用户画像项目拓展

1.4.3 讲解知识

 知识点是什么+原理是什么+工作中怎么用+优化是什么

 如何学习一门新技术或技术栈

Github的开源社区+Flink中文社区+技术的官网

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 5 页
 应用主要分为三个部分,首先应该了解它的应用场景,比如窗口的一些使用场景。然后,进一步地我们去了
解它的编程接口,最后再深入了解它的一些抽象概念。因为一个框架或一项技术,肯定有它的编程接口和抽象
概念来组成它的编程模型。我们可以通过查看文档的方式来熟悉它的应用。在对应用这三个部分有了初步的了
解后,我们就可以通过阅读代码的方式去了解它的一些实现了。

 实现部分也分三个阶段,首先从工作流程开始,可以通过 API 层面不断的下钻来了解它的工作流程。接下来


是它整体的设计模式,通常对一些框架来说,如果能构建一个比较成熟的生态,一定是在设计模式上有一些独
特的地方,使其有一个比较好的扩展性。最后是它的数据结构和算法,因为为了能够处理海量数据并达到高性
能,它的数据结构和算法一定有独到之处。我们可以做些深入了解。

1.4.4 面试中关于技术选型的演变:

以数据仓库中的实时数据仓库为例:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 6 页
Windows应用场景:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 7 页
1.5 面试后

(1)面试官面试后会给一定反馈,及时询问反馈

(2)如果对岗位和公司非常感兴趣可以多问几次,可以说明自己的意愿。

(3)切记:面试后需要找老师沟通存在什么问题

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 8 页
2. V10.0Python基础高频面试题

2.1 Python基础

2.1.1 编写程序在D盘根目录下创建一个文本文件test.txt,并向其中写入字符串hello world。

答:

fp = open(r’D:\test.txt’, ‘a+’)

print(‘hello world’, file=fp)

fp.close()

2.1.2 写出下面代码的优化版本,提高运行效率。

x = list(range(500))

for item in x:

t = 5**5

print(item+t)

答:

x = list(range(500))

t = 5**5

for item in x:

print(item+t)

2.1.3 编写程序,生成一个包含20个随机整数的列表,然后对其中偶数下标的元素进行降序排列,奇数下标的
元素不变。

答:

import random

x = [random.randint(0,100) for i in range(20)]

print(x)

y = x[::2]

y.sort(reverse=True)

x[::2] = y

print(x)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 9 页
2.1.4 写出下面代码的执行结果。

def Join(List, sep=None):

return (sep or ',').join(List)

print(Join(['a', 'b', 'c']))

print(Join(['a', 'b', 'c'],':'))

答:

a,b,c

a:b:c

2.1.5 写出下面代码的运行结果。

def Sum(a, b=3, c=5):

return sum([a, b, c])

print(Sum(a=8, c=2))

print(Sum(8))

print(Sum(8,2))

答:

13

16

15

2.1.6 写出下面代码的运行结果。

def Sum(*p):

return sum(p)

print(Sum(3, 5, 8))

print(Sum(8))

print(Sum(8, 2, 10))

答:

16

20

2.1.7 编写函数,判断一个数字是否为素数,是则返回字符串YES,否则返回字符串NO。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 10 页
答:

import math

def IsPrime(v):

n = int(math.sqrt(v)+1)

for i in range(2,n):

if v%i==0:

return 'No'

else:

return 'Yes'

2.1.8 编写函数,模拟Python内置函数sorted()。

答:

def Sorted(v):

t = v[::]

r = []

while t:

tt = min(t)

r.append(tt)

t.remove(tt)

return r

2.1.9 编写程序,生成包含20个随机数的列表,然后将前10个元素升序排列,后10个元素降序排列,并输出结
果。

答:

import random

x = [random.randint(0,100) for i in range(20)]

print(x)

y = x[0:10]

y.sort()

x[0:10] = y

y = x[10:20]

y.sort(reverse=True)

x[10:20] = y

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 11 页
print(x)

2.1.10 编写程序,运行后用户输入4位整数作为年份,判断其是否为闰年。如果年份能被400整除,则为闰
年;如果年份能被4整除但不能被100整除也为闰年。

答:

x = input('Please input an integer of 4 digits meaning the year:')

x = eval(x)

if x%400==0 or (x%4==0 and not x%100==0):

print('Yes')

else:

print('No')

1、编写程序,实现分段函数计算,如下表所示。

x y

x<0 0

0<=x<5 x

5<=x<10 3x-5

10<=x<20 0.5x-2

20<=x 0

答:

x = input('Please input x:')

x = eval(x)

if x<0 or x>=20:

print(0)

elif 0<=x<5:

print(x)

elif 5<=x<10:

print(3*x-5)

elif 10<=x<20:

print(0.5*x-2)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 12 页
2.1.11 阅读下面的程序,判断其是否可以正常运行,如果可以运行则写出执行结果,如果不能运行则写出
理由。

class Test:

def __init__(self, value):

self.__value = value

@property

def value(self):

return self.__value

t = Test(3)

t.value = 5

print(t.value)

答:不能运行。程序中定义的是只读属性,不能修改属性的值。

2.1.12 下面代码的功能是,随机生成50个介于[1,20]之间的整数,然后统计每个整数出现频率。请把缺少
的代码补全。

import random

x = [random.____________(1,20) for i in range(_______)]

r = dict()

for i in x:

r[i] = r.get(i, _____)+1

for k, v in r.items():

print(k, v)

答:

分别填写randint、50、0

2.2 Python进阶

2.2.1 利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法.

正解1:

def trim(s):

while s[:1] == ' ':

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 13 页
s = s[1:]

while s[-1:] == ' ':

s = s[:-1]

return s

正解2:

def trim(s):

if s[:1] == ' ':

s = trim(s[1:])

if s[-1:] == ' ':

s = trim(s[:-1])

return s

容易写错的方法:

def trim(s):

while s[0] == ' ':

s = s[1:]

while s[-1] == ' ':

s = s[:-1]

return s

解释:当s=''时,s[0]和s[-1]会报IndexError: string index out of range,但是s[:1])和s[-1:]不会。

2.2.2 请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间。

# -*- coding: utf-8 -*-

import time, functoolsdef metric(fn):

@functools.wraps(fn)

def wrapper(*args, **kw):

time0 = time.time()

ret = fn(*args, **kw)

time1 = time.time()

print('%s executed in %s ms' % (fn.__name__, time1-time0))

return ret

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 14 页
return wrapper

2.2.3 装饰器的实质是什么?

或者说为什么装饰器要写2层嵌套函数,里层函数完全就已经实现了装饰的功能为什么不直接用里
层函数名作为装饰器名称?

答:装饰器是要把原来的函数装饰成新的函数,并且返回这个函数本身的高阶函数

2.2.4 Python下多线程的限制以及多进程中传递参数的方式

python多线程有个全局解释器锁(global interpreter lock),这个锁的意思是任一时间只能有一个


线程使用解释器,跟单cpu跑多个程序一个意思,大家都是轮着用的,这叫“并发”,不是“并行”。

多进程间共享数据,可以使用 multiprocessing.Value 和 multiprocessing.Array

2.2.5 Python多线程与多进程的区别:

在UNIX平台上,当某个进程终结之后,该进程需要被其父进程调用wait,否则进程成为僵尸进程
(Zombie)。所以,有必要对每个Process对象调用join()方法 (实际上等同于wait)。对于多线程来说,
由于只有一个进程,所以不存在此必要性。

多进程应该避免共享资源。在多线程中,我们可以比较容易地共享资源,比如使用全局变量或者传
递参数。在多进程情况下,由于每个进程有自己独立的内存空间,以上方法并不合适。此时我们可
以通过共享内存和Manager的方法来共享资源。但这样做提高了程序的复杂度,并因为同步的需要
而降低了程序的效率。

2.2.6 请写一段Python代码实现删除一个list里面的重复元素

>>> l = [1,1,2,3,4,5,4]>>> list(set(l))[1, 2, 3, 4, 5]

或者

d = {}for x in mylist:

d[x] = 1

mylist = list(d.keys())

利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 15 页
['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']:

def normalize(name):

return name[0].upper()+name[1:].lower()

def normalizeList(inputlist):

return list(map(normalize, inputlist))

2.2.7 Python是如何进行内存管理的?

Python引用了一个内存池(memory pool)机制,即Pymalloc机制(malloc:n.分配内存),用于管
理对小块内存的申请和释放
内存池(memory pool)的概念:
当 创建大量消耗小内存的对象时,频繁调用new/malloc会导致大量的内存碎片,致使效率降
低。内存池的概念就是预先在内存中申请一定数量的,大小相等 的内存块留作备用,当有新的内
存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存。这样做最显著的优
势就是能够减少内存碎片,提升效率。
内存池的实现方式有很多,性能和适用范围也不一样。python中的内存管理机制——Pymalloc:
python中的内存管理机制都有两套实现,一套是针对小对象,就是大小小于256bits时,pymalloc
会在内存池中申请内存空间;当大于256bits,则会直接执行new/malloc的行为来申请内存空间。
关于释放内存方面,当一个对象的引用计数变为0时,python就会调用它的析构函数。在析构
时,也采用了内存池机制,从内存池来的内存会被归还到内存池中,以避免频繁地释放动作。

2.2.8 Python里面如何拷贝一个对象?

标准库中的copy模块提供了两个方法来实现拷贝.一个方法是copy,它返回和参数包含内容一样的
对象,使用deepcopy方法,对象中的属性也被复制

2.2.9 Python里面search()和match()的区别?

match()函数只检测re是不是在string的开始位置匹配,search()会扫描整个string查找匹配, 也就是
说match()只有在0位置匹配成功的话才有返回,如果不是开始位置匹配成功的话,match()就返回
none。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 16 页
2.2.10 解释 Python 中的三元表达式

与 C++不同, 在 Python 中我们不需要使用?符号,而是使用如下语法:

[on true] if [expression]else [on false]

如果 [expression] 为真, 则 [on true] 部分被执行。如果表示为假则 [on false] 部分被执行

2.2.11 lambda表达式格式以及应用场景?

lambda函数就是可以接受任意多个参数(包括可选参数)并且返回单个表达式值得函数。

语法:lambda [arg1 [,arg2,.....argn]]:expression

def calc(x,y):

return x*y

# 将上述一般函数改写为匿名函数:

lambda x,y:x*y

应用:1.lambda函数比较轻便,即用即仍,适合完成只在一处使用的简单功能。

2.匿名函数,一般用来给filter,map这样的函数式编程服务

3.作为回调函数,传递给某些应用,比如消息处理。

2.2.12 *args和**kwarg作用

*args代表位置参数,它会接收任意多个参数并把这些参数作为元组传递给函数。

**kwargs代表的关键字参数,允许你使用没有事先定义的参数名。

位置参数一定要放在关键字参数的前面。

作用:使用*args和**kwargs可以非常方便的定义函数,同时可以加强扩展性,以便日后的代码维
护。

2.2.13 is和==的区别

==是python标准操作符中的比较操作符,用来比较判断两个对象的value(值)是否相等;

is也被叫做同一性运算符,这个运算符比较判断的是对象间的唯一身份标识,也就是id是否相同。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 17 页
2.2.14 简述Python的深浅拷贝以及应用场景?

导入模块:import copy

浅拷贝:copy.copy

深拷贝:copy.deepcopy

浅拷贝指仅仅拷贝数据集合的第一层数据,深拷贝指拷贝数据集合的所有层。<br> 所以
对于只有一层的数据集合来说深浅拷贝的意义是一样的,比如字符串,数字,还有仅仅一层的字典、
列表、元祖等.

应用:

浅拷贝在拷贝大量数据且不需要改变内部元素的值的时候,能大量的减少内存的使用;

深拷贝在拷贝大量数据的时候,需要在前后内部元素的内容进行改变的时候,可以修改拷贝出
来的模板

2.2.15 Python垃圾回收机制?

1、回收计数引用为0的对象,释放其占用空间

2、循环垃圾回收器。释放循环引用对象

2.2.16 Python的可变类型和不可变类型?

可变类型:list、dict、set、可变集合

不可变类型:string、int、float、tuple、不可变集合

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 18 页
3. V10.0Linux&Shell基础

3.1 Linux

3.1.1 Linux常用命令

参考答案:find、df、tar、ps、top、netstat等。(尽量说一些高级命令)

3.1.2 Linux查看内存、磁盘存储、io 读写、端口占用、进程等命令

答案:

1、查看内存:top

2、查看磁盘存储情况:df -h

3、查看磁盘IO读写情况:iotop(需要安装一下:yum install iotop)、iotop -o(直接查看输出比较高的磁盘读写


程序)

4、查看端口占用情况:netstat -tunlp | grep 端口号

5、查看进程:ps aux

3.2 Shell

3.2.1 使用Linux命令查询file1中空行所在的行号

答案:

[root@hadoop102 datas]$ awk '/^$/{print NR}' file1.txt

3.2.2 有文件chengji.txt内容如下:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 19 页
张三 40

李四 50

王五 60

使用Linux命令计算第二列的和并输出

[root@hadoop102 datas]$ cat chengji.txt | awk -F " " '{sum+=$2} END{print sum}'

150

3.2.3 Shell脚本里如何检查一个文件是否存在?如果不存在如何处理?

#!/bin/bash

if [ -f file.txt ]; then

echo "文件存在!"

else

echo "文件不存在!"

fi

3.2.4 用Shell写一个脚本,对文本中无序的一列数字排序

[root@hadoop102 ~]# cat test.txt # 1-10

[root@hadoop102 ~]# sort -n test.txt|awk '{a+=$0;print $0}END{print "SUM="a}'

SUM=55

3.2.5 请用Shell脚本写出查找当前文件夹(/home)下所有的文本文件内容中包含有字符”shen”的文件名称

[root@hadoop102 datas]$ grep -r "shen" /home | cut -d ":" -f 1

/home/root/datas/sed.txt

/home/root/datas/cut.txt

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 20 页
4. V10.0Hadoop高频面试题

4.1 HDFS

4.1.1 HDFS的存储机制(读写流程)?

HDFS存储机制,包括HDFS的写入过程和读取过程两个部分

1)客户端向namenode请求上传文件,namenode检查目标文件是否已存在,父目录是否存在。

2)namenode返回是否可以上传。

3)客户端请求第一个 block上传到哪几个datanode服务器上。

4)namenode返回3个datanode节点,分别为dn1、dn2、dn3。

5)客户端请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成。

6)dn1、dn2、dn3逐级应答客户端

7)客户端开始往dn1上传第一个block(先从磁盘读取数据放到一个本地内存缓存),以packet为单位,dn1

收到一个packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放入一个应答队列等待应答

8)当一个block传输完成之后,客户端再次请求namenode上传第二个block的服务器。(重复执行3-7步)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 21 页
1)客户端向namenode请求下载文件,namenode通过查询元数据,找到文件块所在的datanode地址。

2)挑选一台datanode(就近原则,然后随机)服务器,请求读取数据。

3)datanode开始传输数据给客户端(从磁盘里面读取数据放入流,以packet为单位来做校验)。

4)客户端以packet为单位接收,先在本地缓存,然后写入目标文件。

4.1.2 HDFS中大量小文件带来的问题以及解决的方案

问题:

hadoop中目录,文件和块都会以对象的形式保存在namenode的内存中, 大概每个对象会占用150bytes. 小文件数

量多会大量占用namenode的内存; 使namenode读取元数据速度变慢, 启动时间延长; 还因为占用内存过大, 导致

gc时间增加等.

解决办法:

两个角度, 一是从根源解决小文件的产生, 二是解决不了就选择合并.

从数据来源入手, 如每小时抽取一次改为每天抽取一次等方法来积累数据量.

如果小文件无可避免, 一般就采用合并的方式解决. 可以写一个MR任务读取某个目录下的所有小文件, 并重写为一

个大文件.

4.1.3 HDFS三个核心组件时什么,分别有什么作用

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 22 页
1-NameNode. 集群的核心, 是整个文件系统的管理节点. 维护着

a) 文件系统的文件目录结构和元数据信息

b) 文件与数据块列表的对应关系

2-DataNode. 存放具体数据块的节点, 主要负责数据的读写, 定期向NameNode发送心跳

3-SecondaryNameNode. 辅助节点, 同步NameNode中的元数据信息, 辅助NameNode对fsimage和editsLog进

行合并.

4.1.4 fsimage和editlogs是做什么用的?

fsimage文件存储的是Hadoop的元数据文件, 如果namenode发生故障, 最近的fsimage文件会被载入到内存中, 用

来重构元数据的最近状态, 再从相关点开始向前执行edit logs文件中记录的每个事务.

文件系统客户端执行写操作时, 这些事务会首先记录到日志文件中.

在namenode运行期间, 客户端对hdfs的写操作都保存到edit文件中, 久而久之就会造成edit文件变得很大, 这对

namenode的运行没有影响, 但是如果namenode重启, 它会将fsimage中的内容映射到内存中, 然后再一条一条执

行edit文件中的操作, 所以日志文件太大会导致重启速度很慢. 所以在namenode运行的时候就要将edit logs和

fsimage定期合并.

4.1.5 namenode的工作机制

第一阶段:NameNode启动

(1)第一次启动NameNode格式化后,创建Fsimage和Edits文件。如果不是第一次启动,直接加载编辑日志和镜

像文件到内存。

(2)客户端对元数据进行增删改的请求。

(3)NameNode记录操作日志,更新滚动日志。

(4)NameNode在内存中对元数据进行增删改。

第二阶段:Secondary NameNode工作

(1)Secondary NameNode询问NameNode是否需要CheckPoint。直接带回NameNode是否检查结果。

(2)Secondary NameNode请求执行CheckPoint。

(3)NameNode滚动正在写的Edits日志。

(4)将滚动前的编辑日志和镜像文件拷贝到Secondary NameNode。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 23 页
(5)Secondary NameNode加载编辑日志和镜像文件到内存,并合并。

(6)生成新的镜像文件fsimage.chkpoint。

(7)拷贝fsimage.chkpoint到NameNode。

(8)NameNode将fsimage.chkpoint重新命名成fsimage。

4.1.6 datenode工作机制

1)一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据

块的长度,块数据的校验和,以及时间戳。

2)DataNode启动后向NameNode注册,通过后,周期性(1小时)的向NameNode上报所有的块信息。

3)心跳是每3秒一次,心跳返回结果带有NameNode给该DataNode的命令如复制块数据到另一台机器,或删除某

个数据块。如果超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用。

4)集群运行中可以安全加入和退出一些机器。

4.1.7 Hadoop中需要哪些配置文件,其作用是什么?

1、core-site.xml

fs.defaultFS:hdfs://cluster1(域名),这里的值指的是默认的HDFS路径 。

hadoop.tmp.dir:/export/data/hadoop_tmp,这里的路径默认是NameNode、DataNode、secondaryNamenode等

存放数据的公共目录。用户也可以自己单独指定这三类节点的目录。

ha.zookeeper.quorum:hadoop101:2181,hadoop102:2181,hadoop103:2181,这里是ZooKeeper集群的地址和端口。注

意,数量一定是奇数,且不少于三个节点 。

2、hadoop-env.sh

只需设置jdk的安装路径,如:export JAVA_HOME=/usr/local/jdk。

3、hdfs-site.xml

dfs.replication:他决定着系统里面的文件块的数据备份个数,默认为3个。

dfs.data.dir:datanode节点存储在文件系统的目录 。

dfs.name.dir:是namenode节点存储hadoop文件系统信息的本地系统路径 。

4、mapred-site.xml

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 24 页
mapreduce.framework.name: yarn指定mr运行在yarn上。

5、yarn-site.xml

配置yarn.nodemanager.aux-services以及日志聚集等功能

6、slaves、master

配置从节点列表、主节点

4.1.8 列出正常工作的hadoop集群中hadoop都需要启动哪些进程,他们的作用分别是什么?

–namenode =>HDFS的守护进程,负责维护整个文件系统,存储着整个文件系统的元数据信息,有image+edit log

namenode不会持久化存储这些数据,而是在启动时重建这些数据。

–datanode =>是具体文件系统的工作节点,当我们需要某个数据,namenode告诉我们去哪里找,就直接和那个

DataNode对应的服务器的后台进程进行通信,由DataNode进行数据的检索,然后进行具体的读/写操作

–secondarynamenode =>一个冗余的守护进程,相当于一个namenode的元数据的备份机制,定期的更新,和

namenode进行通信,将namenode上的image和edits进行合并,可以作为namenode的备份使用

–resourcemanager =>是yarn平台的守护进程,负责所有资源的分配与调度,client的请求由此负责,监控

nodemanager

–nodemanager => 是单个节点的资源管理,执行来自resourcemanager的具体任务和命令

4.1.9 Linux中的块大小为4KB, 为什么HDFS中块大小为128MB?

块是存储在文件系统中的数据的最小单元. 如果采用4kb的块大小来存放存储在Hadoop中的数据, 就会需要大量的

块, 大大增加了寻找块的时间, 降低了读写效率.

并且, 一个map或者一个reduce都是以一个块为单位处理, 如果块很小, mapreduce任务数就会很多, 任务之间的

切换开销变大, 效率降低

4.1.10 NameNode与SecondaryNameNode 的区别与联系?

1)机制流程同上;

2)区别

(1)NameNode负责管理整个文件系统的元数据,以及每一个路径(文件)所对应的数据块信息。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 25 页
(2)SecondaryNameNode主要用于定期合并命名空间镜像和命名空间镜像的编辑日志。

3)联系:

(1)SecondaryNameNode中保存了一份和namenode一致的镜像文件(fsimage)和编辑日志(edits)。

(2)在主namenode发生故障时(假设没有及时备份数据),可以从SecondaryNameNode恢复数据。

4.1.11 Namenode挂了怎么办?

方法一:将SecondaryNameNode中数据拷贝到namenode存储数据的目录;

方法二:使用-importCheckpoint选项启动namenode守护进程,从而将SecondaryNameNode中数据拷贝到

namenode目录中。

4.1.12 Namenode HA

HA(High Available), 高可用,是保证业务连续性的有效解决方案,一般有两个或两个以上的节点,分为 活动节


点 ( Active )及 备用节点 ( Standby) )。用于实现业务的不中断或短暂中断

NN 是 HDFS 集群的单点故障点.在 HA 具体实现方法不同情况下,HA 框架的流程是一致的, 不一致的就是


如何存储、管理、同步 edits 编辑日志文件。

QJM/Qurom Journal Manager,基本原理就是用 2N+1 台 JournalNode 存储 EditLog,每次写数据操作


有>=N+1 返回成功时即认为该次写成功,数据不会丢失了

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 26 页
4.1.13 Hadoop的namenode宕机,怎么解决?

先分析宕机后的损失,宕机后直接导致client无法访问,内存中的元数据丢失,但是硬盘中的元数据应该还存在,
如果只是节点挂了,重启即可,如果是机器挂了,重启机器后看节点是否能重启,不能重启就要找到原因修复了。
但是最终的解决方案应该是在设计集群的初期就考虑到这个问题,做namenode的HA。

4.2 Hadoop的联邦机制

4.2.1 为什么会出现联邦?

Hadoop的NN所使用的资源受所在服务的物理限制,不能满足实际生产需求。

4.2.2 联邦的实现

采用多台NN组成联邦。NN是独立的,NN之间不需要相互调用。NN是联合的,同属于一个联邦,所管理的DN作
为block的公共存储。

如下图:

 图中概念:

 block pool的概念,每一个namespace都有一个pool,datanodes会存储集群中所有的pool,block pool之间


的管理是独立的,一个namespace生成一个block id时不需要跟其它namespace协调,一个namenode的失败
也不会影响到datanode对其它namenodes的服务。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 27 页
 一个namespace和它的block pool作为一个管理单元,删除后,对应于datanodes中的pool也会被删除。集群
升级时,这个管理单元也独立升级。

 这里引入clusterID来标示集群所有节点。当一个namenode format之后,这个id生成,集群中其它namenode
的format也用这个id。

4.2.3 主要优点

 命名空间可伸缩性——联合添加命名空间水平扩展。DN也随着NN的加入而得到拓展。

 性能——文件系统吞吐量不是受单个Namenode限制。添加更多的Namenode集群扩展文件系统读/写吞吐
量。

 隔离——隔离不同类型的程序,一定程度上控制资源的分配

4.2.4 配置

联邦的配置是向后兼容的,允许在不改变任何配置的情况下让当前运行的单节点环境转换成联邦环境。新的配置方
案确保了在集群环境中的所有节点的配置文件都是相同的。

这里引入了NameServiceID概念,作为namenodes们的后缀。

第一步:配置属性dfs.nameservices,用于datanodes们识别namenodes。

第二步:为每个namenode加入这个后缀。

conf/hdfs-site.xml

<configuration>
<property>
<name>dfs.nameservices</name>
<value>ns1,ns2</value>
</property>
<property>
<name>dfs.namenode.rpc-address.ns1</name>
<value>nn-host1:rpc-port</value>
</property>
<property>
<name>dfs.namenode.http-address.ns1</name>
<value>nn-host1:http-port</value>
</property>
<property>
<name>dfs.namenode.secondary.http-address.ns1</name>
<value>snn-host1:http-port</value>
</property>
<property>
<name>dfs.namenode.rpc-address.ns2</name>

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 28 页
<value>nn-host2:rpc-port</value>
</property>
<property>
<name>dfs.namenode.http-address.ns2</name>
<value>nn-host2:http-port</value>
</property>
<property>
<name>dfs.namenode.secondary.http-address.ns2</name>
<value>snn-host2:http-port</value>
</property>

.... Other common configuration ...


</configuration>

4.2.5 操作

# 创建联邦,不指定ID会自动生成
$HADOOP_HOME/bin/hdfs namenode -format [-clusterId <cluster_id>]
# 升级Hadoop为集群
$HADOOP_HOME/bin/hdfs start namenode --config $HADOOP_CONF_DIR -upgrade -clusterId <cluster_
ID>
# 扩展已有联邦
$HADOOP_HOME/bin/hdfs dfsadmin -refreshNamenodes <datanode_host_name>:<datanode_rpc_port>
# 退出联邦
$HADOOP_HOME/sbin/distribute-exclude.sh <exclude_file>
$HADOOP_HOME/sbin/refresh-namenodes.sh

4.3 MapReduce

4.3.1 MR执行过步骤

1. map任务处理

1.1 读取输入文件(HDFS)内容,解析成key1、value1对。对输入文件的每一行,解析成key、value对。
每一个键值对调用一次map函数。

注:key是当前行的起始位置,单位是字节。第一行的起始位置是0,value是当前行的内容。有多少行
就产生多少键值对。每个键值对调用一个map函数。

注意区别map任务与map函数,map函数仅仅是map任务中的一个步骤。

1.2 覆盖map函数,写自己的逻辑,对输入的key1、value1处理,转换成新的key2、value2输出。

1.3 对输出的key2、value2进行分区。(默认只有一个分区)

1.4 对不同分区的数据,按照key进行排序、分组。相同key的value放到一个集合中。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 29 页
1.5 (可选)分组后的数据进行归约。思考:何时可以规约?

2.reduce任务处理

2.1 对多个map任务的输出,按照不同的分区,通过网络copy到不同的reduce节点。

注:那哪些map任务进入到那些reduce节点,原则是按照分区。

2.2 对多个map任务的输出进行合并、排序。写reduce函数自己的业务逻辑,对输入的key2、values2处
理,转换成新的key3、value3输出。

注:为什么需要合并操作?因为需要将多个map任务输出的结果进行合并,合并之后既可以排序。

分组前后,键值对的数目有变化吗?答案:没有变化。

2.3 把reduce的输出保存到文件(HDFS)中。

4.3.2 请简述MapReduce中combiner、partition的作用

(1)、combiner
有时一个map可能会产生大量的输出,combiner的作用是在map端对输出先做一次合并,以减少网络传输到reducer
的数量。
注意:mapper的输出为combiner的输入,reducer的输入为combiner的输出。

(2)、partition
把map任务输出的中间结果按照key的范围划分成R份(R是预先定义的reduce任务的个数),划分时通常使用hash函
数,如:hash(key) mod R
这样可以保证一段范围内的key,一定会由一个reduce任务来处理。

4.3.3 谈谈Hadoop序列化和反序列化及自定义bean对象实现序列化?

1)序列化和反序列化

序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储(持久化)和网络传

输。

反序列化就是将收到字节序列(或其他数据传输协议)或者是硬盘的持久化数据,转换成内存中的对象。

Java的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带很多额外的信息

(各种校验信息,header,继承体系等),不便于在网络中高效传输。所以,hadoop自己开发了一套序列化

机制(Writable),精简、高效。

2)自定义bean对象要想序列化传输步骤及注意事项:。

(1)必须实现Writable接口

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 30 页
(2)反序列化时,需要反射调用空参构造函数,所以必须有空参构造

(3)重写序列化方法

(4)重写反序列化方法

(5)注意反序列化的顺序和序列化的顺序完全一致

(6)要想把结果显示在文件中,需要重写toString(),且用”\t”分开,方便后续用

(7)如果需要将自定义的bean放在key中传输,则还需要实现comparable接口,因为mapreduce框中

的shuffle过程一定会对key进行排序

4.3.4 如何决定一个job的map和reduce的数量?

1)map数量

splitSize=max{minSize,min{maxSize,blockSize}}

map数量由处理的数据分成的block数量决定default_num = total_size / split_size;

2)reduce数量

reduce的数量job.setNumReduceTasks(x);x 为reduce的数量。不设置的话默认为 1。

4.3.5 Maptask的个数由什么决定?

一个job的map阶段MapTask并行度(个数),由客户端提交job时的切片个数决定。

4.3.6 MapTask工作机制

(1)Read阶段:Map Task通过用户编写的RecordReader,从输入InputSplit中解析出一个个key/value。

(2)Map阶段:该节点主要是将解析出的key/value交给用户编写map()函数处理,并产生一系列新的key/value。

(3)Collect收集阶段:在用户编写map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结

果。在该函数内部,它会将生成的key/value分区(调用Partitioner),并写入一个环形内存缓冲区中。

(4)Spill阶段:即“溢写”,当环形缓冲区满后,MapReduce会将数据写到本地磁盘上,生成一个临时文件。需要

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 31 页
注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作。

溢写阶段详情:

步骤1:利用快速排序算法对缓存区内的数据进行排序,排序方式是,先按照分区编号partition进行排序,然后按照

key进行排序。这样,经过排序后,数据以分区为单位聚集在一起,且同一分区内所有数据按照key有序。

步骤2:按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件output/spillN.out(N表示

当前溢写次数)中。如果用户设置了Combiner,则写入文件之前,对每个分区中的数据进行一次聚集操作。

步骤3:将分区数据的元信息写到内存索引数据结构SpillRecord中,其中每个分区的元信息包括在临时文件中的偏

移量、压缩前数据大小和压缩后数据大小。如果当前内存索引大小超过1MB,则将内存索引写到文件

output/spillN.out.index中。

(5)Combine阶段:当所有数据处理完成后,MapTask对所有临时文件进行一次合并,以确保最终只会生成一个

数据文件。

当所有数据处理完后,MapTask会将所有临时文件合并成一个大文件,并保存到文件output/file.out中,同时生成

相应的索引文件output/file.out.index。

在进行文件合并过程中,MapTask以分区为单位进行合并。对于某个分区,它将采用多轮递归合并的方式。每轮合

并io.sort.factor(默认100)个文件,并将产生的文件重新加入待合并列表中,对文件排序后,重复以上过程,直到

最终得到一个大文件。

让每个MapTask最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量小文件产生的随机读取带来的

开销。

4.3.7 ReduceTask工作机制。

(1)Copy阶段:ReduceTask从各个MapTask上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,

则写到磁盘上,否则直接放到内存中。

(2)Merge阶段:在远程拷贝数据的同时,ReduceTask启动了两个后台线程对内存和磁盘上的文件进行合并,以

防止内存使用过多或磁盘上文件过多。

(3)Sort阶段:按照MapReduce语义,用户编写reduce()函数输入数据是按key进行聚集的一组数据。为了将key

相同的数据聚在一起,Hadoop采用了基于排序的策略。由于各个MapTask已经实现对自己的处理结果进行了局部

排序,因此,ReduceTask只需对所有数据进行一次归并排序即可。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 32 页
(4)Reduce阶段:reduce()函数将计算结果写到HDFS上。

4.3.8 请描述mapReduce有几种排序及排序发生的阶段。

1)排序的分类:

(1)部分排序:

MapReduce根据输入记录的键对数据集排序。保证输出的每个文件内部排序。

(2)全排序:

如何用Hadoop产生一个全局排序的文件?最简单的方法是使用一个分区。但该方法在处理大型文件时效率极

低,因为一台机器必须处理所有输出文件,从而完全丧失了MapReduce所提供的并行架构。

替代方案:首先创建一系列排好序的文件;其次,串联这些文件;最后,生成一个全局排序的文件。主要思路

是使用一个分区来描述输出的全局排序。例如:可以为待分析文件创建3个分区,在第一分区中,记录的单词首字

母a-g,第二分区记录单词首字母h-n, 第三分区记录单词首字母o-z。

(3)辅助排序:(GroupingComparator分组)

Mapreduce框架在记录到达reducer之前按键对记录排序,但键所对应的值并没有被排序。甚至在不同的执行

轮次中,这些值的排序也不固定,因为它们来自不同的map任务且这些map任务在不同轮次中完成时间各不相同。

一般来说,大多数MapReduce程序会避免让reduce函数依赖于值的排序。但是,有时也需要通过特定的方法对键

进行排序和分组等以实现对值的排序。

(4)二次排序:

在自定义排序过程中,如果compareTo中的判断条件为两个即为二次排序。

2)自定义排序WritableComparable

bean对象实现WritableComparable接口重写compareTo方法,就可以实现排序

@Override

public int compareTo(FlowBean o) {

// 倒序排列,从大到小

return this.sumFlow > o.getSumFlow() ? -1 : 1;

3)排序发生的阶段:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 33 页
(1)一个是在map side发生在spill后partition前。

(2)一个是在reduce side发生在copy后 reduce前。

4.3.9 请描述mapReduce中shuffle阶段的工作流程,如何优化shuffle阶段?

分区,排序,溢写,拷贝到对应reduce机器上,增加combiner,压缩溢写的文件。

4.3.10 请描述mapReduce中combiner的作用是什么,一般使用情景,哪些情况不需要及reduce的区别?

1)Combiner的意义就是对每一个maptask的输出进行局部汇总,以减小网络传输量。

2)Combiner能够应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv应该跟reducer的输入kv类型

要对应起来。

3)Combiner和reducer的区别在于运行的位置。

 Combiner是在每一个maptask所在的节点运行;

 Reducer是接收全局所有Mapper的输出结果。

4.3.11 如果没有定义partitioner,那数据在被送达reducer前是如何被分区的?

如果没有自定义的 partitioning,则默认的 partition 算法,即根据每一条数据的 key的 hashcode 值摸运算(%)

reduce 的数量,得到的数字就是“分区号”。

4.3.12 MapReduce 怎么实现 TopN?

可以自定义grouping comparator,或者在map端对数据进行排序,然后再reduce输出时,控制只输出前n个数。

就达到了topn输出的目的。

4.3.13 有可能使 Hadoop 任务输出到多个目录中么?如果可以,怎么做?

1)可以输出到多个目录中,采用自定义OutputFormat。

2)实现步骤:

(1)自定义outputformat,(2)改写recordwriter,具体改写输出数据的方法write()

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 34 页
4.3.14 简述hadoop实现join的几种方法及每种方法的实现。

1)reduce side join

Map端的主要工作:为来自不同表(文件)的key/value对打标签以区别不同来源的记录。然后用连接字段作为

key,其余部分和新加的标志作为value,最后进行输出。

Reduce端的主要工作:在reduce端以连接字段作为key的分组已经完成,我们只需要在每一个分组当中将那些

来源于不同文件的记录(在map阶段已经打标志)分开,最后进行合并就ok了。

2)map join

在map端缓存多张表,提前处理业务逻辑,这样增加map端业务,减少reduce端数据的压力,尽可能的减少

数据倾斜。

具体办法:采用distributedcache

(1)在mapper的setup阶段,将文件读取到缓存集合中。

(2)在驱动函数中加载缓存。

job.addCacheFile(new URI("file:/e:/mapjoincache/pd.txt"));// 缓存普通文件到task运行节点

4.3.15 请简述hadoop怎样实现二级排序。

对map端输出的key进行排序,实现的compareTo方法。 在compareTo方法中排序的条件有二个。

4.3.16 参考下面的MR系统的场景:

--hdfs块的大小为128MB

--输入类型为FileInputFormat

--有三个文件的大小分别是:64KB 130MB 260MB

Hadoop框架会把这些文件拆分为多少块?

4块:64K,130M,128M,132M

4.3.17 Hadoop中RecordReader的作用是什么?

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 35 页
(1)以怎样的方式从分片中读取一条记录,每读取一条记录都会调用RecordReader类;

(2)系统默认的RecordReader是LineRecordReader

(3)LineRecordReader是用每行的偏移量作为map的key,每行的内容作为map的value;

(4)应用场景:自定义读取每一条记录的方式;自定义读入key的类型,如希望读取的key是文件的路径或名

字而不是该行在文件中的偏移量。

4.3.18 给你一个1G的数据文件。分别有id,name,mark,source四个字段,按照mark分组,id排序,手写一
个MapReduce?其中有几个Mapper?

在map端对mark排序,在reduce端对id分组。

@Override

public int compareTo(GroupBean o) {

int result = this.mark.compareTo(o.mark);

if (result == 0)

return Integer.compare(this.id,o.id);

else

return result;

@Override

public int compare(WritableComparable a, WritableComparable b) {

GroupBean aBean = (GroupBean) a;

GroupBean bBean = (GroupBean) b;

int result;

if (aBean.getMark() > bBean. getMark()) {

result = 1;

} else if (aBean. getMark() < bBean. getMark()) {

result = -1;

} else {

result = 0;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 36 页
return result;

2)几个mapper

(1)1024m/128m=8块

4.3.19 你是如何解决Hadoop数据倾斜的问题的,能举个例子吗?

「性能优化」和「数据倾斜」,如果在面试前不好好准备,那就准备在面试时吃亏吧~其实掌握的
多了,很多方法都有相通的地方。

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

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

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

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

在这种情况,大致有如下几种方法:

「局部聚合加全局聚合」

第一次在map阶段对那些导致了数据倾斜的key 加上1到n的随机前缀,这样本来相同的key 也会被


分到多个Reducer 中进行局部聚合,数量就会大大降低。

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

「思想」:二次mr,第一次将key随机散列到不同 reducer 进行处理达到负载均衡目的。第二次


再根据去掉key的随机前缀,按原key进行reduce处理。

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

「增加Reducer,提升并行度」

JobConf.setNumReduceTasks(int)

「实现自定义分区」

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

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 37 页
4.4 Yarn

4.4.1 Yarn 调度流程

 client向RM提交应用程序,其中包括启动该应用的ApplicationMaster的必须信息,例如
ApplicationMaster程序、启动ApplicationMaster的命令、用户程序等

 ResourceManager启动一个container用于运行ApplicationMaster

 启动中的ApplicationMaster向ResourceManager注册自己,启动成功后与RM保持心跳

 ApplicationMaster向ResourceManager发送请求,申请相应数目的container

 申请成功的container,由ApplicationMaster进行初始化。container的启动信息初始化后,AM
与对应的NodeManager通信,要求NM启动container

 NM启动container

 container运行期间,ApplicationMaster对container进行监控。container通过RPC协议向对应
的AM汇报自己的进度和状态等信息

 应用运行结束后,ApplicationMaster向ResourceManager注销自己,并允许属于它的container
被收回

4.4.2 怎么处理Hadoop宕机的问题的?

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 38 页
如果MR造成系统宕机。此时要控制Yarn同时运行的任务数,和每个任务申请的最大内存。调整参
数:yarn.scheduler.maximum-allocation-mb(单个任务可申请的最多物理内存量,默认是
8192MB)。

如果写入文件过量造成NameNode宕机。那么调高Kafka的存储大小,控制从Kafka到HDFS的写入
速度。高峰期的时候用Kafka进行缓存,高峰期过去数据同步会自动跟上。

4.4.3 了解过哪些Hadoop的参数优化

「Hadoop参数调优」有以下几种:

在hdfs-site.xml文件中配置多目录,最好提前配置好,否则更改目录需要重新启动集群

NameNode有一个工作线程池,用来处理不同DataNode的并发心跳以及客户端并发的元数据操作

dfs.namenode.handler.count=20 * log2(Cluster Size)

比如集群规模为10台时,此参数设置为60

 编辑日志存储路径dfs.namenode.edits.dir设置与镜像文件存储路径dfs.namenode.name.dir
尽量分开,达到最低写入延迟

 服务器节点上YARN可使用的物理内存总量,默认是8192(MB),注意,如果你的节点内存资
源不够8GB,则需要调减小这个值,而YARN不会智能的探测节点的物理内存总量

 单个任务可申请的最多物理内存量,默认是8192(MB)

4.4.4 Yarn HA

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 39 页
Hadoop 2.4.0版本开始,Yarn 实现了 ResourceManager HA

由于资源使用情况和 NodeManager 信息都可以通过 NodeManager 的心跳机制重新构建出来,因此只需


要对 ApplicationMaster 相关的信息进行持久化存储即可。

在一个典型的 HA 集群中,两台独立的机器被配置成 ResourceManger。在任意时间,有且只允许一个活动


的 ResourceManger,另外一个备用。切换分为两种方式:

手动切换:在自动恢复不可用时,管理员可用手动切换状态,或是从 Active 到 Standby,或是从 Standby 到


Active。

自动切换:基于 Zookeeper,但是区别于 HDFS 的 HA,2 个节点间无需配置额外的 ZFKC守护进程来同步


数据。

4.4.5 简述Hadoop1与Hadoop2 的架构异同。

加入了yarn解决了资源调度的问题。

加入了对zookeeper的支持实现比较可靠的高可用。

4.4.6 为什么会产生yarn,它解决了什么问题,有什么优势?

Yarn最主要的功能就是解决运行的用户程序与yarn框架完全解耦。

Yarn上可以运行各种类型的分布式运算程序(mapreduce只是其中的一种),比如mapreduce、storm程序,spark

程序……

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 40 页
4.4.7 HDFS的数据压缩算法?及每种算法的应用场景?

1)gzip压缩

优点:压缩率比较高,而且压缩/解压速度也比较快;hadoop本身支持,在应用中处理gzip格式的文件就和直

接处理文本一样;大部分linux系统都自带gzip命令,使用方便。

缺点:不支持split。

应用场景:当每个文件压缩之后在130M以内的(1个块大小内),都可以考虑用gzip压缩格式。例如说一天或

者一个小时的日志压缩成一个gzip文件,运行mapreduce程序的时候通过多个gzip文件达到并发。hive程序,

streaming程序,和java写的mapreduce程序完全和文本处理一样,压缩之后原来的程序不需要做任何修改。

2)Bzip2压缩

优点:支持split;具有很高的压缩率,比gzip压缩率都高;hadoop本身支持,但不支持native;在linux系统下

自带bzip2命令,使用方便。

缺点:压缩/解压速度慢;不支持native。

应用场景:适合对速度要求不高,但需要较高的压缩率的时候,可以作为mapreduce作业的输出格式;或者

输出之后的数据比较大,处理之后的数据需要压缩存档减少磁盘空间并且以后数据用得比较少的情况;或者对单个

很大的文本文件想压缩减少存储空间,同时又需要支持split,而且兼容之前的应用程序(即应用程序不需要修改)

的情况。

3)Lzo压缩

优点:压缩/解压速度也比较快,合理的压缩率;支持split,是hadoop中最流行的压缩格式;可以在linux系统

下安装lzop命令,使用方便。

缺点:压缩率比gzip要低一些;hadoop本身不支持,需要安装;在应用中对lzo格式的文件需要做一些特殊处

理(为了支持split需要建索引,还需要指定inputformat为lzo格式)。

应用场景:一个很大的文本文件,压缩之后还大于200M以上的可以考虑,而且单个文件越大,lzo优点越越明

显。

4)Snappy压缩

优点:高速压缩速度和合理的压缩率。

缺点:不支持split;压缩率比gzip要低;hadoop本身不支持,需要安装;

应用场景:当Mapreduce作业的Map输出的数据比较大的时候,作为Map到Reduce的中间数据的压缩格式;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 41 页
或者作为一个Mapreduce作业的输出和另外一个Mapreduce作业的输入。

4.4.8 介绍Yarn默认的调度器,调度器分类

关于Yarn的知识点考察实际上在面试中占的比重并的不多,像面试中常问的无非就Yarn的Job执行流程或者调

度器的分类,答案往往也都差不多,以下回答做个参考:

Hadoop调度器主要分为三类:

FIFO Scheduler:先进先出调度器:优先提交的,优先执行,后面提交的等待【生产环境不会使用】

Capacity Scheduler:容量调度器:允许看创建多个任务对列,多个任务对列可以同时执行。但是一个队列内

部还是先进先出。【Hadoop2.7.2默认的调度器】

Fair Scheduler:公平调度器:第一个程序在启动时可以占用其他队列的资源(100%占用),当其他队列有任

务提交时,占用资源的队列需要将资源还给该任务。还资源的时候,效率比较慢。【CDH版本的yarn调度器默认】

4.4.9 Hadoop的调度器总结。

目前,Hadoop作业调度器主要有三种:FIFO、Capacity Scheduler和Fair Scheduler。Hadoop2.7.2默认的资

源调度器是Capacity Scheduler。

具体设置详见:yarn-default.xml文件

<property>

<description>The class to use as the resource scheduler.</description>

<name>yarn.resourcemanager.scheduler.class</name>

<value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler</valu
e>

</property>

1)先进先出调度器(FIFO)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 42 页
2)容量调度器(Capacity Scheduler)

3)公平调度器(Fair Scheduler)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 43 页
4.4.10 Mapreduce推测执行算法及原理。

1)作业完成时间取决于最慢的任务完成时间

一个作业由若干个Map任务和Reduce任务构成。因硬件老化、软件Bug等,某些任务可能运行非常慢。

典型案例:系统中有99%的Map任务都完成了,只有少数几个Map老是进度很慢,完不成,怎么办?

2)推测执行机制:

发现拖后腿的任务,比如某个任务运行速度远慢于任务平均速度。为拖后腿任务启动一个备份任务,同时运行。

谁先运行完,则采用谁的结果。

3)执行推测任务的前提条件

(1)每个task只能有一个备份任务;

(2)当前job已完成的task必须不小于0.05(5%)

(3)开启推测执行参数设置。Hadoop2.7.2 mapred-site.xml文件中默认是打开的。

<property>

<name>mapreduce.map.speculative</name>

<value>true</value>

<description>If true, then multiple instances of some map tasks

may be executed in parallel.</description>

</property>

<property>

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 44 页
<name>mapreduce.reduce.speculative</name>

<value>true</value>

<description>If true, then multiple instances of some reduce tasks

may be executed in parallel.</description>

</property>

4)不能启用推测执行机制情况

(1)任务间存在严重的负载倾斜;

(2)特殊任务,比如任务向数据库中写数据。

5)算法原理:

4.5 Hadoop优化

4.5.1 Mapreduce 跑的慢的原因?

Mapreduce 程序效率的瓶颈在于两点:

1)计算机性能

CPU、内存、磁盘健康、网络

2)I/O 操作优化

(1)数据倾斜

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 45 页
(2)map和reduce数设置不合理

(3)reduce等待过久

(4)小文件过多

(5)大量的不可分块的超大文件

(6)spill次数过多

(7)merge次数过多等。

4.5.2 mapreduce 优化方法

1)数据输入:

(1)合并小文件:在执行mr任务前将小文件进行合并,大量的小文件会产生大量的map任务,增大map任务

装载次数,而任务的装载比较耗时,从而导致 mr 运行较慢。

(2)采用ConbinFileInputFormat来作为输入,解决输入端大量小文件场景。

2)map阶段

(1)减少spill次数:通过调整io.sort.mb及sort.spill.percent参数值,增大触发spill的内存上限,减少spill次数,

从而减少磁盘 IO。

(2)减少merge次数:通过调整io.sort.factor参数,增大merge的文件数目,减少merge的次数,从而缩短

mr处理时间。

(3)在 map 之后先进行combine处理,减少 I/O。

3)reduce阶段

(1)合理设置map和reduce数:两个都不能设置太少,也不能设置太多。太少,会导致task等待,延长处理

时间;太多,会导致 map、reduce任务间竞争资源,造成处理超时等错误。

(2)设置map、reduce共存:调整slowstart.completedmaps参数,使map运行到一定程度后,reduce也开

始运行,减少reduce的等待时间。

(3)规避使用reduce,因为Reduce在用于连接数据集的时候将会产生大量的网络消耗。

(4)合理设置reduc端的buffer,默认情况下,数据达到一个阈值的时候,buffer中的数据就会写入磁盘,然

后reduce会从磁盘中获得所有的数据。也就是说,buffer和reduce是没有直接关联的,中间多个一个写磁盘->读磁

盘的过程,既然有这个弊端,那么就可以通过参数来配置,使得buffer中的一部分数据可以直接输送到reduce,从

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 46 页
而减少IO开销:mapred.job.reduce.input.buffer.percent,默认为0.0。当值大于0的时候,会保留指定比例的内存

读buffer中的数据直接拿给reduce使用。这样一来,设置buffer需要内存,读取数据需要内存,reduce计算也要内

存,所以要根据作业的运行情况进行调整。

4)IO传输

(1)采用数据压缩的方式,减少网络IO的的时间。安装Snappy和LZOP压缩编码器。

(2)使用SequenceFile二进制文件

5)数据倾斜问题

(1)数据倾斜现象

数据频率倾斜——某一个区域的数据量要远远大于其他区域。

数据大小倾斜——部分记录的大小远远大于平均值。

(2)如何收集倾斜数据

在reduce方法中加入记录map输出键的详细情况的功能。

public static final String MAX_VALUES = "skew.maxvalues";

private int maxValueThreshold;

@Override

public void configure(JobConf job) {

maxValueThreshold = job.getInt(MAX_VALUES, 100);

@Override

public void reduce(Text key, Iterator<Text> values,

OutputCollector<Text, Text> output,

Reporter reporter) throws IOException {

int i = 0;

while (values.hasNext()) {

values.next();

i++;

if (++i > maxValueThreshold) {

log.info("Received " + i + " values for key " + key);

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 47 页
}

(3)减少数据倾斜的方法

方法1:抽样和范围分区

可以通过对原始数据进行抽样得到的结果集来预设分区边界值。

方法2:自定义分区

另一个抽样和范围分区的替代方案是基于输出键的背景知识进行自定义分区。例如,如果map输出键的单词

来源于一本书。其中大部分必然是省略词(stopword)。那么就可以将自定义分区将这部分省略词发送给固定的一

部分reduce实例。而将其他的都发送给剩余的reduce实例。

方法3:Combine

使用Combine可以大量地减小数据频率倾斜和数据大小倾斜。在可能的情况下,combine的目的就是聚合并

精简数据。

6)常用的调优参数

(1)资源相关参数

(a)以下参数是在用户自己的mr应用程序中配置就可以生效(mapred-default.xml)

配置参数 参数说明

mapreduce.map.memory.mb 一个Map Task可使用的资源上限(单位:MB),默认为1024。


如果Map Task实际使用的资源量超过该值,则会被强制杀
死。

mapreduce.reduce.memory.mb 一个Reduce Task可使用的资源上限(单位:MB),默认为


1024。如果Reduce Task实际使用的资源量超过该值,则会
被强制杀死。

mapreduce.map.cpu.vcores 每个Map task可使用的最多cpu core数目,默认值: 1

mapreduce.reduce.cpu.vcores 每个Reduce task可使用的最多cpu core数目,默认值: 1

mapreduce.reduce.shuffle.parallelcopies 每个reduce去map中拿数据的并行数。默认值是5

mapreduce.reduce.shuffle.merge.percent buffer中的数据达到多少比例开始写入磁盘。默认值0.66

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 48 页
mapreduce.reduce.shuffle.input.buffer.percent buffer大小占reduce可用内存的比例。默认值0.7

mapreduce.reduce.input.buffer.percent 指定多少比例的内存用来存放buffer中的数据,默认值是0.0

(b)应该在yarn启动之前就配置在服务器的配置文件中才能生效(yarn-default.xml)

配置参数 参数说明

yarn.scheduler.minimum-allocation-mb 1024 给应用程序container分配的最小内存

yarn.scheduler.maximum-allocation-mb 8192 给应用程序container分配的最大内存

yarn.scheduler.minimum-allocation-vcores 1 每个container申请的最小CPU核数

yarn.scheduler.maximum-allocation-vcores 32 每个container申请的最大CPU核数

yarn.nodemanager.resource.memory-mb 8192 给containers分配的最大物理内存

(c)shuffle性能优化的关键参数,应在yarn启动之前就配置好(mapred-default.xml)

配置参数 参数说明

mapreduce.task.io.sort.mb 100 shuffle的环形缓冲区大小,默认100m

mapreduce.map.sort.spill.percent 0.8 环形缓冲区溢出的阈值,默认80%

(2)容错相关参数(mapreduce性能优化)

配置参数 参数说明

mapreduce.map.maxattempts 每个Map Task最大重试次数,一旦重试参数超过该值,则认为Map


Task运行失败,默认值:4。

mapreduce.reduce.maxattempts 每个Reduce Task最大重试次数,一旦重试参数超过该值,则认为


Map Task运行失败,默认值:4。

mapreduce.task.timeout Task超时时间,经常需要设置的一个参数,该参数表达的意思为:
如果一个task在一定时间内没有任何进入,即不会读取新的数据,
也没有输出数据,则认为该task处于block状态,可能是卡住了,也
许永远会卡主,为了防止因为用户程序永远block住不退出,则强
制设置了一个该超时时间(单位毫秒),默认是600000。如果你
的程序对每条输入数据的处理时间过长(比如会访问数据库,通过
网络拉取数据等),建议将该参数调大,该参数过小常出现的错误

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 49 页
提示是
“AttemptID:attempt_14267829456721_123456_m_000224_0
Timed out after 300 secsContainer killed by the
ApplicationMaster.”。

4.5.3 HDFS小文件优化方法

1)HDFS小文件弊端

HDFS上每个文件都要在namenode上建立一个索引,这个索引的大小约为150byte,这样当小文件比较多的时

候,就会产生很多的索引文件,一方面会大量占用namenode的内存空间,另一方面就是索引文件过大是的索引速

度变慢。

2)解决方案

1)Hadoop Archive:

是一个高效地将小文件放入HDFS块中的文件存档工具,它能够将多个小文件打包成一个HAR文件,这样在减

少namenode内存使用的同时。

2)Sequence file:

sequence file由一系列的二进制key/value组成,如果key为文件名,value为文件内容,则可以将大批小文件

合并成一个大文件。

3)CombineFileInputFormat:

CombineFileInputFormat是一种新的inputformat,用于将多个文件合并成一个单独的split,另外,它会考虑

数据的存储位置。

4)开启JVM重用

对于大量小文件Job,可以开启JVM重用会减少45%运行时间。

JVM重用理解:一个map运行一个jvm,重用的话,在一个map在jvm上运行完毕后,jvm继续运行其他jvm

具体设置:mapreduce.job.jvm.numtasks值在10-20之间。

4.5.4 MapReduce怎么解决数据均衡问题,如何确定分区号?

数据均衡问题指的就是某个节点或者某几个节点的任务运行的比较慢,拖慢了整个Job的进度。实际上数据均

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 50 页
衡问题就是数据倾斜问题,解决方案同解决数据倾斜的方案。

MapReduce中分区默认是按hashcode来分的,用户可以自定义分区类,需要继承系统的Partitioner类,重写

getPartition()方法即可。

4.5.5 Hadoop中job和Tasks之间的区别是什么?

编写好的一个程序,我们称为Mapreduce程序,一个Mapreduce程序就是一个Job,而一个Job里面可以有一

个或多个Task,Task又可以区分为Map Task和Reduce Task.

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 51 页
5. V10.0ZK高频面试题

5.1 请简述ZooKeeper的选举机制

假设有五台服务器组成的zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,

在存放数据量这一点上,都是一样的。假设这些服务器依序启动,来看看会发生什么。

(1)服务器1启动,此时只有它一台服务器启动了,它发出去的报没有任何响应,所以它的选举状态一直是

LOOKING状态。

(2)服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数

据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上

是3),所以服务器1、2还是继续保持LOOKING状态。

(3)服务器3启动,根据前面的理论分析,服务器3成为服务器1、2、3中的Leader,而与上面不同的是,此时

有三台服务器选举了它,所以它成为了这次选举的Leader。

(4)服务器4启动,根据前面的分析,理论上服务器4应该是服务器1、2、3、4中最大的,但是由于前面已经

有半数以上的服务器选举了服务器3,所以它成为Follower。

(5)服务器5启动,同4一样成为Follower。

注意,如果按照5,4,3,2,1的顺序启动,那么5将成为Leader,因为在满足半数条件后,ZooKeeper集群启动,5

的Id最大,被选举为Leader。

5.2 客户端对ZooKeeper的ServerList的轮询机制

随机,客户端在初始化( new ZooKeeper(String connectString, int sessionTimeout, Watcher watcher) )的过程

中,将所有Server保存在一个List中,然后随机打散,形成一个环。之后从0号位开始一个一个使用。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 52 页
两个注意点:

Server地址能够重复配置,这样能够弥补客户端无法设置Server权重的缺陷,但是也会加大风险。(比如:

192.168.1.1:2181,192.168.1.1:2181,192.168.1.2:2181).

如果客户端在进行Server切换过程中耗时过长,那么将会收到SESSION_EXPIRED. 这也是上面第1点中的加大

风险之处。

5.3 客户端如何正确处理CONNECTIONLOSS(连接断开) 和 SESSIONEXPIRED(Session 过期)两类连接异


常?

在ZooKeeper中,服务器和客户端之间维持的是一个长连接,在 SESSION_TIMEOUT 时间内,服务器会确定

客户端是否正常连接(客户端会定时向服务器发送heart_beat),服务器重置下次SESSION_TIMEOUT时间。因此,在

正常情况下,Session一直有效,并且zk集群所有机器上都保存这个Session信息。在出现问题的情况下,客户端与

服务器之间连接断了(客户端所连接的那台zk机器挂了,或是其它原因的网络闪断),这个时候客户端会主动在地

址列表(初始化的时候传入构造方法的那个参数connectString)中选择新的地址进行连接。

以上即为服务器与客户端之间维持长连接的过程,在这个过程中,用户可能会看到两类异常

CONNECTIONLOSS(连接断开) 和SESSIONEXPIRED(Session 过期)。

发生CONNECTIONLOSS后,此时用户不需要关心我的会话是否可用,应用所要做的就是等待客户端帮我们自

动连接上新的zk机器,一旦成功连接上新的zk机器后,确认之前的操作是否执行成功了。

5.4 一个客户端修改了某个节点的数据,其他客户端能够马上获取到这个最新数据吗?

ZooKeeper不能确保任何客户端能够获取(即Read Request)到一样的数据,除非客户端自己要求,方法是客

户端在获取数据之前调用org.apache.zookeeper.AsyncCallbac k.VoidCallback, java.lang.Object) sync。

通常情况下(这里所说的通常情况满足:1. 对获取的数据是否是最新版本不敏感,2. 一个客户端修改了数据,

其它客户端是否需要立即能够获取最新数据),可以不关心这点。

在其它情况下,最清晰的场景是这样:ZK客户端A对 /my_test 的内容从 v1->v2, 但是ZK客户端B对 /my_test

的内容获取,依然得到的是 v1. 请注意,这个是实际存在的现象,当然延时很短。解决的方法是客户端B先调用 sync(),

再调用 getData()。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 53 页
5.5 ZooKeeper对节点的watch监听是永久的吗?为什么?

不是。

官方声明:一个Watch事件是一个一次性的触发器,当被设置了Watch的数据发生了改变的时候,则服务器将

这个改变发送给设置了Watch的客户端,以便通知它们。

为什么不是永久的,举个例子,如果服务端变动频繁,而监听的客户端很多情况下,每次变动都要通知到所有

的客户端,这太消耗性能了。

一般是客户端执行getData(“/节点A”,true),如果节点A发生了变更或删除,客户端会得到它的watch事件,但

是在之后节点A又发生了变更,而客户端又没有设置watch事件,就不再给客户端发送。

在实际应用中,很多情况下,我们的客户端不需要知道服务端的每一次变动,我只要最新的数据即可。

5.6 ZooKeeper中使用watch的注意事项有哪些?

使用watch需要注意的几点:

① Watches通知是一次性的,必须重复注册.

② 发生CONNECTIONLOSS之后,只要在session_timeout之内再次连接上(即不发生SESSIONEXPIRED),

那么这个连接注册的watches依然在。

③ 节点数据的版本变化会触发NodeDataChanged,注意,这里特意说明了是版本变化。存在这样的情况,只

要成功执行了setData()方法,无论内容是否和之前一致,都会触发NodeDataChanged。

④ 对某个节点注册了watch,但是节点被删除了,那么注册在这个节点上的watches都会被移除。

⑤ 同一个zk客户端对某一个节点注册相同的watch,只会收到一次通知。

⑥ Watcher对象只会保存在客户端,不会传递到服务端。

5.7 能否收到每次节点变化的通知?

如果节点数据的更新频率很高的话,不能。

原因在于:当一次数据修改,通知客户端,客户端再次注册watch,在这个过程中,可能数据已经发生了许多

次数据修改,因此,千万不要做这样的测试:”数据被修改了n次,一定会收到n次通知”来测试server是否正常工作。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 54 页
5.8 能否为临时节点创建子节点?

ZooKeeper中不能为临时节点创建子节点,如果需要创建子节点,应该将要创建子节点的节点创建为永久性节

点。

5.9 是否可以拒绝单个IP对ZooKeeper的访问?如何实现?

ZK本身不提供这样的功能,它仅仅提供了对单个IP的连接数的限制。你可以通过修改iptables来实现对单个ip

的限制。

5.10 ZooKeeper集群中服务器之间是怎样通信的?

Leader服务器会和每一个Follower/Observer服务器都建立TCP连接,同时为每个F/O都创建一个叫做

LearnerHandler的实体。LearnerHandler主要负责Leader和F/O之间的网络通讯,包括数据同步,请求转发和

Proposal提议的投票等。Leader服务器保存了所有F/O的LearnerHandler。

5.11 ZooKeeper是否会自动进行日志清理?如何进行日志清理?

zk自己不会进行日志清理,需要运维人员进行日志清理。

5.12 谈谈你对ZooKeeper的理解?

Zookeeper 作为一个分布式的服务框架,主要用来解决分布式集群中应用系统的一致性问题。ZooKeeper提

供的服务包括:分布式消息同步和协调机制、服务器节点动态上下线、统一配置管理、负载均衡、集群管理等。

ZooKeeper提供基于类似于Linux文件系统的目录节点树方式的数据存储,即分层命名空间。Zookeeper 并不

是用来专门存储数据的,它的作用主要是用来维护和监控你存储的数据的状态变化,通过监控这些数据状态的变化,

从而可以达到基于数据的集群管理,ZooKeeper节点的数据上限是1MB。

我们可以认为Zookeeper=文件系统+通知机制,

对于ZooKeeper的数据结构,每个子目录项如 NameService 都被称作为 znode,这个 znode 是被它所在

的路径唯一标识,如 Server1 这个 znode 的标识为 /NameService/Server1;

znode 可以有子节点目录,并且每个 znode 可以存储数据,注意 EPHEMERAL 类型的目录节点不能有子节

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 55 页
点目录(因为它是临时节点);

znode 是有版本的,每个 znode 中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据;

znode 可以是临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除,

Zookeeper 的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称

为 session,如果 znode 是临时节点,这个 session 失效,znode 也就删除了;

znode 的目录名可以自动编号,如 App1 已经存在,再创建的话,将会自动命名为 App2;

znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监

控的客户端,这个是 Zookeeper 的核心特性,Zookeeper 的很多功能都是基于这个特性实现的,后面在典型的应

用场景中会有实例介绍。

5.13 ZooKeeper节点类型?

1)Znode有两种类型:

短暂(ephemeral):客户端和服务器端断开连接后,创建的节点自己删除 。

持久(persistent):客户端和服务器端断开连接后,创建的节点不删除 。

2)Znode有四种形式的目录节点(默认是persistent )

(1)持久化目录节点(PERSISTENT)

客户端与zookeeper断开连接后,该节点依旧存在 。

(2)持久化顺序编号目录节点(PERSISTENT_SEQUENTIAL)

客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号 。

(3)临时目录节点(EPHEMERAL)

客户端与zookeeper断开连接后,该节点被删除 。

(4)临时顺序编号目录节点(EPHEMERAL_SEQUENTIAL)

客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 56 页
5.14 请说明ZooKeeper的通知机制?

ZooKeeper选择了基于通知(notification)的机制,即:客户端向ZooKeeper注册需要接受通知的znode,通

过znode设置监控点(watch)来接受通知。监视点是一个单次触发的操作,意即监视点会触发一个通知。为了接

收多个通知,客户端必须在每次通知后设置一个新的监视点。在下图阐述的情况下,当节点/task发生变化时,客户

端会受到一个通知,并从ZooKeeper读取一个新值。

5.15 ZooKeeper的监听原理是什么?

在应用程序中,mian()方法首先会创建zkClient,创建zkClient的同时就会产生两个进程,即Listener进程(监

听进程)和connect进程(网络连接/传输进程),当zkClient调用getChildren()等方法注册监视器时,connect进程

向ZooKeeper注册监听器,注册后的监听器位于ZooKeeper的监听器列表中,监听器列表中记录了zkClient的IP,

端口号以及要监控的路径,一旦目标文件发生变化,ZooKeeper就会把这条消息发送给对应的zkClient的Listener()

进程,Listener进程接收到后,就会执行process()方法,在process()方法中针对发生的事件进行处理。

5.16 请说明ZooKeeper使用到的各个端口的作用?

2888:Follower与Leader交换信息的端口。

3888:万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 57 页
就是用来执行选举时服务器相互通信的端口。

5.17 ZooKeeper的部署方式有哪几种?集群中的角色有哪些?集群最少需要几台机器?

ZooKeeper的部署方式有单机模式和集群模式,集群中的角色有Leader和Follower,集群最少3(2N+1)台,根据

选举算法,应保证奇数。

5.18 ZooKeeper集群如果有3台机器,挂掉一台是否还能工作?

对于ZooKeeper集群,过半存活即可使用。

5.19 ZooKeeper使用的ZAB协议与Paxo算法的异同?

Paxos算法是分布式选举算法,Zookeeper使用的 ZAB协议(Zookeeper原子广播),两者的异同如下:

① 相同之处:

比如都有一个Leader,用来协调N个Follower的运行;Leader要等待超半数的Follower做出正确反馈之后才进行提

案;二者都有一个值来代表Leader的周期。

② 不同之处:

ZAB用来构建高可用的分布式数据主备系统(Zookeeper),Paxos是用来构建分布式一致性状态机系统。

5.20 请谈谈对ZooKeeper对事务性的支持?

ZooKeeper对于事务性的支持主要依赖于四个函数,zoo_create_op_init, zoo_delete_op_init, zoo_set_op_init

以及zoo_check_op_init。每一个函数都会在客户端初始化一个operation,客户端程序有义务保留这些operations。

当准备好一个事务中的所有操作后,可以使用zoo_multi来提交所有的操作,由zookeeper服务来保证这一系列操作

的原子性。也就是说只要其中有一个操作失败了,相当于此次提交的任何一个操作都没有对服务端的数据造成影响。

Zoo_multi的返回值是第一个失败操作的状态信号。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 58 页
6. V10.0Hive核心基础面试题(必记必看)

6.1 Hive表关联查询,如何解决数据倾斜的问题?

1)倾斜原因:

map输出数据按key Hash的分配到reduce中,由于key分布不均匀、业务数据本身的特、建表时考虑不周、等

原因造成的reduce 上的数据量差异过大。

(1)key分布不均匀;

(2)业务数据本身的特性;

(3)建表时考虑不周;

(4)某些SQL语句本身就有数据倾斜;

如何避免:对于key为空产生的数据倾斜,可以对其赋予一个随机值。

2)解决方案

(1)参数调节:

hive.map.aggr = true

hive.groupby.skewindata=true

有数据倾斜的时候进行负载均衡,当选项设定位true,生成的查询计划会有两个MR Job。第一个MR Job中,Map

的输出结果集合会随机分布到Reduce中,每个Reduce做部分聚合操作,并输出结果,这样处理的结果是相同的

Group By Key有可能被分发到不同的Reduce中,从而达到负载均衡的目的;第二个MR Job再根据预处理的数据结

果按照Group By Key 分布到 Reduce 中(这个过程可以保证相同的 Group By Key 被分布到同一个Reduce中),

最后完成最终的聚合操作。

(2)SQL 语句调节:

① 选用join key分布最均匀的表作为驱动表。做好列裁剪和filter操作,以达到两表做join 的时候,数据量相对

变小的效果。

② 大小表Join:

使用map join让小的维度表(1000 条以下的记录条数)先进内存。在map端完成reduce.

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 59 页
③ 大表Join大表:

把空值的key变成一个字符串加上随机数,把倾斜的数据分到不同的reduce上,由于null 值关联不上,处理后

并不影响最终结果。

④ count distinct大量相同特殊值:

count distinct 时,将值为空的情况单独处理,如果是计算count distinct,可以不用处理,直接过滤,在最后

结果中加1。如果还有其他计算,需要进行group by,可以先将值为空的记录单独处理,再和其他计算结果进行union。

6.2 请谈一下Hive的特点,Hive和RDBMS有什么异同?

Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供完整的sql

查询功能,可以将sql语句转换为MapReduce任务进行运行。其优点是学习成本低,可以通过类SQL语句快速实现

简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统计分析,但是Hive不支持实时

查询。

Hive与关系型数据库的区别:

6.3 请说明Hive中 Sort By,Order By,Cluster By,Distrbute By各代表什么意思?

order by:会对输入做全局排序,因此只有一个reducer(多个reducer无法保证全局有序)。只有一个reducer,

会导致当输入规模较大时,需要较长的计算时间。

sort by:不是全局排序,其在数据进入reducer前完成排序。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 60 页
distribute by:按照指定的字段对数据进行划分输出到不同的reduce中。

cluster by:除了具有 distribute by 的功能外还兼具 sort by 的功能。

6.4 简要描述数据库中的 null,说出null在hive底层如何存储,并解释select a.* from t1 a left outer join t2 b on


a.id=b.id where b.id is null; 语句的含义?

null与任何值运算的结果都是null, 可以使用is null、is not null函数指定在其值为null情况下的取值。

null在hive底层默认是用'\N'来存储的,可以通过alter table test SET

SERDEPROPERTIES('serialization.null.format' = 'a');来修改。

查询出t1表中与t2表中id相等的所有信息。

6.5 写出Hive中split、coalesce及collect_list函数的用法(可举例)?

split将字符串转化为数组,即:split('a,b,c,d' , ',') ==> ["a","b","c","d"]。

coalesce(T v1, T v2, …) 返回参数中的第一个非空值;如果所有值都为 NULL,那么返回NULL。

collect_list列出该字段所有的值,不去重 select collect_list(id) from table。

6.6 Hive有哪些方式保存元数据,各有哪些特点?

1)Single User Mode:默认安装hive,hive是使用derby内存数据库保存hive的元数据,这样是不可以并发调


用hive的。

2)User Mode:通过网络连接到一个数据库中,是最经常使用到的模式。假设使用本机mysql服务器存储元
数据。这种存储方式需要在本地运行一个mysql服务器,可并发调用

3)Remote Server Mode:在服务器端启动一个 MetaStoreServer,客户端利用 Thrift 协议通过


MetaStoreServer 访问元数据库。

6.7 Hive内部表和外部表的区别?

1)默认创建的表都是管理表,有时也被称为内部表。因为这种表,Hive会(或多或少地)控制着数据的生命
周期。Hive默认情况下会将这些表的数据存储在由配置项hive.metastore.warehouse.dir(例如,
/user/hive/warehouse)所定义的目录的子目录下。 当我们删除一个管理表时,Hive也会删除这个表中数据。管理
表不适合和其他工具共享数据。

2)Hive并非认为其完全拥有这份数据。删除该表并不会删除掉这份数据,不过描述表的元数据信息会被删除
掉。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 61 页
6.8 Hive的HSQL转换为MapReduce的过程?

HiveSQL ->AST(抽象语法树) -> QB(查询块) ->OperatorTree(操作树)->优化后的操作树->mapreduce任

务树->优化后的mapreduce任务树

过程描述如下:

SQL Parser:Antlr定义SQL的语法规则,完成SQL词法,语法解析,将SQL转化为抽象 语法树AST Tree;

Semantic Analyzer:遍历AST Tree,抽象出查询的基本组成单元QueryBlock;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 62 页
Logical plan:遍历QueryBlock,翻译为执行操作树OperatorTree;

Logical plan optimizer: 逻辑层优化器进行OperatorTree变换,合并不必要的ReduceSinkOperator,减少


shuffle数据量;

Physical plan:遍历OperatorTree,翻译为MapReduce任务;

Logical plan optimizer:物理层优化器进行MapReduce任务的变换,生成最终的执行计划;

6.9 Hive底层与数据库交互原理?

由于Hive的元数据可能要面临不断地更新、修改和读取操作,所以它显然不适合使用Hadoop文件系统进行存

储。目前Hive将元数据存储在RDBMS中,比如存储在MySQL、Derby中。元数据信息包括:存在的表、表的列、权

限和更多的其他信息。

6.10 row_number(),rank()和dense_rank()的区别

row_number():根据查询结果的顺序计算排序,多用于分页查询

rank():排序相同时序号重复,总序数不变

dense_rank():排序相同时序号重复时,总序数减少

6.11 Hive中常用的系统函数有哪些

date_add(str,n)、date_sub(str,n) 加减时间

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 63 页
next_day(to_date(str),’MO’) 周指标相关,获取str下周一日期

date_format(str,’yyyy’) 根据格式整理日期

last_day(to_date(str)) 求当月最后一天日期

collect_set(col) 收集数据返回一个以逗号分割的字符串数组

get_json_object(jsondata,’$.object’) 解析json,使用'$. object’获取对象值

NVL(str,replace) 空字段赋值,str为空返回replace值;两个都为空则返回null

6.12 Hive如何实现分区?

建表:create table tablename(col1 string) partitioned by(col2 string);

添加分区:alter table tablename add partition(col2=’202101’);

删除分区:alter table tablename drop partition(col2=’202101’);

6.13 Hive导入数据的五种方式?

1. Load方式,可以从本地或HDFS上导入,本地是copy,HDFS是移动

本地:load data local inpath ‘/root/student.txt’ into table student;

HDFS:load data inpath ‘/user/hive/data/student.txt’ into table student;

2. Insert方式,往表里插入

insert into table student values(1,’zhanshan’);

3. As select方式,根据查询结果创建表并插入数据

create table if not exists stu1 as select id,name from student;

4. Location方式,创建表并指定数据的路径

create external if not exists stu2 like student location '/user/hive/warehouse/student/student.txt';

5. Import方式,先从hive上使用export导出在导入

import table stu3 from ‘/user/export/student’;

6.14 Hive导出数据的五种方式?

1. Insert方式,查询结果导出到本地或HDFS

Insert overwrite local directory ‘/root/insert/student’ select id,name from student;

Insert overwrite directory ‘/user/ insert /student’ select id,name from student;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 64 页
2. Hadoop命令导出本地

hive>dfs -get /user/hive/warehouse/student/ 000000_0 /root/hadoop/student.txt

3. hive Shell命令导出

]$ bin/hive -e ‘select id,name from student;’ > /root/hadoop/student.txt

4. Export导出到HDFS

hive> export table student to ‘/user/export/student’;

5. Sqoop导出

6.15 Hive是怎样保存元数据的?

保存元数据的方式有:内存数据库rerdy,本地mysql数据库,远程mysql数据库,但是本地的mysql
数据用的比较多,因为本地读写速度都比较快

内存数据库derby,安装小,但是数据存在内存,不稳定

mysql数据库,数据存储模式可以自己设置,持久化好,查看方便。

6.16 说说对Hive桶表的理解?

桶表是对数据进行哈希取值,然后放到不同文件中存储。

数据加载到桶表时,会对字段取hash值,然后与桶的数量取模。把数据放到对应的文件中。物理上,
每个桶就是表(或分区)目录里的一个文件,一个作业产生的桶(输出文件)和reduce任务个数相同。

桶表专门用于抽样查询,是很专业性的,不是日常用来存储数据的表,需要抽样查询时,才创建和
使用桶表。

6.17 什么是 metastore?

metadata 即元数据。包含 database、tabel、column names、partitions 信息、bucketing 信


息等的元数据信息。

元数据默认是存储在 Derby 中,建议存储在关系型数据库中。

6.18 Hive如何动态分区?

与分区有关的有两种类型的分区:静态和动态。在静态分区中,您将在加载数据时(显式)指定分
区列。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 65 页
而在动态分区中,您将数据推送到 Hive,然后 Hive 决定哪个值应进入哪个分区。要启用动态分
区,请设置下面的属性:hive.exec.dynamic.parition.mode=nonstrict;

insert overwrite table emp_details_partitioned partition(location)

select * from emp_details;

6.19 如何创建 bucket 表?

默认情况下,在 Hive 中禁用分桶功能,可以通过设置下面的属性强制启用分桶功能:


hive.enforce.bucketing=true;

6.20 Hive 最优的 file formats 是什么?

ORC file formats:

1、ORC 将行的集合存储在一个文件中,并且集合内的行数据将以列式存储。采用列式格式,
压缩非常容易,从而降低了大量的存储成本。

2、当查询时,会查询特定列而不是查询整行,因为记录是以列式存储的。

3、ORC 会基于列创建索引,当查询的时候会很快。

6.21 Hive的数据类型

原始数据类型

整型

TINYINT — 微整型,只占用1个字节,只能存储0-255的整数。

SMALLINT– 小整型,占用2个字节,存储范围–32768 到 32767。

INT– 整型,占用4个字节,存储范围-2147483648到2147483647。

BIGINT– 长整型,占用8个字节,存储范围-263到263-1。

布尔型

BOOLEAN — TRUE/FALSE

浮点型

FLOAT– 单精度浮点数。

DOUBLE– 双精度浮点数。

字符串型

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 66 页
STRING– 不设定长度。

复合数据类型

Structs:一组由任意数据类型组成的结构。比如,定义一个字段C的类型为STRUCT {a INT; b
STRING},则可以使用a和C.b来获取其中的元素值;

Maps:一组无序的键/值对。键的类型必须是原子的,值可以是任何类型,同一个映射的键的类型
必须相同,值得类型也必须相同

Arrays:一组有序字段。字段的类型必须相同

6.22 描述一下Hive动态分区和分桶使用场景和使用方法

1.分区

按照数据表的某列或某些列分为多个分区,分区从形式上可以理解为文件夹,比如我们要收集某个
大型网站的日志数据,一个网站每天的日志数据存在同一张表上,由于每天会生成大量的日志,导
致数据表的内容巨大,在查询时进行全表扫描耗费的资源非常多。那其实这个情况下,我们可以按
照日期对数据进行分区,不同日期的数据存放在不同的分区,在查询时只要指定分区字段的值就可
以直接从该分区查找。分区是以字段的形式在表结构中存在,通过describe table命令可以查看字
段存在,但是该字段不存放实际的数据内容,仅仅是分区的表示。

1. 静态分区

create table if not exists sopdm.wyp2(id int,name string,tel string)

partitioned by(age int) row format delimited fields terminated by ‘,’ stored as textfile;

-- overwrite是覆盖,into是追加

insert into table sopdm.wyp2 partition(age=‘25’) select id,name.tel from sopdm.wyp;

2. 动态分区

-- 设置为true表示开启动态分区功能(默认为false)

set hive.exec.dynamic.partition=true;

-- 设置为nonstrict,表示允许所有分区都是动态的(默认为strict)

set hive.exec.dynamic.partition.mode=nonstrict;

-- insert overwrite是覆盖,insert into是追加

insert overwrite table sopdm.wyp2 partition(age) select id,name.tel,age from sopdm.wyp;

3. 静态分区和动态分区的区别

静态分区与动态分区的主要区别在于静态分区是手动指定,而动态分区是通过数据来进行判断。详
细来说:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 67 页
静态分区:

• 静态分区是在编译期间指定的指定分区名。

• 支持load和insert两种插入方式。

• 适用于分区数少,分区名可以明确的数据。

动态分区:

• 根据分区字段的实际值,动态进行分区。

• 是在sql执行的时候进行分区。

• 需要先将动态分区设置打开。set hive.exec.dynamic.partition.mode=nonstrict

• 只能用insert方式。

• 通过普通表选出的字段包含分区字段,分区字段放置在最后,多个分区字段按照分区顺序放置。

2.分桶

分桶是相对分区进行更细粒度的划分。分桶将整个数据内容安装某列属性值得hash值进行区分,如
果按照name属性分为3个桶,就是对name属性值的hash值对3取模,按照取模结果对数据分桶。
如取模结果为0的数据记录存放到一个文件,取模为1的数据存放到一个文件,取模为2的数据存放
到一个文件。

CREATE TABLE bucketed_user(id INT) name STRING CLUSTERED BY (id) INTO 4 BUCKETS;

对于每一个表(table)或者分区,可以进一步组织成桶,也就是说桶是更为细粒度的数据范围划
分。Hive也是针对某一列进行桶的组织。Hive采用对列值哈希,然后除以桶的个数求余的方式决定
该条记录存放在哪个桶当中。把表(或者分区)组织成桶(Bucket)有两个理由:

1)获得更高的查询处理效率

桶为表加上了额外的结构,Hive在处理有些查询时能利用这个结构。具体而言,连接两个在(包含
连接列的)相同列上划分了桶的表,可以使用Map端连接(Map-side join)高效的实现。比如JOIN
操作。对于JOIN操作两个表有一个相同的列,如果对这两个表都进行了桶操作。那么将保存相同列
值的桶进行JOIN操作就可以,可以大大减少JOIN的数据量。

2)使取样(sampling)更高效

在处理大规模数据集时,在开发和修改查询的阶段,如果能在数据集的一小部分数据上试运行查询,
会带来很多方便。

6.23 Hive join查询的时候on和where有什么区别

左右关联时:

• 条件不为主表条件时,放在on和where后面一样。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 68 页
• 条件为主表条件时,放在on后面,结果为主表全量,放在where后面为主表条件筛选过后的全量。

-- 1. select * from a left join b on a.id=b.id and a.dt=20181115;

-- 2.select * from a left join b on a.id=b.id and b.dt=20181115;

-- 3.select * from a join b on a.id=b.id and a.dt=20181115;

-- 4.select * from a left join b on a.id=b.id where a.dt=20181115;

-- sql1: 如果是left join 在on 上写主表a的条件不会生效,全表扫描。

-- sql2: 如果是left join 在 on 上写副表b的条件会生效,但是语义与写到where条件不同。

-- sql3: 如果是inner join 在on 上写主表a,副表b的条件都会生效。

-- sql4: 建议这么写,大家写sql 大部分的语义都是先过滤数据然后再join,所以在不了解 join on +


条件的情况下,条件尽量别写到on后

6.24 Hive里面的left join是怎么执行的?

不考虑where条件下,left join会把左表所有数据查询出来,on及其后面的条件仅仅会影响右表的
数据(符合就显示,不符合全部为null)。

在join阶段。where字句的条件都不会被使用,仅在join阶段完成以后,where子句条件才会被使用,
它将从匹配阶段产生的数据中检索过滤。

所以左连接关注的是左边的主表数据,不应该把on后面的从表中的条件加到where后,这样会影响
原有主表的数据。

where后面:是先连接生成临时查询结果,然后再筛选on后面:先根据条件过滤筛选,再连接生成
临时查询结果。

对于条件在on加个and还是用子查询。查询结果是一模一样的,至于如何使用这个需要分情况,用
子查询的话会多一个maptask,但是如果利用这个子查询能过滤很多数据的话,用子查询还是比较
建议的,因为不会加载太多的数据到内存中,如果过滤数据不多的情况下,建议用on后面加and条
件。

6.25 Hive内部表,外部表,分区表

1.内部表

• 与数据库中的Table在概念上是类似的。

• 每一个Table在Hive中都有一个相应的目录存储数据。

• 所有的Table数据(不包括 External Table)都保存在这个目录中。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 69 页
• 删除表时,元数据与数据都会被删除。

2.外部表

• 指向已经在HDFS中存在的数据,可以创建Partition。

• 它和内部表在元数据的组织上是相同的,而实际数据的存储则有较大的差异。

• 外部表只有一个过程,加载数据和创建表同时完成,并不会移动到数据库目录中,只是与外部数
据建立一个连接,当删除一个外部表时,仅删除连接和元数据。

3.分区表

• Partition 对应于数据库的Partition列的密集索引。

• 在Hive中,表中的一个Partition对应于表下的一个目录,所有的Partition的数据都存储在对应的
目录中。

6.26 生产环境中为什么建议使用外部表?

1)因为外部表不会加载数据到Hive,减少数据传输,数据还能共享。

2)Hive不会修改数据,所以无需担心数据的损坏。

3)删除表时,只删除表结构,不删除数据。

6.27 metastore 安装方式有什么区别

内嵌模式

内嵌模式使用的是内嵌的 Derby 数据库来存储元数据,也不需要额外起 Metastore 服务。


这个是默认的,配置简单,但是一次只能一个客户端连接,适用于用来实验,不适用于生产环境。

本地元存储

本地安装 mysql 替代 derby 存储元数据,这种安装方式和嵌入式的区别在于,不再使用内


嵌的 Derby 作为元数据的存储介质,而是使用其他数据库比如 MySQL 来存储元数据。hive 服
务和 metastore 服务运行在同一个进程中,mysql 是单独的进程,可以同一台机器,也可以在远
程机器上。

远程元存储(HiveServer2)

Hive 服务和 metastore 在不同的进程内,可能是不同的机器,该模式需要将


hive.metastore.uris 设置为 metastore 服务器 URL,如果有多个 metastore 服务器,将 URL 之
间用逗号分隔,metastore 服务器 URL 的格式为 thrift://127.0.0.1:9083。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 70 页
6.28 insert into 和 override write区别?

insert into:将某一张表中的数据写到另一张表中

override write:覆盖之前的内容。

6.29 写出将text.txt文件放入hive中test表‘2016-10-10’分区的语句,test的分区字段是 l_date

LOAD DATA LOCAL INPATH '/your/path/test.txt' OVERWRITE INTO TABLE test PARTITION (l_date='2016-10-10')

6.30 Hive如何进行权限控制?

目前hive支持简单的权限管理,默认情况下是不开启,这样所有的用户都具有相同的权限,同时也是超级管理

员,也就对hive中的所有表都有查看和改动的权利,这样是不符合一般数据仓库的安全原则的。Hive可以是基于元

数据的权限管理,也可以基于文件存储级别的权限管理。

为了使用Hive的授权机制,有两个参数必须在hive-site.xml中设置:

<property>

<name>hive.security.authorization.enabled</name>

<value>true</value>

<description>enable or disable the hive client authorization</description>

</property>

<property>

<name>hive.security.authorization.createtable.owner.grants</name>

<value>ALL</value>

<description>the privileges automatically granted to the owner whenever a table gets created. An
example like "select,drop" will grant select and drop privilege to the owner of the table</description>

</property>

Hive支持以下权限:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 71 页
Hive授权的核心就是用户(user)、组(group)、角色(role)。

Hive中的角色和平常我们认知的角色是有区别的,Hive中的角色可以理解为一部分有一些相同“属性”的用户或

组或角色的集合。这里有个递归的概念,就是一个角色可以是一些角色的集合。

下面举例进行说明:

用户 组

张三 G_db1

李四 G_db2

王五 G_bothdb

如上有三个用户分别属于G_db1、G_db2、G_alldb。G_db1、G_db2、G_ bothdb分别表示该组用户可以访问

数据库1、数据库2和可以访问1、2两个数据库。现在可以创建role_db1和role_db2,分别并授予访问数据库1和数据

库2的权限。这样只要将role_eb1赋给G_db1(或者该组的所有用户),将role_eb2赋给G_db2,就可以是实现指定

用户访问指定数据库。最后创建role_bothdb指向role_db1、role_db2(role_bothdb不需要指定访问那个数据库),

然后role_bothdb授予G_bothdb,则G_bothdb中的用户可以访问两个数据库。

Hive的用户和组使用的是Linux机器上的用户和组,而角色必须自己创建。

角色管理:

--创建和删除角色

create role role_name;

drop role role_name;

--展示所有roles

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 72 页
show roles

--赋予角色权限

grant select on database db_name to role role_name;

grant select on [table] t_name to role role_name;

--查看角色权限

show grant role role_name on database db_name;

show grant role role_name on [table] t_name;

--角色赋予用户

grant role role_name to user user_name

--回收角色权限

revoke select on database db_name from role role_name;

revoke select on [table] t_name from role role_name;

--查看某个用户所有角色

show role grant user user_name;

6.31 Hive 中的压缩格式TextFile、SequenceFile、RCfile 、ORCfile各有什么区别?

1. TextFile

默认格式,存储方式为行存储,数据不做压缩,磁盘开销大,数据解析开销大。 可结合Gzip、Bzip2使用(系统

自动检查,执行查询时自动解压),但使用这种方式,压缩后的文件不支持split,Hive不会对数据进行切分,从而无

法对数据进行并行操作。并且在反序列化过程中,必须逐个字符判断是不是分隔符和行结束符,因此反序列化开销

会比SequenceFile高几十倍。

2. SequenceFile

SequenceFile是Hadoop API提供的一种二进制文件支持,,存储方式为行存储,其具有使用方便、可分割、

可压缩的特点。

SequenceFile支持三种压缩选择:NONE,RECORD,BLOCK。Record压缩率低,一般建议使用BLOCK压缩。

优势是文件和hadoop api中的MapFile是相互兼容的

3. RCFile

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 73 页
存储方式:数据按行分块,每块按列存储。结合了行存储和列存储的优点:

首先,RCFile 保证同一行的数据位于同一节点,因此元组重构的开销很低;

其次,像列存储一样,RCFile 能够利用列维度的数据压缩,并且能跳过不必要的列读取;

RCFile的一个行组包括三个部分:

第一部分是行组头部的【同步标识】,主要用于分隔 hdfs 块中的两个连续行组

第二部分是行组的【元数据头部】,用于存储行组单元的信息,包括行组中的记录数、每个列的字节数、列中

每个域的字节数

第三部分是【表格数据段】,即实际的列存储数据。在该部分中,同一列的所有域顺序存储。

从图可以看出,首先存储了列 A 的所有域,然后存储列 B 的所有域等。

数据追加:RCFile 不支持任意方式的数据写操作,仅提供一种追加接口,这是因为底层的 HDFS当前仅仅支

持数据追加写文件尾部。

行组大小:行组变大有助于提高数据压缩的效率,但是可能会损害数据的读取性能,因为这样增加了 Lazy 解

压性能的消耗。而且行组变大会占用更多的内存,这会影响并发执行的其他MR作业。 考虑到存储空间和查询效率

两个方面,Facebook 选择 4MB 作为默认的行组大小,当然也允许用户自行选择参数进行配置。

4. ORCFile

存储方式:数据按行分块 每块按照列存储。

压缩快 快速列存取。

效率比rcfile高,是rcfile的改良版本。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 74 页
以下为RCFile、TextFile、SequenceFile三种文件的存储情况:

[hadoop@master ~]$ hadoop dfs -dus /user/Hive/warehouse/*


hdfs://master :9000/user/Hive/warehouse/hbase_table_1 0
hdfs://master :9000/user/Hive/warehouse/hbase_table_2 0
hdfs://master :9000/user/Hive/warehouse/orcfile_table 0
hdfs://master :9000/user/Hive/warehouse/rcfile_table 102638073
hdfs://master :9000/user/Hive/warehouse/seqfile_table 112497695
hdfs://master :9000/user/Hive/warehouse/testfile_table 536799616
hdfs://master :9000/user/Hive/warehouse/textfile_table 107308067
[hadoop@singlehadoop ~]$ hadoop dfs -ls /user/Hive/warehouse/*/
-rw-r--r-- 2 hadoop supergroup 51328177 2021-03-20 00:42 /user/Hive/warehouse/rcfile_table/000000_0
-rw-r--r-- 2 hadoop supergroup 51309896 2021-03-20 00:43 /user/Hive/warehouse/rcfile_table/000001_0
-rw-r--r-- 2 hadoop supergroup 56263711 2021-03-20 01:20 /user/Hive/warehouse/seqfile_table/000000_0
-rw-r--r-- 2 hadoop supergroup 56233984 2021-03-20 01:21 /user/Hive/warehouse/seqfile_table/000001_0
-rw-r--r-- 2 hadoop supergroup 536799616 2021-03-19 23:15 /user/Hive/warehouse/testfile_table/weibo.txt
-rw-r--r-- 2 hadoop supergroup 53659758 2021-03-19 23:24 /user/Hive/warehouse/textfile_table/000000_0.gz
-rw-r--r-- 2 hadoop supergroup 53648309 2021-03-19 23:26 /user/Hive/warehouse/textfile_table/000001_1.gz

总结:相比TEXTFILE和SEQUENCEFILE,RCFILE由于列式存储方式,数据加载时性能消耗较大,但是具有较好

的压缩比和查询响应。

数据仓库的特点是一次写入、多次读取,因此,整体来看,RCFILE相比其余两种格式具有较明显的优势。

6.32 Hive join过程中大表小表的放置顺序?

将最大的表放置在JOIN语句的最右边,或者直接使用/*+ streamtable(table_name) */指出。

在编写带有 join 操作的代码语句时,应该将条目少的表/子查询放在 Join 操作符的左边。因为在 Reduce 阶

段,位于 Join 操作符左边的表的内容会被加载进内存,载入条目较少的表可以有效减少 OOM(out of memory)

即内存溢出。所以对于同一个 key 来说,对应的 value 值小的放前,大的放后,这便是“小表放前”原则。若一条

语句中有多个 Join,依据 Join 的条件相同与否,有不同的处理方法。

6.33 Hive的两张表关联,使用MapReduce怎么实现?

如果其中有一张表为小表,直接使用map端join的方式(map端加载小表)进行聚合。

如果两张都是大表,那么采用联合key,联合key的第一个组成部分是join on中的公共字段,第二部分是一个flag,

0代表表A,1代表表B,由此让Reduce区分客户信息和订单信息;在Mapper中同时处理两张表的信息,将join on

公共字段相同的数据划分到同一个分区中,进而传递到一个Reduce中,然后在Reduce中实现聚合。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 75 页
6.34 Hive中使用什么代替in查询?

在Hive 0.13版本之前,通过left outer join实现SQL中的in查询,0.13版本之后,Hive已经支持in查询。

6.35 所有的Hive任务都会有MapReduce的执行吗?

不是,从Hive0.10.0版本开始,对于简单的不需要聚合的类似SELECT <col> from <table> LIMIT n语句,不需

要起MapReduce job,直接通过Fetch task获取数据。

6.36 Hive的函数:UDF、UDAF、UDTF的区别?

UDF: 单行进入,单行输出

UDAF: 多行进入,单行输出

UDTF: 单行输入,多行输出

(1)UDF(User-Defined-Function)

一进一出

(2)UDAF(User-Defined Aggregation Function)

聚集函数,多进一出

类似于:count/max/min

(3)UDTF(User-Defined Table-Generating Functions)

一进多出

如lateral view explore()

6.37 Hive自定义UDF函数的流程?

1)写一个类继承(org.apache.hadoop.hive.ql.)UDF类;

2)覆盖方法evaluate();

3)打JAR包;

4)通过hive命令将JAR添加到Hive的类路径:

hive> add jar /home/ubuntu/ToDate.jar;

5)注册函数:

hive> create temporary function xxx as 'XXX';

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 76 页
6)使用函数;

7)[可选] drop临时函数;

6.38 Hive优化措施

6.38.1 Fetch抓取

Fetch抓取是指,Hive中对某些情况的查询可以不必使用MapReduce计算。例如:SELECT * FROM employees;

在这种情况下,Hive可以简单地读取employee对应的存储目录下的文件,然后输出查询结果到控制台。

在hive-default.xml.template文件中hive.fetch.task.conversion默认是more,老版本hive默认是minimal,该属

性修改为more以后,在全局查找、字段查找、limit查找等都不走mapreduce。

<property>

<name>hive.fetch.task.conversion</name>

<value>more</value>

<description>

Expects one of [none, minimal, more].

Some select queries can be converted to single FETCH task minimizing latency.

Currently the query should be single sourced not having any subquery and should not have

any aggregations or distincts (which incurs RS), lateral views and joins.

0. none : disable hive.fetch.task.conversion

1. minimal : SELECT STAR, FILTER on partition columns, LIMIT only

2. more : SELECT, FILTER, LIMIT only (support TABLESAMPLE and virtual columns)

</description>

</property>

案例实操:

1)把hive.fetch.task.conversion设置成none,然后执行查询语句,都会执行mapreduce程序。

hive (default)> set hive.fetch.task.conversion=none;

hive (default)> select * from emp;

hive (default)> select ename from emp;

hive (default)> select ename from emp limit 3;

2)把hive.fetch.task.conversion设置成more,然后执行查询语句,如下查询方式都不会执行mapreduce程序。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 77 页
hive (default)> set hive.fetch.task.conversion=more;

hive (default)> select * from emp;

hive (default)> select ename from emp;

hive (default)> select ename from emp limit 3;

6.38.2 本地模式

大多数的Hadoop Job是需要Hadoop提供的完整的可扩展性来处理大数据集的。不过,有时Hive的输入数据

量是非常小的。在这种情况下,为查询触发执行任务时消耗可能会比实际job的执行时间要多的多。对于大多数这

种情况,Hive可以通过本地模式在单台机器上处理所有的任务。对于小数据集,执行时间可以明显被缩短。

用户可以通过设置hive.exec.mode.local.auto的值为true,来让Hive在适当的时候自动启动这个优化。

set hive.exec.mode.local.auto=true; //开启本地mr

//设置local mr的最大输入数据量,当输入数据量小于这个值时采用local mr的方式,默认为

134217728,即128M

set hive.exec.mode.local.auto.inputbytes.max=50000000;

//设置local mr的最大输入文件个数,当输入文件个数小于这个值时采用local mr的方式,默认为4

set hive.exec.mode.local.auto.input.files.max=10;

案例实操:

1)开启本地模式,并执行查询语句

hive (default)> set hive.exec.mode.local.auto=true;

hive (default)> select * from emp cluster by deptno;

Time taken: 1.328 seconds, Fetched: 14 row(s)

2)关闭本地模式,并执行查询语句

hive (default)> set hive.exec.mode.local.auto=false;

hive (default)> select * from emp cluster by deptno;

Time taken: 20.09 seconds, Fetched: 14 row(s)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 78 页
6.38.3 表的优化

6.38.3.1 小表、大表Join

将key相对分散,并且数据量小的表放在join的左边,这样可以有效减少内存溢出错误发生的几率;再进一步,

可以使用Group让小的维度表(1000条以下的记录条数)先进内存。在map端完成reduce。

实际测试发现:新版的hive已经对小表JOIN大表和大表JOIN小表进行了优化。小表放在左边和右边已经没有

明显区别。

案例实操

(0)需求:测试大表JOIN小表和小表JOIN大表的效率

(1)建大表、小表和JOIN后表的语句

create table bigtable(id bigint, time bigint, uid string, keyword string, url_rank int, click_num

int, click_url string) row format delimited fields terminated by '\t';

create table smalltable(id bigint, time bigint, uid string, keyword string, url_rank int, click_num

int, click_url string) row format delimited fields terminated by '\t';

create table jointable(id bigint, time bigint, uid string, keyword string, url_rank int, click_num

int, click_url string) row format delimited fields terminated by '\t';

(2)分别向大表和小表中导入数据

hive (default)> load data local inpath '/opt/module/datas/bigtable' into table bigtable;

hive (default)>load data local inpath '/opt/module/datas/smalltable' into table smalltable;

(3)关闭mapjoin功能(默认是打开的)

set hive.auto.convert.join = false;

(4)执行小表JOIN大表语句

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 79 页
insert overwrite table jointable

select b.id, b.time, b.uid, b.keyword, b.url_rank, b.click_num, b.click_url

from smalltable s

left join bigtable b

on b.id = s.id;

Time taken: 35.921 seconds

(5)执行大表JOIN小表语句

insert overwrite table jointable

select b.id, b.time, b.uid, b.keyword, b.url_rank, b.click_num, b.click_url

from bigtable b

left join smalltable s

on s.id = b.id;

Time taken: 34.196 seconds

6.38.3.2 大表Join大表

1)空KEY过滤

有时join超时是因为某些key对应的数据太多,而相同key对应的数据都会发送到相同的reducer上,从而导致

内存不够。此时我们应该仔细分析这些异常的key,很多情况下,这些key对应的数据是异常数据,我们需要在SQL

语句中进行过滤。例如key对应的字段为空,操作如下:

案例实操

(1)配置历史服务器

配置mapred-site.xml

<property>

<name>mapreduce.jobhistory.address</name>

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 80 页
<value>hadoop102:10020</value>

</property>

<property>

<name>mapreduce.jobhistory.webapp.address</name>

<value>hadoop102:19888</value>

</property>

启动历史服务器

sbin/mr-jobhistory-daemon.sh start historyserver

查看jobhistory

http://192.168.1.102:19888/jobhistory

(2)创建原始数据表、空id表、合并后数据表

create table ori(id bigint, time bigint, uid string, keyword string, url_rank int, click_num int,

click_url string) row format delimited fields terminated by '\t';

create table nullidtable(id bigint, time bigint, uid string, keyword string, url_rank int, click_num

int, click_url string) row format delimited fields terminated by '\t';

create table jointable(id bigint, time bigint, uid string, keyword string, url_rank int, click_num

int, click_url string) row format delimited fields terminated by '\t';

(3)分别加载原始数据和空id数据到对应表中

hive (default)> load data local inpath '/opt/module/datas/ori' into table ori;

hive (default)> load data local inpath '/opt/module/datas/nullid' into table nullidtable;

(4)测试不过滤空id

hive (default)> insert overwrite table jointable

select n.* from nullidtable n left join ori o on n.id = o.id;

Time taken: 42.038 seconds

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 81 页
(5)测试过滤空id

hive (default)> insert overwrite table jointable

select n.* from (select * from nullidtable where id is not null ) n left join ori o on n.id = o.id;

Time taken: 31.725 seconds

2)空key转换

有时虽然某个key为空对应的数据很多,但是相应的数据不是异常数据,必须要包含在join的结果中,此时我

们可以表a中key为空的字段赋一个随机的值,使得数据随机均匀地分不到不同的reducer上。例如:

案例实操:

不随机分布空null值:

(1)设置5个reduce个数

set mapreduce.job.reduces = 5;

(2)JOIN两张表

insert overwrite table jointable

select n.* from nullidtable n left join ori b on n.id = b.id;

结果:可以看出来,出现了数据倾斜,某些reducer的资源消耗远大于其他reducer。

随机分布空null值

(1)设置5个reduce个数

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 82 页
set mapreduce.job.reduces = 5;

(2)JOIN两张表

insert overwrite table jointable

select n.* from nullidtable n full join ori o on

case when n.id is null then concat('hive', rand()) else n.id end = o.id;

结果:可以看出来,消除了数据倾斜,负载均衡reducer的资源消耗

6.38.3.3 MapJoin

如果不指定MapJoin或者不符合MapJoin的条件,那么Hive解析器会将Join操作转换成Common Join,即:在

Reduce阶段完成join。容易发生数据倾斜。可以用MapJoin把小表全部加载到内存在map端进行join,避免reducer

处理。

1)开启MapJoin参数设置:

(1)设置自动选择Mapjoin

set hive.auto.convert.join = true; 默认为true

(2)大表小表的阀值设置(默认25M一下认为是小表):

set hive.mapjoin.smalltable.filesize=25000000;

2)MapJoin工作机制

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 83 页
首先是Task A,它是一个Local Task(在客户端本地执行的Task),负责扫描小表b的数据,将其转换成一个

HashTable的数据结构,并写入本地的文件中,之后将该文件加载到DistributeCache中。

接下来是Task B,该任务是一个没有Reduce的MR,启动MapTasks扫描大表a,在Map阶段,根据a的每一条记

录去和DistributeCache中b表对应的HashTable关联,并直接输出结果。

由于MapJoin没有Reduce,所以由Map直接输出结果文件,有多少个Map Task,就有多少个结果文件。

案例实操:

(1)开启Mapjoin功能

set hive.auto.convert.join = true; 默认为true

(2)执行小表JOIN大表语句

insert overwrite table jointable

select b.id, b.time, b.uid, b.keyword, b.url_rank, b.click_num, b.click_url

from smalltable s

join bigtable b

on s.id = b.id;

Time taken: 24.594 seconds

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 84 页
(3)执行大表JOIN小表语句

insert overwrite table jointable

select b.id, b.time, b.uid, b.keyword, b.url_rank, b.click_num, b.click_url

from bigtable b

join smalltable s

on s.id = b.id;

Time taken: 24.315 seconds

6.38.3.4 Group By

默认情况下,Map阶段同一Key数据分发给一个reduce,当一个key数据过大时就倾斜了。

并不是所有的聚合操作都需要在Reduce端完成,很多聚合操作都可以先在Map端进行部分聚合,最后在Reduce

端得出最终结果。

1)开启Map端聚合参数设置

(1)是否在Map端进行聚合,默认为True

hive.map.aggr = true

(2)在Map端进行聚合操作的条目数目

hive.groupby.mapaggr.checkinterval = 100000

(3)有数据倾斜的时候进行负载均衡(默认是false)

hive.groupby.skewindata = true

当选项设定为 true,生成的查询计划会有两个MR Job。第一个MR Job中,Map的输出结果会随机分布到Reduce

中,每个Reduce做部分聚合操作,并输出结果,这样处理的结果是相同的Group By Key有可能被分发到不同的

Reduce中,从而达到负载均衡的目的;第二个MR Job再根据预处理的数据结果按照Group By Key分布到Reduce

中(这个过程可以保证相同的Group By Key被分布到同一个Reduce中),最后完成最终的聚合操作。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 85 页
6.38.3.5 Count(Distinct) 去重统计

数据量小的时候无所谓,数据量大的情况下,由于COUNT DISTINCT操作需要用一个Reduce Task来完成,这

一个Reduce需要处理的数据量太大,就会导致整个Job很难完成,一般COUNT DISTINCT使用先GROUP BY再

COUNT的方式替换:

案例实操

(1)创建一张大表

hive (default)> create table bigtable(id bigint, time bigint, uid string, keyword string, url_rank

int, click_num int, click_url string) row format delimited fields terminated by '\t';

(2)加载数据

hive (default)> load data local inpath '/opt/module/datas/bigtable' into table bigtable;

(3)设置5个reduce个数

set mapreduce.job.reduces = 5;

(4)执行去重id查询

hive (default)> select count(distinct id) from bigtable;

Stage-Stage-1: Map: 1 Reduce: 1 Cumulative CPU: 7.12 sec HDFS Read: 120741990 HDFS Write:

7 SUCCESS

Total MapReduce CPU Time Spent: 7 seconds 120 msec

OK

c0

100001

Time taken: 23.607 seconds, Fetched: 1 row(s)

Time taken: 34.941 seconds, Fetched: 1 row(s)

(5)采用GROUP by去重id

hive (default)> select count(id) from (select id from bigtable group by id) a;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 86 页
Stage-Stage-1: Map: 1 Reduce: 5 Cumulative CPU: 17.53 sec HDFS Read: 120752703 HDFS

Write: 580 SUCCESS

Stage-Stage-2: Map: 3 Reduce: 1 Cumulative CPU: 4.29 sec HDFS Read: 9409 HDFS Write: 7

SUCCESS

Total MapReduce CPU Time Spent: 21 seconds 820 msec

OK

_c0

100001

Time taken: 50.795 seconds, Fetched: 1 row(s)

虽然会多用一个Job来完成,但在数据量大的情况下,这个绝对是值得的。

6.38.3.6 笛卡尔积

尽量避免笛卡尔积,join的时候不加on条件,或者无效的on条件,Hive只能使用1个reducer来完成笛卡尔积

6.38.3.7 行列过滤

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

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

后再过滤,比如:

案例实操:

(1)测试先关联两张表,再用where条件过滤

hive (default)> select o.id from bigtable b

join ori o on o.id = b.id

where o.id <= 10;

Time taken: 34.406 seconds, Fetched: 100 row(s)

Time taken: 26.043 seconds, Fetched: 100 row(s)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 87 页
(2)通过子查询后,再关联表

hive (default)> select b.id from bigtable b

join (select id from ori where id <= 10 ) o on b.id = o.id;

Time taken: 30.058 seconds, Fetched: 100 row(s)

Time taken: 29.106 seconds, Fetched: 100 row(s)

6.38.3.8 动态分区调整

关系型数据库中,对分区表Insert数据时候,数据库自动会根据分区字段的值,将数据插入到相应的分区中,

Hive中也提供了类似的机制,即动态分区(Dynamic Partition),只不过,使用Hive的动态分区,需要进行相应的配

置。

1)开启动态分区参数设置

(1)开启动态分区功能(默认true,开启)

hive.exec.dynamic.partition=true

(2)设置为非严格模式(动态分区的模式,默认strict,表示必须指定至少一个分区为静态分区,nonstrict模

式表示允许所有的分区字段都可以使用动态分区。)

hive.exec.dynamic.partition.mode=nonstrict

(3)在所有执行MR的节点上,最大一共可以创建多少个动态分区。

hive.exec.max.dynamic.partitions=1000

(4)在每个执行MR的节点上,最大可以创建多少个动态分区。该参数需要根据实际的数据来设定。比如:

源数据中包含了一年的数据,即day字段有365个值,那么该参数就需要设置成大于365,如果使用默认值100,则

会报错。

hive.exec.max.dynamic.partitions.pernode=100

(5)整个MR Job中,最大可以创建多少个HDFS文件。

hive.exec.max.created.files=100000

(6)当有空分区生成时,是否抛出异常。一般不需要设置。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 88 页
hive.error.on.empty.partition=false

2)案例实操

需求:将ori中的数据按照时间(如:20111230000008),插入到目标表ori_partitioned_target的相应分区中。

(1)创建分区表

create table ori_partitioned(id bigint, time bigint, uid string, keyword string, url_rank int,

click_num int, click_url string)

partitioned by (p_time bigint)

row format delimited fields terminated by '\t';

(2)加载数据到分区表中

hive (default)> load data local inpath '/opt/module/datas/ds1' into table ori_partitioned

partition(p_time='20111230000010') ;

hive (default)> load data local inpath '/opt/module/datas/ds2' into table ori_partitioned

partition(p_time='20111230000011') ;

(3)创建目标分区表

create table ori_partitioned_target(id bigint, time bigint, uid string, keyword string, url_rank int,

click_num int, click_url string) PARTITIONED BY (p_time STRING) row format delimited fields

terminated by '\t';

(4)设置动态分区

set hive.exec.dynamic.partition = true;

set hive.exec.dynamic.partition.mode = nonstrict;

set hive.exec.max.dynamic.partitions = 1000;

set hive.exec.max.dynamic.partitions.pernode = 100;

set hive.exec.max.created.files = 100000;

set hive.error.on.empty.partition = false;

hive (default)> insert overwrite table ori_partitioned_target partition (p_time)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 89 页
select id, time, uid, keyword, url_rank, click_num, click_url, p_time from ori_partitioned;

(5)查看目标分区表的分区情况

hive (default)> show partitions ori_partitioned_target;

6.38.3.9 分桶

6.38.3.10 分区

6.38.4 数据倾斜

6.38.4.1 Map数

1)通常情况下,作业会通过input的目录产生一个或者多个map任务。

主要的决定因素有:input的文件总个数,input的文件大小,集群设置的文件块大小。

2)是不是map数越多越好?

答案是否定的。如果一个任务有很多小文件(远远小于块大小128m),则每个小文件也会被当做一个块,用

一个map任务来完成,而一个map任务启动和初始化的时间远远大于逻辑处理的时间,就会造成很大的资源浪费。

而且,同时可执行的map数是受限的。

3)是不是保证每个map处理接近128m的文件块,就高枕无忧了?

答案也是不一定。比如有一个127m的文件,正常会用一个map去完成,但这个文件只有一个或者两个小字段,

却有几千万的记录,如果map处理的逻辑比较复杂,用一个map任务去做,肯定也比较耗时。

针对上面的问题2和3,我们需要采取两种方式来解决:即减少map数和增加map数;

6.38.4.2 小文件进行合并

在map执行前合并小文件,减少map数:CombineHiveInputFormat具有对小文件进行合并的功能(系统默认

的格式)。HiveInputFormat没有对小文件合并功能。

set hive.input.format= org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 90 页
6.38.4.3 复杂文件增加Map数

当input的文件都很大,任务逻辑复杂,map执行非常慢的时候,可以考虑增加Map数,来使得每个map处理

的数据量减少,从而提高任务的执行效率。

增加map的方法为:根据

computeSliteSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M公式,调整maxSize最大值。

让maxSize最大值低于blocksize就可以增加map的个数。

案例实操:

(1)执行查询

hive (default)> select count(*) from emp;

Hadoop job information for Stage-1: number of mappers: 1; number of reducers: 1

(2)设置最大切片值为100个字节

hive (default)> set mapreduce.input.fileinputformat.split.maxsize=100;

hive (default)> select count(*) from emp;

Hadoop job information for Stage-1: number of mappers: 6; number of reducers: 1

6.38.4.4 Reduce数

1)调整reduce个数方法一

(1)每个Reduce处理的数据量默认是256MB

hive.exec.reducers.bytes.per.reducer=256000000

(2)每个任务最大的reduce数,默认为1009

hive.exec.reducers.max=1009

(3)计算reducer数的公式

N=min(参数2,总输入数据量/参数1)

2)调整reduce个数方法二

在hadoop的mapred-default.xml文件中修改

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 91 页
设置每个job的Reduce个数

set mapreduce.job.reduces = 15;

3)reduce个数并不是越多越好

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

2)另外,有多少个reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下

一个任务的输入,则也会出现小文件过多的问题;

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

理数据量大小要合适;

6.38.4.5 并行执行

Hive会将一个查询转化成一个或者多个阶段。这样的阶段可以是MapReduce阶段、抽样阶段、合并阶段、limit

阶段。或者Hive执行过程中可能需要的其他阶段。默认情况下,Hive一次只会执行一个阶段。不过,某个特定的job

可能包含众多的阶段,而这些阶段可能并非完全互相依赖的,也就是说有些阶段是可以并行执行的,这样可能使得

整个job的执行时间缩短。不过,如果有更多的阶段可以并行执行,那么job可能就越快完成。

通过设置参数hive.exec.parallel值为true,就可以开启并发执行。不过,在共享集群中,需要注意下,如果job

中并行阶段增多,那么集群利用率就会增加。

set hive.exec.parallel=true; //打开任务并行执行

set hive.exec.parallel.thread.number=16; //同一个sql允许最大并行度,默认为8。

当然,得是在系统资源比较空闲的时候才有优势,否则,没资源,并行也起不来。

6.38.4.6 严格模式

Hive提供了一个严格模式,可以防止用户执行那些可能意向不到的不好的影响的查询。

通过设置属性hive.mapred.mode值为默认是非严格模式nonstrict 。开启严格模式需要修改

hive.mapred.mode值为strict,开启严格模式可以禁止3种类型的查询。

<property>

<name>hive.mapred.mode</name>

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 92 页
<value>strict</value>

<description>

The mode in which the Hive operations are being performed.

In strict mode, some risky queries are not allowed to run. They include:

Cartesian Product.

No partition being picked up for a query.

Comparing bigints and strings.

Comparing bigints and doubles.

Orderby without limit.

</description>

</property>

1)对于分区表,除非where语句中含有分区字段过滤条件来限制范围,否则不允许执行。换句话说,就是用户不允

许扫描所有分区。进行这个限制的原因是,通常分区表都拥有非常大的数据集,而且数据增加迅速。没有进行分区

限制的查询可能会消耗令人不可接受的巨大资源来处理这个表。

2)对于使用了order by语句的查询,要求必须使用limit语句。因为order by为了执行排序过程会将所有的结果数据

分发到同一个Reducer中进行处理,强制要求用户增加这个LIMIT语句可以防止Reducer额外执行很长一段时间。

3)限制笛卡尔积的查询。对关系型数据库非常了解的用户可能期望在执行JOIN查询的时候不使用ON语句而是使用

where语句,这样关系数据库的执行优化器就可以高效地将WHERE语句转化成那个ON语句。不幸的是,Hive并不

会执行这种优化,因此,如果表足够大,那么这个查询就会出现不可控的情况。

6.38.4.7 JVM重用

JVM重用是Hadoop调优参数的内容,其对Hive的性能具有非常大的影响,特别是对于很难避免小文件的场景

或task特别多的场景,这类场景大多数执行时间都很短。

Hadoop的默认配置通常是使用派生JVM来执行map和Reduce任务的。这时JVM的启动过程可能会造成相当大

的开销,尤其是执行的job包含有成百上千task任务的情况。JVM重用可以使得JVM实例在同一个job中重新使用N次。

N的值可以在Hadoop的mapred-site.xml文件中进行配置。通常在10-20之间,具体多少需要根据具体业务场景测试

得出。

<property>

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

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 93 页
<value>10</value>

<description>How many tasks to run per jvm. If set to -1, there is

no limit.

</description>

</property>

这个功能的缺点是,开启JVM重用将一直占用使用到的task插槽,以便进行重用,直到任务完成后才能释放。

如果某个“不平衡的”job中有某几个reduce task执行的时间要比其他Reduce task消耗的时间多的多的话,那么保留

的插槽就会一直空闲着却无法被其他的job使用,直到所有的task都结束了才会释放。

6.38.4.8 推测执行

在分布式集群环境下,因为程序Bug(包括Hadoop本身的bug),负载不均衡或者资源分布不均等原因,会造

成同一个作业的多个任务之间运行速度不一致,有些任务的运行速度可能明显慢于其他任务(比如一个作业的某个

任务进度只有50%,而其他所有任务已经运行完毕),则这些任务会拖慢作业的整体执行进度。为了避免这种情况

发生,Hadoop采用了推测执行(Speculative Execution)机制,它根据一定的法则推测出“拖后腿”的任务,并为这

样的任务启动一个备份任务,让该任务与原始任务同时处理同一份数据,并最终选用最先成功运行完成任务的计算

结果作为最终结果。

设置开启推测执行参数:Hadoop的mapred-site.xml文件中进行配置

<property>

<name>mapreduce.map.speculative</name>

<value>true</value>

<description>If true, then multiple instances of some map tasks

may be executed in parallel.</description>

</property>

<property>

<name>mapreduce.reduce.speculative</name>

<value>true</value>

<description>If true, then multiple instances of some reduce tasks

may be executed in parallel.</description>

</property>

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 94 页
不过hive本身也提供了配置项来控制reduce-side的推测执行:

<property>

<name>hive.mapred.reduce.tasks.speculative.execution</name>

<value>true</value>

<description>Whether speculative execution for reducers should be turned on. </description>

</property>

关于调优这些推测执行变量,还很难给一个具体的建议。如果用户对于运行时的偏差非常敏感的话,那么可以

将这些功能关闭掉。如果用户因为输入数据量很大而需要执行长时间的map或者Reduce task的话,那么启动推测

执行造成的浪费是非常巨大大。

6.38.4.9 EXPLAIN(执行计划)

1)基本语法

EXPLAIN [EXTENDED | DEPENDENCY | AUTHORIZATION] query

2)案例实操

(1)查看下面这条语句的执行计划

hive (default)> explain select * from emp;

hive (default)> explain select deptno, avg(sal) avg_sal from emp group by deptno;

(2)查看详细执行计划

hive (default)> explain extended select * from emp;

hive (default)> explain extended select deptno, avg(sal) avg_sal from emp group by deptno;

6.38.4.10 Hive小文件合并?

此 部 分设 置 , 要 根 据硬 件 内 存 来 进行 调 整 , 个 人 电脑 配 置 较 低 , 不 建议 修 改 。

hive.merge.mapfiles

是 否 开启 合 并 Map端 小 文件 , 在 Map-only的 任 务结 束 时 合 并 小文 件 , true是 打 开。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 95 页
hive.merge.mapredfiles

是 否 开启 合 并 Reduce端 小 文件 , 在 map-reduce作 业 结束 时 合 并 小 文件 。 true是 打 开。

hive.merge.size.per.task

合 并 后 MR输 出 文件 的 大 小 , 默认 为 256M。

hive.merge.smallfiles.avgsize

当 输 出 文 件 的 平 均 大 小 小 于 此 设 置 值 时 , 启 动 一 个 独 立 的 map-reduce任 务 进 行 文 件
merge, 默 认值 为 16M。

6.38.4.11 压缩配置

(1)Map输出压缩

除了创建表时指定保存数据时压缩,在查询分析过程中,Map的输出也可以进行压缩。由于
map任务的输出需要写到磁盘并通过网络传输到reducer节点,所以通过使用LZO、LZ4或者Snappy
这样的快速压缩方式,是可以获得性能提升的,因为需要传输的数据减少了。

MapReduce配置项:

 mapreduce.map.output.compress

设置是否启动map输出的压缩机制,默认为false。在需要减少网络传输的时候,可以设置为
true。

(2)Reduce结果压缩

是否对任务输出产生的结果进行压缩,默认值false。对传输数据进行压缩,既可以减少文件的
存储空间,又可以加快数据在网络不同节点之间的传输速度。

(3)Hive的Map-Reduce之间是否进行压缩

控制 Hive 在多个 map-reduce 作业之间生成的中间 files 是否被压缩。压缩编解码器和其


他选项由上面Job的变量mapreduce.output.fileoutputformat.compress.*确定。

set hive.exec.compress.intermediate=true;

(4)Hive查询最终结果压缩

控制是否压缩查询的最终输出(到 local/hdfs 文件或 Hive table)。压缩编解码器和其他选项由


上面Job中的变量mapreduce.output.fileoutputformat.compress.*确定。

set hive.exec.compress.output=true;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 96 页
6.39 拉链表使用

拉链表就是之前我们讲过的SCD2,它的优点是即满足了反应数据的历史状态,又能在最大程
度上节省存储。

拉链表的实现需要在原始字段基础上增加两个新字段:

 start_time(表示该条记录的生命周期开始时间——周期快照时的状态)

 end_time(该条记录的生命周期结束时间)

拉链表实现步骤:

1. 建立增量数据临时表update;

2. 抽取昨日增量数据到update表;

3. 建立临时合并表tmp;

4. 合并昨日增量数据与历史数据,将重复的旧数据end_time更新为昨日,也就是从今天起不
再生效;新数据end_time改为’9999-12-31’,也就是当前有效;合并后的数据写入到tmp表;

5. 将临时表的数据,覆盖到拉链表中;

6. 下次抽取需要重建update表和tmp表。

查询拉链表数据时,可以通过start_time和end_time查询出快照数据。

6.40 Hive为什么要分桶?

(1)获得更高的查询处理效率

在分区数量过于庞大以至于可能导致文件系统崩溃时,或数据集找不到合理的分区字段时,我
们就需要使用分桶来解决问题了。

分区中的数据可以被进一步拆分成桶,不同于分区对列直接进行拆分,桶往往使用列的哈希值
对数据打散,并分发到各个不同的桶中从而完成数据的分桶过程。

注意,hive使用对分桶所用的值进行hash,并用hash结果除以桶的个数做取余运算的方式来分
桶,保证了每个桶中都有数据,但每个桶中的数据条数不一定相等。

如果另外一个表也按照同样的规则分成了一个个小文件。两个表join的时候,就不必要扫描整
个表,只需要匹配相同分桶的数据即可,从而提升效率。

在数据量足够大的情况下,分桶比分区有更高的查询效率。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 97 页
(2)数据采样

在真实的大数据分析过程中,由于数据量较大,开发和自测的过程比较慢,严重影响系统的开
发进度。此时就可以使用分桶来进行数据采样。采样使用的是一个具有代表性的查询结果而不是全
部结果,通过对采样数据的分析,来达到快速开发和自测的目的,节省大量的研发成本。

(3)分桶和分区的区别

1. 分桶和分区两者不干扰,可以把分区表进一步分桶;

2. 分桶对数据的处理比分区更加细粒度化:分区针对的是数据的存储路径;分桶针对的
是数据文件;

3. 分桶是按照列的哈希函数进行分割的,相对比较平均;而分区是按照列的值来进行分
割的,容易造成数据倾斜。

(4) 文本数据处理

注意:对于分桶表,不能使用load data的方式进行数据插入操作,因为load data导入的数据


不会有分桶结构。

如何避免针对桶表使用load data插入数据的误操作呢?

--限制对桶表进行load操作

set hive.strict.checks.bucketing = true;

也可以在CM的hive配置项中修改此配置,当针对桶表执行load data操作时会报错。

那么对于文本数据如何处理呢?

(1. 先创建临时表,通过load data将txt文本导入临时表。

--创建临时表

create table temp_buck(id int, name string)

row format delimited fields terminated by '\t';

--导入数据

load data local inpath '/tools/test_buck.txt' into table temp_buck;

(2. 使用insert select语句间接的把数据从临时表导入到分桶表。

--启用桶表

set hive.enforce.bucketing=true;

--限制对桶表进行load操作

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 98 页
set hive.strict.checks.bucketing = true;

--insert select

insert into table test_buck select id, name from temp_buck;

--分桶成功

6.41 如何理解 Map Join

MapJoin顾名思义,就是在Map阶段进行表之间的连接。而不需要进入到Reduce阶段才进行连
接。这样就节省了在Shuffle阶段时要进行的大量数据传输。从而起到了优化作业的作用。

要使MapJoin能够顺利进行,那就必须满足这样的条件:除了一份表的数据分布在不同的Map
中外,其他连接的表的数据必须在每个Map中有完整的拷贝。

所以并不是所有的场景都适合用MapJoin。它通常会用在如下的一些情景:在二个要连接的表
中,有一个很大,有一个很小,这个小表可以存放在内存中而不影响性能。

这样我们就把小表文件复制到每一个Map任务的本地,再让Map把文件读到内存中待用。

在Hive v0.7之前,需要使用hint提示 /*+ mapjoin(table) */才会执行MapJoin。Hive v0.7之后


的版本已经不需要给出MapJoin的指示就进行优化。现在可以通过如下配置参数来进行控制:

set hive.auto.convert.join=true;

Hive还提供另外一个参数--表文件的大小作为开启和关闭MapJoin的阈值:

--旧版本为hive.mapjoin.smalltable.filesize

set hive.auto.convert.join.noconditionaltask.size=512000000

注意,如果hive.auto.convert.join是关闭的,则本参数不起作用。否则,如果参与连接的N个
表(或分区)中的N-1个 的总大小小于512MB,则直接将连接转为Map连接。默认值为20MB。

MapJoin的使用场景:

1. 关联操作中有一张表非常小 2. 不等值的链接操作

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 99 页
(1)大小表关联

select f.a,f.b from A t join B f on ( f.a=t.a and f.ftime=20110802)

该语句中B表有30亿行记录,A表只有100行记录,而且B表中数据倾斜特别严重,有一个key
上有15亿行记录,在运行过程中特别的慢,而且在reduece的过程中遇到执行时间过长或者内存不
够的问题。

MAPJION会把小表全部读入内存中,在map阶段直接拿另外一个表的数据和内存中表数据做
匹配,由于在map时进行了join操作,省去了reduce运行的效率会高很多。

这样就不会由于数据倾斜导致某个reduce上落数据太多而失败。于是原来的sql可以通过使用
hint的方式指定join时使用mapjoin。

select /*+ mapjoin(A)*/ f.a,f.b from A t join B f on ( f.a=t.a and f.ftime=20110802)

在实际使用中,只要根据业务调整小表的阈值即可,hive会自动帮我们完成mapjoin,提高执
行的效率。

(2)不等连接

mapjoin还有一个很大的好处是能够进行不等连接的join操作,如果将不等条件写在where中,
那么mapreduce过程中会进行笛卡尔积,运行效率特别低,如果使用mapjoin操作,在map的过程
中就完成了不等值的join操作,效率会高很多。

select A.a ,A.b from A join B where A.a>B.a

(3)使用限制

LEFT OUTER JOIN的左表必须是大表;

RIGHT OUTER JOIN的右表必须是大表;

INNER JOIN左表或右表均可以作为大表;

FULL OUTER JOIN不能使用MAPJOIN;

MAPJOIN支持小表为子查询;

使用MAPJOIN时需要引用小表或是子查询时,需要引用别名;

在MAPJOIN中,可以使用不等值连接或者使用OR连接多个条件;

在MAPJOIN中最多支持指定6张小表,否则报语法错误;

如果使用MAPJOIN,则所有小表占用的内存总和不得超过设置的内存(解压后的逻辑数据量)。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 100 页


6.42 如何理解Bucket-MapJoin

(1)作用

两个表join的时候,小表不足以放到内存中,但是又想用map side join这个时候就要用到bucket


Map join。其方法是两个join表在join key上都做hash bucket,并且把你打算复制的那个(相对)
小表的bucket数设置为大表的倍数。这样数据就会按照key join,做hash bucket。小表依然复制到
所有节点,Map join的时候,小表的每一组bucket加载成hashtable,与对应的一个大表bucket做
局部join,这样每次只需要加载部分hashtable就可以了。

(2)条件

1) set hive.optimize.bucketmapjoin = true;


2) 一个表的bucket数是另一个表bucket数的整数倍
3) bucket列 == join列
4) 必须是应用在map join的场景中

注意:如果表不是bucket的,则只是做普通join。

6.43 如何理解SMB Join

全称Sort Merge Bucket Join。

 作用

大表对小表应该使用MapJoin来进行优化,但是如果是大表对大表,如果进行shuffle,那就非
常可怕,第一个慢不用说,第二个容易出异常,此时就可以使用SMB Join来提高性能。SMB Join
基于bucket-mapjoin的有序bucket,可实现在map端完成join操作,可以有效地减少或避免shuffle
的数据量。SMB join的条件和Map join类似但又不同。

 条件

bucket mapjoin SMB join

set hive.optimize.bucketmapjoin = true;

set hive.auto.convert.sortmerge.join=true;
set hive.optimize.bucketmapjoin = true;
set hive.optimize.bucketmapjoin.sortedmerge = true;

set hive.auto.convert.sortmerge.join.noconditionaltask=true;

一个表的bucket数是另一个表bucket数的整数倍 小表的bucket数=大表bucket数

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 101 页


bucket列 == join列 Bucket 列 == Join 列 == sort 列

必须是应用在map join的场景中 必须是应用在bucket mapjoin 的场景中

 注意事项

hive并不检查两个join的表是否已经做好bucket且sorted,需要用户自己去保证join的表数据
sorted,否则可能数据不正确。

有两个办法:

1)hive.enforce.sorting 设置为 true。开启强制排序时,插数据到表中会进行强制排序,默认


false。

2)插入数据时通过在sql中用distributed c1 sort by c1 或者 cluster by c1

另外,表创建时必须是CLUSTERED且SORTED,如下:

create table test_smb_2(mid string,age_id string)

CLUSTERED BY(mid) SORTED BY(mid) INTO 500 BUCKETS;

综上,涉及到分桶表操作的齐全配置为:

--写入数据强制分桶

set hive.enforce.bucketing=true;

--写入数据强制排序

set hive.enforce.sorting=true;

--开启bucketmapjoin

set hive.optimize.bucketmapjoin = true;

--开启SMB Join

set hive.auto.convert.sortmerge.join=true;

set hive.auto.convert.sortmerge.join.noconditionaltask=true;

开启MapJoin的配置(hive.auto.convert.join和hive.auto.convert.join.noconditionaltask.size),
还有限制对桶表进行load操作(hive.strict.checks.bucketing)可以直接设置在hive的配置项中,无
需在sql中声明。

自动尝试SMB联接(hive.optimize.bucketmapjoin.sortedmerge)也可以在设置中进行提前配置。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 102 页


6.44 Hive数据倾斜

数据倾斜Skewin:如果数据量很大或者出现了数据倾斜比较严重的情况,如何来优化呢?

6.44.1 表连接数据倾斜(Join skewin)

(1)运行时优化

set hive.optimize.skewjoin=true;

默认关闭。

如果大表和大表进行join操作,则可采用skewjoin(倾斜关联)来开启对倾斜数据的优化。

skewjoin原理:

1. 对于skewjoin.key,在执行job时,将它们存入临时的HDFS目录,其它数据正常执行

2. 对倾斜数据开启map join操作(多个map并行处理),对非倾斜值采取普通join操作

3. 将倾斜数据集和非倾斜数据集进行合并Union操作。

开启skewin以后,究竟多大的数据才会被认为是倾斜了的数据呢?

set hive.skewjoin.key=100000;

默认值100000。

如果join的key对应的记录条数超过这个值,就认为这个key产生了数据倾斜,则会对其进行分
拆优化。

(2)编译时优化

上面的配置项其实应该理解为hive.optimize.skewjoin.runtime,也就是sql运行时来对偏斜信息
进行优化;除此之外还有另外一个配置:

set hive.optimize.skewjoin.compiletime=true;

默认关闭。

此参数的用处和上面的hive.optimize.skewjoin一致,但在编译sql时就已经将执行计划优化完
毕。但要注意的是,只有在表的元数据中存储的有数据倾斜信息时,才能生效。因此建议runtime
和compiletime都设置为true。

可以通过建表语句来指定数据倾斜元数据:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 103 页


CREATE TABLE list_bucket_single (key STRING, value STRING)
-- 倾斜的字段和需要拆分的key值
SKEWED BY (key) ON (1,5,6)
-- 为倾斜值创建子目录单独存放
[STORED AS DIRECTORIES];

(3)Union优化

应用了表连接倾斜优化以后,会在执行计划中插入一个新的union操作,此时建议开启对union
的优化配置:

set hive.optimize.union.remove=true;

默认关闭。

此项配置减少对Union all子查询中间结果的二次读写,可以避免union输出的额外扫描过程,
当我们开启了skewjoin时尤其有用,建议同时开启。

set hive.optimize.skewjoin=true;

set hive.optimize.skewjoin.compiletime=true;

set hive.optimize.union.remove=true;

6.44.2 分组统计数据倾斜(Groupby skewin)

(1)Map阶段聚合

hive.map.aggr=true;

开启map端combiner。此配置可以在group by语句中提高HiveQL聚合的执行性能。这个设置
可以将顶层的聚合操作放在Map阶段执行,从而减轻数据传输和Reduce阶段的执行时间,提升总
体性能。默认开启,无需显示声明。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 104 页


(2)两个MRJob

hive.groupby.skewindata=true;

默认关闭。

这个配置项是用于决定group by操作是否支持倾斜数据的负载均衡处理。当数据出现倾斜时,
如果该变量设置为true,那么Hive会自动进行负载均衡。

当选项设定为 true,生成的查询计划会有两个 MR Job。

第一个MR Job中,Map 的输出结果集合会随机分布到Reduce中,每个Reduce做部分聚合操


作,并输出结果,这样处理的结果是相同的Group By Key有可能被分发到不同的Reduce中,从而
达到负载均衡的目的;

第二个MR Job再根据预处理的数据结果按照Group By Key分布到Reduce中(这个过程可以保


证相同的Group By Key被分布到同一个Reduce中),最后完成最终的聚合操作。

注意:在多个列上进行的去重操作与hive环境变量hive.groupby.skewindata存在冲突。

当hive.groupby.skewindata=true时,hive不支持多列上的去重操作,并报错:

Error in semantic analysis: DISTINCT on different columns notsupported with skew in data.

比如:

(1) SELECT count(DISTINCT uid) FROM log

(2) SELECT ip, count(DISTINCT uid) FROM log GROUP BY ip

(3) SELECT ip, count(DISTINCT uid, uname) FROMlog GROUP BY ip

(4) SELECT ip, count(DISTINCTuid), count(DISTINCT uname) FROMlog GROUP BY ip

1、2、3能够正常执行,但是4会报错。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 105 页


3.2 Hive索引

Hive支持索引,但是Hive的索引与关系型数据库中的索引并不相同,比如,Hive不支持主键或
者外键。

Hive索引可以建立在表中的某些列上,以提升一些操作的效率,例如减少MapReduce任务中
需要读取的数据块的数量。

在可以预见到分区数据非常庞大的情况下,分桶和索引常常是优于分区的。而分桶由于SMB
Join对关联键要求严格,所以并不是总能生效。

3.2.1 Hive索引

Hive的索引目的是提高Hive表指定列的查询速度。

没有索引时,类似'WHERE tab1.col1 = 10' 的查询,Hive会加载整张表或分区,然后处理所有


的rows,但是如果在字段col1上面存在索引时,那么只会加载和处理文件的一部分。

在每次建立、更新数据后,Hive索引不会自动更新,需要手动进行更新(重建索引以构建索引
表),会触发一个mr job。

Hive索引使用过程繁杂,而且性能一般,在Hive3.0中已被删除,在工作环境中不推荐优先使
用,在分区数量过多或查询字段不是分区字段时,索引可以作为补充方案同时使用。推荐使用ORC
文件格式的索引类型进行查询。

3.2.2 Row Group Index

一 个 ORC文 件 包 含 一 个 或 多 个 stripes(groups of row data), 每 个 stripe中 包 含 了 每 个


column的 min/max值 的 索 引 数 据 , 当 查 询 中 有 <,>,=的 操 作 时 , 会 根 据 min/max值 , 跳 过
扫 描 不包 含 的 stripes。

而 其 中 为 每 个 stripe建 立 的 包 含 min/max值 的 索 引 ,就 称 为 Row Group Index行 组 索 引 ,


也 叫 min-max Index大 小 对比 索 引 , 或 者 Storage Index。

在 建 立 ORC格 式 表 时 ,指 定 表 参 数 ’orc.create.index’=’true’之 后 ,便 会 建 立 Row Group


Index,需 要 注 意 的 是 ,为 了 使 Row Group Index有 效 利 用 ,向 表 中 加 载 数 据 时,必 须 对 需
要 使 用 索 引 的 字 段 进 行 排 序 , 否 则 , min/max会 失 去 意 义 。 另 外 , 这 种 索 引 主 要 用 于 数 值
型 字 段的 查 询过 滤 优 化 上 。

设 置 hive.optimize.index.filter为 true, 并 重启 hive

创 建 表:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 106 页


CREATE TABLE lxw1234_orc2 stored AS ORC
TBLPROPERTIES
(
'orc.compress'='SNAPPY',
-- 开启行组索引
'orc.create.index'='true'
)
AS
SELECT CAST(siteid AS INT) AS id,
pcid
FROM lxw1234_text
-- 插入的数据保持排序
DISTRIBUTE BY id sort BY id;

查询:

set hive.optimize.index.filter=true;
SELECT COUNT(1) FROM lxw1234_orc1 WHERE id >= 1382 AND id <= 1399;

3.2.3 Bloom Filter Index

在 建 表 时 候 , 通 过 表 参 数 ” orc.bloom.filter.columns ” = ” pcid ” 来 指 定 为 那 些 字 段 建 立
BloomFilter索 引 ,这 样 ,在 生 成 数 据 的 时 候 ,会 在 每 个 stripe中 ,为 该 字 段 建 立 BloomFilter
的 数 据 结 构 , 当 查 询 条 件 中 包 含 对 该 字 段 的 =号 过 滤 时 候 , 先 从 BloomFilter中 获 取 以 下 是
否 包 含该 值 , 如 果 不包 含 , 则 跳 过该 stripe。

创建:

CREATE TABLE lxw1234_orc2 stored AS ORC


TBLPROPERTIES
(
'orc.compress'='SNAPPY',
'orc.create.index'='true',
-- pcid字段开启BloomFilter索引
"orc.bloom.filter.columns"="pcid"
)
AS
SELECT CAST(siteid AS INT) AS id,
pcid
FROM lxw1234_text
DISTRIBUTE BY id sort BY id;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 107 页


查询:

SET hive.optimize.index.filter=true;
SELECT COUNT(1) FROM lxw1234_orc1 WHERE id >= 0 AND id <= 1000
AND pcid IN ('0005E26F0DCCDB56F9041C','A');

只 有 在数 据 量 较 大 时, 使 用 索 引 才能 带 来 性 能 优势 。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 108 页


7. V10.0HiveSQL业务强化练习(必记必看)

7.1 场景举例.教育背景学生成绩分析

成绩的数据格式:时间,学校,年纪,姓名,科目,成绩

样例数据如下:

2013,北大,1,裘容絮,语文,97

2013,北大,1,庆眠拔,语文,52

2013,北大,1,乌洒筹,语文,85

2012,清华,0,钦尧,英语,61

2015,北理工,3,冼殿,物理,81

2016,北科,4,况飘索,化学,92

2014,北航,2,孔须,数学,70

2012,清华,0,王脊,英语,59

2014,北航,2,方部盾,数学,49

2014,北航,2,东门雹,数学,77

问题:

7.1.1 情景题:分组TOPN

# 1.分组TOPN选出 今年每个学校,每个年级,分数前三的科目.

hive -e "

set mapreduce.job.queuename=low;

select t.*

from

select

school,

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 109 页


class,

subjects,

score,

row_number() over (partition by school,class,subjects order by score desc) rank_code

from spark_test_wx

where partition_id = "2017"

)t

where t.rank_code <= 3;

"

1)row_number函数: row_number() 按指定的列进行分组生成行序列, 从 1 开始, 如果两行记录的分组列相同, 则

行序列+1。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 110 页


2)over 函数:是一个窗口函数.

over(order by score) 按照score排序进行累计,order by是个默认的开窗函数.

over(partition by class)按照班级分区

over(partition by class order by score)按照班级分区,并按着分数排序.

over(order by score range between 2 preceding and 2 following):窗口范围为当前行的数据幅度减2加2

后的范围内的数据求和。

-- 今年,北航,每个班级,每科的分数,及分数上下浮动2分的总和

select school,class,subjects,score,

sum(score) over(order by score range between 2 preceding and 2 following) sscore

from spark_test_wx

where partition_id = "2017" and school="北航"

over(order by score rows between 2 preceding and 2 following):窗口范围为当前行前后各移动2行。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 111 页


提问,上述sql有没有可优化的点.

-- row_number() over (distribute by school,class,subjects sort by score desc) rank_code

7.1.2 情景题:where与having

hive -e "

-- 今年 清华 1年级 总成绩大于200分的学生 以及学生数

set mapreduce.job.queuename=low;

select school,class,name,sum(score) as total_score,

count(1) over (partition by school,class) nct

from spark_test_wx

where partition_id = "2017" and school="清华" and class = 1

group by school,class,name

having total_score>200;

"

having是分组(group by)后的筛选条件,分组后的数据组内再筛选,也就是说HAVING子句可以让我们筛选成组

后的各组数据。

where则是在分组,聚合前先筛选记录。也就是说作用在GROUP BY子句和HAVING子句前。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 112 页


7.1.3 情景题:数据倾斜

今年加入进来了10个学校,学校数据差异很大计算每个学校的平均分。

该题主要是考察数据倾斜的处理方式。

Group by 方式很容易产生数据倾斜。需要注意一下几点

1)Map端部分聚合

hive.map.aggr=true(用于设定是否在 map 端进行聚合,默认值为真,相当于combine)

hive.groupby.mapaggr.checkinterval=100000(用于设定 map 端进行聚合操作的条数)

2)有数据倾斜时进行负载均衡

设定hive.groupby.skewindata,当选项设定为true是,生成的查询计划有两个MapReduce任务。

在第一个MapReduce中,map的输出结果集合会随机分布到reduce中, 每个reduce做部分聚合操作,

并输出结果。这样处理的结果是,相同的Group By Key有可能分发到不同的reduce中,从而达到负载均衡的

目的;

第二个MapReduce任务再根据预处理的数据结果按照Group By Key分布到reduce 中(这个过程可以保

证相同的Group By Key分布到同一个reduce 中),最后完成最终的聚合操作。

7.1.4 情景题:分区表

假设我创建了一张表,其中包含了2016年客户完成的所有交易的详细信息:CREATE TABLE transaction_details

(cust_id INT, amount FLOAT, month STRING, country STRING) ROW FORMAT DELIMITED FIELDS TERMINATED

BY ‘,’ ;

现在我插入了100万条数据,我想知道每个月的总收入。

问:如何高效的统计出结果。写出步骤即可。

1)首先分析这个需求,其实并不难,但是由于题目说了,要高效。而且数据量也不小,直接写sql查询估计肯定会挂。

2)分析:

(1)我们可以通过根据每个月对表进行分区来解决查询慢的问题。 因此,对于每个月我们将只扫描分区的数据,

而不是整个数据集。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 113 页


(2)但是我们不能直接对现有的非分区表进行分区。 所以我们会采取以下步骤来解决这个问题:

(3)创建一个分区表,partitioned_transaction:

create table partitioned_transaction (cust_id int, amount float, country string) partitioned by (month string)

row format delimited fields terminated by ‘,’ ;

(4)在Hive中启用动态分区:

SET hive.exec.dynamic.partition = true;

SET hive.exec.dynamic.partition.mode = nonstrict;

(5)将数据从非分区表导入到新创建的分区表中:

insert overwrite table partitioned_transaction partition (month) select cust_id, amount, country, month

from transaction_details;

(6)使用新建的分区表实现需求。

7.2 HiveSQL面试手撕十题

7.2.1 第一题:访问量统计

需求

我们有如下的用户访问数据

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

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 114 页


要求使用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

实现

数据准备

CREATE TABLE test_sql.test1 (

userId string,

visitDate string,

visitCount INT )

ROW format delimited FIELDS TERMINATED BY "\t";

INSERT INTO TABLE test_sql.test1

VALUES

( '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

SELECT t2.userid,

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 115 页


t2.visitmonth,

subtotal_visit_cnt,

sum(subtotal_visit_cnt) over (partition BY userid

ORDER BY visitmonth) AS total_visit_cnt

FROM

(SELECT userid,

visitmonth,

sum(visitcount) AS subtotal_visit_cnt

FROM

(SELECT userid,

date_format(regexp_replace(visitdate,'/','-'),'yyyy-MM') AS visitmonth,

visitcount

FROM test_sql.test1) t1

GROUP BY userid,

visitmonth)t2

ORDER BY t2.userid,

t2.visitmonth

7.2.2 第二题:电商场景TopK统计

需求

有50W个京东店铺,每个顾客访客访问任何一个店铺的任何一个商品时都会产生一条访问日志,

访问日志存储的表名为Visit,访客的用户id为user_id,被访问的店铺名称为shop,数据如下:

user_id shop

u1 a

u2 b

u1 b

u1 a

u3 c

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 116 页


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)每个店铺的UV(访客数)

(2)每个店铺访问次数top3的访客信息。输出店铺名称、访客id、访问次数

实现

数据准备

CREATE TABLE test_sql.test2 (

user_id string,

shop string )

ROW format delimited FIELDS TERMINATED BY '\t';

INSERT INTO TABLE test_sql.test2 VALUES

( 'u1', 'a' ),

( 'u2', 'b' ),

( 'u1', 'b' ),

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 117 页


( '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' );

查询SQL实现

(1) 方式1:

//每个店铺的UV(访客数)

SELECT shop,count(DISTINCT user_id)

FROM test_sql.test2

GROUP BY shop

方式2:

//每个店铺的UV(访客数)

SELECT t.shop,count(*)

FROM

(SELECT user_id,shop

FROM test_sql.test2

GROUP BY user_id,shop) t

GROUP BY t.shop

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 118 页


//每个店铺访问次数top3的访客信息。输出店铺名称、访客id、访问次数

SELECT t2.shop,t2.user_id,t2.cnt

FROM

(SELECT t1.*,

row_number() over(partition BY t1.shop ORDER BY t1.cnt DESC) rank

FROM

(SELECT user_id,shop,count(*) AS cnt

FROM test_sql.test2

GROUP BY user_id,shop) t1)t2

WHERE rank <= 3

7.2.3 第三题:订单量统计

需求

已知一个表STG.ORDER,有如下字段:Date,Order_id,User_id,amount。

数据样例:2017-01-01,10029028,1000003251,33.57。

请给出sql进行统计:

(1)给出 2017年每个月的订单数、用户数、总成交金额。

(2)给出2017年11月的新客数(指在11月才有第一笔订单)

实现

数据准备

CREATE TABLE test_sql.test3 (

dt string,

order_id string,

user_id string,

amount DECIMAL ( 10, 2 ) )

ROW format delimited FIELDS TERMINATED BY '\t';

INSERT INTO TABLE test_sql.test3 VALUES ('2017-01-01','10029028','1000003251',33.57);

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 119 页


INSERT INTO TABLE test_sql.test3 VALUES ('2017-01-01','10029029','1000003251',33.57);

INSERT INTO TABLE test_sql.test3 VALUES ('2017-01-01','100290288','1000003252',33.57);

INSERT INTO TABLE test_sql.test3 VALUES ('2017-02-02','10029088','1000003251',33.57);

INSERT INTO TABLE test_sql.test3 VALUES ('2017-02-02','100290281','1000003251',33.57);

INSERT INTO TABLE test_sql.test3 VALUES ('2017-02-02','100290282','1000003253',33.57);

INSERT INTO TABLE test_sql.test3 VALUES ('2017-11-02','10290282','100003253',234);

INSERT INTO TABLE test_sql.test3 VALUES ('2018-11-02','10290284','100003243',234);

查询SQL

(1)给出 2017年每个月的订单数、用户数、总成交金额。

SELECT t1.mon,

count(t1.order_id) AS order_cnt,

count(DISTINCT t1.user_id) AS user_cnt,

sum(amount) AS total_amount

FROM

(SELECT order_id,

user_id,

amount,

date_format(dt,'yyyy-MM') mon

FROM test_sql.test3

WHERE date_format(dt,'yyyy') = '2017') t1

GROUP BY t1.mon

(2)给出2017年11月的新客数(指在11月才有第一笔订单)

SELECT count(user_id)

FROM test_sql.test3

GROUP BY user_id

HAVING date_format(min(dt),'yyyy-MM')='2017-11';

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 120 页


7.2.4 第四题:大数据排序统计

需求

有一个5000万的用户文件(user_id,name,age),一个2亿记录的用户看电影的记录文件(user_id,url),根据年龄
段观看电影的次数进行排序?

实现

数据准备

CREATE TABLE test_sql.test4user

(user_id string,name string,age int);

CREATE TABLE test_sql.test4log

(user_id string,url string);

INSERT INTO TABLE test_sql.test4user VALUES('001','u1',10);

INSERT INTO TABLE test_sql.test4user VALUES('002','u2',15);

INSERT INTO TABLE test_sql.test4user VALUES('003','u3',15);

INSERT INTO TABLE test_sql.test4user VALUES('004','u4',20);

INSERT INTO TABLE test_sql.test4user VALUES('005','u5',25);

INSERT INTO TABLE test_sql.test4user VALUES('006','u6',35);

INSERT INTO TABLE test_sql.test4user VALUES('007','u7',40);

INSERT INTO TABLE test_sql.test4user VALUES('008','u8',45);

INSERT INTO TABLE test_sql.test4user VALUES('009','u9',50);

INSERT INTO TABLE test_sql.test4user VALUES('0010','u10',65);

INSERT INTO TABLE test_sql.test4log VALUES('001','url1');

INSERT INTO TABLE test_sql.test4log VALUES('002','url1');

INSERT INTO TABLE test_sql.test4log VALUES('003','url2');

INSERT INTO TABLE test_sql.test4log VALUES('004','url3');

INSERT INTO TABLE test_sql.test4log VALUES('005','url3');

INSERT INTO TABLE test_sql.test4log VALUES('006','url1');

INSERT INTO TABLE test_sql.test4log VALUES('007','url5');

INSERT INTO TABLE test_sql.test4log VALUES('008','url7');

INSERT INTO TABLE test_sql.test4log VALUES('009','url5');

INSERT INTO TABLE test_sql.test4log VALUES('0010','url1');

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 121 页


查询SQL

SELECT

t2.age_phase,

sum(t1.cnt) as view_cnt

FROM

(SELECT user_id,

count(*) cnt

FROM test_sql.test4log

GROUP BY user_id) t1

JOIN(SELECT user_id,

CASE WHEN age <= 10 AND age > 0 THEN '0-10'

WHEN age <= 20 AND age > 10 THEN '10-20'

WHEN age >20 AND age <=30 THEN '20-30'

WHEN age >30 AND age <=40 THEN '30-40'

WHEN age >40 AND age <=50 THEN '40-50'

WHEN age >50 AND age <=60 THEN '50-60'

WHEN age >60 AND age <=70 THEN '60-70'

ELSE '70以上' END as age_phase

FROM test_sql.test4user) t2 ON t1.user_id = t2.user_id

GROUP BY t2.age_phase

7.2.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

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 122 页


2019-02-12,test_2,19

2019-02-13,test_1,23

2019-02-15,test_2,19

2019-02-16,test_2,19

实现

数据准备

CREATE TABLE test5(

dt string,

user_id string,

age int)

ROW format delimited fields terminated BY ',';

INSERT INTO TABLE test_sql.test5 VALUES ('2019-02-11','test_1',23);

INSERT INTO TABLE test_sql.test5 VALUES ('2019-02-11','test_2',19);

INSERT INTO TABLE test_sql.test5 VALUES ('2019-02-11','test_3',39);

INSERT INTO TABLE test_sql.test5 VALUES ('2019-02-11','test_1',23);

INSERT INTO TABLE test_sql.test5 VALUES ('2019-02-11','test_3',39);

INSERT INTO TABLE test_sql.test5 VALUES ('2019-02-11','test_1',23);

INSERT INTO TABLE test_sql.test5 VALUES ('2019-02-12','test_2',19);

INSERT INTO TABLE test_sql.test5 VALUES ('2019-02-13','test_1',23);

INSERT INTO TABLE test_sql.test5 VALUES ('2019-02-15','test_2',19);


INSERT INTO TABLE test_sql.test5 VALUES ('2019-02-16','test_2',19);

查询SQL

SELECT sum(total_user_cnt) total_user_cnt,

sum(total_user_avg_age) total_user_avg_age,

sum(two_days_cnt) two_days_cnt,

sum(avg_age) avg_age

FROM

(SELECT 0 total_user_cnt,

0 total_user_avg_age,

count(*) AS two_days_cnt,

cast(sum(age) / count(*) AS decimal(5,2)) AS avg_age

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 123 页


FROM

(SELECT user_id,max(age) age

FROM

(SELECT user_id,max(age) age

FROM

(SELECT user_id,age,date_sub(dt,rank) flag

FROM

(SELECT dt,user_id,max(age) age,

row_number() over(PARTITION BY user_id

ORDER BY dt) rank

FROM test_sql.test5

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(*) total_user_cnt,

cast(sum(age) /count(*) AS decimal(5,2)) total_user_avg_age,

0 two_days_cnt,

0 avg_age

FROM

(SELECT user_id,

max(age) age

FROM test_sql.test5

GROUP BY user_id) t5) t6

7.2.6 第六题:电商购买金额统计实战

需求

请用sql写出所有用户中在今年10月份第一次购买商品的金额,

表ordertable字段:(购买用户:userid,金额:money,购买时间:paymenttime(格式:2017-10-01),订单id:orderid

实现

数据准备

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 124 页


CREATE TABLE test_sql.test6 (

userid string,

money decimal(10,2),

paymenttime string,

orderid string);

INSERT INTO TABLE test_sql.test6 VALUES('001',100,'2017-10-01','123');

INSERT INTO TABLE test_sql.test6 VALUES('001',200,'2017-10-02','124');

INSERT INTO TABLE test_sql.test6 VALUES('002',500,'2017-10-01','125');

INSERT INTO TABLE test_sql.test6 VALUES('001',100,'2017-11-01','126');

查询SQL

SELECT

userid,

paymenttime,

money,

orderid

from(SELECT userid,

money,

paymenttime,

orderid,

row_number() over (PARTITION BY userid

ORDER BY paymenttime) rank

FROM test_sql.test6

WHERE date_format(paymenttime,'yyyy-MM') = '2017-10') t

WHERE rank = 1

7.2.7 第七题:教育领域SQL实战

需求

现有图书管理数据库的三个数据模型如下:

图书(数据表名:BOOK)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 125 页


序号 字段名称 字段描述 字段类型

1 BOOK_ID 总编号 文本

2 SORT 分类号 文本

3 BOOK_NAME 书名 文本

4 WRITER 作者 文本

5 OUTPUT 出版单位 文本

6 PRICE 单价 数值(保留小数点后2位)

读者(数据表名:READER)

序号 字段名称 字段描述 字段类型

1 READER_ID 借书证号 文本

2 COMPANY 单位 文本

3 NAME 姓名 文本

4 SEX 性别 文本

5 GRADE 职称 文本

6 ADDR 地址 文本

借阅记录(数据表名:BORROW LOG)

序号 字段名称 字段描述 字段类型

1 READER_ID 借书证号 文本

2 BOOK_ID 总编号 文本

3 BORROW_DATE 借书日期 日期

(1)创建图书管理库的图书、读者和借阅三个基本表的表结构。请写出建表语句。

(2)找出姓李的读者姓名(NAME)和所在单位(COMPANY)。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 126 页


(3)查找“高等教育出版社”的所有图书名称(BOOK_NAME)及单价(PRICE),结果按单价降序排序。

(4)查找价格介于10元和20元之间的图书种类(SORT)出版单位(OUTPUT)和单价(PRICE),结果按出版单位
(OUTPUT)和单价(PRICE)升序排序。

(5)查找所有借了书的读者的姓名(NAME)及所在单位(COMPANY)。

(6)求”科学出版社”图书的最高单价、最低单价、平均单价。

(7)找出当前至少借阅了2本图书(大于等于2本)的读者姓名及其所在单位。

(8)考虑到数据安全的需要,需定时将“借阅记录”中数据进行备份,请使用一条SQL语句,在备份用户bak下创建
与“借阅记录”表结构完全一致的数据表BORROW_LOG_BAK.井且将“借阅记录”中现有数据全部复制到
BORROW_L0G_ BAK中。

(9)现在需要将原Oracle数据库中数据迁移至Hive仓库,请写出“图书”在Hive中的建表语句(Hive实现,提示:列
分隔符|;数据表数据需要外部导入:分区分别以month_part、day_part 命名)

(10)Hive中有表A,现在需要将表A的月分区 201505 中 user_id为20000的user_dinner字段更新为


bonc8920,其他用户user_dinner字段数据不变,请列出更新的方法步骤。(Hive实现,提示:Hlive中无update
语法,请通过其他办法进行数据更新)

实现

(1)-- 创建图书表book

CREATE TABLE test_sql.book(book_id string,

`SORT` string,

book_name string,

writer string,

OUTPUT string,

price decimal(10,2));

INSERT INTO TABLE test_sql.book VALUES ('001','TP391','信息处理','author1','机械工业出版社','20');

INSERT INTO TABLE test_sql.book VALUES ('002','TP392','数据库','author12','科学出版社','15');

INSERT INTO TABLE test_sql.book VALUES ('003','TP393','计算机网络','author3','机械工业出版社','29');

INSERT INTO TABLE test_sql.book VALUES ('004','TP399','微机原理','author4','科学出版社','39');

INSERT INTO TABLE test_sql.book VALUES ('005','C931','管理信息系统','author5','机械工业出版社','40');

INSERT INTO TABLE test_sql.book VALUES ('006','C932','运筹学','author6','科学出版社','55');

-- 创建读者表reader

CREATE TABLE test_sql.reader (reader_id string,

company string,

name string,

sex string,

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 127 页


grade string,

addr string);

INSERT INTO TABLE test_sql.reader VALUES ('0001','阿里巴巴','jack','男','vp','addr1');

INSERT INTO TABLE test_sql.reader VALUES ('0002','百度','robin','男','vp','addr2');

INSERT INTO TABLE test_sql.reader VALUES ('0003','腾讯','tony','男','vp','addr3');

INSERT INTO TABLE test_sql.reader VALUES ('0004','京东','jasper','男','cfo','addr4');

INSERT INTO TABLE test_sql.reader VALUES ('0005','网易','zhangsan','女','ceo','addr5');

INSERT INTO TABLE test_sql.reader VALUES ('0006','搜狐','lisi','女','ceo','addr6');

-- 创建借阅记录表borrow_log

CREATE TABLE test_sql.borrow_log(reader_id string,

book_id string,

borrow_date string);

INSERT INTO TABLE test_sql.borrow_log VALUES ('0001','002','2019-10-14');

INSERT INTO TABLE test_sql.borrow_log VALUES ('0002','001','2019-10-13');

INSERT INTO TABLE test_sql.borrow_log VALUES ('0003','005','2019-09-14');

INSERT INTO TABLE test_sql.borrow_log VALUES ('0004','006','2019-08-15');

INSERT INTO TABLE test_sql.borrow_log VALUES ('0005','003','2019-10-10');

INSERT INTO TABLE test_sql.borrow_log VALUES ('0006','004','2019-17-13');

(2)找出姓李的读者姓名(NAME)和所在单位(COMPANY)。

SELECT name,

company

FROM test_sql.reader

WHERE name LIKE '李%';

(3)查找“高等教育出版社”的所有图书名称(BOOK_NAME)及单价(PRICE),结果按单价降序排序。

SELECT book_name,

price

FROM test_sql.book

WHERE OUTPUT = "高等教育出版社"

ORDER BY price DESC;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 128 页


(4)查找价格介于10元和20元之间的图书种类(SORT)出版单位(OUTPUT)和单价(PRICE),结果按出版单位
(OUTPUT)和单价(PRICE)升序排序。

SELECT sort,

output,

price

FROM test_sql.book

WHERE price >= 10 and price <= 20

ORDER BY output,price ;

(5)查找所有借了书的读者的姓名(NAME)及所在单位(COMPANY)。

SELECT b.name,

b.company

FROM test_sql.borrow_log a

JOIN test_sql.reader b ON a.reader_id = b.reader_id;

(6)求”科学出版社”图书的最高单价、最低单价、平均单价。

SELECT max(price),

min(price),

avg(price)

FROM test_sql.book

WHERE OUTPUT = '科学出版社';

(7)找出当前至少借阅了2本图书(大于等于2本)的读者姓名及其所在单位。

SELECT b.name,

b.company

FROM

(SELECT reader_id

FROM test_sql.borrow_log

GROUP BY reader_id

HAVING count(*) >= 2) a

JOIN test_sql.reader b ON a.reader_id = b.reader_id;

(8)考虑到数据安全的需要,需定时将“借阅记录”中数据进行备份,请使用一条SQL语句,在备份用户bak下创建与“借
阅记录”表结构完全一致的数据表BORROW_LOG_BAK.井且将“借阅记录”中现有数据全部复制到BORROW_L0G_
BAK中。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 129 页


CREATE TABLE test_sql.borrow_log_bak AS

SELECT *

FROM test_sql.borrow_log;

(9)现在需要将原Oracle数据库中数据迁移至Hive仓库,请写出“图书”在Hive中的建表语句(Hive实现,提示:列分
隔符|;数据表数据需要外部导入:分区分别以month_part、day_part 命名)

CREATE TABLE book_hive (

book_id string,

SORT string,

book_name string,

writer string,

OUTPUT string,

price DECIMAL ( 10, 2 ) )

partitioned BY ( month_part string, day_part string )

ROW format delimited FIELDS TERMINATED BY '\\|' stored AS textfile;

(10)Hive中有表A,现在需要将表A的月分区 201505 中 user_id为20000的user_dinner字段更新为


bonc8920,其他用户user_dinner字段数据不变,请列出更新的方法步骤。(Hive实现,提示:Hlive中无update
语法,请通过其他办法进行数据更新)

方式1:配置hive支持事务操作,分桶表,orc存储格式

方式2:第一步找到要更新的数据,将要更改的字段替换为新的值,第二步找到不需要更新的数
据,第三步将上两步的数据插入一张新表中。

7.2.8 第八题:服务日志SQL统计

需求

有一个线上服务器访问日志格式如下(用sql答题)

时间 接口 ip地址

2016/11/9 14:22 /api/user/login 110.23.5.33

2016/11/9 14:23 /api/user/detail 57.3.2.16

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 130 页


2016/11/9 15:59 /api/user/login 200.6.5.166

... ... ...

求11月9号下午14点(14-15点),访问/api/user/login接口的top10的ip地址

实现

数据准备

CREATE TABLE test_sql.test8(`date` string,

interface string,

ip string);

INSERT INTO TABLE test_sql.test8 VALUES ('2016-11-09 11:22:05','/api/user/login','110.23.5.23');

INSERT INTO TABLE test_sql.test8 VALUES ('2016-11-09 11:23:10','/api/user/detail','57.3.2.16');

INSERT INTO TABLE test_sql.test8 VALUES ('2016-11-09 23:59:40','/api/user/login','200.6.5.166');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 11:14:23','/api/user/login','136.79.47.70');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 11:15:23','/api/user/detail','94.144.143.141');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 11:16:23','/api/user/login','197.161.8.206');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 12:14:23','/api/user/detail','240.227.107.145');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 13:14:23','/api/user/login','79.130.122.205');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 14:14:23','/api/user/detail','65.228.251.189');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 14:15:23','/api/user/detail','245.23.122.44');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 14:17:23','/api/user/detail','22.74.142.137');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 14:19:23','/api/user/detail','54.93.212.87');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 14:20:23','/api/user/detail','218.15.167.248');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 14:24:23','/api/user/detail','20.117.19.75');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 15:14:23','/api/user/login','183.162.66.97');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 16:14:23','/api/user/login','108.181.245.147');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 14:17:23','/api/user/login','22.74.142.137');

INSERT INTO TABLE test_sql.test8 VALUES('2016-11-09 14:19:23','/api/user/login','22.74.142.137');

查询SQL

求11月9号下午14点(14-15点),访问/api/user/login接口的top10的ip地址

SELECT ip,count(*) AS cnt

FROM test_sql.test8

WHERE date_format(date,'yyyy-MM-dd HH') >= '2016-11-09 14'

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 131 页


AND date_format(date,'yyyy-MM-dd HH') < '2016-11-09 15'

AND interface='/api/user/login'

GROUP BY ip

ORDER BY cnt desc

LIMIT 10;

7.2.9 第九题:充值日志SQL实战

需求

有一个充值日志表credit_log,字段如下:

`dist_id` int '区组id',

`account` string '账号',

`money` int '充值金额',`

create_time` string '订单时间'

请写出SQL语句,查询充值日志表2019年01月02号每个区组下充值额最大的账号,要求结果:

区组id,账号,金额,充值时间

实现:

数据准备

CREATE TABLE test_sql.test9(

dist_id string COMMENT '区组id',

account string COMMENT '账号',

`money` decimal(10,2) COMMENT '充值金额',

create_time string COMMENT '订单时间');

INSERT INTO TABLE test_sql.test9 VALUES ('1','11',100006,'2019-01-02 13:00:01');

INSERT INTO TABLE test_sql.test9 VALUES ('1','22',110000,'2019-01-02 13:00:02');

INSERT INTO TABLE test_sql.test9 VALUES ('1','33',102000,'2019-01-02 13:00:03');

INSERT INTO TABLE test_sql.test9 VALUES ('1','44',100300,'2019-01-02 13:00:04');

INSERT INTO TABLE test_sql.test9 VALUES ('1','55',100040,'2019-01-02 13:00:05');

INSERT INTO TABLE test_sql.test9 VALUES ('1','66',100005,'2019-01-02 13:00:06');

INSERT INTO TABLE test_sql.test9 VALUES ('1','77',180000,'2019-01-03 13:00:07');

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 132 页


INSERT INTO TABLE test_sql.test9 VALUES ('1','88',106000,'2019-01-02 13:00:08');

INSERT INTO TABLE test_sql.test9 VALUES ('1','99',100400,'2019-01-02 13:00:09');

INSERT INTO TABLE test_sql.test9 VALUES ('1','12',100030,'2019-01-02 13:00:10');

INSERT INTO TABLE test_sql.test9 VALUES ('1','13',100003,'2019-01-02 13:00:20');

INSERT INTO TABLE test_sql.test9 VALUES ('1','14',100020,'2019-01-02 13:00:30');

INSERT INTO TABLE test_sql.test9 VALUES ('1','15',100500,'2019-01-02 13:00:40');

INSERT INTO TABLE test_sql.test9 VALUES ('1','16',106000,'2019-01-02 13:00:50');

INSERT INTO TABLE test_sql.test9 VALUES ('1','17',100800,'2019-01-02 13:00:59');

INSERT INTO TABLE test_sql.test9 VALUES ('2','18',100800,'2019-01-02 13:00:11');

INSERT INTO TABLE test_sql.test9 VALUES ('2','19',100030,'2019-01-02 13:00:12');

INSERT INTO TABLE test_sql.test9 VALUES ('2','10',100000,'2019-01-02 13:00:13');

INSERT INTO TABLE test_sql.test9 VALUES ('2','45',100010,'2019-01-02 13:00:14');

INSERT INTO TABLE test_sql.test9 VALUES ('2','78',100070,'2019-01-02 13:00:15');

查询SQL

WITH TEMP AS

(SELECT dist_id,account,sum(`money`) sum_money

FROM test_sql.test9

WHERE date_format(create_time,'yyyy-MM-dd') = '2019-01-02'

GROUP BY dist_id,account)

SELECT t1.dist_id,t1.account,t1.sum_money

FROM

(SELECT temp.dist_id,

temp.account,

temp.sum_money,

rank() over(partition BY temp.dist_id

ORDER BY temp.sum_money DESC) ranks

FROM TEMP) t1

WHERE ranks = 1

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 133 页


7.2.10 第十题:电商分组TopK实战

需求

有一个账号表如下,请写出SQL语句,查询各自区组的money排名前十的账号(分组取前10)

dist_id string '区组id',

account string '账号',

gold int '金币'

实现

数据准备

CREATE TABLE test_sql.test10(

`dist_id` string COMMENT '区组id',

`account` string COMMENT '账号',

`gold` int COMMENT '金币');

INSERT INTO TABLE test_sql.test10 VALUES ('1','77',18);

INSERT INTO TABLE test_sql.test10 VALUES ('1','88',106);

INSERT INTO TABLE test_sql.test10 VALUES ('1','99',10);

INSERT INTO TABLE test_sql.test10 VALUES ('1','12',13);

INSERT INTO TABLE test_sql.test10 VALUES ('1','13',14);

INSERT INTO TABLE test_sql.test10 VALUES ('1','14',25);

INSERT INTO TABLE test_sql.test10 VALUES ('1','15',36);

INSERT INTO TABLE test_sql.test10 VALUES ('1','16',12);

INSERT INTO TABLE test_sql.test10 VALUES ('1','17',158);

INSERT INTO TABLE test_sql.test10 VALUES ('2','18',12);

INSERT INTO TABLE test_sql.test10 VALUES ('2','19',44);

INSERT INTO TABLE test_sql.test10 VALUES ('2','10',66);

INSERT INTO TABLE test_sql.test10 VALUES ('2','45',80);

INSERT INTO TABLE test_sql.test10 VALUES ('2','78',98);

查询SQL

SELECT dist_id,

account,

gold

FROM(SELECT dist_id,

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 134 页


account,

gold,

row_number () over (PARTITION BY dist_id

ORDER BY gold DESC) rank

FROM test_sql.test10) t

WHERE rank <= 10

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 135 页


8. V10.0Flume高频面试题

8.1 Flume有哪些组件,flume的source、channel、sink具体是做什么的

source组件是专门用来收集数据的,可以处理各种类型、各种格式的日志数据,包括avro、thrift、exec、jms、
spooling directory、netcat、sequence generator、syslog、http、legacy

source组件把数据收集来以后,临时存放在channel中,即channel组件在agent中是专门用来存放临时数据的
——对采集到的数据进行简单的缓存,可以存放在memory、jdbc、file等等。

sink组件是用于把数据发送到目的地的组件,目的地包括hdfs、logger、avro、thrift、ipc、file、null、Hbase、
solr、自定义。

8.2 你是如何实现flume数据传输的监控的

使用第三方框架Ganglia实时监控flume。

8.3 Flume的source,sink,channel的作用?你们source是什么类型?

source组件是专门用来收集数据的,可以处理各种类型、各种格式的日志数据,包括avro、thrift、exec、jms、
spooling directory、netcat、sequence generator、syslog、http、legacy

source组件把数据收集来以后,临时存放在channel中,即channel组件在agent中是专门用来存放临时数据的
——对采集到的数据进行简单的缓存,可以存放在memory、jdbc、file等等。

sink组件是用于把数据发送到目的地的组件,目的地包括hdfs、logger、avro、thrift、ipc、file、null、Hbase、
solr、自定义。

监控后台日志:exec

监控后台产生日志的端口:netcat

8.4 Flume参数调优

1. Source

增加Source个数(使用Tair Dir Source时可增加FileGroups个数)可以增大Source的读取数据的能力。例如:

当某一个目录产生的文件过多时需要将这个文件目录拆分成多个文件目录,同时配置好多个Source 以保证Source

有足够的能力获取到新产生的数据。

batchSize参数决定Source一次批量运输到Channel的event条数,适当调大这个参数可以提高Source搬运

Event到Channel时的性能。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 136 页


2. Channel

type 选择memory时Channel的性能最好,但是如果Flume进程意外挂掉可能会丢失数据。type选择file时

Channel的容错性更好,但是性能上会比memory channel差。

使用file Channel时dataDirs配置多个不同盘下的目录可以提高性能。

Capacity 参数决定Channel可容纳最大的event条数。transactionCapacity 参数决定每次Source往channel里

面写的最大event条数和每次Sink从channel里面读的最大event条数。transactionCapacity需要大于Source和Sink的

batchSize参数。

3. Sink

增加Sink的个数可以增加Sink消费event的能力。Sink也不是越多越好够用就行,过多的Sink会占用系统资源,

造成系统资源不必要的浪费。

batchSize参数决定Sink一次批量从Channel读取的event条数,适当调大这个参数可以提高Sink从Channel搬出

event的性能。

8.5 Flume的事务机制

Flume的事务机制(类似数据库的事务机制):Flume使用两个独立的事务分别负责从Soucrce到Channel,以及从

Channel到Sink的事件传递。比如spooling directory source 为文件的每一行创建一个事件,一旦事务中所有的事

件全部传递到Channel且提交成功,那么Soucrce就将该文件标记为完成。同理,事务以类似的方式处理从Channel

到Sink的传递过程,如果因为某种原因使得事件无法记录,那么事务将会回滚。且所有的事件都会保持到Channel

中,等待重新传递。

8.6 Flume采集数据会丢失吗?

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

8.7 Flume使用场景

线上数据一般主要是落地(存储到磁盘)或者通过socket传输给另外一个系统,这种情况下,你很难推动线上

应用或服务去修改接口,实现直接向kafka里写数据,这时候你可能就需要flume这样的系统帮你去做传输。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 137 页


8.8 Flume丢包问题

单机upd的flume source的配置,100+M/s数据量,10w qps flume就开始大量丢包,因此很多公司在搭建系统

时,抛弃了Flume,自己研发传输系统,但是往往会参考Flume的Source-Channel-Sink模式。

一些公司在Flume工作过程中,会对业务日志进行监控,例如Flume agent中有多少条日志,Flume到Kafka后

有多少条日志等等,如果数据丢失保持在1%左右是没有问题的,当数据丢失达到5%左右时就必须采取相应措施。

8.9 数据怎么采集到Kafka,实现方式

使用官方提供的flumeKafka插件,插件的实现方式是自定义了flume的sink,将数据从channle中取出,通过

kafka的producer写入到kafka中,可以自定义分区等。

8.10 flume管道内存,flume宕机了数据丢失怎么解决

1)Flume的channel分为很多种,可以将数据写入到文件。

2)防止非首个agent宕机的方法数可以做集群或者主备

8.11 flume不采集Nginx日志,通过Logger4j采集日志,优缺点

优点:Nginx的日志格式是固定的,但是缺少sessionid,通过logger4j采集的日志是带有sessionid的,而session

可以通过redis共享,保证了集群日志中的同一session落到不同的tomcat时,sessionId还是一样的,而且logger4j

的方式比较稳定,不会宕机。

缺点:不够灵活,logger4j的方式和项目结合过于紧密,而flume的方式比较灵活,拔插式比较好,不会影响

项目性能。

8.12 flume和kafka采集日志区别,采集日志时中间停了,怎么记录之前的日志。

Flume采集日志是通过流的方式直接将日志收集到存储层,而kafka试讲日志缓存在kafka集群,待后期可以采

集到存储层。

Flume采集中间停了,可以采用文件的方式记录之前的日志,而kafka是采用offset的方式记录之前的日志。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 138 页


8.13 详细介绍Flume有哪些组件?

1)source:用于采集数据,Source是产生数据流的地方,同时Source会将产生的数据流传输到Channel,这

个有点类似于Java IO部分的Channel。

2)channel:用于桥接Sources和Sinks,类似于一个队列。

3)sink:从Channel收集数据,将数据写到目标源(可以是下一个Source,也可以是HDFS或者HBase)。

8.14 你是如何实现flume数据传输断点续传?

TailDirSource支持断点续传。通过Json格式文件写入上次传递位置信息,断点续传从下个位置开始。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 139 页


9. V10.0Spark核心基础Spark base(必记必看)

9.1 阐述下对Spark的并行度理解

Spark作业中,各个stage的task的数量,代表Spark作业在各个阶段stage的并行度。

分为资源并行度(物理并行度)和数据并行度(逻辑并行度)

如何设置Task数量:

9.2 设置Application的并行度

如何根据数据量(Task数目)配置资源

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 140 页


9.3 Spark有几种部署方式?请分别简要论述

1)Local:运行在一台机器上,通常是练手或者测试环境。

2)Standalone:构建一个基于Mster+Slaves的资源调度集群,Spark任务提交给Master运行。是Spark自身的一个调
度系统。

3)Yarn: Spark客户端直接连接Yarn,不需要额外构建Spark集群。有yarn-client和yarn-cluster两种模式,主要区别
在于:Driver程序的运行节点。

4)Mesos:国内大环境比较少用。

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

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

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

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 141 页


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

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

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 142 页


9.6 Spark-submit 面试题

面 试 题 1:

bin/spark-submit

--class com.huawei.cluster \

--master yarn-cluster \

--driver-cores 2 \

--driver-memory 30G \

--conf spark.shuffle.service.ennabled=true \

--conf spark.memory.storageFraction=0.30 \

--conf spark.memory.fraction=0.7 \

--conf spark.default.parallelism=2800 \

--conf spark.sql.shuffle.partitions1=1400 \

--conf spark.yarn.executor.memeoryOverhead=4096 \

--executor-memory 30g \

--executor-cores 8 \

--num-executors 20 \

/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/examples/jars/spark-examples_2.11-2.2.0.jar \

100

问题1:这个任务一共需要多少的Cores和Memory,答案:162cores,630G,

问题2:程序运行时每个executor上的storage内存和execution内存多少,6.3g,14.7g

--executor-memory MEM Memory per executor (e.g. 1000M, 2G) (Default: 1G).

--num-executors NUM Number of executors to launch (Default: 2).

--executor-cores NUM Number of cores per executor. (Default: 1 in YARN mode,

or all available cores on the worker in standalone mode)

Execution内存解析:

Spark在一个Executor中的内存分为三块,一块是execution内存,一块是storage内存,一块是other内存。

execution和storage是Spark Executor中内存的大户,other占用内存相对少很多,这里就不说了。在
spark-1.6.0以前的版本,execution和storage的内存分配是固定的,(称作静态内存模型),使用的参数配置分别

spark.shuffle.memoryFraction(execution内存占Executor总内存大小,default 0.2)和

spark.storage.memoryFraction (storage内存占Executor内存大小,default 0.6),因为是1.6.0以前这两块

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 143 页


内存是互相隔离的,这就导致了Executor的内存利用率不高,而且需要根据Application的具体情况,使用者自己来
调节这两个参数才能优化Spark的内存使用。

在spark2.x及以后的版本,execution内存和storage内存可以相互借用,(称作动态内存模型),提高了内存
的Spark中内存的使用率,同时也减少了OOM的情况。

spark.memory.storageFraction (default 0.5)

这个参数设置内存表示 Executor内存中 storage/(storage+execution),虽然spark-1.6.0+的版本内存storage


和execution的内存已经是可以互相借用的了,但是借用和赎回也是需要消耗性能的,所以如果明知道程序中storage
是多是少就可以调节一下这个参数。

spark.default.parallelism=2800解析:

[概念解析]

spark中有partition的概念(和slice是同一个概念,在spark1.2中官网已经做出了说明),一般每个partition对
应一个task。在我的测试过程中,如果没有设置spark.default.parallelism参数,spark计算出来的partition非常巨大,
与我的cores非常不搭。我在两台机器上(8cores *2 +6g * 2)上,spark计算出来的partition达到2.8万个,也就是
2.9万个tasks,每个task完成时间都是几毫秒或者零点几毫秒,执行起来非常缓慢。在我尝试设置了
spark.default.parallelism 后,任务数减少到10,执行一次计算过程从minute降到20second。

参数3设置:

spark.default.parallelism只有在处理RDD时才会起作用,对Spark SQL的无效。

#Default number of partitions in RDDs returned by transformations like join, reduceByKey, and parallelize
when not set by user.

spark.sql.shuffle.partitions则是对sparks SQL专用的设置

#Configures the number of partitions to use when shuffling data for joins or aggregations

参数4设置:

spark.yarn.executor.memoryOverhead,executor执行的时候,用的内存可能会超过executor-memoy,所以
会为executor额外预留一部分内存。spark.yarn.executor.memoryOverhead代表了这部分内存。这个参数如果没有
设置,会有一个自动计算公式(位于ClientArguments.scala中),如果超过spark.yarn.executor.memoryOverhead内
存,yarn会将应用kill掉。

什么时候需要调节Executor的堆外内存大小?

当出现一下异常时:

shuffle file cannot find,executor lost、task lost,out of memory

出现这种问题的现象大致有这么两种情况:

1.Executor挂掉了,对应的Executor上面的block manager也挂掉了,找不到对应的shuffle map output文件,


Reducer端不能够拉取数据

2.Executor并没有挂掉,而是在拉取数据的过程出现了问题。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 144 页


9.7 为什么要用Yarn来部署Spark?

因为 Yarn 支持动态资源配置。Standalone 模式只支持简单的固定资源分配策略,每个任务固定数量的 core,各


Job 按顺序依次分配在资源,资源不够的时候就排队。这种模式比较适合单用户的情况,多用户的情境下,会有可
能有些用户的任务得不到资源。

Yarn 作为通用的种子资源调度平台,除了 Spark 提供调度服务之外,还可以为其他系统提供调度,如 Hadoop


MapReduce, Hive 等。

9.8 Spark作业提交流程是怎么样的

spark-submit 提交代码,执行 new SparkContext(),在 SparkContext 里构


造 DAGScheduler 和 TaskScheduler。

TaskScheduler 会通过后台的一个进程,连接 Master,向 Master 注册 Application。

Master 接收到 Application 请求后,会使用相应的资源调度算法,在 Worker 上为这个 Application 启动


多个 Executer。

Executor 启动后,会自己反向注册到 TaskScheduler 中。 所有 Executor 都注册到 Driver 上之后,


SparkContext 结束初始化,接下来往下执行我们自己的代码。

每执行到一个 Action,就会创建一个 Job。Job 会提交给 DAGScheduler。

DAGScheduler 会将 Job划分为多个 stage,然后每个 stage 创建一个 TaskSet。

TaskScheduler 会把每一个 TaskSet 里的 Task,提交到 Executor 上执行。

Executor 上有线程池,每接收到一个 Task,就用 TaskRunner 封装,然后从线程池里取出一个线程执行这


个 task。(TaskRunner 将我们编写的代码,拷贝,反序列化,执行 Task,每个 Task 执行 RDD 里的一个 partition)

9.9 Spark on yarn 作业执行流程,yarn-client 和 yarn cluster 有什么区别

From: https://www.iteblog.com/archives/1223.html

Spark On Yarn 的优势 1. Spark 支持资源动态共享,运行于 Yarn 的框架都共享一个集中配置好的资源池 2. 可以


很方便的利用 Yarn 的资源调度特性来做分类·,隔离以及优先级控制负载,拥有更灵活的调度策略 3. Yarn 可以
自由地选择 executor 数量 4. Yarn 支持 Spark 安全的集群管理器,使用 Yarn,Spark 可以运行于 Kerberized
Hadoop 之上,在它们进程之间进行安全认证

yarn-client 和 yarn cluster 的异同

1. 从广义上讲,yarn-cluster 适用于生产环境。而 yarn-client 适用于交互和调试,也就是希望快速地看到


application 的输出。

2. 2. 从深层次的含义讲,yarn-cluster 和 yarn-client 模式的区别其实就是 Application Master 进程的区别,


yarn-cluster 模式下,driver 运行在 AM(Application Master)中,它负责向 YARN 申请资源,并监督作业的运行

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 145 页


状况。当用户提交了作业之后,就可以关掉 Client,作业会继续在 YARN 上运行。然而 yarn-cluster 模式不适
合运行交互类型的作业。而 yarn-client 模式下,Application Master 仅仅向 YARN 请求 executor,Client 会和
请求的 container 通信来调度他们工作,也就是说 Client 不能离开。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 146 页


9.10 Repartition和Coalesce关系与区别

1)关系:

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

2)区别:

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

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

9.11 分别简述Spark中的缓存机制并指出两者的区别与联系

都是做RDD持久化的

cache:内存,不会截断血缘关系,使用计算过程中的数据缓存。

checkpoint:磁盘,截断血缘关系,在ck之前必须没有任何任务提交才会生效,ck过程会额外提交一次任务。

10. V10.0Spark核心基础Spark Core(必记必看)

10.1 如何理解Spark中的血统概念(RDD)

RDD在Lineage依赖方面分为两种Narrow Dependencies与Wide Dependencies用来解决数据容错时的高效性以及


划分任务时候起到重要作用。

10.2 简述Spark的宽窄依赖,以及Spark如何划分stage,每个stage又根据什么决定task个数?

Stage:根据RDD之间的依赖关系的不同将Job划分成不同的Stage,遇到一个宽依赖则划分一个Stage。

Task:Stage是一个TaskSet,将Stage根据分区数划分成一个个的Task。

10.3 请列举Spark的action算子(不少于6个),并简述功能(重点)

1)reduce:

2)collect:

3)first:

4)take:

5)aggregate:

6)countByKey:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 147 页


7)foreach:

8)saveAsTextFile:

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

reduceBykey:

groupByKey:

…ByKey:

10.5 请列举Spark的groupByKey算子底层实现

GroupByKey算子底层实现是基于combineByKey结合ShuffleRDD构建。

combineByKey(createCombiner: V=>C, mergeValue: (C, V) =>C, mergeCombiners: (C, C) =>C):

对相同K,把V合并成一个集合。

1.createCombiner: combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之


前的某个元素的键相同。如果这是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那
个键对应的累加器的初始值

2.mergeValue: 如果这是一个在处理当前分区之前已经遇到的键,它会使用mergeValue()方法将该键的累加器对应
的当前值与这个新的值进行合并

3.mergeCombiners: 由于每个分区都是独立处理的, 因此对于同一个键可以有多个累加器。如果有两个或者更多


的分区都有对应同一个键的累加器, 就需要使用用户提供的 mergeCombiners() 方法将各个分区的结果进行合
并。

10.6 简述Spark的两种核心Shuffle(HashShuffle与SortShuffle)的工作流程(包括未优化的HashShuffle、优
化的HashShuffle、普通的SortShuffle与bypass的SortShuffle)(重点)

未经优化的HashShuffle:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 148 页


优化后的Shuffle:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 149 页


普通的SortShuffle:

当 shuffle read task 的 数 量 小 于 等 于 spark.shuffle.sort。

bypassMergeThreshold 参数的值时(默认为 200),就会启用 bypass 机制。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 150 页


10.7 Spark常用算子reduceByKey与groupByKey的区别(重点)

reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v]。

groupByKey:按照key进行分组,直接进行shuffle。

开发指导:reduceByKey比groupByKey,建议使用。但是需要注意是否会影响业务逻辑。

10.8 简述Spark中共享变量(广播变量和累加器)的基本原理

累加器(accumulator)是Spark中提供的一种分布式的变量机制,其原理类似于mapreduce,即分布式的改变,然
后聚合这些改变。累加器的一个常见用途是在调试时对作业执行过程中的事件进行计数。而广播变量用来高效分发
较大的对象。

共享变量出现的原因:

通常在向 Spark 传递函数时,比如使用 map() 函数或者用 filter() 传条件时,可以使用驱动器程序中定义的变量,


但是集群中运行的每个任务都会得到这些变量的一份新的副本,更新这些副本的值也不会影响驱动器中的对应变
量。

Spark的两个共享变量,累加器与广播变量,分别为结果聚合与广播这两种常见的通信模式突破了这一限制。

10.9 Transformation和action是什么?举几个常用方法

RDD 创建后就可以在 RDD 上进行数据处理。RDD 支持两种操作: 1. 转换(transformation): 即从现有的数据集

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 151 页


创建一个新的数据集 2. 动作(action): 即在数据集上进行计算后,返回一个值给 Driver 程序

RDD 的转化操作是返回一个新的 RDD 的操作,比如 map() 和 filter() ,而行动操作则是向驱动器程序返回结果


或把结果写入外部系统的操作,会触发实际的计算,比如 count() 和 first() 。Spark 对待转化操作和行动操作的
方式很不一样,因此理解你正在进行的操作的类型是很重要的。如果对于一个特定的函数是属于转化操作还是行动
操作感到困惑,你可以看看它的返回值类型:转化操作返回的是 RDD,而行动操作返回的是其他的数据类型。

RDD 中所有的 Transformation 都是惰性的,也就是说,它们并不会直接计算结果。相反的它们只是记住了这些


应用到基础数据集(例如一个文件)上的转换动作。只有当发生一个要求返回结果给 Driver 的 Action 时,这些
Transformation 才会真正运行。

这个设计让 Spark 更加有效的运行。

10.10 宽依赖、窄依赖怎么理解?

窄依赖指的是每一个 parent RDD 的 partition 最多被子 RDD 的一个 partition 使用(一子一亲)

宽依赖指的是多个子 RDD 的 partition 会依赖同一个 parent RDD的 partition(多子一亲)

RDD 作为数据结构,本质上是一个只读的分区记录集合。一个 RDD 可以包含多个分区,每个分区就是一个


dataset 片段。RDD 可以相互依赖。

首先,窄依赖可以支持在同一个 cluster node上,以 pipeline 形式执行多条命令(也叫同一个 stage 的操作),


例如在执行了 map 后,紧接着执行 filter。相反,宽依赖需要所有的父分区都是可用的,可能还需要调用类似
MapReduce 之类的操作进行跨节点传递。

其次,则是从失败恢复的角度考虑。窄依赖的失败恢复更有效,因为它只需要重新计算丢失的 parent partition 即


可,而且可以并行地在不同节点进行重计算(一台机器太慢就会分配到多个节点进行),相反,宽依赖牵涉 RDD 各
级的多个 parent partition。

10.11 Job 和 Task 怎么理解

Job Spark 的 Job 来源于用户执行 action 操作(这是 Spark 中实际意义的 Job),就是从 RDD 中获取结果的
操作,而不是将一个 RDD 转换成另一个 RDD 的 transformation 操作。

Task 一个 Stage 内,最终的 RDD 有多少个 partition,就会产生多少个 task。看一看图就明白了,可以数一数


每个 Stage 有多少个 Task。

10.12 讲讲Checkpoint机制?

通过上述分析可以看出在以下两种情况下,RDD需要加检查点。
(1)DAG中的Lineage过长,如果重算,则开销太大(如在PageRank中)。
(2)在宽依赖上做Checkpoint获得的收益更大。
在RDD计算中,通过检查点机制进行容错,传统做检查点有两种方式:通过冗余数据和日志记录更新操作。在RDD
中的doCheckPoint方法相当于通过冗余数据来缓存数据,而之前介绍的血统就是通过相当粗粒度的记录更新操作来

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 152 页


实现容错的。

10.13 检查点的本质

检查点(本质是通过将RDD写入Disk做检查点)是为了通过lineage做容错的辅助,lineage过长会造成容错成本过
高,这样就不如在中间阶段做检查点容错,如果之后有节点出现问题而丢失分区,从做检查点的RDD开始重做
Lineage,就会减少开销。

10.14 什么时候需要启用checkpoint?

(1)使用了stateful转换-如果 application 中使用了updateStateByKey或reduceByKeyAndWindow等 stateful 操


作,必须提供 checkpoint 目录来允许定时的RDD checkpoint
(2)希望能从意外中恢复Driver。

10.15 导出checkpoint数据

<1> checkpoint 的时机


在 Spark Streaming 中,JobGenerator 用于生成每个 batch 对应的 jobs,它有一个定时器,定时器的周期即初
始化 StreamingContext 时设置的 batchDuration。这个周期一到,JobGenerator 将调用generateJobs方法来生
成并提交 jobs,这之后调用 doCheckpoint 方法来进行 checkpoint。doCheckpoint 方法中,会判断当前时间与
streaming application start 的时间之差是否是 checkpoint duration 的倍数,只有在是的情况下才进行
checkpoint。
<2> checkpoint 的形式

最终 checkpoint 的形式是将类 Checkpoint的实例序列化后写入外部存储,值得一提的是,有专门的一条线程来


做将序列化后的 checkpoint 写入外部存储。

10.16 Spark的内存模型

Spark的Executor的内存管理是基于JVM的内存管理之上,Spark对JVM堆内(On-Heap)空间进行了更为详细的分配,
以便充分利用内存,同时Spark引入堆外内存(OffHeap)内存,可以直接在Worker节点的系统内存中开辟空间,进一
步优化内存使用。

Spark的堆内(On-Heap)空间是由--executor-memory或spark.executor.memory参数配置,Executor内运行的并发
任务共享JVM堆内内存。而且该堆内内存是一种逻辑上的管理,因为对象的释放都是由JVM完成。

Spark引入堆外内存(OffHeap)内存主要是为了提高Shuffle排序的效率,存储优化过的二进制数据。从2.0之后Spark
可以直接操作系统的堆外内存,减少不必要的开销。改参数默认不开启,通过spark.memory.offHeap.ennable参数
启用,并由spark.memory.offHeap.size参数设定堆外空间大小。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 153 页


默认情况下,Spark 仅仅使用了堆内内存。Executor 端的堆内内存区域大致可以分为以下四大块:

 Execution 内存:主要用于存放 Shuffle、Join、Sort、Aggregation 等计算过程中的临时数据

 Storage 内存:主要用于存储 spark 的 cache 数据,例如RDD的缓存、unroll数据;

 用户内存(User Memory):主要用于存储 RDD 转换操作所需要的数据,例如 RDD 依赖等信息。

 预留内存(Reserved Memory):系统预留内存,会用来存储Spark内部对象。

Execution 内存和 Storage 内存动态调整

上面两张图中的 Execution 内存和 Storage 内存之间存在一条虚线,这是为什么呢?

在 Spark 1.5 之前,Execution 内存和 Storage 内存分配是静态的,换句话说就是如果 Execution 内存不足,


即使 Storage 内存有很大空闲程序也是无法利用到的;反之亦然。这就导致我们很难进行内存的调优工作,我们
必须非常清楚地了解 Execution 和 Storage 两块区域的内存分布。而目前 Execution 内存和 Storage 内存可以
互相共享的。也就是说,如果 Execution 内存不足,而 Storage 内存有空闲,那么 Execution 可以从 Storage 中
申请空间;反之亦然。所以上图中的虚线代表 Execution 内存和 Storage 内存是可以随着运作动态调整的,这样
可以有效地利用内存资源。Execution 内存和 Storage 内存之间的动态调整可以概括如下:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 154 页


具体的实现逻辑如下:

1 程序提交的时候我们都会设定基本的 Execution 内存和 Storage 内存区域(通过


spark.memory.storageFraction 参数设置);

2 在程序运行时,如果双方的空间都不足时,则存储到硬盘;将内存中的块存储到磁盘的策略是按照 LRU
规则进行的。若己方空间不足而对方空余时,可借用对方的空间;(存储空间不足是指不足以放下一个完整的 Block);

3 Execution 内存的空间被对方占用后,可让对方将占用的部分转存到硬盘,然后"归还"借用的空间

4 Storage 内存的空间被对方占用后,目前的实现是无法让对方"归还",因为需要考虑 Shuffle过程中的很多


因素,实现起来较为复杂;而且 Shuffle 过程产生的文件在后面一定会被使用到,而 Cache在内存的数据不一定
在后面使用。

注意:上面说的借用对方的内存需要借用方和被借用方的内存类型都一样,都是堆内内存或者都是堆外内存,
不存在堆内内存不够去借用堆外内存的空间。

Task 之间内存分布

为了更好地使用使用内存,Executor 内运行的 Task 之间共享着 Execution 内存。具体的,Spark 内部维护


了一个 HashMap 用于记录每个 Task 占用的内存。当 Task 需要在 Execution 内存区域申请 numBytes 内存,
其先判断 HashMap 里面是否维护着这个 Task 的内存使用情况,如果没有,则将这个 Task 内存使用置为0,并
且以 TaskId 为 key,内存使用为 value 加入到 HashMap 里面。之后为这个 Task 申请 numBytes 内存,如果
Execution 内存区域正好有大于 numBytes 的空闲内存,则在 HashMap 里面将当前 Task 使用的内存加上
numBytes,然后返回;如果当前 Execution 内存区域无法申请到每个 Task 最小可申请的内存,则当前 Task 被
阻塞,直到有其他任务释放了足够的执行内存,该任务才可以被唤醒。每个 Task 可以使用 Execution 内存大小范
围为 1/2N ~ 1/N,其中 N 为当前 Executor 内正在运行的 Task 个数。一个 Task 能够运行必须申请到最小内存
为 (1/2N * Execution 内存);当 N = 1 的时候,Task 可以使用全部的 Execution 内存。

比如如果 Execution 内存大小为 10GB,当前 Executor 内正在运行的 Task 个数为5,则该 Task 可以申请
的内存范围为 10 / (2 * 5) ~ 10 / 5,也就是 1GB ~ 2GB的范围。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 155 页


10.17 Spark的小文件读取?

10.18 如何理解Spark的RDD数据结构分区?

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 156 页


10.19 Spark何时缓存数据

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 157 页


10.20 Spark如何获取每个分区的索引?

10.21 Spark的Job调度模式

用户通过不同的线程提交的Job可以并发运行,但是受到资源的限制。Job到调度池(pool)内申请资源,调
度池会根据工程的配置,决定采用哪种调度模式。

FIFO模式

在默认情况下,Spark的调度器以FIFO(先进先出)方式调度Job的执行。每个Job被切分为多个Stage。第一
个Job优先获取所有可用的资源,接下来第二个Job再获取剩余资源。以此类推,如果第一个Job并没有占用所有的
资源,则第二个Job还可以继续获取剩余资源,这样多个Job可以并行运行。如果第一个Job很大,占用所有资源,

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 158 页


则第二个Job就需要等待第一个任务执行完,释放空余资源,再申请和分配Job。如果是相同的Job不同的Stage,则
优先执行较早的Stage。

在FAIR共享模式调度下,Spark在多Job之间以轮询(round robin)方式为任务分配资源,所有的任务拥有大
致相当的优先级来共享集群的资源。这就意味着当一个长任务正在执行时,短任务仍可以分配到资源,提交并执行,
并且获得不错的响应时间。这样就不用像以前一样需要等待长任务执行完才可以。这种调度模式很适合多用户的场
景。

从spark0.8开始,可以配置公平调度器,spark 分配tasks是以一种轮询的方式,短的job可以提前被执行完。
可以在sparkContext中设置spark.scheduler.mode=FAIR

10.22 广播变量使用需要注意什么?

1、广播变量在Driver端定义

2、广播变量在Execoutor只能读取不能修改

3、广播变量的值只能在Driver端修改

4、不能将RDD广播出去,RDD不存数据,可以将RDD的结果广播出去,rdd.collect()

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 159 页


5、广播变量使用BlockManager管理,广播变量一般存储在内存中(Executoion区域)

11. V10.0Spark核心基础Spark SQL

11.1 简述SparkSQL中RDD、DataFrame、DataSet三者区别与联系? (笔试重点)

1)RDD

优点:

编译时类型安全

编译时就能检查出类型错误

面向对象的编程风格

直接通过类名点的方式来操作数据

缺点:

序列化和反序列化的性能开销

无论是集群间的通信, 还是IO操作都需要对对象的结构和数据进行序列化和反序列化。

GC的性能开销 ,频繁的创建和销毁对象, 势必会增加GC

2)DataFrame

DataFrame引入了schema和off-heap

schema : RDD每一行的数据, 结构都是一样的,这个结构就存储在schema中。 Spark通过schema就能够读懂数据,


因此在通信和IO时就只需要序列化和反序列化数据, 而结构的部分就可以省略了。

3)DataSet

DataSet结合了RDD和DataFrame的优点,并带来的一个新的概念Encoder。

当序列化数据时,Encoder产生字节码与off-heap进行交互,能够达到按需访问数据的效果,而不用反序列化整个
对象。Spark还没有提供自定义Encoder的API,但是未来会加入。

三者之间的转换:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 160 页


11.2 SparkSQL中join操作与left join操作的区别?

join和sql中的inner join操作很相似,返回结果是前面一个集合和后面一个集合中匹配成功的,过滤掉关联不
上的。

leftJoin类似于SQL中的左外关联left outer join,返回结果以第一个RDD为主,关联不上的记录为空。

部分场景下可以使用left semi join替代left join:

因为 left semi join 是 in(keySet) 的关系,遇到右表重复记录,左表会跳过,性能更高,而 left join 则会一直


遍历。但是left semi join 中最后 select 的结果中只许出现左表中的列名,因为右表只有 join key 参与关联计算了

11.3 coalesce和repartition的区别

coalesce和repartition都用于改变分区,coalesce用于缩小分区且不会进行shuffle,repartition用于增大分区(提供
并行度)会进行shuffle,在spark中减少文件个数会使用coalesce来减少分区来到这个目的。但是如果数据量过大,
分区数过少会出现OOM所以coalesce缩小分区个数也需合理

11.4 cache缓存级别

DataFrame的cache默认采用 MEMORY_AND_DISK 这和RDD 的默认方式不一样RDD cache 默认采用


MEMORY_ONLY

11.5 释放缓存和缓存

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

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

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 161 页


11.6 Spark Shuffle默认并行度

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

11.7 kryo序列化

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

11.8 创建临时表和全局临时表

DataFrame.createTempView() 创建普通临时表

DataFrame.createGlobalTempView() 创建全局临时表

11.9 BroadCast join 广播join

原理:先将小表数据查询出来聚合到driver端,再广播到各个executor端,使表与表join时

进行本地join,避免进行网络传输产生shuffle。

使用场景:大表join小表 只能广播小表

11.10 控制Spark reduce缓存,调优Shuffle

spark.reducer.maxSizeInFilght 此参数为reduce task能够拉取多少数据量的一个参数默认48MB,当集群资源足够


时,增大此参数可减少reduce拉取数据量的次数,从而达到优化shuffle的效果,一般调大为96MB,资源够大可继续
往上跳。

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

11.11 注册UDF函数

SparkSession.udf.register 方法进行注册

11.12 SparkSQL中join操作与left join操作的区别?

join 和 sql 中的 inner join 操作很相似,返回结果是前面一个集合和后面一个集合中匹配成功的,过滤掉关联不上的。

leftJoin 类似于 SQL 中的左外关联 left outer join,返回结果以第一个 RDD 为主,关联不上的记录为空。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 162 页


部分场景下可以使用 left semi join 替代 left join:

因为 left semi join 是 in(keySet) 的关系,遇到右表重复记录,左表会跳过,性能更高,而 left join 则会一直遍历。

但是 left semi join 中最后 select 的结果中只许出现左表中的列名,因为右表只有 join key 参与关联计算了

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 163 页


12. V10.0Spark核心基础Spark机器学习和图计算

12.1 Spark 机器学习和 Spark 图计算接触过没有,能举例说明你用它做过什么吗?

Spark 提供了很多机器学习库,我们只需要填入数据,设置参数就可以用了。使用起来非常方便。 另外一方面,


由于它把所有的东西都写到了内部,我们无法修改其实现过程。要想修改里面的某个环节,还的修改源码,重新编
译。 比如 kmeans 算法,如果没有特殊需求,很方便。但是spark内部使用的两个向量间的距离是欧式距离。如
果你想改为余弦或者马氏距离,就的重新编译源码了。 Spark 里面的机器学习库都是一些经典的算法,这些代码
网上也好找。这些代码使用起来叫麻烦,但是很灵活。 Spark 有一个很大的优势,那就是 RDD。模型的训练完全
是并行的。

12.2 Spark 的 ML 和 MLLib 两个包区别和联系

技术角度上,面向的数据集类型不一样: ML 的 API 是面向 Dataset 的(Dataframe 是 Dataset 的子集,也


就是 Dataset[Row]),mllib 是面对 RDD 的。Dataset 和 RDD 有啥不一样呢?Dataset 的底端是 RDD。Dataset
对 RDD 进行了更深一层的优化,比如说有 sql 语言类似的黑魔法,Dataset 支持静态类型分析所以在 compile
time 就能报错,各种 combinators(map,foreach 等)性能会更好,等等。
编程过程上,构建机器学习算法的过程不一样: ML 提倡使用 pipelines,把数据想成水,水从管道的一段流入,
从另一端流出。ML 是1.4比 Mllib 更高抽象的库,它解决如果简洁的设计一个机器学习工作流的问题,而不是具体
的某种机器学习算法。未来这两个库会并行发展。

13. V10.0Spark面试进阶

13.1 Spark master使用zookeeper进行HA的,有哪些元数据保存在Zookeeper?

答:spark通过这个参数spark.deploy.zookeeper.dir指定master元数据在zookeeper中保存的位置,包括

Worker,Driver和Application以及Executors。standby节点要从zk中,获得元数据信息,恢复集群运行状态,才能

对外继续提供服务,作业提交资源申请等,在恢复前是不能接受请求的。另外,Master切换需要注意2点

1)在Master切换的过程中,所有的已经在运行的程序皆正常运行!因为Spark Application在运行前就已经通

过Cluster Manager获得了计算资源,所以在运行时Job本身的调度和处理和Master是没有任何关系的!

2) 在Master的切换过程中唯一的影响是不能提交新的Job:一方面不能够提交新的应用程序给集群,因为只

有Active Master才能接受新的程序的提交请求;另外一方面,已经运行的程序中也不能够因为Action操作触发新的

Job的提交请求;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 164 页


13.2 Spark master HA 主从切换过程不会影响集群已有的作业运行,为什么?

答:因为程序在运行之前,已经申请过资源了,driver和Executors通讯,不需要和master进行通讯的。

13.3 如何配置spark master的HA?

1)配置zookeeper

2)修改spark_env.sh文件,spark的master参数不在指定,添加如下代码到各个master节点

export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER

-Dspark.deploy.zookeeper.url=zk01:2181,zk02:2181,zk03:2181 -Dspark.deploy.zookeeper.dir=/spark"

3)将spark_env.sh分发到各个节点

4)找到一个master节点,执行./start-all.sh,会在这里启动主master,其他的master备节点,启动master命

令: ./sbin/start-master.sh

5)提交程序的时候指定master的时候要指定三台master,例如

./spark-shell --master spark://master01:7077,master02:7077,master03:7077

13.4 driver的功能是什么?

1)一个Spark作业运行时包括一个Driver进程,也是作业的主进程,具有main函数,并且有SparkContext的实例,

是程序的人口点;

2)功能:负责向集群申请资源,向master注册信息,负责了作业的调度,负责作业的解析、生成Stage并调度Task

到Executor上。包括DAGScheduler,TaskScheduler。

13.5 Spark中Work的主要工作是什么?

主要功能:管理当前节点内存,CPU的使用状况,接收master分配过来的资源指令,通过ExecutorRunner启

动程序分配任务,worker就类似于包工头,管理分配新进程,做计算的服务,相当于process服务。

需要注意的是:

1)worker会不会汇报当前信息给master,worker心跳给master主要只有workid,它不会发送资源信息以心跳

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 165 页


的方式给mater,master分配的时候就知道work,只有出现故障的时候才会发送资源。

2)worker不会运行代码,具体运行的是Executor是可以运行具体appliaction写的业务逻辑代码,操作代码的

节点,它不会运行程序的代码的。

13.6 Spark为什么比mapreduce快?

1)基于内存计算,减少低效的磁盘交互;

2)高效的调度算法,基于DAG;

3)容错机制Linage,精华部分就是DAG和Lingae

13.7 简单说一下hadoop和spark的shuffle相同和差异?

1)从 high-level 的角度来看,两者并没有大的差别。 都是将 mapper(Spark 里是 ShuffleMapTask)的输出进

行 partition,不同的 partition 送到不同的 reducer(Spark 里 reducer 可能是下一个 stage 里的

ShuffleMapTask,也可能是 ResultTask)。Reducer 以内存作缓冲区,边 shuffle 边 aggregate 数据,等到数据

aggregate 好以后进行 reduce() (Spark 里可能是后续的一系列操作)。

2)从 low-level 的角度来看,两者差别不小。 Hadoop MapReduce 是 sort-based,进入 combine() 和 reduce()

的 records 必须先 sort。这样的好处在于 combine/reduce() 可以处理大规模的数据,因为其输入数据可以通过

外排得到(mapper 对每段数据先做排序,reducer 的 shuffle 对排好序的每段数据做归并)。目前的 Spark 默

认选择的是 hash-based,通常使用 HashMap 来对 shuffle 来的数据进行 aggregate,不会对数据进行提前排序。

如果用户需要经过排序的数据,那么需要自己调用类似 sortByKey() 的操作;如果你是Spark 1.1的用户,可以将

spark.shuffle.manager设置为sort,则会对数据进行排序。在Spark 1.2中,sort将作为默认的Shuffle实现。

3)从实现角度来看,两者也有不少差别。 Hadoop MapReduce 将处理流程划分出明显的几个阶段:map(), spill,

merge, shuffle, sort, reduce() 等。每个阶段各司其职,可以按照过程式的编程思想来逐一实现每个阶段的功能。

在 Spark 中,没有这样功能明确的阶段,只有不同的 stage 和一系列的 transformation(),所以 spill, merge,

aggregate 等操作需要蕴含在 transformation() 中。

如果我们将 map 端划分数据、持久化数据的过程称为 shuffle write,而将 reducer 读入数据、aggregate 数据

的过程称为 shuffle read。那么在 Spark 中,问题就变为怎么在 job 的逻辑或者物理执行图中加入 shuffle write

和 shuffle read 的处理逻辑?以及两个处理逻辑应该怎么高效实现?

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 166 页


Shuffle write由于不要求数据有序,shuffle write 的任务很简单:将数据 partition 好,并持久化。之所以要持久化,

一方面是要减少内存存储空间压力,另一方面也是为了 fault-tolerance。

13.8 Mapreduce和Spark的都是并行计算,那么他们有什么相同和区别

答:两者都是用mr模型来进行并行计算:

1)hadoop的一个作业称为job,job里面分为map task和reduce task,每个task都是在自己的进程中运行的,当task

结束时,进程也会结束。

2)spark用户提交的任务成为application,一个application对应一个sparkcontext,app中存在多个job,每触发一

次action操作就会产生一个job。这些job可以并行或串行执行,每个job中有多个stage,stage是shuffle过程中

DAGSchaduler通过RDD之间的依赖关系划分job而来的,每个stage里面有多个task,组成taskset有TaskSchaduler

分发到各个executor中执行,executor的生命周期是和app一样的,即使没有job运行也是存在的,所以task可以快

速启动读取内存进行计算。

3)hadoop的job只有map和reduce操作,表达能力比较欠缺而且在mr过程中会重复的读写hdfs,造成大量的io操

作,多个job需要自己管理关系。

spark的迭代计算都是在内存中进行的,API中提供了大量的RDD操作如join,groupby等,而且通过DAG图可以实

现良好的容错。

13.9 spark工作机制?

答:用户在client端提交作业后,会由Driver运行main方法并创建spark context上下文。

执行add算子,形成dag图输入dagscheduler,按照add之间的依赖关系划分stage输入task scheduler。 task

scheduler会将stage划分为task set分发到各个节点的executor中执行。

13.10 spark的优化怎么做?

答: spark调优比较复杂,但是大体可以分为三个方面来进行,1)平台层面的调优:防止不必要的jar包分发,提

高数据的本地性,选择高效的存储格式如parquet,2)应用程序层面的调优:过滤操作符的优化降低过多小任务,

降低单条记录的资源开销,处理数据倾斜,复用RDD进行缓存,作业并行化执行等等,3)JVM层面的调优:设置

合适的资源量,设置合理的JVM,启用高效的序列化方法如kyro,增大off head内存等等

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 167 页


13.11 描述Yarn执行一个任务的过程?

1)客户端client向ResouceManager提交Application,ResouceManager接受Application

并根据集群资源状况选取一个node来启动Application的任务调度器driver(ApplicationMaster)

2)ResouceManager找到那个node,命令其该node上的nodeManager来启动一个新的

JVM进程运行程序的driver(ApplicationMaster)部分,driver(ApplicationMaster)启动时会首先向

ResourceManager注册,说明由自己来负责当前程序的运行。

3)driver(ApplicationMaster)开始下载相关jar包等各种资源,基于下载的jar等信息决定向ResourceManager申

请具体的资源内容。

4)ResouceManager接受到driver(ApplicationMaster)提出的申请后,会最大化的满足

资源分配请求,并发送资源的元数据信息给driver(ApplicationMaster);

5)driver(ApplicationMaster)收到发过来的资源元数据信息后会根据元数据信息发指令给具体机器上的

NodeManager,让其启动具体的container。

6)NodeManager收到driver发来的指令,启动container,container启动后必须向driver(ApplicationMaster)注

册。

7)driver(ApplicationMaster)收到container的注册,开始进行任务的调度和计算,直到

任务完成。

补充:如果ResourceManager第一次没有能够满足driver(ApplicationMaster)的资源请求,后续发现有空闲的资

源,会主动向driver(ApplicationMaster)发送可用资源的元数据信息以提供更多的资源用于当前程序的运行。

13.12 Yarn中的container是由谁负责销毁的,在Hadoop Mapreduce中container可以复用么?

答:ApplicationMaster负责销毁,在Hadoop Mapreduce不可以复用,在spark on yarn程序container可以复用

13.13 spark on yarn Cluster 模式下,ApplicationMaster和driver是在同一个进程么?

是,driver 位于ApplicationMaster进程中。该进程负责申请资源,还负责监控程序、资源的动态情况。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 168 页


13.14 如何使用命令查看application运行的日志信息

yarn logs -applicationId <app ID>

13.15 Spark on Yarn 模式有哪些优点?

1)与其他计算框架共享集群资源(eg.Spark框架与MapReduce框架同时运行,如果不用Yarn进行资源分配,

MapReduce分到的内存资源会很少,效率低下);资源按需分配,进而提高集群资源利用等。

2)相较于Spark自带的Standalone模式,Yarn的资源分配更加细致

3)Application部署简化,例如Spark,Storm等多种框架的应用由客户端提交后,由Yarn负责资源的管理和调度,

利用Container作为资源隔离的单位,以它为单位去使用内存,cpu等。

4)Yarn通过队列的方式,管理同时运行在Yarn集群中的多个服务,可根据不同类型的应用程序负载情况,调整对

应的资源使用量,实现资源弹性管理。

13.16 谈谈你对container的理解?

1)Container作为资源分配和调度的基本单位,其中封装了的资源如内存,CPU,磁盘,网络带宽等。 目前yarn

仅仅封装内存和CPU

2)Container由ApplicationMaster向ResourceManager申请的,由ResouceManager中的资源调度器异步分配给

ApplicationMaster

3)Container的运行是由ApplicationMaster向资源所在的NodeManager发起的,Container运行时需提供内部执行

的任务命令.

13.17 运行在yarn中Application有几种类型的container?

1)运行ApplicationMaster的Container:这是由ResourceManager(向内部的资源调度器)申请和启动的,用户提

交应用程序时,可指定唯一的ApplicationMaster所需的资源;

2)运行各类任务的Container:这是由ApplicationMaster向ResourceManager申请的,并由ApplicationMaster与

NodeManager通信以启动。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 169 页


13.18 Spark on Yarn架构是怎么样的?(画图)

Yarn提到的App Master可以理解为Spark中Standalone模式中的driver。Container中运行着Executor,在Executor中

以多线程并行的方式运行Task。运行过程和第二题相似。

1、客户端提交一个Application,在客户端启动一个Driver进程。

2、Driver进程会向RS(ResourceManager)发送请求,启动AM(ApplicationMaster)的资源。

3、RS收到请求,随机选择一台NM(NodeManager)启动AM。这里的NM相当于Standalone中的Worker
节点。

4、AM启动后,会向RS请求一批container资源,用于启动Executor.

RS会找到一批NM返回给AM,用于启动Executor。

5、AM会向NM发送命令启动Executor。

6、Executor启动后,会反向注册给Driver,Driver发送task到Executor,执行情况和结果返回给Driver端。

13.19 Executor启动时,资源通过哪几个参数指定?

1)num-executors是executor的数量

2)executor-memory 是每个executor使用的内存

3)executor-cores 是每个executor分配的CPU

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 170 页


13.20 列出你所知道的调度器,说明其工作原理

1)Fifo schedular 默认的调度器 先进先出

2)Capacity schedular 计算能力调度器 选择占用内存小 优先级高的

3)Fair schedular 调肚脐 公平调度器 所有job 占用相同资源

13.21 YarnClient模式下,执行Spark SQL报这个错,Exception in thread "Thread-2"


java.lang.OutOfMemoryError: PermGen space,但是在Yarn Cluster模式下正常运行,可能是什么原因?

1)原因查询过程中调用的是Hive的获取元数据信息、SQL解析,并且使用Cglib等进行序列化反序列化,中间可能

产生较多的class文件,导致JVM中的持久代使用较多Cluster模式的持久代默认大小是64M,Client模式的持久代默

认大小是32M,而Driver端进行SQL处理时,其持久代的使用可能会达到90M,导致OOM溢出,任务失败。

yarn-cluster模式下出现,yarn-client模式运行时倒是正常的,原来在$SPARK_HOME/bin/spark-class文件中已经设

置了持久代大小:

JAVA_OPTS="-XX:MaxPermSize=256m $OUR_JAVA_OPTS"

2)解决方法:在Spark的conf目录中的spark-defaults.conf里,增加对Driver的JVM配置,因为Driver才负责SQL的解

析和元数据获取。配置如下:

spark.driver.extraJavaOptions -XX:PermSize=128M -XX:MaxPermSize=256M

13.22 spark.driver.extraJavaOptions这个参数是什么意思,你们生产环境配了多少?

传递给executors的JVM选项字符串。例如GC设置或者其它日志设置。注意,在这个选项中设置Spark属性或者堆大

小是不合法的。Spark属性需要用SparkConf对象或者spark-submit脚本用到的spark-defaults.conf文件设置。堆内

存可以通过spark.executor.memory设置。

13.23 导致Executor产生FULL gc 的原因,可能导致什么问题?

答:可能导致Executor僵死问题,海量数据的shuffle和数据倾斜等都可能导致full gc。以shuffle为例,伴随着大量

的Shuffle写操作,JVM的新生代不断GC,Eden Space写满了就往Survivor Space写,同时超过一定大小的数据会直

接写到老生代,当新生代写满了之后,也会把老的数据搞到老生代,如果老生代空间不足了,就触发FULL GC,还

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 171 页


是空间不够,那就OOM错误了,此时线程被Blocked,导致整个Executor处理数据的进程被卡住。

13.24 Spark执行任务时出现java.lang.OutOfMemoryError: GC overhead limit exceeded和


java.lang.OutOfMemoryError: java heap space原因和解决方法?

答:原因:加载了太多资源到内存,本地的性能也不好,gc时间消耗的较多

解决方法:

1)增加参数,-XX:-UseGCOverheadLimit,关闭这个特性,同时增加heap大小,-Xmx1024m

2)下面这个两个参数调大点

export SPARK_EXECUTOR_MEMORY=6000M

export SPARK_DRIVER_MEMORY=7000M

13.25 Spark使用parquet文件存储格式能带来哪些好处?

1)如果说HDFS 是大数据时代分布式文件系统首选标准,那么parquet则是整个大数据时代文件存储格式实时

首选标准。

2)速度更快:从使用spark sql操作普通文件CSV和parquet文件速度对比上看,绝大多数情况会比使用csv等普

通文件速度提升10倍左右,在一些普通文件系统无法在spark上成功运行的情况下,使用parquet很多时候可以成功

运行。

3)parquet的压缩技术非常稳定出色,在spark sql中对压缩技术的处理可能无法正常的完成工作(例如会导

致lost task,lost executor)但是此时如果使用parquet就可以正常的完成。

4)极大的减少磁盘I/o,通常情况下能够减少75%的存储空间,由此可以极大的减少spark sql处理数据的时候的

数据输入内容,尤其是在spark1.6x中有个下推过滤器在一些情况下可以极大的减少磁盘的IO和内存的占用,(下推

过滤器)。

5)spark 1.6x parquet方式极大的提升了扫描的吞吐量,极大提高了数据的查找速度spark1.6和spark1.5x相比

而言,提升了大约1倍的速度,在spark1.6X中,操作parquet时候cpu也进行了极大的优化,有效的降低了cpu消耗。

6)采用parquet可以极大的优化spark的调度和执行。我们测试spark如果用parquet可以有效的减少stage的执

行消耗,同时可以优化执行路径。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 172 页


13.26 Spark应用程序的执行过程是什么?

1)构建Spark Application的运行环境(启动SparkContext),SparkContext向资源管理器(可以是Standalone、

Mesos或YARN)注册并申请运行Executor资源;

2)资源管理器分配Executor资源并启动StandaloneExecutorBackend,Executor运行情况将随着心跳发送到

资源管理器上;

3)SparkContext构建成DAG图,将DAG图分解成Stage,并把Taskset发送给Task Scheduler。Executor向

SparkContext申请Task,Task Scheduler将Task发放给Executor运行同时SparkContext将应用程序代码发放给

Executor;

4)Task在Executor上运行,运行完毕释放所有资源。

13.27 如何理解Standalone模式下,Spark资源分配是粗粒度的?

spark默认情况下资源分配是粗粒度的,也就是说程序在提交时就分配好资源,后面执行的时候使用分配好的

资源,除非资源出现了故障才会重新分配。比如Spark shell启动,已提交,一注册,哪怕没有任务,worker都会分

配资源给executor。

13.28 FAIR调度模式的优点和缺点?

所有的任务拥有大致相当的优先级来共享集群资源,spark多以轮训的方式为任务分配资源,不管长任务还是

端任务都可以获得资源,并且获得不错的响应时间,对于短任务,不会像FIFO那样等待较长时间了,通过参数

spark.scheduler.mode 为FAIR指定。

13.29 请列举你碰到的CPU密集型的应用场景,你有做哪些优化?

1)CPU 密集型指的是系统的 硬盘/内存 效能 相对 CPU 的效能 要好很多,此时,系统运作,大部分的状

况是 CPU Loading 100%,CPU 要读/写 I/O (硬盘/内存),I/O在很短的时间就可以完成,而 CPU 还有许多运算

要处理,CPU Loading 很高。->cpu是瓶颈。

I/O 密集型指的是系统的CPU效能相对硬盘/内存的效能要好很多,此时,系统运作,大部分的状况是 CPU 在

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 173 页


等 I/O (硬盘/内存) 的读/写,此时 CPU Loading 不高。->IO是瓶颈。

2)CPU密集型主要特点是要进行大量的计算,常见应用场景有:图计算、大量的逻辑判断程序,机器学习等,

Mahout其实就是针对CPU密集的一个apache项目。

优化的点主要有,1)降低任务的并行执行,务越多,花在任务切换的时间就越多,CPU执行任务的效率就越

低,2)优化计算逻辑,减少计算逻辑的复杂度,3)尽量减少使用高强度压缩方式,对原始数据的压缩和解压缩会

增加CPU的负担

13.30 spark怎么整合hive?

1)将hive的配置文件hive-site.xml复制到Spark conf目录下

2)根据hive的配置参数hive.metastore.uris的情况,采用不同的集成方式

a. jdbc方式:hive.metastore.uris没有给定配置值,为空(默认情况),SparkSQL通过hive配置的javax.jdo.option.XXX

相关配置值直接连接metastore数据库直接获取hive表元数据, 需要将连接数据库的驱动添加到Spark应用的

classpath中

b.metastore服务方式:hive.metastore.uris给定了具体的参数值,SparkSQL通过连接hive提供的metastore服务来获

取hive表的元数据, 直接启动hive的metastore服务即可完成SparkSQL和Hive的集成:

3)使用metastore服务方式,对hive-site.xml进行配置

<property>

<name>hive.metastore.uris</name>

<value> trhift://mfg-hadoop:9083</value>

</property>

4)启动hive service metastore服务

bin/hive --service metastore &

5)启动spark-sql测试,执行 show databases命令,检查是不是和hive的数据库一样的。

13.31 Spark读取数据,是几个Partition呢?

从2方面介绍和回答,一是说下partition是什么,二是说下partition如何建的。

1)spark中的partion是弹性分布式数据集RDD的最小单元,RDD是由分布在各个节点上的partion组成的。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 174 页


partion是指的spark在计算过程中,生成的数据在计算空间内最小单元,同一份数据(RDD)的partion大小不一,

数量不定,是根据application里的算子和最初读入的数据分块数量决定的,这也是为什么叫“弹性分布式”数据集的

原因之一。Partition不会根据文件的偏移量来截取的(比如有3个Partition,1个是头多少M的数据,1个是中间多少

M的数据,1个是尾部多少M的数据),而是从一个原文件这个大的集合里根据某种计算规则抽取符合的数据来形成

一个Partition的;

2)如何创建分区,有两种情况,创建 RDD 时和通过转换操作得到新 RDD 时。对于前者,在调用 textFile 和

parallelize 方法时候手动指定分区个数即可。例如 sc.parallelize(Array(1, 2, 3, 5, 6), 2) 指定创建得到的 RDD 分区

个数为 2。如果没有指定,partition数等于block数;对于后者,直接调用 repartition 方法即可。实际上分区的个

数是根据转换操作对应多个 RDD 之间的依赖关系来确定,窄依赖子 RDD 由父 RDD 分区个数决定,例如 map

操作,父 RDD 和子 RDD 分区个数一致;Shuffle 依赖则由分区器(Partitioner)决定,例如 groupByKey(new

HashPartitioner(2)) 或者直接 groupByKey(2) 得到的新 RDD 分区个数等于 2。

13.32 spark-submit的时候如何引入外部jar包

方法一:spark-submit –jars

根据spark官网,在提交任务的时候指定–jars,用逗号分开。这样做的缺点是每次都要指定jar包,如果jar包少

的话可以这么做,但是如果多的话会很麻烦。

命令:spark-submit --master yarn-client --jars ***.jar,***.jar

方法二:extraClassPath

提交时在spark-default中设定参数,将所有需要的jar包考到一个文件里,然后在参数中指定该目录就可以了,

较上一个方便很多:

spark.executor.extraClassPath=/home/hadoop/wzq_workspace/lib/*

spark.driver.extraClassPath=/home/hadoop/wzq_workspace/lib/*

需要注意的是,你要在所有可能运行spark任务的机器上保证该目录存在,并且将jar包考到所有机器上。这样

做的好处是提交代码的时候不用再写一长串jar了,缺点是要把所有的jar包都拷一遍。

13.33 cache后面能不能接其他算子,它是不是action操作?

答:cache可以接其他算子,但是接了算子之后,起不到缓存应有的效果,因为会重新触发cache。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 175 页


cache不是action操作

13.34 reduceByKey是不是action?

答:不是,很多人都会以为是action,reduce rdd是action

13.35 Spark提交你的jar包时所用的命令是什么?

答:spark-submit。

13.36 Spark有哪些聚合类的算子,我们应该尽量避免什么类型的算子?

答:在我们的开发过程中,能避免则尽可能避免使用reduceByKey、join、distinct、repartition等会进行shuffle的算

子,尽量使用map类的非shuffle算子。这样的话,没有shuffle操作或者仅有较少shuffle操作的Spark作业,可以大

大减少性能开销。

13.37 对于Spark中的数据倾斜问题你有什么好的方案?

1)前提是定位数据倾斜,是OOM了,还是任务执行缓慢,看日志,看WebUI

2)解决方法,有多个方面

(1)避免不必要的shuffle,如使用广播小表的方式,将reduce-side-join提升为map-side-join

(2)分拆发生数据倾斜的记录,分成几个部分进行,然后合并join后的结果

(3)改变并行度,可能并行度太少了,导致个别task数据压力大

(4)两阶段聚合,先局部聚合,再全局聚合

(5)自定义paritioner,分散key的分布,使其更加均匀

详细解决方案参考我们的教案或者博文《Spark数据倾斜优化方法》

13.38 RDD创建有哪几种方式?

1)使用程序中的集合创建rdd

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 176 页


2)使用本地文件系统创建rdd

3)使用hdfs创建rdd,

4)基于数据库db创建rdd

5)基于Nosql创建rdd,如hbase

6)基于数据流,如socket创建rdd

如果只回答了前面三种,是不够的,只能说明你的水平还是入门级的,实践过程中有很多种创建方式。

13.39 Spark并行度怎么设置比较合适

spark并行度,每个core承载2~4个partition,如,32个core,那么64~128之间的并行度,也就是设置64~128个partion,

并行读和数据规模无关,只和内存使用量和cpu使用时间有关。

13.40 Spark中数据的位置是被谁管理的?

每个数据分片都对应具体物理位置,数据的位置是被blockManager,无论

数据是在磁盘,内存还是tacyan,都是由blockManager管理。

13.41 Spark的数据本地性有哪几种?

答:Spark中的数据本地性有三种:

1)PROCESS_LOCAL是指读取缓存在本地节点的数据

2)NODE_LOCAL是指读取本地节点硬盘数据

3)ANY是指读取非本地节点数据

通常读取数据PROCESS_LOCAL>NODE_LOCAL>ANY,尽量使数据以PROCESS_LOCAL或NODE_LOCAL方

式读取。其中PROCESS_LOCAL还和cache有关,如果RDD经常用的话将该RDD cache到内存中,注意,由于cache

是lazy的,所以必须通过一个action的触发,才能真正的将该RDD cache到内存中。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 177 页


13.42 rdd有几种操作类型?

1)transformation,rdd由一种转为另一种rdd

2)action

3)cronroller,crontroller是控制算子,cache,persist,对性能和效率的有很好的支持

三种类型,不要回答只有2种操作

13.43 collect功能是什么,其底层是怎么实现的?

答:driver通过collect把集群中各个节点的内容收集过来汇总成结果,collect返回结果是Array类型的,collect把各

个节点上的数据抓过来,抓过来数据是Array型,collect对Array抓过来的结果进行合并,合并后Array中只有一个元

素,是tuple类型(KV类型的)的。

13.44 Spark程序执行,有时候默认为什么会产生很多task,怎么修改默认task执行个数?

1)因为输入数据有很多task,尤其是有很多小文件的时候,有多少个输入

block就会有多少个task启动;

2)spark中有partition的概念,每个partition都会对应一个task,task越多,在处理大规模数据的时候,就会越有效

率。不过task并不是越多越好,如果平时测试,或者数据量没有那么大,则没有必要task数量太多。

3)参数可以通过spark_home/conf/spark-default.conf配置文件设置:

spark.sql.shuffle.partitions 50

spark.default.parallelism 10

第一个是针对spark sql的task数量

第二个是非spark sql程序设置生效

13.45 为什么Spark Application在没有获得足够的资源,job就开始执行了,可能会导致什么问题发生?

答:会导致执行该job时候集群资源不足,导致执行job结束也没有分配足够的资源,分配了部分Executor,该job

就开始执行task,应该是task的调度线程和Executor资源申请是异步的;如果想等待申请完所有的资源再执行job的:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 178 页


需要将spark.scheduler.maxRegisteredResourcesWaitingTime设置的很大;

spark.scheduler.minRegisteredResourcesRatio 设置为1,但是应该结合实际考虑

否则很容易出现长时间分配不到资源,job一直不能运行的情况。

13.46 Spark为什么要持久化,一般什么场景下要进行persist操作?

为什么要进行持久化?

spark所有复杂一点的算法都会有persist身影,spark默认数据放在内存,spark很多内容都是放在内存的,非常适合

高速迭代,1000个步骤

只有第一个输入数据,中间不产生临时数据,但分布式系统风险很高,所以容易出错,就要容错,rdd出错或者分

片可以根据血统算出来,如果没有对父rdd进行persist 或者cache的化,就需要重头做。

以下场景会使用persist

1)某个步骤计算非常耗时,需要进行persist持久化

2)计算链条非常长,重新恢复要算很多步骤,很好使,persist

3)checkpoint所在的rdd要持久化persist,

lazy级别,框架发现有checnkpoint,checkpoint时单独触发一个job,需要重算一遍,checkpoint前,要持久化,

写个rdd.cache或者rdd.persist,将结果保存起来,再写checkpoint操作,这样执行起来会非常快,不需要重新计算

rdd链条了。checkpoint之前一定会进行persist。

4)shuffle之后为什么要persist,shuffle要进性网络传输,风险很大,数据丢失重来,恢复代价很大

5)shuffle之前进行persist,框架默认将数据持久化到磁盘,这个是框架自动做的。

13.47 为什么要进行序列化

序列化可以减少数据的体积,减少存储空间,高效存储和传输数据,不好的是使用的时候要反序列化,非常消耗CPU。

13.48 下面这段代码输出结果是什么?

def joinRdd(sc:SparkContext) {

val name= Array(

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 179 页


Tuple2(1,"spark"),

Tuple2(2,"tachyon"),

Tuple2(3,"hadoop")

val score= Array(

Tuple2(1,100),

Tuple2(2,90),

Tuple2(3,80)

val namerdd=sc.parallelize(name);

val scorerdd=sc.parallelize(score);

val result = namerdd.join(scorerdd);

result .collect.foreach(println);

答案:

(1,(Spark,100))

(2,(tachyon,90))

(3,(hadoop,80))

13.49 Spark累加器有哪些特点?

1)累加器在全局唯一的,只增不减,记录全局集群的唯一状态;

2)在exe中修改它,在driver读取;

3)executor级别共享的,广播变量是task级别的共享两个application不可以共享累加器,但是同一个app不同

的job可以共享。

13.50 spark hashParitioner的弊端是什么?

HashPartitioner分区的原理很简单,对于给定的key,计算其hashCode,并除于分区的个数取余,如果余数小

于0,则用余数+分区的个数,最后返回的值就是这个key所属的分区ID;弊端是数据不均匀,容易导致数据倾斜,

极端情况下某几个分区会拥有rdd的所有数据。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 180 页


13.51 RangePartitioner分区的原理?

RangePartitioner分区则尽量保证每个分区中数据量的均匀,而且分区与分区之间是有序的,也就是说一个分

区中的元素肯定都是比另一个分区内的元素小或者大;但是分区内的元素是不能保证顺序的。简单的说就是将一定

范围内的数映射到某一个分区内。其原理是水塘抽样。

13.52 介绍parition和block有什么关联关系?

1)hdfs中的block是分布式存储的最小单元,等分,可设置冗余,这样设计有一部分磁盘空间的浪费,但是整

齐的block大小,便于快速找到、读取对应的内容;

2)Spark中的partion是弹性分布式数据集RDD的最小单元,RDD是由分布在各个节点上的partion组成的。

partion是指的spark在计算过程中,生成的数据在计算空间内最小单元,同一份数据(RDD)的partion大小不一,

数量不定,是根据application里的算子和最初读入的数据分块数量决定;

3)block位于存储空间、partion位于计算空间,block的大小是固定的、partion大小是不固定的,是从2个不

同的角度去看数据。

13.53 hbase预分区个数和spark过程中的reduce个数相同么

预分区数和spark的map个数相同,reduce个数如果没有设置和reduce前的map数相同。

13.54 Spark如何自定义partitioner分区器?

1)spark默认实现了HashPartitioner和RangePartitioner两种分区策略,我们也可以自己扩展分区策略,自定

义分区器的时候继承org.apache.spark.Partitioner类,实现类中的三个方法:

def numPartitions: Int:这个方法需要返回你想要创建分区的个数;

def getPartition(key: Any): Int:这个函数需要对输入的key做计算,然后返回该key的分区ID,范围一定是0到

numPartitions-1;

equals():这个是Java标准的判断相等的函数,之所以要求用户实现这个函数是因为Spark内部会比较两个RDD

的分区是否一样。

2)使用,调用parttionBy方法中传入自定义分区对象。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 181 页


13.55 spark中task有几种类型?

Spark中的Task有2种类型:

1)result task类型,最后一个task;

2)shuffleMapTask类型,除了最后一个task都是此类型;

13.56 rangePartioner分区器特点?

rangePartioner尽量保证每个分区中数据量的均匀,而且分区与分区之间是有序的,一个分区中的元素肯定都是比

另一个分区内的元素小或者大;但是分区内的元素是不能保证顺序的。简单的说就是将一定范围内的数映射到某一

个分区内。RangePartitioner作用:将一定范围内的数映射到某一个分区内,在实现中,分界的算法尤为重要。算

法对应的函数是rangeBounds。

13.57 手撕代码-如何使用Spark解决TopN问题?

可以做一个简单的wordcount文件

def wordcount(): Unit ={

val conf = new SparkConf().setAppName("wordcount").setMaster("local[*]")

val sc = new SparkContext(conf)

sc.setLogLevel("ERROR")

val rdd1 = sc.textFile("song.txt")

val sortWord = rdd1.flatMap(_.split(" "))

.map(x => (x,1))

.reduceByKey((v1,v2) => v1 + v2)

.filter(x => x._1 != "")

.sortBy(x => x._2,false,1)

.top(2)

.foreach(println)

sc.stop()}

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 182 页


13.58 窄依赖父RDD的partition和子RDD的parition是不是都是一对一的关系?

不一定,除了一对一的窄依赖,还包含一对固定个数的窄依赖(就是对父RDD的依赖的Partition的数量不会随

着RDD数量规模的改变而改变),比如join操作的每个partiion仅仅和已知的partition进行join,这个join操作是窄依

赖,依赖固定数量的父rdd,因为是确定的partition关系。

13.59 Spark中的shuffle和Hadoop的Shuffle区别和联系分析?

13.60 spark.default.parallelism这个参数有什么意义,实际生产中如何设置?

1)参数用于设置每个stage的默认task数量。这个参数极为重要,如果不设置可能会直接影响你的Spark作业

性能;

2)很多人都不会设置这个参数,会使得集群非常低效,你的cpu,内存再多,如果task始终为1,那也是浪费,

spark官网建议task个数为CPU的核数*executor的个数的2~3倍。

13.61 spark.storage.memoryFraction参数的含义,实际生产中如何调优?

1)用于设置RDD持久化数据在Executor内存中能占的比例,默认是0.6,,默认Executor 60%的内存,可以用

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 183 页


来保存持久化的RDD数据。根据你选择的不同的持久化策略,如果内存不够时,可能数据就不会持久化,或者数据

会写入磁盘;

2)如果持久化操作比较多,可以提高spark.storage.memoryFraction参数,使得更多的持久化数据保存在内

存中,提高数据的读取性能,如果shuffle的操作比较多,有很多的数据读写操作到JVM中,那么应该调小一点,节

约出更多的内存给JVM,避免过多的JVM gc发生。在web ui中观察如果发现gc时间很长,可以设置

spark.storage.memoryFraction更小一点。

13.62 spark.shuffle.memoryFraction参数含义,及优化经验?

1)spark.shuffle.memoryFraction是shuffle调优中 重要参数,shuffle从上一个task拉去数据过来,要在Executor

进行聚合操作,聚合操作时使用Executor内存的比例由该参数决定,默认是20%如果聚合时数据超过了该大小,那

么就会spill到磁盘,极大降低性能;

2)如果Spark作业中的RDD持久化操作较少,shuffle操作较多时,建议降低持久化操作的内存占比,提高shuffle

操作的内存占比比例,避免shuffle过程中数据过多时内存不够用,必须溢写到磁盘上,降低了性能。此外,如果发

现作业由于频繁的gc导致运行缓慢,意味着task执行用户代码的内存不够用,那么同样建议调低这个参数的值。

13.63 介绍一下你对Unified Memory Management内存管理模型的理解?

Spark中的内存使用分为两部分:执行(execution)与存储(storage)。执行内存主要用于shuffles、joins、

sorts和aggregations,存储内存则用于缓存或者跨节点的内部数据传输。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 184 页


13.64 如何理解Spark的动态内存占用机制?

13.65 列举你了解的序列化方法,并谈谈序列化有什么好处?

1)序列化:将对象转换为字节流,本质也可以理解为将链表的非连续空间转为连续空间存储的数组,可以将

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 185 页


数据进行流式传输或者块存储,反序列化就是将字节流转为对象。kyro,Java的serialize等

2)spark中的序列化常见于

· 进程间通讯:不同节点的数据传输

· 数据持久化到磁盘

在spark中扮演非常重要的角色,序列化和反序列化的程度会影响到数据传输速度,甚至影响集群的传输效率,

因此,高效的序列化方法有2点好处:a.提升数据传输速度,b.提升数据读写IO效率。

13.66 常见的数压缩方式,你们生产集群采用了什么压缩方式,提升了多少效率?

1)数据压缩,大片连续区域进行数据存储并且存储区域中数据重复性高的状况下,可以使用适当的压缩算法。

数组,对象序列化后都可以使用压缩,数更紧凑,减少空间开销。常见的压缩方式有snappy,LZO,gz等

2)Hadoop生产环境常用的是snappy压缩方式(使用压缩,实际上是CPU换IO吞吐量和磁盘空间,所以如果

CPU利用率不高,不忙的情况下,可以大大提升集群处理效率)。snappy压缩比一般20%~30%之间,并且压缩和

解压缩效率也非常高(参考数据如下):

(1)GZIP的压缩率最高,但是其实CPU密集型的,对CPU的消耗比其他算法要多,压缩和解压速度也慢;

(2)LZO的压缩率居中,比GZIP要低一些,但是压缩和解压速度明显要比GZIP快很多,解压速度快的更多;

(3)Zippy/Snappy的压缩率最低,而压缩和解压速度要稍微比LZO要快一些。

提升了多少效率可以从2方面回答,1)数据存储节约多少存储,2)任务执行消耗时间节约了多少,可以举个

实际例子展开描述。

13.67 简要描述Spark写数据的流程?

1)RDD调用compute方法,进行指定分区的写入

2)CacheManager中调用BlockManager判断数据是否已经写入,如果未写,则写入

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 186 页


3)BlockManager中数据与其他节点同步

4)BlockManager根据存储级别写入指定的存储层

5)BlockManager向主节点汇报存储状态中

13.68 Spark RDD 和 MapReduce2的区别

1)mr2只有2个阶段,数据需要大量访问磁盘,数据来源相对单一 ,spark RDD ,可以无数个阶段进行迭代计算,

数据来源非常丰富,数据落地介质也非常丰富spark计算基于内存;

2)MapReduce2需要频繁操作磁盘IO需要 大家明确的是如果是SparkRDD的话,你要知道每一种数据来源对

应的是什么,RDD从数据源加载数据,将数据放到不同的partition针对这些partition中的数据进行迭代式计算计算完

成之后,落地到不同的介质当中。

13.69 spark和Mapreduce快? 为什么快呢? 快在哪里呢?

Spark更加快的主要原因有几点:

1)基于内存计算,减少低效的磁盘交互;

2)高效的调度算法,基于DAG;

3)容错机制Lingage,主要是DAG和Lianage,及时spark不使用内存技术,也大大快于mapreduce。

13.70 Spark sql又为什么比hive快呢?

计算引擎不一样,一个是spark计算模型,一个是mapreudce计算模型。

另外spark sql中有RBO CBO的优化。

13.71 RDD的数据结构是怎么样的? 五大属性

RDD对象,包含如下5个核心属性。

1)一个分区列表,每个分区里是RDD的部分数据(或称数据块)。

2)一个依赖列表,存储依赖的其他RDD。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 187 页


3)一个名为compute的计算函数,用于计算RDD各分区的值。

4)分区器(可选),用于键/值类型的RDD,比如某个RDD是按散列来分区。

5)计算各分区时优先的位置列表(可选),比如从HDFS上的文件生成RDD时,RDD分区的位置优先选择数

据所在的节点,这样可以避免数据移动带来的开销。

13.72 RDD算子里操作一个外部map比如往里面put数据,然后算子外再遍历map,会有什么问题吗?

频繁创建额外对象,容易oom。

13.73 画图,讲讲Spark shuffle的过程。

SortShuffleManager的运行机制主要分成两种,一种是普通运行机制,另一种是bypass运行机制。当shuffle read task


的数量小于等于spark.shuffle.sort.bypassMergeThreshold参数的值时(默认为200),就会启用bypass机制。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 188 页


Sort shuffle的bypass机制

bypass运行机制的触发条件如下:

1)shuffle map task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值。

2)不是聚合类的shuffle算子(比如reduceByKey)。

此时task会为每个reduce端的task都创建一个临时磁盘文件,并将数据按key进行hash然后根据key的hash值,将key
写入对应的磁盘文件之中。当然,写入磁盘文件时也是先写入内存缓冲,缓冲写满之后再溢写到磁盘文件的。最后,
同样会将所有临时磁盘文件都合并成一个磁盘文件,并创建一个单独的索引文件。

13.74 ShuffleWriter分为几种?

根据源码分析如下:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 189 页


14. V10.0大数据数据倾斜专题

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

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

1.公司一的数据分析师在做join的时候发生了数据倾斜,会导致有几百万用户的相关数据集中到了一台服务器

上,几百万的用户数据,说大也不大,正常字段量的数据的话64G还是能轻松处理掉的。

2.公司二的数据分析师在做join的时候也发生了数据倾斜,可能会有1个亿的用户相关数据集中到了一台机器上

了。这时候一台机器就很难搞定了,最后会很难算出结果。

14.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执行时间特别久,整体任务卡在某个阶段不能结束;

 正常运行的任务突然失败;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 190 页


14.2 数据倾斜产生原因

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

他们在做数据运算的时候会涉及到,count distinct、group by、join on等操作,这些都会触发Shuffle动作。

一旦触发Shuffle,所有相同key的值就会被拉到一个或几个Reducer节点上,容易发生单点计算问题,导致数据倾斜。

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

1)key分布不均匀;

2)建表时考虑不周

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

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

ip(IP表):ip,register_user_cnt

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

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

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

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

3)业务数据激增

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

10000%,其余城市的数据量不变。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 191 页


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

14.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的数据,过滤掉

 无损的方法:对分布不均匀的数据,单独计算

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 192 页


 先对key做一层hash,先将数据随机打散让它的并行度变大,再汇集

 数据预处理

14.4 定位导致数据倾斜代码

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

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

aggregateByKey、join、cogroup、repartition等。

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

14.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倍。此时更加能够确定是发生了

数据倾斜。

推断倾斜代码

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 193 页


知道数据倾斜发生在哪一个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上,供我们遍历和打印输出。

通过对单词计数程序的分析,希望能够让大家了解最基本的stage划分的原理,以及stage划分后shuffle操作是

如何在两个stage的边界处执行的。然后我们就知道如何快速定位出发生数据倾斜的stage对应代码的哪一个部分

了。

比如我们在Spark Web UI或者本地log中发现,stage1的某几个task执行得特别慢,判定stage1出现了数据倾斜,

那么就可以回到代码中,定位出stage1主要包括了reduceByKey这个shuffle类算子,此时基本就可以确定是是该算

子导致了数据倾斜问题。

此时,如果某个单词出现了100万次,其他单词才出现10次,那么stage1的某个task就要处理100万数据,整个

stage的速度就会被这个task拖慢。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 194 页


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

这种情况下去定位出问题的代码就比较容易了。我们建议直接看yarn-client模式下本地log的异常栈,或者是

通过YARN查看yarn-cluster模式下的log中的异常栈。一般来说,通过异常栈信息就可以定位到你的代码中哪一行发

生了内存溢出。然后在那行代码附近找找,一般也会有shuffle类算子,此时很可能就是这个算子导致了数据倾斜。

但是大家要注意的是,不能单纯靠偶然的内存溢出就判定发生了数据倾斜。因为自己编写的代码的bug,以及

偶然出现的数据异常,也可能会导致内存溢出。因此还是要按照上面所讲的方法,通过Spark Web UI查看报错的那

个stage的各个task的运行时间以及分配的数据量,才能确定是否是由于数据倾斜才导致了这次内存溢出。

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

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

印样本数据中各个key的出现次数。

val sampledPairs = pairs.sample(false, 0.1)

val sampledWordCounts = sampledPairs.countByKey()

sampledWordCounts.foreach(println(_))

14.6 Spark 数据倾斜的解决方案

14.6.1 使用Hive ETL预处理数据

14.6.1.1 适用场景

导致数据倾斜的是Hive表。如果该Hive表中的数据本身很不均匀(比如某个key对应了100万数据,其他key才

对应了10条数据),而且业务场景需要频繁使用Spark对Hive表执行某个分析操作,那么比较适合使用这种技术方

案。

14.6.1.2 实现思路

此时可以评估一下,是否可以通过Hive来进行数据预处理(即通过Hive ETL预先对数据按照key进行聚合,或

者是预先和其他表进行join),然后在Spark作业中针对的数据源就不是原来的Hive表了,而是预处理后的Hive表。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 195 页


此时由于数据已经预先进行过聚合或join操作了,那么在Spark作业中也就不需要使用原先的shuffle类算子执行这类

操作了。

14.6.1.3 方案实现原理

这种方案从根源上解决了数据倾斜,因为彻底避免了在Spark中执行shuffle类算子,那么肯定就不会有数据倾

斜的问题了。但是这里也要提醒一下大家,这种方式属于治标不治本。因为毕竟数据本身就存在分布不均匀的问题,

所以Hive ETL中进行group by或者join等shuffle操作时,还是会出现数据倾斜,导致Hive ETL的速度很慢。我们只

是把数据倾斜的发生提前到了Hive ETL中,避免Spark程序发生数据倾斜而已。

14.6.1.4 方案优缺点

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

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

14.6.1.5 方案实践经验

在一些Java系统与Spark结合使用的项目中,会出现Java代码频繁调用Spark作业的场景,而且对Spark作业的

执行性能要求很高,就比较适合使用这种方案。将数据倾斜提前到上游的Hive ETL,每天仅执行一次,只有那一次

是比较慢的,而之后每次Java调用Spark作业时,执行速度都会很快,能够提供更好的用户体验。

14.6.1.6 项目实践经验

在美团·点评的交互式用户行为分析系统中使用了这种方案,该系统主要是允许用户通过Java Web系统提交数

据分析统计任务,后端通过Java提交Spark作业进行数据分析统计。要求Spark作业速度必须要快,尽量在10分钟以

内,否则速度太慢,用户体验会很差。所以我们将有些Spark作业的shuffle操作提前到了Hive ETL中,从而让Spark

直接使用预处理的Hive中间表,尽可能地减少Spark的shuffle操作,大幅度提升了性能,将部分作业的性能提升了6

倍以上。

14.6.2 过滤少数导致倾斜的key

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 196 页


14.6.2.1 方案适用场景

如果发现导致倾斜的key就少数几个,而且对计算本身的影响并不大的话,那么很适合使用这种方案。比如99%

的key就对应10条数据,但是只有一个key对应了100万数据,从而导致了数据倾斜。

14.6.2.2 方案实现思路

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

过滤掉那少数几个key。

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

key。

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

进行采样,然后计算出每个key的数量,取数据量最多的key过滤掉即可。

14.6.2.3 方案实现原理

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

14.6.2.4 方案优缺点

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

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

14.6.2.5 方案实践经验

在项目中我们也采用过这种方案解决数据倾斜。有一次发现某一天Spark作业在运行的时候突然OOM了,追

查之后发现,是Hive表中的某一个key在那天数据异常,导致数据量暴增。因此就采取每次执行前先进行采样,计

算出样本中数据量最大的几个key之后,直接在程序中将那些key给过滤掉。

14.6.3 提高shuffle操作的并行度

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 197 页


14.6.3.1 方案适用场景

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

14.6.3.2 方案实现思路

在对RDD执行shuffle算子时,给shuffle算子传入一个参数,比如reduceByKey(1000),该参数就设置了这个

shuffle算子执行时shuffle read task的数量,即spark.sql.shuffle.partitions,该参数代表了shuffle read task的并行度,

默认是200,对于很多场景来说都有点过小。

14.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

的执行时间都会变短了。具体原理如下图所示。

14.6.3.4 方案优缺点

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

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

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 198 页


14.6.3.5 方案实践经验

该方案通常无法彻底解决数据倾斜,因为如果出现一些极端情况,比如某个key对应的数据量有100万,那么

无论你的task数量增加到多少,这个对应着100万数据的key肯定还是会分配到一个task中去处理,因此注定还是会

发生数据倾斜的。所以这种方案只能说是在发现数据倾斜时尝试使用的第一种手段,尝试去用最简单的方法缓解数

据倾斜而已,或者是和其他方案结合起来使用。

14.6.4 两阶段聚合(局部聚合+全局聚合)

14.6.4.1 方案适用场景

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

种方案。

14.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 {

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 199 页


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进行全局聚合。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 200 页


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;

});

14.6.4.3 方案实现原理

将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被一个task处理的数据分散到

多个task上去做局部聚合,进而解决单个task处理数据量过多的问题。接着去除掉随机前缀,再次进行全局聚合,

就可以得到最终的结果。具体原理见下图。

14.6.4.4 方案优缺点

优点

对于聚合类的shuffle操作导致的数据倾斜,效果是非常不错的。通常都可以解决掉数据倾斜,或者至少是大幅度缓

解数据倾斜,将Spark作业的性能提升数倍以上。

缺点

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

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 201 页


14.6.5 将reduce join转为map join

14.6.5.1 方案适用场景

在对RDD使用join类操作,或者是在Spark SQL中使用join语句时,而且join操作中的一个RDD或表的数据量比

较小(比如几百M或者一两G),比较适用此方案。

14.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();

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 202 页


// 可以将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后的数据。

14.6.5.3 方案实现原理

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

再进行join,此时就是reduce join。

但是如果一个RDD是比较小的,则可以采用广播小RDD全量数据+map算子来实现与join同样的效果,也就是

map join,此时就不会发生shuffle操作,也就不会发生数据倾斜。具体原理如下图所示。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 203 页


14.6.5.4 方案优缺点

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

缺点:适用场景较少,因为这个方案只适用于一个大表和一个小表的情况。毕竟我们需要将小表进行广播,此

时会比较消耗内存资源,driver和每个Executor内存中都会驻留一份小RDD的全量数据。如果我们广播出去的RDD

数据比较大,比如10G以上,那么就可能发生内存溢出了。因此并不适合两个都是大表的情况。

14.6.6 采样倾斜key并分拆join操作

14.6.6.1 方案适用场景

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

RDD/Hive表中的key分布情况。

如果出现数据倾斜,是因为其中某一个RDD/Hive表中的少数几个key的数据量过大,而另一个RDD/Hive表中

的所有key都分布比较均匀,那么采用这个解决方案是比较合适的。

14.6.6.2 方案实现思路

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

数量,计算出来数据量最大的是哪几个key。

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

机数作为前缀;

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

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 204 页


接着将需要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(

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 205 页


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,取最多行数的key

final 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);

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 206 页


}

});

// 从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 {

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 207 页


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)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 208 页


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);

14.6.6.3 方案实现原理

对于join导致的数据倾斜,如果只是某几个key导致了倾斜,可以将少数几个key分拆成独立RDD,并附加随机

前缀打散成n份去进行join,此时这几个key对应的数据就不会集中在少数几个task上,而是分散到多个task进行join

了。具体原理见下图。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 209 页


14.6.6.4 方案优缺点

优点:对于join导致的数据倾斜,如果只是某几个key导致了倾斜,采用该方式可以用最有效的方式打散key进

行join。而且只需要针对少数倾斜key对应的数据进行扩容n倍,不需要对全量数据进行扩容。避免了占用过多内存。

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

14.6.7 使用随机前缀和扩容RDD进行join

14.6.7.1 方案适用场景

如果在进行join操作时,RDD中有大量的key导致数据倾斜,那么进行分拆key也没什么意义,此时就只能使用

最后一种方案来解决问题了。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 210 页


14.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

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 211 页


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);

14.6.7.3 方案实现原理

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

中去处理,而不是让一个task处理大量的相同key。

该方案与“解决方案六”的不同之处就在于,上一种方案是尽量只对少数倾斜key对应的数据进行特殊处理,由

于处理过程需要扩容RDD,因此上一种方案扩容RDD后对内存的占用并不大;

而这一种方案是针对有大量倾斜key的情况,没法将部分key拆分出来进行单独处理,因此只能对整个RDD进

行数据扩容,对内存资源要求很高。

14.6.7.4 方案优缺点

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

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

源要求很高。

14.6.7.5 方案实践经验

曾经开发一个数据需求的时候,发现一个join导致了数据倾斜。优化之前,作业的执行时间大约是60分钟左右;

使用该方案优化之后,执行时间缩短到10分钟左右,性能提升了6倍。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 212 页


14.6.8 多种方案组合使用

在实践中发现,很多情况下,如果只是处理较为简单的数据倾斜场景,那么使用上述方案中的某一种基本就可

以解决。但是如果要处理一个较为复杂的数据倾斜场景,那么可能需要将多种方案组合起来使用。

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

导致倾斜的k,预处理一部分数据,并过滤一部分数据来缓解;

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

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

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

决自己的数据倾斜问题。

14.7 Spark数据倾斜处理小结

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 213 页


15. V10.0Java基础高频面试题

15.1 HashMap底层源码,数据结构

HashMap的底层结构在jdk1.7中由数组+链表实现,在jdk1.8中由数组+链表+红黑树实现,以数组+链表的结构为例。

15.2 HashMap和Hashtable区别

1) 线程安全性不同

HashMap 是线程不安全的,HashTable 是线程安全的,其中的方法是 Synchronize 的,在多线程并发的情况

下,可以直接使用 HashTabl,但是使用 HashMap 时必须自己增加同步处理。

2) 是否提供 contains 方法

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 214 页


HashMap 只有 containsValue 和 containsKey 方法;HashTable 有 contains、containsKey 和 containsValue

三个方法,其中 contains 和 containsValue 方法功能相同。

3) key 和 value 是否允许 null 值

Hashtable 中,key 和 value 都不允许出现 null 值。HashMap 中,null 可以作为键,这样的键只有一个;可以

有一个或多个键所对应的值为 null。

4) 数组初始化和扩容机制

HashTable 在不指定容量的情况下的默认容量为 11,而 HashMap 为 16,Hashtable 不要求底层数组的容量一

定要为 2 的整数次幂,而 HashMap 则要求一定为 2 的整数次幂。

Hashtable 扩容时,将容量变为原来的 2 倍加 1,而 HashMap 扩容时,将容量变为原来的 2 倍。

15.3 StringBuffer和StringBuild区别

1、StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,

2、只是 StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而

StringBuilder 没有这个修饰,可以被认为是线程不安全的。

3、在单线程程序下,StringBuilder 效率更快,因为它不需要加锁,不具备多线程安全而 StringBuffer 则每次

都需要判断锁,效率相对更低

15.4 final、finally、finalize这三个关键字的区别是什么?

final:修饰符(关键字)有三种用法:修饰类、变量和方法。修饰类时,意味着它不能再派生出新的子类,即

不能被继承,因此它和 abstract 是反义词。修饰变量时,该变量使用中不被改变,必须在声明时给定初值,在引用

中只能读取不可修改,即为常量。修饰方法时,也同样只能使用,不能在子类中被重写。

finally:通常放在 try…catch 的后面构造最终执行代码块,这就意味着程序无论正常执行还是发生异常,这里

的代码只要 JVM 不关闭都能执行,可以将释放外部资源的代码写在 finally 块中。

finalize:Object 类中定义的方法,Java 中允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前

做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写 finalize() 方法可以整理系统资源或

者执行其他清理工作。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 215 页


15.5 ==和equals区别

== : 如果比较的是基本数据类型,那么比较的是变量的值

如果比较的是引用数据类型,那么比较的是地址值(两个对象是否指向同一块内存)

equals:如果没重写 equals 方法比较的是两个对象的地址值。如果重写了 equals 方法后我们往往比较的是对象

中的属性的内容

equals 方法是从 Object 类中继承的,默认的实现就是使用==

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

java虚拟机主要分为以下几个区:

1) 方法区:

a. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法
区里的常量池和对类型的卸载

b. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。

c. 该区域是被线程共享的。

d. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说
常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。

2) 虚拟机栈:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 216 页


a. 虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,每个方法在执行的时候都会创建一个栈帧,用于
存储局部变量表、操作数栈、动态链接和方法出口等信息。

b. 虚拟机栈是线程私有的,它的生命周期与线程相同。

c. 局部变量表里存储的是基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个
对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部
变量所需的内存空间在编译器间确定

d. 操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和
出栈的方式

e. 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,
持有这个引用是为了支持方法调用过程中的
动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。

3) 本地方法栈:
本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。

4) 堆:

java堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常
发生垃圾回收操作。

5) 程序计数器:

内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、
异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM
情况的区域。

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

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。

主要有一下四种类加载器:

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类的方式实现。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 217 页


15.8 Synchronized与Lock的区别

1)Synchronized能实现的功能Lock都可以实现,而且Lock比Synchronized更好用,更灵活。

2)Synchronized可以自动上锁和解锁;Lock需要手动上锁和解锁

15.9 Runnable和Callable的区别

1)Runnable接口中的方法没有返回值;Callable接口中的方法有返回值

2)Runnable接口中的方法没有抛出异常;Callable接口中的方法抛出了异常

3)Runnable接口中的落地方法是call方法;Callable接口中的落地方法是run方法

15.10 什么是分布式锁

当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。分
布式锁可以将标记存在内存,只是该内存不是某个进程分配的内存而是公共内存,如 Redis,通过set
(key,value,nx,px,timeout)方法添加分布式锁。

15.11 什么是分布式事务

分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不
同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不
同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。

15.12 break和continue之间的区别是什么?

Break: 用于终止循环的, 即: 循环不再继续向下执行. 且它可以应用在switch.case语句中.

Continue: 用于结束本次循环进行下一次循环的.

15.13 方法重载和方法重写的区别是什么?

方法重载(Overload): 同一个类中出现方法名相同, 但是参数列表不同的两个或者以上的方法时, 称为方法重载. 方


法重载与返回值的数据类型无关.

方法重写(Override): 子父类间, 子类出现和父类一模一样的方法时, 称为方法重写, 方法重写要求返回值的数据类


型也必须一致或者有子父类关系.

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 218 页


15.14 Java中继承的特点是什么?

类与类之间: 只支持单继承, 不支持多继承, 但是可以多层继承.

接口与接口之间: 可以单继承, 也可以多继承.

15.15 接口中变量和方法的默认修饰符是什么?

变量: public static final 方法: public abstract

15.16 不能和abstract关键字共存的关键字有哪些?

static: 因为static修饰符的方法可以被 类名. 的方式调用, 而抽象方法是没有方法体的, 假设 类名.抽象方法(), 这


样做是: 无意义的.

final: 被final修饰的方法子类无法重写, 但是抽象方法要求子类必须重写, 它们不能共存是因为: 设计理念冲突.

private: 被private修饰的内容子类无法继承, 但是抽象方法要求子类必须重写, 它们不能共存是因为: 设计理念冲


突.

15.17 请说一下你对集合体系的了解.

 单列集合: 顶层接口是Collection.

List体系:

特点:

有序, 可重复, 元素有索引.

常用子类:

ArrayList: 底层数据结构采用数组实现, 所以查询(修改)较快, 增删相对较慢.

LinkedList: 底层数据结构采用链表实现, 所以查询(修改)相对较慢, 增删较块.

Set体系:

特点:

无序, 唯一, 元素有索引.

常用子类:

HashSet: 底层采用哈希表(数组 + 链表)的方式实现, 增删改查相对都快.

它底层依赖HashMap.

 双列集合: 顶层接口是Map.

特点:

键具有唯一性, 值可以重复, 数据结构只针对于键有效.

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 219 页


常用子类:

HashMap: 底层采用哈希表(数组 + 链表)的方式实现, 增删改查相对都快.

15.18 HashSet集合保证元素唯一性的原理是什么?

HashSet集合保证元素的唯一性依赖 haschCode()和equals()方法.

15.19 Writer, 字节缓冲流, 字符缓冲流的默认缓冲区的大小是多少?

2KB, 8KB, 16KB.

15.20 多线程并行和多线程并发的区别是什么?

多线程并行:指的是两个或者以上的线程同时执行(前提:需要多核CPU)

多线程并发:两个或者以上的线程同时请求执行, 但是同一瞬间, CPU只能执行一个, 于是就安排它们交替执行, 因


为时间间隔非常短, 我们看起来好像是同时执行的, 其实不是.

15.21 类加载器的加载机制有哪些?

 全盘加载:就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器
负责载入,除非显示使用另外一个类加载器来载入

 父类委托:就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器
无法加载该类时才尝试从自己的类路径中加载该类

 缓存机制:保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜
索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class
对象,存储到缓存区

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 220 页


16. V10.0Hbase高频面试题

16.1 HBase的特点是什么?

1)大:一个表可以有数十亿行,上百万列;

2)无模式:每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有

截然不同的列;

3)面向列:面向列(族)的存储和权限控制,列(族)独立检索;

4)稀疏:空(null)列并不占用存储空间,表可以设计的非常稀疏;

5)数据多版本:每个单元中的数据可以有多个版本,默认情况下版本号自动分配,是单元格插入时的时间戳;

6)数据类型单一:Hbase中的数据都是字符串,没有类型。

16.2 HBase和Hive的区别?

① 两者是什么?

Apache Hive是一个构建在Hadoop基础设施之上的数据仓库。通过Hive可以使用HQL语言查询存放在HDFS上

的数据。HQL是一种类SQL语言,这种语言最终被转化为Map/Reduce. 虽然Hive提供了SQL查询功能,但是Hive

不能够进行交互查询--因为它只能够在Haoop上批量的执行Hadoop。

Apache HBase是一种Key/Value系统,它运行在HDFS之上。和Hive不一样,Hbase的能够在它的数据库上实

时运行,而不是运行MapReduce任务。Hive被分区为表格,表格又被进一步分割为列簇。列簇必须使用schema定

义,列簇将某一类型列集合起来(列不要求schema定义)。例如,“message”列簇可能包含:“to”, ”from” “date”,

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 221 页


“subject”, 和”body”. 每一个 key/value对在Hbase中被定义为一个cell,每一个key由row-key,列簇、列和时间戳。

在Hbase中,行是key/value映射的集合,这个映射通过row-key来唯一标识。Hbase利用Hadoop的基础设施,可以

利用通用的设备进行水平的扩展。

② 两者的特点

Hive帮助熟悉SQL的人运行MapReduce任务。因为它是JDBC兼容的,同时,它也能够和现存的SQL工具整合

在一起。运行Hive查询会花费很长时间,因为它会默认遍历表中所有的数据。虽然有这样的缺点,一次遍历的数据

量可以通过Hive的分区机制来控制。分区允许在数据集上运行过滤查询,这些数据集存储在不同的文件夹内,查询

的时候只遍历指定文件夹(分区)中的数据。这种机制可以用来,例如,只处理在某一个时间范围内的文件,只要

这些文件名中包括了时间格式。

HBase通过存储key/value来工作。它支持四种主要的操作:增加或者更新行,查看一个范围内的cell,获取指

定的行,删除指定的行、列或者是列的版本。版本信息用来获取历史数据(每一行的历史数据可以被删除,然后通

过Hbase compactions就可以释放出空间)。虽然HBase包括表格,但是schema仅仅被表格和列簇所要求,列不需

要schema。Hbase的表格包括增加/计数功能。

③ 限制

Hive目前不支持更新操作。另外,由于hive在hadoop上运行批量操作,它需要花费很长的时间,通常是几分

钟到几个小时才可以获取到查询的结果。Hive必须提供预先定义好的schema将文件和目录映射到列,并且Hive与

ACID不兼容。

HBase查询是通过特定的语言来编写的,这种语言需要重新学习。类SQL的功能可以通过Apache Phonenix实

现,但这是以必须提供schema为代价的。另外,Hbase也并不是兼容所有的ACID特性,虽然它支持某些特性。最

后但不是最重要的--为了运行Hbase,Zookeeper是必须的,zookeeper是一个用来进行分布式协调的服务,这些

服务包括配置服务,维护元信息和命名空间服务。

④ 应用场景

Hive适合用来对一段时间内的数据进行分析查询,例如,用来计算趋势或者网站的日志。Hive不应该用来进行

实时的查询。因为它需要很长时间才可以返回结果。

Hbase非常适合用来进行大数据的实时查询。Facebook用Hbase进行消息和实时的分析。它也可以用来统计

Facebook的连接数。

⑤ 总结

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 222 页


Hive和Hbase是两种基于Hadoop的不同技术--Hive是一种类SQL的引擎,并且运行MapReduce任务,Hbase

是一种在Hadoop之上的NoSQL 的Key/vale数据库。当然,这两种工具是可以同时使用的。就像用Google来搜索,

用FaceBook进行社交一样,Hive可以用来进行统计查询,HBase可以用来进行实时查询,数据也可以从Hive写到

Hbase,设置再从Hbase写回Hive。

16.3 HBase适用于怎样的情景?

① 半结构化或非结构化数据

对于数据结构字段不够确定或杂乱无章很难按一个概念去进行抽取的数据适合用HBase。以上面的例子为例,

当业务发展需要存储author的email,phone,address信息时RDBMS需要停机维护,而HBase支持动态增加。

② 记录非常稀疏

RDBMS的行有多少列是固定的,为null的列浪费了存储空间。而如上文提到的,HBase为null的Column不会被

存储,这样既节省了空间又提高了读性能。

③ 多版本数据

如上文提到的根据Row key和Column key定位到的Value可以有任意数量的版本值,因此对于需要存储变动历

史记录的数据,用HBase就非常方便了。比如上例中的author的Address是会变动的,业务上一般只需要最新的值,

但有时可能需要查询到历史值。

④ 超大数据量

当数据量越来越大,RDBMS数据库撑不住了,就出现了读写分离策略,通过一个Master专门负责写操作,多

个Slave负责读操作,服务器成本倍增。随着压力增加,Master撑不住了,这时就要分库了,把关联不大的数据分

开部署,一些join查询不能用了,需要借助中间层。随着数据量的进一步增加,一个表的记录越来越大,查询就变

得很慢,于是又得搞分表,比如按ID取模分成多个表以减少单个表的记录数。经历过这些事的人都知道过程是多么

的折腾。采用HBase就简单了,只需要加机器即可,HBase会自动水平切分扩展,跟Hadoop的无缝集成保障了其数

据可靠性(HDFS)和海量数据分析的高性能(MapReduce)。

16.4 描述HBase的rowKey的设计原则?

① Rowkey长度原则

Rowkey 是一个二进制码流,Rowkey 的长度被很多开发者建议说设计在10~100 个字节,不过建议是越短越

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 223 页


好,不要超过16 个字节。

原因如下:

(1)数据的持久化文件HFile 中是按照KeyValue 存储的,如果Rowkey 过长比如100 个字节,1000 万列数

据光Rowkey 就要占用100*1000 万=10 亿个字节,将近1G 数据,这会极大影响HFile 的存储效率;

(2)MemStore 将缓存部分数据到内存,如果Rowkey 字段过长内存的有效利用率会降低,系统将无法缓存

更多的数据,这会降低检索效率。因此Rowkey 的字节长度越短越好。

(3)目前操作系统是都是64 位系统,内存8 字节对齐。控制在16 个字节,8 字节的整数倍利用操作系统的

最佳特性。

② Rowkey散列原则

如果Rowkey 是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将Rowkey的高位作为散列字段,

由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个Regionserver 实现负载均衡的几率。如果没

有散列字段,首字段直接是时间信息将产生所有新数据都在一个 RegionServer 上堆积的热点现象,这样在做数据

检索的时候负载将会集中在个别RegionServer,降低查询效率。

③ Rowkey唯一原则

必须在设计上保证其唯一性。

16.5 描述HBase中scan和get的功能以及实现的异同?

HBase的查询实现只提供两种方式:

1)按指定RowKey 获取唯一一条记录,get方法(org.apache.hadoop.hbase.client.Get)

Get 的方法处理分两种 : 设置了ClosestRowBefore 和没有设置ClosestRowBefore的rowlock。主要是用来保证行

的事务性,即每个get 是以一个row 来标记的。一个row中可以有很多family 和column。

2)按指定的条件获取一批记录,scan方法(org.apache.Hadoop.hbase.client.Scan)实现条件查询功能使用的

就是scan 方式。

(1)scan 可以通过setCaching 与setBatch 方法提高速度(以空间换时间);

(2)scan 可以通过setStartRow 与setEndRow 来限定范围([start,end)start 是闭区间,end 是开区间)。范

围越小,性能越高。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 224 页


(3)scan 可以通过setFilter 方法添加过滤器,这也是分页、多条件查询的基础。

16.6 请描述HBase中scan对象的setCache和setBatch方法的使用?

setCache用于设置缓存,即设置一次RPC请求可以获取多行数据。对于缓存操作,如果行的数据量非常大,多

行数据有可能超过客户端进程的内存容量,由此引入批量处理这一解决方案。

setBatch 用于设置批量处理,批量可以让用户选择每一次ResultScanner实例的next操作要取回多少列,例如,

在扫描中设置setBatch(5),则一次next()返回的Result实例会包括5列。如果一行包括的列数超过了批量中设置的值,

则可以将这一行分片,每次next操作返回一片,当一行的列数不能被批量中设置的值整除时,最后一次返回的Result

实例会包含比较少的列,如,一行17列,batch设置为5,则一共返回4个Result实例,这4个实例中包括的列数分别

为5、5、5、2。

组合使用扫描器缓存和批量大小,可以让用户方便地控制扫描一个范围内的行键所需要的RPC调用次数。

Cache设置了服务器一次返回的行数,而Batch设置了服务器一次返回的列数。

假如我们建立了一张有两个列族的表,添加了10行数据,每个行的每个列族下有10列,这意味着整个表一共有

200列(或单元格,因为每个列只有一个版本),其中每行有20列。

① Batch参数决定了一行数据分为几个Result,它只针对一行数据,Batch再大,也只能将一行的数据放入一

个Result中。所以当一行数据有10列,而Batch为100时,也只能将一行的所有列都放入一个Result,不会混合其他行;

② 缓存值决定一次RPC返回几个Result,根据Batch划分的Result个数除以缓存个数可以得到RPC消息个数(之

前定义缓存值决定一次返回的行数,这是不准确的,准确来说是决定一次RPC返回的Result个数,由于在引入Batch

之前,一行封装为一个Result,因此定义缓存值决定一次返回的行数,但引入Batch后,更准确的说法是缓存值决定

了一次RPC返回的Result个数);

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 225 页


RPC请求次数 = (行数 * 每行列数) / Min(每行的列数,批量大小) / 扫描器缓存

下图展示了缓存和批量两个参数如何联动,下图中有一个包含9行数据的表,每行都包含一些列。使用了一个

缓存为6、批量大小为3的扫描器,需要三次RPC请求来传送数据:

16.7 请详细描述HBase中一个cell的结构?

HBase中通过row和columns确定的为一个存贮单元称为cell。

Cell:由{row key, column(=<family> + <label>), version}唯一确定的单元。cell 中的数据是没有类型的,全

部是字节码形式存贮。

16.8 以start-hbase.sh为起点,HBase启动的流程是什么?

start-hbase.sh 的流程如下:

1. 运行 hbase-config.sh

hbase-config.sh的作用:

① 装载相关配置,如HBASE_HOME目录,conf目录,regionserver机器列表,JAVA_HOME 目录等,它会调

用$HBASE_HOME/conf/hbase-env.sh ;

② 解析参数(0.96 版本及以后才可以带唯一参数 autorestart,作用就是重启);

③ 调用 hbase-daemon.sh 来启动 master;

④ 调用 hbase-daemons.sh 来启动 regionserver zookeeper master-backup。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 226 页


2. hbase-env.sh 的作用:

主要是配置 JVM 及其 GC 参数,还可以配置 log 目录及参数,配置是否需要 hbase 管理 ZK,配置进程 id

目录等。

3. hbase-daemons.sh 的作用:

根据需要启动的进程,如 zookeeper,则调用 zookeepers.sh如 regionserver,则调用 regionservers.sh,

如 master-backup,则调用 master-backup.sh。

4. zookeepers.sh 的作用:

如果 hbase-env.sh 中的 HBASE_MANAGES_ZK"="true",那么通过ZKServerTool这个类解析xml配置文件,

获取 ZK 节点列表,然后通过 SSH 向这些节点发送远程命令执行。

5. regionservers.sh 的作用:

与 zookeepers.sh 类似,通过配置文件,获取 regionserver 机器列表,然后 SSH 向这些机器发送远程命令。

6.master-backup.sh 的作用:

通过 backup-masters 这个配置文件,获取 backup-masters 机器列表,然后 SSH 向这些机器发送远程命

令。

16.9 简述HBase中compact用途是什么,什么时候触发,分为哪两种,有什么区别,有哪些相关配置参数?

在hbase中每当有memstore数据flush到磁盘之后,就形成一个storefile,当storeFile的数量达到一定程度后,

就需要将 storefile 文件来进行 compaction 操作。

Compact 的作用:

① 合并文件

② 清除过期,多余版本的数据

③ 提高读写数据的效率

HBase 中实现了两种 compaction 的方式:minor and major. 这两种 compaction 方式的区别是:

1、Minor 操作只用来做部分文件的合并操作以及包括 minVersion=0 并且设置 ttl 的过期版本清理,不做任

何删除数据、多版本数据的清理工作。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 227 页


2、Major 操作是对 Region 下的HStore下的所有StoreFile执行合并操作,最终的结果是整理合并出一个文件。

16.10 每天百亿数据存入HBase,如何保证数据的存储正确和在规定时间里全部录入完毕,不残留数据?

需求分析:

1)百亿数据:证明数据量非常大;

2)存入HBase:证明是跟HBase的写入数据有关;

3)保证数据的正确:要设计正确的数据结构保证正确性;

4)在规定时间内完成:对存入速度是有要求的。

解决思路:

1)数据量百亿条,什么概念呢?假设一整天60x60x24 = 86400秒都在写入数据,那么每秒的写入条数高达100

万条,HBase当然是支持不了每秒百万条数据的,所以这百亿条数据可能不是通过实时地写入,而是批量地导入。

批量导入推荐使用BulkLoad方式(推荐阅读:Spark之读写HBase),性能是普通写入方式几倍以上;

2)存入HBase:普通写入是用JavaAPI put来实现,批量导入推荐使用BulkLoad;

3)保证数据的正确:这里需要考虑RowKey的设计、预建分区和列族设计等问题;

4)在规定时间内完成也就是存入速度不能过慢,并且当然是越快越好,使用BulkLoad。

16.11 HBase如何给web前端提供接口来访问?

使用JavaAPI来编写WEB应用,使用HBase提供的RESTFul接口。

16.12 请列举几个HBase优化方法?

1)减少调整

减少调整这个如何理解呢?HBase中有几个内容会动态调整,如region(分区)、HFile,所以通过一些方法来

减少这些会带来I/O开销的调整。

· Region

如果没有预建分区的话,那么随着region中条数的增加,region会进行分裂,这将增加I/O开销,所以解决方

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 228 页


法就是根据你的RowKey设计来进行预建分区,减少region的动态分裂。

· HFile

HFile是数据底层存储文件,在每个memstore进行刷新时会生成一个HFile,当HFile增加到一定程度时,会将属

于一个region的HFile进行合并,这个步骤会带来开销但不可避免,但是合并后HFile大小如果大于设定的值,那么

HFile会重新分裂。为了减少这样的无谓的I/O开销,建议估计项目数据量大小,给HFile设定一个合适的值。

2)减少启停

数据库事务机制就是为了更好地实现批量写入,较少数据库的开启关闭带来的开销,那么HBase中也存在频繁

开启关闭带来的问题。

· 关闭Compaction,在闲时进行手动Compaction。

因为HBase中存在Minor Compaction和Major Compaction,也就是对HFile进行合并,所谓合并就是I/O读写,

大量的HFile进行肯定会带来I/O开销,甚至是I/O风暴,所以为了避免这种不受控制的意外发生,建议关闭自动

Compaction,在闲时进行compaction。

· 批量数据写入时采用BulkLoad。

如果通过HBase-Shell或者JavaAPI的put来实现大量数据的写入,那么性能差是肯定并且还可能带来一些意想不

到的问题,所以当需要写入大量离线数据时建议使用BulkLoad

3)减少数据量

虽然我们是在进行大数据开发,但是如果可以通过某些方式在保证数据准确性同时减少数据量,何乐而不为呢?

· 开启过滤,提高查询速度

开启BloomFilter,BloomFilter是列族级别的过滤,在生成一个StoreFile同时会生成一个MetaBlock,用于查询

时过滤数据

· 使用压缩:一般推荐使用Snappy和LZO压缩

4)合理设计

在一张HBase表格中RowKey和ColumnFamily的设计是非常重要,好的设计能够提高性能和保证数据的准确性

· RowKey设计:应该具备以下几个属性

散列性:散列性能够保证相同相似的rowkey聚合,相异的rowkey分散,有利于查询。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 229 页


简短性:rowkey作为key的一部分存储在HFile中,如果为了可读性将rowKey设计得过长,那么将会增加存储压

力。

唯一性:rowKey必须具备明显的区别性。

业务性:举例来说:

假如我的查询条件比较多,而且不是针对列的条件,那么rowKey的设计就应该支持多条件查询。

如果我的查询要求是最近插入的数据优先,那么rowKey则可以采用叫上Long.Max-时间戳的方式,这样rowKey

就是递减排列。

· 列族的设计

列族的设计需要看应用场景

多列族设计的优劣:

优势:HBase中数据时按列进行存储的,那么查询某一列族的某一列时就不需要全盘扫描,只需要扫描某一列

族,减少了读I/O;其实多列族设计对减少的作用不是很明显,适用于读多写少的场景

劣势:降低了写的I/O性能。原因如下:数据写到store以后是先缓存在memstore中,同一个region中存在多

个列族则存在多个store,每个store都一个memstore,当其实memstore进行flush时,属于同一个region的store中

的memstore都会进行flush,增加I/O开销。

16.13 HBase中RowFilter和BloomFilter原理?

1)RowFilter原理简析

RowFilter顾名思义就是对rowkey进行过滤,那么rowkey的过滤无非就是相等(EQUAL)、大于(GREATER)、

小于(LESS),大于等于(GREATER_OR_EQUAL),小于等于(LESS_OR_EQUAL)和不等于(NOT_EQUAL)几种过滤方式。

Hbase中的RowFilter采用比较符结合比较器的方式来进行过滤。

比较器的类型如下:

BinaryComparator

BinaryPrefixComparator

NullComparator

BitComparator

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 230 页


RegexStringComparator

SubStringComparator

例子:

Filter rowFilter = new RowFilter(CompareFilter.CompareOp.EQUAL,

new BinaryComparator(Bytes.toBytes(rowKeyValue)));

Scan scan = new Scan();

scan.setFilter(rowFilter)

...

在上面例子中,比较符为EQUAL,比较器为BinaryComparator

2)BloomFilter原理简析

· 主要功能:提供随机读的性能

· 存储开销:BloomFilter是列族级别的配置,一旦表格中开启BloomFilter,那么在生成StoreFile时同时会生成

一份包含BloomFilter结构的文件MetaBlock,所以会增加一定的存储开销和内存开销

· 粒度控制:ROW和ROWCOL

· BloomFilter的原理

简单说一下BloomFilter原理:

① 内部是一个bit数组,初始值均为0

② 插入元素时对元素进行hash并且映射到数组中的某一个index,将其置为1,再进行多次不同的hash算法,

将映射到的index置为1,同一个index只需要置1次。

③ 查询时使用跟插入时相同的hash算法,如果在对应的index的值都为1,那么就可以认为该元素可能存在,

注意,只是可能存在

④ 所以BlomFilter只能保证过滤掉不包含的元素,而不能保证误判包含

· 设置:在建表时对某一列设置BloomFilter即可

16.14 HBase的导入导出方式?

1)导入:bin/hbase org.apache.hadoop.hbase.mapreduce.Driver import 表名 路径

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 231 页


路径:来源

本地路径 file:///path

HDFS hdfs://cluster1/path

2)导出:bin/hbase org.apache.hadoop.hbase.mapreduce.Driver export 表名 路径

路径:目的地

本地路径 file:///path

HDFS hdfs://cluster1/path

16.15 Region如何预建分区?

预分区的目的主要是在创建表的时候指定分区数,提前规划表有多个分区,以及每个分区的区间范围,这样在

存储的时候rowkey按照分区的区间存储,可以避免region热点问题。

通常有两种方案:

方案1:shell 方法

create 'tb_splits', {NAME => 'cf',VERSIONS=> 3},{SPLITS => ['10','20','30']}

方案2: JAVA程序控制

· 取样,先随机生成一定数量的rowkey,将取样数据按升序排序放到一个集合里;

· 根据预分区的region个数,对整个集合平均分割,即是相关的splitKeys;

· HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][]splitkeys)可以指定预分区的splitKey,

即是指定region间的rowkey临界值。

16.16 HRegionServer宕机如何处理?

1)ZooKeeper会监控HRegionServer的上下线情况,当ZK发现某个HRegionServer宕机之后会通知HMaster

进行失效备援;

2)该HRegionServer会停止对外提供服务,就是它所负责的region暂时停止对外提供服务;

3)HMaster会将该HRegionServer所负责的region转移到其他HRegionServer上,并且会对HRegionServer上

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 232 页


存在memstore中还未持久化到磁盘中的数据进行恢复;

4) 这个恢复的工作是由WAL重播来完成,这个过程如下:

· wal实际上就是一个文件,存在/hbase/WAL/对应RegionServer路径下。

· 宕机发生时,读取该RegionServer所对应的路径下的wal文件,然后根据不同的region切分成不同的临时

文件recover.edits。

· 当region被分配到新的RegionServer中,RegionServer读取region时会进行是否存在recover.edits,如果有则

进行恢复。

16.17 HBase读写流程?

读:

① HRegionServer保存着meta表以及表数据,要访问表数据,首先Client先去访问zookeeper,从zookeeper

里面获取meta表所在的位置信息,即找到这个meta表在哪个HRegionServer上保存着。

② 接着Client通过刚才获取到的HRegionServer的IP来访问Meta表所在的HRegionServer,从而读取到Meta,

进而获取到Meta表中存放的元数据。

③ Client通过元数据中存储的信息,访问对应的HRegionServer,然后扫描所在HRegionServer的Memstore

和Storefile来查询数据。

④ 最后HRegionServer把查询到的数据响应给Client。

写:

① Client先访问zookeeper,找到Meta表,并获取Meta表元数据。

② 确定当前将要写入的数据所对应的HRegion和HRegionServer服务器。

③ Client向该HRegionServer服务器发起写入数据请求,然后HRegionServer收到请求并响应。

④ Client先把数据写入到HLog,以防止数据丢失。

⑤ 然后将数据写入到Memstore。

⑥ 如果HLog和Memstore均写入成功,则这条数据写入成功

⑦ 如果Memstore达到阈值,会把Memstore中的数据flush到Storefile中。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 233 页


⑧ 当Storefile越来越多,会触发Compact合并操作,把过多的Storefile合并成一个大的Storefile。

⑨ 当Storefile越来越大,Region也会越来越大,达到阈值后,会触发Split操作,将Region一分为二。

16.18 HBase内部机制是什么?

Hbase是一个能适应联机业务的数据库系统

物理存储:hbase的持久化数据是将数据存储在HDFS上。

存储管理:一个表是划分为很多region的,这些region分布式地存放在很多regionserver上Region内部还可以

划分为store,store内部有memstore和storefile。

版本管理:hbase中的数据更新本质上是不断追加新的版本,通过compact操作来做版本间的文件合并Region

的split。

集群管理:ZooKeeper + HMaster + HRegionServer。

16.19 HTable API有没有线程安全问题,在程序是单例还是多例?

在单线程环境下使用hbase的htable是没有问题,但是突然高并发多线程情况下就可能出现问题。

以下为Htable的API说明:

This class is not thread safe for updates; the underlying write buffer can be corrupted if multiple threads
contend over a single HTable instance.

当有多个线程竞争时可能把当前正在写的线程corrupted,那么原因是什么呢?

根据Htable的源码:

public HTable(final byte [] tableName)

throws IOException {

this(HBaseConfiguration.create(), tableName);

public static Configuration create() {

Configuration conf = new Configuration();

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 234 页


return addHbaseResources(conf);

从上面我们可以看到每一个HTable的实例化过程都要创建一个新的conf,我们甚至可以认为一个conf对应的

是一个HTable的connection,因此如果客户端对于同一个表,每次新new 一个configuration对象的话,那么意味

着这两个HTable虽然操作的是同一个table,但是建立的是两条链接connection,它们的socket不是共用的,在多线

程的情况下,经常会有new Htable的情况发生,而每一次的new都可能是一个新的connection,而我们知道zk上的

链接是有限制的如果链接达到一定阈值的话,那么新建立的链接很有可能挤掉原先的connection,而导致线程不安

全。

因此hbase官方文档建议我们:HTable不是线程安全的。建议使用同一个HBaseConfiguration实例来创建

HTable实例,这样可以共享ZooKeeper和socket实例。例如,最好这样做:

HBaseConfiguration conf = HBaseConfiguration.create();

HTable table1 = new HTable(conf, "myTable");

HTable table2 = new HTable(conf, "myTable");

而不是这样:

HBaseConfiguration conf1 = HBaseConfiguration.create();

HTable table1 = new HTable(conf1, "myTable");

HBaseConfiguration conf2 = HBaseConfiguration.create();

HTable table2 = new HTable(conf2, "myTable");

当然最方便的方法就是使用HTablepool了,维持一个线程安全的map里面存放的是tablename和其引用的映

射,可以认为是一个简单的计数器,当需要new 一个HTable实例时直接从该pool中取,用完放回。

16.20 HBase有没有并发问题?(企业)

针对HBase在高并发情况下的性能,我们进行如下测试:

测试版本:hbase 0.94.1、 hadoop 1.0.2、 jdk-6u32-linux-x64.bin、snappy-1.0.5.tar.gz

测试hbase搭建:14台存储机器+2台master、DataNode和regionserver放在一起。

测试一:高并发读(4w+/s) + 少量写(允许分拆、负载均衡)

症状:1-2天后,hbase挂掉(系统性能极差,不到正常的10%)。其实并非全部挂掉,而是某些regionserver挂了,

并在几个小时内引发其他regionserver挂掉。系统无法恢复:单独启regionserver无法恢复正常。重启后正常。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 235 页


测试二:高并发读(4w+/s)

症状:1-2天后,hbase挂掉(系统性能极差,不到正常的10%)。后发现是由于zookeeper.session.timeout设置

不正确导致(参见regionserver部分:http://hbase.apache.org/book.html#trouble)。重启后正常。

测试三:高并发读(4w+/s)

症状:1-2天后,hbase挂掉(系统性能极差,不到正常的10%)。从log未看出问题,但regionserver宕机,且

datanode也宕机。重启后正常。

测试四:高并发读(4w+/s)+禁止分拆、禁止majorcompaction、禁止负载均衡(balance_switch命令)

症状:1-2天后,hbase挂掉(系统性能极差,不到正常的10%)。从log未看出问题,但regionserver宕机,且

datanode也宕机。重启后正常。

测试期间,还发现过:无法获取".MATE."表的内容(想知道regionserver的分布情况)、hbase无法正确停止、hbase

无法正确启动(日志恢复失败,文件错误,最终手动删除日志重启)。

16.21 Hbase中的memstore是用来做什么的?

hbase为了保证随机读取的性能,所以hfile里面的rowkey是有序的。当客户端的请求在到达regionserver之后,

为了保证写入rowkey的有序性,所以不能将数据立刻写入到hfile中,而是将每个变更操作保存在内存中,也就是

memstore中。memstore能够很方便的支持操作的随机插入,并保证所有的操作在内存中是有序的。当memstore

达到一定的量之后,会将memstore里面的数据flush到hfile中,这样能充分利用hadoop写入大文件的性能优势,提

高写入性能。

由于memstore是存放在内存中,如果regionserver因为某种原因死了,会导致内存中数据丢失。所有为了保证

数据不丢失,hbase将更新操作在写入memstore之前会写入到一个write ahead log(WAL)中。WAL文件是追加、顺

序写入的,WAL每个regionserver只有一个,同一个regionserver上所有region写入同一个的WAL文件。这样当某

个regionserver失败时,可以通过WAL文件,将所有的操作顺序重新加载到memstore中。

16.22 HBase在进行模型设计时重点在什么地方?一张表中定义多少个Column Family最合适?为什么?

Column Family的个数具体看表的数据,一般来说划分标准是根据数据访问频度,如一张表里有些列访问相对

频繁,而另一些列访问很少,这时可以把这张表划分成两个列族,分开存储,提高访问效率。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 236 页


整体来说, 通常建议越少越好, 太多的列族会影响我们整个hbase的读写效率,导致读取一行数据需要跨越更多

的列族(底层跨越更多的内存页和文件)

16.23 如何提高HBase客户端的读写性能?请举例说明

1 开启bloomfilter过滤器,开启bloomfilter比没开启要快3、4倍

2 Hbase对于内存有特别的需求,在硬件允许的情况下配足够多的内存给它

3 通过修改hbase-env.sh中的

export HBASE_HEAPSIZE=3000 #这里默认为1000m

4 增大RPC数量

通过修改hbase-site.xml中的hbase.regionserver.handler.count属性,可以适当的放大RPC数量,默认值

为10有点小。

16.24 HBase集群安装注意事项?

① HBase需要HDFS的支持,因此安装HBase前确保Hadoop集群安装完成;

② HBase需要ZooKeeper集群的支持,因此安装HBase前确保ZooKeeper集群安装完成;

③ 注意HBase与Hadoop的版本兼容性;

④ 注意hbase-env.sh配置文件和hbase-site.xml配置文件的正确配置;

⑤ 注意regionservers配置文件的修改;

5 注意集群中的各个节点的时间必须同步,否则启动HBase集群将会报错;

16.25 请描述如何解决HBase中region太小和region太大带来的冲突?

Region过大会发生多次compaction,将数据读一遍并重写一遍到hdfs 上,占用io,region过小会造成多次split,

region 会下线,影响访问服务,最佳的解决方法是调整hbase.hregion. max.filesize 为256m。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 237 页


16.26 解释一下布隆过滤器原理

在日常生活中,包括在设计计算机软件时,我们经常要判断一个元素是否在一个集合中。比如在字处理软件中,

需要检查一个英语单词是否拼写正确(也就是要判断它是否在已知的字典中);在 FBI,一个嫌疑人的名字是否已

经在嫌疑名单上;在网络爬虫里,一个网址是否被访问过等等。最直接的方法就是将集合中全部的元素存在计算机

中,遇到一个新元素时,将它和集合中的元素直接比较即可。一般来讲,计算机中的集合是用哈希表(hash table)

来存储的。它的好处是快速准确,缺点是费存储空间。当集合比较小时,这个问题不显著,但是当集合巨大时,哈

希表存储效率低的问题就显现出来了。比如说,一个象 Yahoo,Hotmail 和 Gmai 那样的公众电子邮件(email)

提供商,总是需要过滤来自发送垃圾邮件的人(spamer)的垃圾邮件。一个办法就是记录下那些发垃圾邮件的 email

地址。由于那些发送者不停地在注册新的地址,全世界少说也有几十亿个发垃圾邮件的地址,将他们都存起来则需

要大量的网络服务器。如果用哈希表,每存储一亿个 email 地址, 就需要 1.6GB 的内存(用哈希表实现的具体

办法是将每一个 email 地址对应成一个八字节的信息指纹googlechinablog.com/2006/08/blog-post.html,然后

将这些信息指纹存入哈希表,由于哈希表的存储效率一般只有 50%,因此一个 email 地址需要占用十六个字节。

一亿个地址大约要 1.6GB, 即十六亿字节的内存)。因此存贮几十亿个邮件地址可能需要上百 GB 的内存。除非

是超级计算机,一般服务器是无法存储的。

布隆过滤器只需要哈希表 1/8 到 1/4 的大小就能解决同样的问题。

Bloom Filter是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素

是否属于这个集合。Bloom Filter的这种高效是有一定代价的:在判断一个元素是否属于某个集合时,有可能会把

不属于这个集合的元素误认为属于这个集合(false positive)。因此,Bloom Filter不适合那些“零错误”的应用场合。

而在能容忍低错误率的应用场合下,Bloom Filter通过极少的错误换取了存储空间的极大节省。

下面我们具体来看Bloom Filter是如何用位数组表示集合的。初始状态时,Bloom Filter是一个包含m位的位数

组,每一位都置为0。

为了表达S={x1, x2,…,xn}这样一个n个元素的集合,Bloom Filter使用k个相互独立的哈希函数(Hash Function),

它们分别将集合中的每个元素映射到{1,…,m}的范围中。对任意一个元素x,第i个哈希函数映射的位置hi(x)就会被置

为1(1≤i≤k)。注意,如果一个位置多次被置为1,那么只有第一次会起作用,后面几次将没有任何效果。在下图中,

k=3,且有两个哈希函数选中同一个位置(从左边数第五位)。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 238 页


在判断y是否属于这个集合时,我们对y应用k次哈希函数,如果所有hi(y)的位置都是1(1≤i≤k),那么我们就认

为y是集合中的元素,否则就认为y不是集合中的元素。下图中y1就不是集合中的元素。y2或者属于这个集合,或者

刚好是一个false positive。

· 为了add一个元素,用k个hash function将它hash得到bloom filter中k个bit位,将这k个bit位置1。

· 为了query一个元素,即判断它是否在集合中,用k个hash function将它hash得到k个bit位。若这k bits全为1,

则此元素在集合中;若其中任一位不为1,则此元素比不在集合中(因为如果在,则在add时已经把对应的k个bits

位置为1)。

· 不允许remove元素,因为那样的话会把相应的k个bits位置为0,而其中很有可能有其他元素对应的位。因此

remove会引入false negative,这是绝对不被允许的。

布隆过滤器决不会漏掉任何一个在黑名单中的可疑地址。但是,它有一条不足之处,也就是它有极小的可能将

一个不在黑名单中的电子邮件地址判定为在黑名单中,因为有可能某个好的邮件地址正巧对应个八个都被设置成一

的二进制位。好在这种可能性很小,我们把它称为误识概率。

布隆过滤器的好处在于快速,省空间,但是有一定的误识别率,常见的补救办法是在建立一个小的白名单,存

储那些可能别误判的邮件地址。

16.27 Hbase是怎么进行预分区操作?

解: 在Hbase中主要有二种预分区方案, 一种为手动预分区, 一种为自动预分区, 手动预分区


指的是我们在建表的时候, 通过命令或者的API进行预分区操作, 在手动分区下, 我们可以自定义
分区, 也可以基于hbase提供的分区算法来实现, 分区后, 多个region会被master分配到不同的
regionServer上进行管理, 从而保证负载均衡.

而自动预分区则指的是, 随着我们表中数据越来越多 当表中数据, 也就是region中数据达到一定


的阈值后, 会自动进行分区, 阈值的多少取决于下面的这个公式来计算:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 239 页


16.28 如何实现HBase的二级索引?

方案一: 通常情况下,较原生方式,我们可以采用ES或者Solr来实现hbase的二级索引的操作, 当
用户要写入数据时候, 基于hbase的observer协处理器拦截下来, 使用es或者Solr来构建hbase的
索引数据, 这样当查询hbase中数据时候, 可以先去ES中查询到对应的数据, 然后根据结果, 在从
hbase中获取最终的完整的结果

方案二: 基于Phoenix实现, Phoenix是一款基于hbase的SQL客户端, 可以使用SQL的方式来操


作hbase, 同时为了提升整体的查询性能, Phoenix中提供了各种索引(全局索引, 本地索引, 覆盖索
引以及函数索引), 这些索引都是基于Hbase的协处理器(主要是ObServer协处理器)而实现的, 二基
于索引的查询方案, 也是Phoenix实现hbase二级索引的方式

16.29 Hbase的storeFile(compact)合并机制是什么?

compact合并机制:

指的memStore中不断进行flush刷新操作, 就会产生多个storeFile的文件, 当storeFile的文


件达到一定阈值后, 就会触发compact的合并机制, 将多个storeFile合并为一个大的HFile文件

阈值: 达到3个及以上

整个合并过程分为两大阶段:

minor :

作用: 将多个小的storeFile合并为一个较大的Hfile操作

阈值: 达到3个及以上

注意: 此合并过程, 仅仅将多个合并为一个, 对数据进行排序操作, 如果此时数据有过期, 或


者有标记为删除数据, 此时不做任何的处理 (类似于 内存合并中基础型)

所以说, 此合并操作, 效率比较高

major:

作用: 将较大的HFile 和 之前的大的Hfile进行合并形成一个更大的Hfile文件 (全局合并)

阈值: 默认 7天

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 240 页


注意: 此合并过程, 会将那些过期的数据, 或者已经标记删除的数据, 在这次合并中, 全部
都清除掉

由于这是一种全局合并操作, 对性能影响比较大, 在实际生产中, 建议 关闭掉自动合并,


采用手动触发的方案

16.30 Hbase的flush刷新机制?

flush刷新机制(溢写合并机制):

流程: 客户端不断将数据写入到memStore内存中, 当内存中数据达到一定阈值后, 需要


将数据溢写刷新的HDFS中 形成一个storeFile文件

阈值: 128M 或者 1小时 满足了那个都会触发flush机制

内部详细流程: hbase 2.0架构 以上流程

1) 客户端不断向memStore中写入数据, 当memStore只数据达到阈值后, 就会
启动flush操作

2) 首先hbase会先关闭掉当前这个已经达到阈值的内存空间, 然后开启一个新
的memStore的空间,用于继续写入工作

3) 将这个达到阈值的内存空间数据放入到 内存队列中, 此队列的特性是只读,


在hbase 2.0架构中, 可以设置此队列的数据尽可能晚的刷新到HDFS中, 当这个队列中数据达到某
个阈值后(内存不足), 这个时候触发flush刷新操作 (队列中可能存储了多个memStore的数据)

4) flush线程会将队列中所有的数据全部读取出来, 然后对数据进行排序合并操
作, 将合并后数据存储到HDFS中, 形成一个storeFile的文件

注意: 在 hbase2.0以下的架构中, 不存在推迟刷新功能, 同样也不存在 合并数据的


操作

当memStore数据达到阈值后, 放入到队列中, 专门有一个flush刷新监控队列,


一旦有数据直接刷新到HDFS上

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 241 页


注意说明:

hbase 2.0 只是提供了基于内存的合并功能, 但是默认情况下 不开启的, 所以 在默认情况


下 整个flush机制基本和2.0以下的版本是一致的, 但是一旦开启了, 就是刚刚描述流程

合并方案: 三种

基础型(basic): 直接将多个memStore数据合并在一起直接刷新到HDFS上,如果数据存在过期
的数据, 或者是已经标记为删除的数据, 基础型不做任何处理

饥渴型(eager): 在将多个memStore合并的过程中, 积极判断数据是否存在过期, 或者是否已


经标记删除, 如果有, 直接过滤掉这些标记删除和过期的数据即可

适应性(adaptive): 检查数据是否有过期数据, 如果过期数据量达到一定阈值后, 就会自动使


用饥渴型, 否则就使用基础型

16.31 如何解决hbase中数据热点问题?

所谓数据热点, 指的是大量的数据写到hbase的某一个或者某几个region中, 导致其余的region没


有数据, 其他region对应regionServer的节点承受了大量的并发请求, 此时就出现了热点问题

解决方案: 通过预分区和设计良好的rowkey来解决

 加盐处理(加随机数) : 可以在rowkey前面动态添加一些随机数, 从而保证数据可以均匀落在


不同region中
 基本保证数据落在不同region
 将相关性比较强的数据分散在不同的额region中, 导致查询的效率有一定降低
 hash处理: 根据rowkey计算其hash值, 在rowkey前面hash计算值即可 (MD5 HASH)
 让相关性比较强的数据可以被放置到同一个region中
 如果相关数据比较多, 依然会导致热点问题
 反转策略: 比如说手机号反转 或者 时间戳的反转
 好处: 基本保证数据落在不同region
 弊端: 将相关性比较强的数据分散在不同的region中, 导致查询的效率有一定降低

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 242 页


17. V10.0Kafka高频面试题

17.1 请说明什么是Apache Kafka?

Apache Kafka是由Apache开发的一种发布订阅消息系统,它是一个分布式的、分区的和重复的日志服务。

17.2 请说明什么是传统的消息传递方法?

传统的消息传递方法包括两种:

排队:在队列中,一组用户可以从服务器中读取消息,每条消息都发送给其中一个人。

发布-订阅:在这个模型中,消息被广播给所有的用户。

17.3 请说明Kafka相对于传统的消息传递方法有什么优势?

高性能:单一的Kafka代理可以处理成千上万的客户端,每秒处理数兆字节的读写操作,Kafka性能远超过传统

的ActiveMQ、RabbitMQ等,而且Kafka支持Batch操作;

可扩展:Kafka集群可以透明的扩展,增加新的服务器进集群;

容错性: Kafka每个Partition数据会复制到几台服务器,当某个Broker失效时,Zookeeper将通知生产者和消

费者从而使用其他的Broker;

17.4 Kafka服务器能接收到的最大信息是多少?

Kafka服务器可以接收到的消息的最大大小是1000000字节。

17.5 Kafka中的ZooKeeper是什么?Kafka是否可以脱离ZooKeeper独立运行?

Zookeeper是一个开放源码的、高性能的协调服务,它用于Kafka的分布式应用。

不可以,不可能越过Zookeeper直接联系Kafka broker,一旦Zookeeper停止工作,它就不能服务客户端请求。

Zookeeper主要用于在集群中不同节点之间进行通信,在Kafka中,它被用于提交偏移量,因此如果节点在任何

情况下都失败了,它都可以从之前提交的偏移量中获取,除此之外,它还执行其他活动,如: leader检测、分布式

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 243 页


同步、配置管理、识别新节点何时离开或连接、集群、节点实时状态等等。

17.6 解释Kafka的用户如何消费信息?

在Kafka中传递消息是通过使用sendfile API完成的。它支持将字节Socket转移到磁盘,通过内核空间保存副本,

并在内核用户之间调用内核。

17.7 解释如何提高远程用户的吞吐量?

如果用户位于与broker不同的数据中心,则可能需要调优Socket缓冲区大小,以对长网络延迟进行摊销。

17.8 解释一下,在数据制作过程中,你如何能从Kafka得到准确的信息?

在数据中,为了精确地获得Kafka的消息,你必须遵循两件事: 在数据消耗期间避免重复,在数据生产过程中

避免重复。

这里有两种方法,可以在数据生成时准确地获得一个语义:

每个分区使用一个单独的写入器,每当你发现一个网络错误,检查该分区中的最后一条消息,以查看您的最后

一次写入是否成功

在消息中包含一个主键(UUID或其他),并在用户中进行反复制

17.9 解释如何减少ISR中的扰动?broker什么时候离开ISR?

ISR是一组与leaders完全同步的消息副本,也就是说ISR中包含了所有提交的消息。ISR应该总是包含所有的副

本,直到出现真正的故障。如果一个副本从leader中脱离出来,将会从ISR中删除。

17.10 Kafka为什么需要复制?

Kafka的信息复制确保了任何已发布的消息不会丢失,并且可以在机器错误、程序错误或更常见些的软件升级

中使用。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 244 页


17.11 如果副本在ISR中停留了很长时间表明什么?

如果一个副本在ISR中保留了很长一段时间,那么它就表明,跟踪器无法像在leader收集数据那样快速地获取

数据。

17.12 请说明如果首选的副本不在ISR中会发生什么?

如果首选的副本不在ISR中,控制器将无法将leadership转移到首选的副本。

17.13 Kafka有可能在生产后发生消息偏移吗?

在大多数队列系统中,作为生产者的类无法做到这一点,它的作用是触发并忘记消息。broker将完成剩下的工

作,比如使用id进行适当的元数据处理、偏移量等。

作为消息的用户,你可以从Kafka broker中获得补偿。如果你注视SimpleConsumer类,你会注意到它会获取包

括偏移量作为列表的MultiFetchResponse对象。此外,当你对Kafka消息进行迭代时,你会拥有包括偏移量和消息

发送的MessageAndOffset对象。

17.14 请说明Kafka 的消息投递保证(delivery guarantee)机制以及如何实现?

Kafka支持三种消息投递语义:

① At most once 消息可能会丢,但绝不会重复传递

② At least one 消息绝不会丢,但可能会重复传递

③ Exactly once 每条消息肯定会被传输一次且仅传输一次,很多时候这是用户想要的

consumer在从broker读取消息后,可以选择commit,该操作会在Zookeeper中存下该consumer在该partition

下读取的消息的offset,该consumer下一次再读该partition时会从下一条开始读取。如未commit,下一次读取的开

始位置会跟上一次commit之后的开始位置相同。

可以将consumer设置为autocommit,即consumer一旦读到数据立即自动commit。如果只讨论这一读取消息

的过程,那Kafka是确保了Exactly once。但实际上实际使用中consumer并非读取完数据就结束了,而是要进行进

一步处理,而数据处理与commit的顺序在很大程度上决定了消息从broker和consumer的delivery guarantee

semantic。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 245 页


·读完消息先commit再处理消息。这种模式下,如果consumer在commit后还没来得及处理消息就crash了,下

次重新开始工作后就无法读到刚刚已提交而未处理的消息,这就对应于At most once。

·读完消息先处理再commit消费状态(保存offset)。这种模式下,如果在处理完消息之后commit之前Consumer

crash了,下次重新开始工作时还会处理刚刚未commit的消息,实际上该消息已经被处理过了,这就对应于At least

once。

·如果一定要做到Exactly once,就需要协调offset和实际操作的输出。经典的做法是引入两阶段提交,但由于许

多输出系统不支持两阶段提交,更为通用的方式是将offset和操作输入存在同一个地方。比如,consumer拿到数据

后可能把数据放到HDFS,如果把最新的offset和数据本身一起写到HDFS,那就可以保证数据的输出和offset的更新

要么都完成,要么都不完成,间接实现Exactly once。(目前就high level API而言,offset是存于Zookeeper中的,

无法存于HDFS,而low level API的offset是由自己去维护的,可以将之存于HDFS中)。

总之,Kafka默认保证At least once,并且允许通过设置producer异步提交来实现At most once,而Exactly once

要求与目标存储系统协作,Kafka提供的offset可以较为容易地实现这种方式。

17.15 如何保证Kafka的消息有序

Kafka对于消息的重复、丢失、错误以及顺序没有严格的要求。

Kafka只能保证一个partition中的消息被某个consumer消费时是顺序的,事实上,从Topic角度来说,当有多个

partition时,消息仍然不是全局有序的。

17.16 kafka数据丢失问题,及如何保证

1)数据丢失:

acks=1的时候(只保证写入leader成功),如果刚好leader挂了。数据会丢失。

acks=0的时候,使用异步模式的时候,该模式下kafka无法保证消息,有可能会丢。

2)brocker如何保证不丢失:

acks=all : 所有副本都写入成功并确认。

retries = 一个合理值。

min.insync.replicas=2 消息至少要被写入到这么多副本才算成功。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 246 页


unclean.leader.election.enable=false 关闭unclean leader选举,即不允许非ISR中的副本被选举为leader,以

避免数据丢失。

3)Consumer如何保证不丢失

如果在消息处理完成前就提交了offset,那么就有可能造成数据的丢失。

enable.auto.commit=false 关闭自动提交offset

处理完数据之后手动提交。

17.17 kafka的balance是怎么做的

官方原文

Producers publish data to the topics of their choice. The producer is able to choose which message
to assign to which partition within the topic. This can be done in a round-robin fashion simply to
balance load or it can be done according to some semantic partition function (say based on some
key in the message). More on the use of partitioning in a second.

翻译:

生产者将数据发布到他们选择的主题。生产者可以选择在主题中分配哪个分区的消息。这可以通过循环的方式

来完成,只是为了平衡负载,或者可以根据一些语义分区功能(比如消息中的一些键)来完成。更多关于分区在一

秒钟内的使用。

17.18 kafka的消费者方式

consumer采用pull(拉)模式从broker中读取数据。

push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。它的目标是尽可能

以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。

而pull模式则可以根据consumer的消费能力以适当的速率消费消息。

对于Kafka而言,pull模式更合适,它可简化broker的设计,consumer可自主控制消费消息的速率,同时

consumer可以自己控制消费方式——即可批量消费也可逐条消费,同时还能选择不同的提交方式从而实现不同的

传输语义。

pull模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直等待数据到达。为了避免这种情况,

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 247 页


我们在我们的拉请求中有参数,允许消费者请求在等待数据到达的“长轮询”中进行阻塞。

17.19 为什么kafka可以实现高吞吐?单节点kafka的吞吐量也比其他消息队列大,为什么?

Kafka是分布式消息系统,需要处理海量的消息,Kafka的设计是把所有的消息都写入速度低容量大的硬盘,以
此来换取更强的存储能力,但实际上,使用硬盘并没有带来过多的性能损失。kafka主要使用了以下几个方式实现了
超高的吞吐率

顺序读写

kafka的消息是不断追加到文件中的,这个特性使kafka可以充分利用磁盘的顺序读写性能顺序读写不需要硬盘磁头
的寻道时间,只需很少的扇区旋转时间,所以速度远快于随机读写

零拷贝

先简单了解下文件系统的操作流程,例如一个程序要把文件内容发送到网络,这个程序是工作在用户空间,文件
和网络socket属于硬件资源,两者之间有一个内核空间在操作系统内部,整个过程为:

在Linux kernel2.2 之后出现了一种叫做”零拷贝(zero-copy)”系统调用机制,就是跳过“用户缓冲区”的拷贝,建


立一个磁盘空间和内存的直接映射,数据不再复制到“用户态缓冲区”系统上下文切换减少为2次,可以提升一倍的性

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 248 页


文件分段
kafka的队列topic被分为了多个区partition,每个partition又分为多个段segment,所以一个队列中的消息实际
上是保存在N多个片段文件中通过分段的方式,每次文件操作都是对一个小文件的操作,非常轻便,同时也增加了
并行处理能力

批量发送

Kafka允许进行批量发送消息,先将消息缓存在内存中,然后一次请求批量发送出去比如可以指定缓存的消息达
到某个量的时候就发出去,或者缓存了固定的时间后就发送出去如100条消息就发送,或者每5秒发送一次这种策略
将大大减少服务端的I/O次数

数据压缩

Kafka还支持对消息集合进行压缩,Producer可以通过GZIP或Snappy格式对消息集合进行压缩压缩的好处就是
减少传输的数据量,减轻对网络传输的压力Producer压缩之后,在Consumer需进行解压,虽然增加了CPU的工作,

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 249 页


但在对大数据处理上,瓶颈在网络上而不是CPU,所以这个成本很值得

17.20 Kafka的ISR

ISR代表In-Sync Replicas,在Kafka里表示目前处于同步状态的那些副本(replica)。

Kafka规定一条消息只有当ISR中所有的副本都复制成功时,才能被消费。

17.21 请说明kafka生产者的数据分发策略有几种?

1) hash取模计算法 在发送数据的时候需要传递 key 和 value .默认根据key的hash

2) 粘性分区(2.4版本下: 轮询方案 )

当生产者去发送数据时候, 一般都是采用批量的发送方案, 当发送一批数据到broker端后 首


先会先随机选择其中一个分片, 然后尽可能粘住这个分片, 将这一批数据全部交给这一个分片

老版本轮询方案:

当生产者去发送数据时候, 一般都是采用批量的发送方案, 当发送一批数据到broker端后, 根


据分片的数量, 将一批数据切分为多个小的批次, 一个批次对应一个分片, 然后写入到topic的各个
分片上

粘性分区好处: 减少中间这个切分的方案, 直接将一批全部写入给某一个分片即可 同时也会


减少了中间ack响应的次数 从而来提升效率

3) 指定给某一个分片: 在发生数据的时候, 可以设置定制的分区编号, 来实现

4) 自定义分发策略:

4.1) 创建一个类, 实现 Partitioner接口,

4.2) 重写其接口中方法: partition(主要的方法) close

partition方法中参数:

String topic,

Object key,

byte[] keyBytes,

Object value,

byte[] valueBytes,

cluster cluster : 通过此对象 可以获取对应topic有几个分片

4.3) 将自定义的分区类 配置到生产者的配置对象中:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 250 页


key: partitioner.class

value: org.apache.kafka.clients.producer.internals.DefaultPartitioner

17.22 请简单描述一下kafka中消费者的负载均衡机制

Kakfa的消费者负载均衡规定, 在一个消费者组内, 监控某一个topic的消费者的数量最多和这个


topic的分片数量是相等的 如果大于了分片的数量, 必然会有消费者处于闲置的状态.

17.23 当消费者无法及时消费kafka中数据, 出现了消息积压, 如何解决呢?

1) 可以增加消费者的数量(注意: 最多和topic的分片数量相等, 并保证都在一个组内)

2) 如果无法增加, 可以调整topic的分片数量, 以此来增加更多的消费者

3) 调整消费者的消息的机制, 让其消费的更快

17.24 请说明kakfa中消息的存储和查询的机制

在kafka中, 数据的存储都是分布式存储, 一个topic的数据被分在了多个分片上, 然后最终让


每个分片的多个副本来存储, 而副本的数据就是存储在kafka的设定的数据目录下, 在每个副本下,
数据都是分文件段的形式来存储, 一个文件段中主要包含两个文件一个log文件. 一个index文件,
index文件存储了log数据文件的索引信息, 保证后续的查询更快, 每个文件段最多存储1GB的数据,
达到后, 就会滚动形成一个新的文件段, 同时文件名称代表了此文件存储消息的起始偏移量信息

文件查询机制: 当查询某一个topic的也是先去从各个主副本中确定数据在那个副本中, 然后找


到这个副本的对应的文件段, 接着查询这个文件段中index文件, 找到消息在log文件的物理偏移量
位置, 最终到log文件中顺序查询到这条消息

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 251 页


18. V10.0Flink必备基础(必记必看)

18.1 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 则使用两阶段提交协议来解决这个问题。

18.2 Flink的组件栈有哪些?

根据 Flink 官网描述,Flink 是一个分层架构的系统,每一层所包含的组件都提供了特定的抽象,用来服务于

上层组件。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 252 页


自下而上,每一层分别代表:Deploy 层:该层主要涉及了Flink的部署模式,在上图中我们可以看出,Flink 支

持包括local、Standalone、Cluster、Cloud等多种部署模式。Runtime 层:Runtime层提供了支持 Flink 计算的核

心实现,比如:支持分布式 Stream 处理、JobGraph到ExecutionGraph的映射、调度等等,为上层API层提供基础

服务。API层:API 层主要实现了面向流(Stream)处理和批(Batch)处理API,其中面向流处理对应DataStream

API,面向批处理对应DataSet API,后续版本,Flink有计划将DataStream和DataSet API进行统一。Libraries层:该

层称为Flink应用框架层,根据API层的划分,在API层之上构建的满足特定应用的实现计算框架,也分别对应于面向

流处理和面向批处理两类。面向流处理支持:CEP(复杂事件处理)、基于SQL-like的操作(基于Table的关系操作);

面向批处理支持:FlinkML(机器学习库)、Gelly(图处理)。

18.3 Flink 的运行必须依赖 Hadoop组件吗?

Flink可以完全独立于Hadoop,在不依赖Hadoop组件下运行。但是做为大数据的基础设施,Hadoop体系是

任何大数据框架都绕不过去的。Flink可以集成众多Hadooop 组件,例如Yarn、Hbase、HDFS等等。例如,Flink

可以和Yarn集成做资源调度,也可以读写HDFS,或者利用HDFS做检查点。

18.4 你们的Flink集群规模多大?

大家注意,这个问题看起来是问你实际应用中的Flink集群规模,其实还隐藏着另一个问题:Flink可以支持多

少节点的集群规模?在回答这个问题时候,可以将自己生产环节中的集群规模、节点、内存情况说明,同时说明部

署模式(一般是Flink on Yarn),除此之外,用户也可以同时在小集群(少于5个节点)和拥有 TB 级别状态的上

千个节点上运行 Flink 任务。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 253 页


18.5 Flink的基础编程模型了解吗?

上图是来自Flink官网的运行流程图。通过上图我们可以得知,Flink 程序的基本构建是数据输入来自一个 Source,

Source 代表数据的输入端,经过 Transformation 进行转换,然后在一个或者多个Sink接收器中结束。数据流

(stream)就是一组永远不会停止的数据记录流,而转换(transformation)是将一个或多个流作为输入,并生成

一个或多个输出流的操作。执行时,Flink程序映射到 streaming dataflows,由流(streams)和转换操作

(transformation operators)组成。

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

Flink 程序在运行时主要有 TaskManager,JobManager,Client三种角色。其中JobManager扮演着集群中的管理

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 254 页


者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。

18.7 说说 Flink 资源管理中 Task Slot 的概念

在Flink架构角色中我们提到,TaskManager是实际负责执行计算的Worker,TaskManager 是一个 JVM 进程,并

会以独立的线程来执行一个task或多个subtask。为了控制一个 TaskManager 能接受多少个 task,Flink 提出了

Task Slot 的概念。简单的说,TaskManager会将自己节点上管理的资源分为不同的Slot:固定大小的资源子集。这

样就避免了不同Job的Task互相竞争内存资源,但是需要主要的是,Slot只会做内存的隔离。没有做CPU的隔离。

18.8 说说 Flink 的常用算子?

Flink 最常用的常用算子包括:Map:DataStream → DataStream,输入一个参数产生一个参数,map的功能是对

输入的参数进行转换操作。Filter:过滤掉指定条件的数据。KeyBy:按照指定的key进行分组。Reduce:用来进行

结果汇总合并。Window:窗口函数,根据某些特性将每个key的数据进行分组(例如:在5s内到达的数据)

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

Flink中的任务被分为多个并行任务来执行,其中每个并行的实例处理一部分数据。这些并行实例的数量被称为并行

度。我们在实际生产环境中可以从四个不同层面设置并行度:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 255 页


操作算子层面(Operator Level)

执行环境层面(Execution Environment Level)

客户端层面(Client Level)

系统层面(System Level)

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

18.10 Flink的Slot和parallelism有什么区别?

官网上十分经典的图:

slot是指taskmanager的并发执行能力,假设我们将 taskmanager.numberOfTaskSlots 配置为3 那么每一个

taskmanager 中分配3个 TaskSlot, 3个 taskmanager 一共有9个TaskSlot。

parallelism是指taskmanager实际使用的并发能力。假设我们把 parallelism.default 设置为1,那么9个

TaskSlot 只能用1个,有8个空闲。

18.11 Flink有没有重启策略?说说有哪几种?

Flink 实现了多种重启策略。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 256 页


固定延迟重启策略(Fixed Delay Restart Strategy)

故障率重启策略(Failure Rate Restart Strategy)

没有重启策略(No Restart Strategy)

Fallback重启策略(Fallback Restart Strategy)

18.12 用过Flink中的分布式缓存吗?如何使用?

Flink实现的分布式缓存和Hadoop有异曲同工之妙。目的是在本地读取文件,并把他放在 taskmanager 节点

中,防止task重复拉取。

val env = ExecutionEnvironment.getExecutionEnvironment

// register a file from HDFS


env.registerCachedFile("hdfs:///path/to/your/file", "hdfsFile")

// register a local executable file (script, executable, ...)


env.registerCachedFile("file:///path/to/exec/file", "localExecFile", true)

// define your program and execute


...
val input: DataSet[String] = ...
val result: DataSet[Integer] = input.map(new MyMapper())
...
env.execute()

18.13 说说Flink中的广播变量,使用时需要注意什么?

我们知道Flink是并行的,计算过程可能不在一个 Slot 中进行,那么有一种情况即:当我们需要访问同一份数据。

那么Flink中的广播变量就是为了解决这种情况。我们可以把广播变量理解为是一个公共的共享变量,我们可以把一

个dataset 数据集广播出去,然后不同的task在节点上都能够获取到,这个数据在每个节点上只会存在一份。

18.14 说说Flink中的窗口?

来一张官网经典的图:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 257 页


Flink 支持两种划分窗口的方式,按照time和count。如果根据时间划分窗口,那么它就是一个time-window 如

果根据数据划分窗口,那么它就是一个count-window。flink支持窗口的两个重要属性(size和interval)如果

size=interval,那么就会形成tumbling-window(无重叠数据) 如果size>interval,那么就会形成sliding-window(有重

叠数据) 如果size< interval, 那么这种窗口将会丢失数据。比如每5秒钟,统计过去3秒的通过路口汽车的数据,将

会漏掉2秒钟的数据。通过组合可以得出四种基本窗口:

time-tumbling-window 无重叠数据的时间窗口,设置方式举例:timeWindow(Time.seconds(5))

time-sliding-window 有重叠数据的时间窗口,设置方式举例:timeWindow(Time.seconds(5),

Time.seconds(3))

count-tumbling-window无重叠数据的数量窗口,设置方式举例:countWindow(5)

count-sliding-window 有重叠数据的数量窗口,设置方式举例:countWindow(5,3)

18.15 说说Flink中的状态存储?

Flink在做计算的过程中经常需要存储中间状态,来避免数据丢失和状态恢复。选择的状态存储策略不同,会

影响状态持久化如何和 checkpoint 交互。Flink提供了三种状态存储方式:MemoryStateBackend、FsStateBackend、

RocksDBStateBackend。

18.16 Flink中的时间有哪几类

Flink 中的时间和其他流式计算系统的时间一样分为三类:事件时间,摄入时间,处理时间三种。如果以

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 258 页


EventTime 为基准来定义时间窗口将形成EventTimeWindow,要求消息本身就应该携带EventTime。如果以

IngesingtTime 为基准来定义时间窗口将形成 IngestingTimeWindow,以 source 的systemTime为准。如果以

ProcessingTime 基准来定义时间窗口将形成 ProcessingTimeWindow,以 operator 的systemTime 为准。

18.17 Flink 中水印是什么概念,起到什么作用?

Watermark 是 Apache Flink 为了处理 EventTime 窗口计算提出的一种机制, 本质上是一种时间戳。 一般

来讲Watermark经常和Window一起被用来处理乱序事件。

18.18 Flink Table & SQL 熟悉吗?TableEnvironment这个类有什么作用

TableEnvironment是Table API和SQL集成的核心概念。这个类主要用来:

在内部catalog中注册表

注册外部catalog

执行SQL查询

注册用户定义(标量,表或聚合)函数

将DataStream或DataSet转换为表

持有对ExecutionEnvironment或StreamExecutionEnvironment的引用

19. V10.0Flink进阶中级

19.1 Flink是如何支持批流一体的?

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 259 页


本道面试题考察的其实就是一句话:Flink的开发者认为批处理是流处理的一种特殊情况。批处理是有限的流处理。

Flink 使用一个引擎支持了DataSet API 和 DataStream API。

19.2 Flink是如何做到高效的数据交换的?

在一个Flink Job中,数据需要在不同的task中进行交换,整个数据交换是有 TaskManager 负责的,TaskManager 的

网络组件首先从缓冲buffer中收集records,然后再发送。Records 并不是一个一个被发送的,而是积累一个批次再

发送,batch 技术可以更加高效的利用网络资源。

19.3 Flink是如何做容错的?

Flink 实现容错主要靠强大的CheckPoint机制和State机制。Checkpoint 负责定时制作分布式快照、对程序中的状

态进行备份;State 用来存储计算过程中的中间状态。

19.4 Flink 分布式快照的原理是什么?

Flink的分布式快照是根据Chandy-Lamport算法量身定做的。简单来说就是持续创建分布式数据流及其状态的一致

快照。

核心思想是在 input source 端插入 barrier,控制 barrier 的同步来实现 snapshot 的备份和 exactly-once 语义。

19.5 Flink是如何保证Exactly-once语义的?

Flink通过实现两阶段提交和状态保存来实现端到端的一致性语义。 分为以下几个步骤:

开始事务(beginTransaction)创建一个临时文件夹,来写把数据写入到这个文件夹里面

预提交(preCommit)将内存中缓存的数据写入文件并关闭

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 260 页


正式提交(commit)将之前写完的临时文件放入目标目录下。这代表着最终的数据会有一些延迟

中止(abort)如果回滚,删除临时文件

若失败发生在预提交成功后,正式提交前。可以根据状态来提交预提交的数据,也可删除预提交的数据。

19.6 Flink 的 kafka 连接器有什么特别的地方?

Flink源码中有一个独立的connector模块,所有的其他connector都依赖于此模块,Flink 在1.9版本发布的全新

kafka连接器,摒弃了之前连接不同版本的kafka集群需要依赖不同版本的connector这种做法,只需要依赖一个

connector即可。

19.7 说说 Flink的内存管理是如何做的?

Flink 并不是将大量对象存在堆上,而是将对象都序列化到一个预分配的内存块上。此外,Flink大量的使用了

堆外内存。如果需要处理的数据超出了内存限制,则会将部分数据存储到硬盘上。Flink 为了直接操作二进制数据

实现了自己的序列化框架。理论上Flink的内存管理分为三部分:

Network Buffers:这个是在TaskManager启动的时候分配的,这是一组用于缓存网络数据的内存,每个块是

32K,默认分配2048个,可以通过“taskmanager.network.numberOfBuffers”修改

Memory Manage pool:大量的Memory Segment块,用于运行时的算法(Sort/Join/Shuffle等),这部分启

动的时候就会分配。下面这段代码,根据配置文件中的各种参数来计算内存的分配方法。(heap or off-heap,这

个放到下节谈),内存的分配支持预分配和lazy load,默认懒加载的方式。

User Code,这部分是除了Memory Manager之外的内存用于User code和TaskManager本身的数据结构。

19.8 说说 Flink的序列化如何做的?

Java本身自带的序列化和反序列化的功能,但是辅助信息占用空间比较大,在序列化对象时记录了过多的类信

息。Apache Flink摒弃了Java原生的序列化方法,以独特的方式处理数据类型和序列化,包含自己的类型描述符,

泛型类型提取和类型序列化框架。TypeInformation 是所有类型描述符的基类。它揭示了该类型的一些基本属性,

并且可以生成序列化器。TypeInformation 支持以下几种类型:

BasicTypeInfo: 任意Java 基本类型或 String 类型

BasicArrayTypeInfo: 任意Java基本类型数组或 String 数组

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 261 页


WritableTypeInfo: 任意 Hadoop Writable 接口的实现类

TupleTypeInfo: 任意的 Flink Tuple 类型(支持Tuple1 to Tuple25)。Flink tuples 是固定长度固定类型的Java

Tuple实现

CaseClassTypeInfo: 任意的 Scala CaseClass(包括 Scala tuples)

PojoTypeInfo: 任意的 POJO (Java or Scala),例如,Java对象的所有成员变量,要么是 public 修饰符定义,

要么有 getter/setter 方法

GenericTypeInfo: 任意无法匹配之前几种类型的类

针对前六种类型数据集,Flink皆可以自动生成对应的TypeSerializer,能非常高效地对数据集进行序列化和反

序列化。

19.9 Flink中的Window出现了数据倾斜,你有什么解决办法?

window产生数据倾斜指的是数据在不同的窗口内堆积的数据量相差过多。本质上产生这种情况的原因是数据

源头发送的数据量速度不同导致的。出现这种情况一般通过以下方式解决:

1、在数据进入窗口前做预聚合

2、重新设计窗口聚合的key

3、使用再平衡算子rebalance等

19.10 Flink中在使用聚合函数 GroupBy、Distinct、KeyBy 等函数时出现数据热点该如何解决?

数据倾斜和数据热点是所有大数据框架绕不过去的问题。处理这类问题主要从3个方面入手:

在业务上规避这类问题

例如一个假设订单场景,北京和上海两个城市订单量增长几十倍,其余城市的数据量不变。这时候我们在进行

聚合的时候,北京和上海就会出现数据堆积,我们可以单独数据北京和上海的数据。

Key的设计上

把热key进行拆分,比如上个例子中的北京和上海,可以把北京和上海按照地区进行拆分聚合。

参数设置

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 262 页


Flink 1.9.0 SQL(Blink Planner) 性能优化中一项重要的改进就是升级了微批模型,即 MiniBatch。原理是缓存

一定的数据后再触发处理,以减少对State的访问,从而提升吞吐和减少数据的输出量。

19.11 Flink任务延迟高,想解决这个问题,你会如何入手?

在Flink的后台任务管理中,我们可以看到Flink的哪个算子和task出现了反压。最主要的手段是资源调优和算子

调优。资源调优即是对作业中的Operator的并发数(parallelism)、CPU(core)、堆内存(heap_memory)等

参数进行调优。作业参数调优包括:并行度的设置,State的设置,checkpoint的设置。

19.12 Flink是如何处理反压的?

Flink 内部是基于 producer-consumer 模型来进行消息传递的,Flink的反压设计也是基于这个模型。Flink 使

用了高效有界的分布式阻塞队列,就像 Java 通用的阻塞队列(BlockingQueue)一样。下游消费者消费变慢,上

游就会受到阻塞。

19.13 Flink的反压和Strom有哪些不同?

Storm 是通过监控 Bolt 中的接收队列负载情况,如果超过高水位值就会将反压信息写到 Zookeeper ,

Zookeeper 上的 watch 会通知该拓扑的所有 Worker 都进入反压状态,最后 Spout 停止发送 tuple。Flink中的

反压使用了高效有界的分布式阻塞队列,下游消费变慢会导致发送端阻塞。二者最大的区别是Flink是逐级反压,而

Storm是直接从源头降速。

19.14 Operator Chains(算子链)这个概念你了解吗?

为了更高效地分布式执行,Flink会尽可能地将operator的subtask链接(chain)在一起形成task。每个task在一个线

程中执行。将operators链接成task是非常有效的优化:它能减少线程之间的切换,减少消息的序列化/反序列化,

减少数据在缓冲区的交换,减少了延迟的同时提高整体的吞吐量。这就是我们所说的算子链。

19.15 Flink什么情况下才会把Operator chain在一起形成算子链?

两个operator chain在一起的的条件:

上下游的并行度一致

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 263 页


下游节点的入度为1 (也就是说下游节点没有来自其他节点的输入)

上下游节点都在同一个 slot group 中(下面会解释 slot group)

下游节点的 chain 策略为 ALWAYS(可以与上下游链接,map、flatmap、filter等默认是ALWAYS)

上游节点的 chain 策略为 ALWAYS 或 HEAD(只能与下游链接,不能与上游链接,Source默认是HEAD)

两个节点间数据分区方式是 forward(参考理解数据流的分区)

用户没有禁用 chain

19.16 消费kafka数据的时候,如何处理脏数据?

可以在处理前加一个fliter算子,将不符合规则的数据过滤出去。

20. V10.0Flink进阶高级

20.1 Flink Job的提交流程

用户提交的Flink Job会被转化成一个DAG任务运行,分别是:StreamGraph、JobGraph、ExecutionGraph,Flink

中JobManager与TaskManager,JobManager与Client的交互是基于Akka工具包的,是通过消息驱动。整个Flink Job

的提交还包含着ActorSystem的创建,JobManager的启动,TaskManager的启动和注册。

20.2 Flink所谓"三层图"结构是哪几个"图"?

一个Flink任务的DAG生成计算图大致经历以下三个过程:

 StreamGraph 最接近代码所表达的逻辑层面的计算拓扑结构,按照用户代码的执行顺序向

StreamExecutionEnvironment添加StreamTransformation构成流式图。

 JobGraph 从StreamGraph生成,将可以串联合并的节点进行合并,设置节点之间的边,安排资源共享slot槽

位和放置相关联的节点,上传任务所需的文件,设置检查点配置等。相当于经过部分初始化和优化处理的任务

图。

 ExecutionGraph 由JobGraph转换而来,包含了任务具体执行所需的内容,是最贴近底层实现的执行图。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 264 页


20.3 JobManger在集群中扮演了什么角色?

JobManager 负责整个 Flink 集群任务的调度以及资源的管理,从客户端中获取提交的应用,然后根据集群中

TaskManager 上 TaskSlot 的使用情况,为提交的应用分配相应的 TaskSlot 资源并命令 TaskManager 启动从客

户端中获取的应用。JobManager 相当于整个集群的 Master 节点,且整个集群有且只有一个活跃的

JobManager ,负责整个集群的任务管理和资源管理。JobManager 和 TaskManager 之间通过 Actor System 进

行通信,获取任务执行的情况并通过 Actor System 将应用的任务执行情况发送给客户端。同时在任务执行的过程

中,Flink JobManager 会触发 Checkpoint 操作,每个 TaskManager 节点 收到 Checkpoint 触发指令后,完成

Checkpoint 操作,所有的 Checkpoint 协调过程都是在 Fink JobManager 中完成。当任务完成后,Flink 会将任

务执行的信息反馈给客户端,并且释放掉 TaskManager 中的资源以供下一次提交任务使用。

20.4 JobManger在集群启动过程中起到什么作用?

JobManager的职责主要是接收Flink作业,调度Task,收集作业状态和管理TaskManager。它包含一个Actor,

并且做如下操作:

RegisterTaskManager: 它由想要注册到JobManager的TaskManager发送。注册成功会通过

AcknowledgeRegistration消息进行Ack。

SubmitJob: 由提交作业到系统的Client发送。提交的信息是JobGraph形式的作业描述信息。

CancelJob: 请求取消指定id的作业。成功会返回CancellationSuccess,否则返回CancellationFailure。

UpdateTaskExecutionState: 由TaskManager发送,用来更新执行节点(ExecutionVertex)的状态。成功则返回

true,否则返回false。

RequestNextInputSplit: TaskManager上的Task请求下一个输入split,成功则返回NextInputSplit,否则返回

null。

JobStatusChanged: 它意味着作业的状态(RUNNING, CANCELING, FINISHED,等)发生变化。这个消息由

ExecutionGraph发送。

20.5 TaskManager在集群中扮演了什么角色?

TaskManager 相当于整个集群的 Slave 节点,负责具体的任务执行和对应任务在每个节点上的资源申请和管

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 265 页


理。客户端通过将编写好的 Flink 应用编译打包,提交到 JobManager,然后 JobManager 会根据已注册在

JobManager 中 TaskManager 的资源情况,将任务分配给有资源的 TaskManager节点,然后启动并运行任务。

TaskManager 从 JobManager 接收需要部署的任务,然后使用 Slot 资源启动 Task,建立数据接入的网络连接,

接收数据并开始数据处理。同时 TaskManager 之间的数据交互都是通过数据流的方式进行的。可以看出,Flink 的

任务运行其实是采用多线程的方式,这和 MapReduce 多 JVM 进行的方式有很大的区别,Flink 能够极大提高

CPU 使用效率,在多个任务和 Task 之间通过 TaskSlot 方式共享系统资源,每个 TaskManager 中通过管理多个

TaskSlot 资源池进行对资源进行有效管理。

20.6 TaskManager在集群启动过程中起到什么作用?

TaskManager的启动流程较为简单: 启动类:org.apache.flink.runtime.taskmanager.TaskManager 核心启

动方法 : selectNetworkInterfaceAndRunTaskManager 启动后直接向JobManager注册自己,注册完成后,进行

部分模块的初始化。

20.7 Flink 计算资源的调度是如何实现的?

TaskManager中最细粒度的资源是Task slot,代表了一个固定大小的资源子集,每个TaskManager会将其所占

有的资源平分给它的slot。

通过调整 task slot 的数量,用户可以定义task之间是如何相互隔离的。每个 TaskManager 有一个slot,也就

意味着每个task运行在独立的 JVM 中。每个 TaskManager 有多个slot的话,也就是说多个task运行在同一个JVM

中。

而在同一个JVM进程中的task,可以共享TCP连接(基于多路复用)和心跳消息,可以减少数据的网络传输,

也能共享一些数据结构,一定程度上减少了每个task的消耗。 每个slot可以接受单个task,也可以接受多个连续task

组成的pipeline,如下图所示,FlatMap函数占用一个taskslot,而key Agg函数和sink函数共用一个taskslot:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 266 页


20.8 简述Flink的数据抽象及数据交换过程?

Flink 为了避免JVM的固有缺陷例如java对象存储密度低,FGC影响吞吐和响应等,实现了自主管理内存。

MemorySegment就是Flink的内存抽象。默认情况下,一个MemorySegment可以被看做是一个32kb大的内存块的

抽象。这块内存既可以是JVM里的一个byte[],也可以是堆外内存(DirectByteBuffer)。在MemorySegment这个

抽象之上,Flink在数据从operator内的数据对象在向TaskManager上转移,预备被发给下个节点的过程中,使用的

抽象或者说内存对象是Buffer。对接从Java对象转为Buffer的中间对象是另一个抽象StreamRecord。

20.9 Flink 中的分布式快照机制是如何实现的?

Flink的容错机制的核心部分是制作分布式数据流和操作算子状态的一致性快照。 这些快照充当一致性

checkpoint,系统可以在发生故障时回滚。 Flink用于制作这些快照的机制在“分布式数据流的轻量级异步快照”中进

行了描述。 它受到分布式快照的标准Chandy-Lamport算法的启发,专门针对Flink的执行模型而定制。

barriers在数据流源处被注入并行数据流中。快照n的barriers被插入的位置(我们称之为Sn)是快照所包含的

数据在数据源中最大位置。例如,在Apache Kafka中,此位置将是分区中最后一条记录的偏移量。 将该位置Sn报

告给checkpoint协调器(Flink的JobManager)。然后barriers向下游流动。当一个中间操作算子从其所有输入流中

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 267 页


收到快照n的barriers时,它会为快照n发出barriers进入其所有输出流中。 一旦sink操作算子(流式DAG的末端)从

其所有输入流接收到barriers n,它就向checkpoint协调器确认快照n完成。在所有sink确认快照后,意味快照着已

完成。一旦完成快照n,job将永远不再向数据源请求Sn之前的记录,因为此时这些记录(及其后续记录)将已经通

过整个数据流拓扑,也即是已经被处理结束。

20.10 简单说说FlinkSQL的是如何实现的?

首先大家要知道 Flink 的SQL解析是基于Apache Calcite这个开源框架。

Flink 将 SQL 校验、SQL 解析以及 SQL 优化交给了Apache Calcite。Calcite 在其他很多开源项目里也都应

用到了,譬如 Apache Hive, Apache Drill, Apache Kylin, Cascading。Calcite 在新的架构中处于核心的地位,如下

图所示。

构建抽象语法树的事情交给了 Calcite 去做。SQL query 会经过 Calcite 解析器转变成 SQL 节点树,通过

验证后构建成 Calcite 的抽象语法树(也就是图中的 Logical Plan)。另一边,Table API 上的调用会构建成 Table

API 的抽象语法树,并通过 Calcite 提供的 RelBuilder 转变成 Calcite 的抽象语法树。然后依次被转换成逻辑执

行计划和物理执行计划。在提交任务后会分发到各个 TaskManager 中运行,在运行时会使用 Janino 编译器编译

代码后运行。

基于此,一次完整的SQL解析过程如下:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 268 页


 用户使用对外提供Stream SQL的语法开发业务应用

 用calcite对StreamSQL进行语法检验,语法检验通过后,转换成calcite的逻辑树节点;最终形成calcite的逻辑

计划

 采用Flink自定义的优化规则和calcite火山模型、启发式模型共同对逻辑树进行优化,生成最优的Flink物理计划

 对物理计划采用janino codegen生成代码,生成用低阶API DataStream 描述的流应用,提交到Flink平台执行

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 269 页


21. V10.0数据结构和算法核心基础

摘要:这一部分主要是数据结构和算法相关的面试题目,虽然只有15道题目,但是包含的信息量还是很大的,

很多题目背后的解题思路和算法是非常值得玩味的。

21.1 给出下面的二叉树先序、中序、后序遍历的序列?

答:先序序列:ABDEGHCF;中序序列:DBGEHACF;后序序列:DGHEBFCA。

补充:二叉树也称为二分树,它是树形结构的一种,其特点是每个结点至多有二棵子树,并且二叉树的子树有左右

之分,其次序不能任意颠倒。二叉树的遍历序列按照访问根节点的顺序分为先序(先访问根节点,接下来先序访问

左子树,再先序访问右子树)、中序(先中序访问左子树,然后访问根节点,最后中序访问右子树)和后序(先后

序访问左子树,再后序访问右子树,最后访问根节点)。如果知道一棵二叉树的先序和中序序列或者中序和后序序

列,那么也可以还原出该二叉树。

例如,已知二叉树的先序序列为:xefdzmhqsk,中序序列为:fezdmxqhks,那么还原出该二叉树应该如下图

所示:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 270 页


21.2 你知道的排序算法都哪些?用Java写一个排序系统。

答:稳定的排序算法有:插入排序、选择排序、冒泡排序、鸡尾酒排序、归并排序、二叉树排序、基数排序等;不

稳定排序算法包括:希尔排序、堆排序、快速排序等。

下面是关于排序算法的一个列表:

下面按照策略模式给出一个排序系统,实现了冒泡排序。

Sorter.java

package com.jackfrued.util;

import java.util.Comparator;

/** 排序器接口(策略模式: 将算法封装到具有共同接口的独立的类中使得它们可以相互替换) * */

public interface Sorter {

/** 排序 @param list 待排序的数组 */

public <T extends Comparable<T>> void sort(T[] list);

/**

* 排序

* @param list 待排序的数组

* @param comp 比较两个对象的比较器

*/

public <T> void sort(T[] list, Comparator<T> comp);

BubbleSorter.java

package com.jackfrued.util;

import java.util.Comparator;

/** 冒泡排序

* @author

*/

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 271 页


public class BubbleSorter implements Sorter {

@Override

public <T extends Comparable<T>> void sort(T[] list) {

boolean swapped = true;

for(int i = 1; i < list.length && swapped;i++) {

swapped= false;

for(int j = 0; j < list.length - i; j++) {

if(list[j].compareTo(list[j+ 1]) > 0 ) {

T temp = list[j];

list[j]= list[j + 1];

list[j+ 1] = temp;

swapped= true;

} } } }

@Override

public <T> void sort(T[] list,Comparator<T> comp) {

boolean swapped = true;

for(int i = 1; i < list.length && swapped; i++) {

swapped = false;

for(int j = 0; j < list.length - i; j++) {

if(comp.compare(list[j], list[j + 1]) > 0 ) {

T temp = list[j];

list[j]= list[j + 1];

list[j+ 1] = temp;

swapped= true;

} } } } }

21.3 写一个二分查找(折半搜索)的算法。

答:折半搜索,也称二分查找算法、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数

组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元

素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为

空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。

import java.util.Comparator;

public class MyUtil {

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 272 页


public static <T extends Comparable<T>> int binarySearch(T[] x, T key) {

return binarySearch(x, 0, x.length- 1, key);

public static <T> int binarySearch(T[] x, T key, Comparator<T> comp) {

int low = 0;

int high = x.length - 1;

while (low <= high) {

int mid = (low + high) >>> 1;

int cmp = comp.compare(x[mid], key);

if (cmp < 0) {

low = mid + 1;

else if (cmp > 0) {

high = mid - 1;

else {

return mid;

return -1;

private static <T extends Comparable<T>> int binarySearch(T[] x, int low, int high, T key) {

if(low <= high) {

int mid = low + ((high -low) >> 1);

if(key.compareTo(x[mid]) == 0) {

return mid;

else if(key.compareTo(x[mid])< 0) {

return binarySearch(x,l ow, mid - 1, key);

else {

return binarySearch(x, mid + 1, high, key);

return -1;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 273 页


说明:两个版本一个用递归实现,一个用循环实现。需要注意的是计算中间位置时不应该使用(high+ low) / 2的方

式,因为加法运算可能导致整数越界,这里应该使用一下三种方式之一:low+ (high – low) / 2或low + (high –

low) >> 1或(low + high) >>> 1(注:>>>是逻辑右移,不带符号位的右移)

21.4 统计一篇英文文章中单词个数。

答:这是普通Java写法

import java.io.FileReader;

public class WordCounting {

public static void main(String[] args) {

try(FileReader fr = new FileReader("a.txt")) {

int counter = 0;

boolean state = false;

int currentChar;

while((currentChar= fr.read()) != -1) {

if(currentChar== ' ' || currentChar == '\n'

|| currentChar == '\t' || currentChar == '\r') {

state = false;

else if(!state) {

state = true;

counter++;

System.out.println(counter);

catch(Exceptione) {

e.printStackTrace();

这个程序可能有很多种写法,这里选择的是Dennis M. Ritchie和Brian W. Kernighan老师在他们不朽的著作《The C

Programming Language》中给出的代码。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 274 页


21.5 输入年月日,计算该日期是这一年的第几天。

答:

import java.util.Scanner;

public class DayCounting {

public static void main(String[] args) {

int[][] data = {

{31,28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},

{31,29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}

};

Scanner sc = newScanner(System.in);

System.out.print("请输入年月日(1980 11 28): ");

int year = sc.nextInt();

int month = sc.nextInt();

int date = sc.nextInt();

int[] daysOfMonth = data[(year % 4 == 0 && year % 100 != 0 || year % 400 == 0)?1 : 0];

int sum = 0;

for(int i = 0; i < month -1; i++) {

sum += daysOfMonth[i];

sum += date;

System.out.println(sum);

sc.close();

21.6 约瑟夫环

15个基督教徒和15个非教徒在海上遇险,必须将其中一半的人投入海中,其余的人才能幸免于难,于是30个人围成

一圈,从某一个人开始从1报数,报到9的人就扔进大海,他后面的人继续从1开始报数,重复上面的规则,直到剩下

15个人为止。结果由于上帝的保佑,15个基督教徒最后都幸免于难,问原来这些人是怎么排列的,哪些位置是基督

教徒,哪些位置是非教徒。

答:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 275 页


public class Josephu {

private static final int DEAD_NUM = 9;

public static void main(String[] args) {

boolean[] persons = new boolean[30];

for(int i = 0; i < persons.length; i++) {

persons[i] = true;

int counter = 0;

int claimNumber = 0;

int index = 0;

while(counter < 15) {

if(persons[index]) {

claimNumber++;

if(claimNumber == DEAD_NUM) {

counter++;

claimNumber= 0;

persons[index]= false;

index++;

if(index >= persons.length) {

index= 0;

for(boolean p : persons) {

if(p) {

System.out.print("基");

else {

System.out.print("非");

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 276 页


21.7 回文素数

所谓回文数就是顺着读和倒着读一样的数(例如:11,121,1991…),回文素数就是既是回文数又是素数(只能被1和自

身整除的数)的数。编程找出11~9999之间的回文素数。

答:

public class PalindromicPrimeNumber {

public static void main(String[] args) {

for(int i = 11; i <= 9999; i++) {

if(isPrime(i) && isPalindromic(i)) {

System.out.println(i);

public static boolean isPrime(int n) {

for(int i = 2; i <= Math.sqrt(n); i++) {

if(n % i == 0) {

return false;

return true;

public static boolean isPalindromic(int n) {

int temp = n;

int sum = 0;

while(temp > 0) {

sum= sum * 10 + temp % 10;

temp/= 10;

return sum == n;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 277 页


21.8 对于一个有N个整数元素的一维数组,找出它的子数组(数组中下标连续的元素组成的数组)之和最大
值。

答:下面给出几个例子(最大子数组用粗体表示):

1)数组:{ 1, -2, 3,5, -3, 2 },结果是:8

2)数组:{ 0, -2, 3, 5, -1, 2 },结果是:9

3)数组:{ -9, -2,-3, -5, -3 },结果是:-2

可以使用动态规划的思想求解:

public class MaxSum {

private static int max(int x, int y) {

return x > y? x: y;

public static int maxSum(int[] array) {

int n = array.length;

int[] start = new int[n];

int[] all = new int[n];

all[n - 1] = start[n - 1] = array[n - 1];

for(int i = n - 2; i >= 0;i--) {

start[i] = max(array[i], array[i] + start[i + 1]);

all[i] = max(start[i], all[i + 1]);

return all[0];

public static void main(String[] args) {

int[] x1 = { 1, -2, 3, 5,-3, 2 };

int[] x2 = { 0, -2, 3, 5,-1, 2 };

int[] x3 = { -9, -2, -3,-5, -3 };

System.out.println(maxSum(x1)); // 8

System.out.println(maxSum(x2)); // 9

System.out.println(maxSum(x3)); //-2

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 278 页


21.9 用递归实现字符串倒转

答:

public class StringReverse {

public static String reverse(String originStr) {

if(originStr == null || originStr.length()== 1) {

return originStr;

return reverse(originStr.substring(1))+ originStr.charAt(0);

public static void main(String[] args) {

System.out.println(reverse("hello"));

21.10 输入一个正整数,将其分解为素数的乘积。

答:

public class DecomposeInteger {

private static List<Integer> list = newArrayList<Integer>();

public static void main(String[] args) {

System.out.print("请输入一个数: ");

Scanner sc = newScanner(System.in);

int n = sc.nextInt();

decomposeNumber(n);

System.out.print(n + " = ");

for(int i = 0; i < list.size() - 1; i++) {

System.out.print(list.get(i) + " * ");

System.out.println(list.get(list.size() - 1));

public static void decomposeNumber(int n) {

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 279 页


if(isPrime(n)) {

list.add(n);

list.add(1);

else {

doIt(n, (int)Math.sqrt(n));

public static void doIt(int n, int div) {

if(isPrime(div) && n % div == 0) {

list.add(div);

decomposeNumber(n / div);

else {

doIt(n, div - 1);

public static boolean isPrime(int n) {

for(int i = 2; i <= Math.sqrt(n);i++) {

if(n % i == 0) {

return false;

return true;

21.11 一个有n级的台阶,一次可以走1级、2级或3级,问走完n级台阶有多少种走法。

答:可以通过递归求解。

public class GoSteps {

public static int countWays(int n) {

if(n < 0) {

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 280 页


return 0;

else if(n == 0) {

return 1;

else {

return countWays(n - 1) + countWays(n - 2) + countWays(n -3);

public static void main(String[] args) {

System.out.println(countWays(5)); // 13

21.12 写一个算法判断一个英文单词的所有字母是否全都不同(不区分大小写)。

答:

public class AllNotTheSame {

public static boolean judge(String str) {

String temp = str.toLowerCase();

int[] letterCounter = new int[26];

for(int i = 0; i <temp.length(); i++) {

int index = temp.charAt(i)- 'a';

letterCounter[index]++;

if(letterCounter[index] > 1) {

return false;

return true;

public static void main(String[] args) {

System.out.println(judge("hello"));

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 281 页


System.out.print(judge("smile"));

21.13 有一个已经排好序的整数数组,其中存在重复元素,请将重复元素删除掉,例如,A= [1, 1, 2, 2, 3],


处理之后的数组应当为A= [1, 2, 3]。

答:

import java.util.Arrays;

public class RemoveDuplication {

public static int[] removeDuplicates(int a[]) {

if(a.length <= 1) {

return a;

int index = 0;

for(int i = 1; i < a.length; i++) {

if(a[index] != a[i]) {

a[++index] = a[i];

int[] b = new int[index + 1];

System.arraycopy(a, 0, b, 0, b.length);

return b;

public static void main(String[] args) {

int[] a = {1, 1, 2, 2, 3};

a = removeDuplicates(a);

System.out.println(Arrays.toString(a));

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 282 页


21.14 给一个数组,其中有一个重复元素占半数以上,找出这个元素。

答:

public class FindMost {

public static <T> T find(T[] x){

T temp = null;

for(int i = 0, nTimes = 0; i< x.length;i++) {

if(nTimes == 0) {

temp= x[i];

nTimes= 1;

else {

if(x[i].equals(temp)) {

nTimes++;

else {

nTimes--;

return temp;

public static void main(String[] args) {

String[]strs = {"hello","kiss","hello","hello","maybe"};

System.out.println(find(strs));

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 283 页


21.15 链表转置/二叉树转置

public TreeNode invertTree(TreeNode root) {

if(root==null)

return null;

// TreeNode nodetemp=root.right;

TreeNode nodetemp=root.left;

root.left=invertTree(root.right);

root.right=invertTree(nodetemp);

return root;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 284 页


21.16 设计模式:单例模式-工厂-代理与装饰模式区别,适用场景

21.17 简单实现一个LRU算法

一个LRU算法最简单的有如下的功能:这里我们先不考虑用哈希表做优化

实际上就是一个链表,通过链表来模拟实现

import java.util.Hashtable;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 285 页


public class LRUCache {

/**

* 链表节点

*/

class CacheNode {

CacheNode prev;//前一节点

CacheNode next;//后一节点

Object value;//值

Object key;//键

CacheNode() {

private int cacheSize;

private Hashtable nodes;//缓存容器

private int currentSize;

private CacheNode first;//链表头

private CacheNode last;//链表尾

public LRUCache(int i) {

currentSize = 0;

cacheSize = i;

nodes = new Hashtable(i);//缓存容器

/**

* 获取缓存中对象

* @param key

* @return

*/

public Object get(Object key) {

CacheNode node = (CacheNode) nodes.get(key);

if (node != null) {

moveToHead(node);

return node.value;

} else {

return null;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 286 页


}

/**

* 添加缓存

* @param key

* @param value

*/

public void put(Object key, Object value) {

CacheNode node = (CacheNode) nodes.get(key);

if (node == null) {

//缓存容器是否已经超过大小.

if (currentSize >= cacheSize) {

if (last != null)//将最少使用的删除

nodes.remove(last.key);

removeLast();

} else {

currentSize++;

node = new CacheNode();

node.value = value;

node.key = key;

//将最新使用的节点放到链表头,表示最新使用的.

moveToHead(node);

nodes.put(key, node);

/**

* 将缓存删除

* @param key

* @return

*/

public Object remove(Object key) {

CacheNode node = (CacheNode) nodes.get(key);

if (node != null) {

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 287 页


if (node.prev != null) {

node.prev.next = node.next;

if (node.next != null) {

node.next.prev = node.prev;

if (last == node)

last = node.prev;

if (first == node)

first = node.next;

return node;

public void clear() {

first = null;

last = null;

/**

* 删除链表尾部节点

* 表示 删除最少使用的缓存对象

*/

private void removeLast() {

//链表尾不为空,则将链表尾指向null. 删除连表尾(删除最少使用的缓存对象)

if (last != null) {

if (last.prev != null)

last.prev.next = null;

else

first = null;

last = last.prev;

/**

* 移动到链表头,表示这个节点是最新使用过的

* @param node

*/

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 288 页


private void moveToHead(CacheNode node) {

if (node == first) {

//如果当前节点就是链表的头,不做处理

return;

if (node.prev != null) {

//如果当前节点的前一个元素不为null,把前一个元素的下一个指向当前元素的下一个元素

node.prev.next = node.next;

if (node.next != null) {

//如果当前节点的后一个元素不为null,把后一个元素的前一个指向当前元素的前一个元素

node.next.prev = node.prev;

if (last == node) {

//如果当前元素为最后一个元素,则最后一个元素变成当前元素的前一个元素

last = node.prev;

if (first != null) {

//如果第一个算不为null,则当前元素的下一个指向为原来的第一个元素

//原来的第一个元素的前一个元素变成当前元素

node.next = first;

first.prev = node;

first = node;

node.prev = null;

if (last == null) {

last = first;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 289 页


22. V10.0大数据数据挖掘基础

22.1 机器学习数据集的理解

22.2 机器学习中特征的理解

def:特征选择和降维
特征选择:原有特征选择出子集,不改变原来的特征空间
降维:将原有的特征重组成为包含信息更多的特征,改变了原有的特征空间
降维的主要方法
Principal Component Analysis(主成分分析)
Singular Value Decomposition(奇异值分解)
Sammon’s Mapping(Sammon映射)
特征选择的方法
Filter方法:卡方检验、信息增益、相关系数

Wrapper方法

其主要思想是:将子集的选择看作是一个搜索寻优问题,生成不同的组合,对组合进行评价,再与其他的组合进行
比较。这样就将子集的选择看作是一个是一个优化问题,这里有很多的优化算法可以解决,尤其是一些启发式的优
化算法,如GA,PSO,DE,ABC等,详见“优化算法——人工蜂群算法(ABC)”,“优化算法——粒子群算法(PSO)”。

Embedded方法

其主要思想是:在模型既定的情况下学习出对提高模型准确性最好的属性。这句话并不是很好理解,其实是讲在确
定模型的过程中,挑选出那些对模型的训练有重要意义的属性。
主要方法:正则化。岭回归就是在基本线性回归的过程中加入了正则项。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 290 页


22.3 机器学习三要素如何理解?

统计学习=模型+策略+算法

模型:规律y=ax+b

策略:什么样的模型是好的模型?损失函数

算法:如何高效找到最优参数,模型中的参数a和b

22.4 机器学习中,有哪些特征选择的工程方法?

数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已
1. 计算每一个特征与相应变量的相关性:工程上常用的手段有计算皮尔逊系数和互信息系数,皮尔逊系数只能衡量
线性相关性而互信息系数能够很好地度量各种相关性,但是计算相对复杂一些,好在很多toolkit里边都包含了这个
工具(如sklearn的MINE),得到相关性之后就可以排序选择特征了;
2. 构建单个特征的模型,通过模型的准确性为特征排序,借此来选择特征;
3.通过L1正则项来选择特征:L1正则方法具有稀疏解的特性,因此天然具备特征选择的特性,但是要注意,L1没有
选到的特征不代表不重要,原因是两个具有高相关性的特征可能只保留了一个,如果要确定哪个特征重要应再通过
L2正则方法交叉检验*;
4. 训练能够对特征打分的预选模型:RandomForest和Logistic Regression等都能对模型的特征打分,通过打分获得
相关性后再训练最终模型;
5.通过特征组合后再来选择特征:如对用户id和用户特征最组合来获得较大的特征集再来选择特征,这种做法在推
荐系统和广告系统中比较常见,这也是所谓亿级甚至十亿级特征的主要来源,原因是用户数据比较稀疏,组合特征
能够同时兼顾全局模型和个性化模型,这个问题有机会可以展开讲。
6.通过深度学习来进行特征选择:目前这种手段正在随着深度学习的流行而成为一种手段,尤其是在计算机视觉领
域,原因是深度学习具有自动学习特征的能力,这也是深度学习又叫unsupervised feature learning的原因。从深
度学习模型中选择某一神经层的特征后就可以用来进行最终目标模型的训练了。

22.5 机器学习中的正负样本

在分类问题中,这个问题相对好理解一点,比如人脸识别中的例子,正样本很好理解,就是人脸的图片,负样本的
选取就与问题场景相关,具体而言,如果你要进行教室中学生的人脸识别,那么负样本就是教室的窗子、墙等等,
也就是说,不能是与你要研究的问题毫不相关的乱七八糟的场景图片,这样的负样本并没有意义。负样本可以根据
背景生成,有时候不需要寻找额外的负样本。一般3000-10000的正样本需要5,000,000-100,000,000的负样本来学
习,在互金领域一般在入模前将正负比例通过采样的方法调整到3:1-5:1。

22.6 线性分类器与非线性分类器的区别及优劣

区别:所谓线性分类器即用一个超平面将正负样本分离开,表达式为 y=wx 。这里强调的是平面。


而非线性的分类界面没有这个限制,可以是曲面,多个超平面的组合等。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 291 页


典型的线性分类器有感知机,LDA,逻辑斯特回归,SVM(线性核);
典型的非线性分类器有朴素贝叶斯(有文章说这个本质是线性的,http://dataunion.org/12344.html),kNN,决
策树,SVM(非线性核)

优缺点:1.线性分类器判别简单、易实现、且需要的计算量和存储量小。
为解决比较复杂的线性不可分样本分类问题,提出非线性判别函数。:超曲面,非线性判别函数计算复杂,实际应
用上受到较大的限制。在线性分类器的基础上,用分段线性分类器可以实现复杂的分类面。解决问题比较简便的方
法是采用多个线性分界面将它们分段连接,用分段线性判别划分去逼近分界的超曲面。

如果一个问题是非线性问题并且它的类边界不能够用线性超平面估计得很好,那么非线性分类器通常会比线性分类
器表现得更精准。如果一个问题是线性的,那么最好使用简单的线性分类器来处理。

22.7 如何解决过拟合问题?

解释过拟合:模型在训练集表现好,在真实数据表现不好,即模型的泛化能力不够。从另外一个方面来讲,模型在
达到经验损失最小的时候,模型复杂度较高,结构风险没有达到最优。
解决:

学习方法上:限制机器的学习,使机器学习特征时学得不那么彻底,因此这样就可以降低机器学到局部特征和错误
特征的几率,使得识别正确率得到优化.

数据上:要防止过拟合,做好特征的选取。训练数据的选取也是很关键的,良好的训练数据本身的局部特征应尽可
能少,噪声也尽可能小.

22.8 L1和L2正则的区别,如何选择L1和L2正则?

L0正则化的值是模型参数中非零参数的个数。

也就是如果我们使用L0范数,即希望w的大部分元素都是0. (w是稀疏的)所以可以用于ML中做稀疏编码,特征选
择。通过最小化L0范数,来寻找最少最优的稀疏特征项。但不幸的是,L0范数的最优化问题是一个NP hard问题,
而且理论上有证明,L1范数是L0范数的最优凸近似,因此通常使用L1范数来代替。

L1正则化表示各个参数绝对值之和。

L1范数的解通常是稀疏性的,倾向于选择数目较少的一些非常大的值或者数目较多的insignificant的小值。

L2正则化标识各个参数的平方的和的开方值。

L2范数越小,可以使得w的每个元素都很小,接近于0,但L1范数不同的是他不会让它等于0而是接近于0.

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 292 页


22.9 有监督学习和无监督学习的区别

有监督学习:对具有概念标记(分类)的训练样本进行学习,以尽可能对训练样本集外的数据进行标记(分类)预
测。这里,所有的标记(分类)是已知的。因此,训练样本的岐义性低。监督学习中只要输入样本集,机器就可以从
中推演出制定目标变量的可能结果.如协同过滤推荐算法,通过对训练集进行监督学习,并对测试集进行预测,从而达
到预测的目的.
无监督学习:对没有概念标记(分类)的训练样本进行学习,以发现训练样本集中的结构性知识。这里,所有的标
记(分类)是未知的。因此,训练样本的岐义性高。聚类就是典型的无监督学习
监督学习的典型例子就是决策树、神经网络以及疾病监测,而无监督学习就是很早之前的西洋双陆棋和聚类。

22.10 有了解过哪些机器学习的算法?

问题分析:

考官主要考察的是学员是否对于人工智能方面的感兴趣,是否有空闲时间了解过机器学习算法这方面的知识,有则
更好。

核心答案讲解:

学员需要根据自己的实际情况去回答,学员也可以自己课下空余的时间去掌握一两常用的机器学习算法,以此来增
加自己的筹码:

决策树模型:

* 构建决策树三要素

* 1-特征选择

* 信息熵---信息增益---信息增益大的

* H(x)= -sum(pi*log(pi))

* Gain(A)=Info(D)-Info_A(D)

* Gini:Gini系数是对信息熵关于ln(x)在x=0处的一阶泰勒展开近似得到是信息GIni系数

* 2-决策树的构成

* ID3算法:

* 算法输入:特征和样本构成的数据集

* 算法输出:ID3的决策树

* 算法步骤:

* 1-如果所有的属性都被处理完毕

* 2-计算各个节点的信息增益,选择最大的信息增益所代表的特征构建决策树(信息熵--sum(pi*log(pi)))

* 3-如果上述的属性没有处理完毕,需要从剩余的属性中继续获取其他的信息增益较大的值对应的特征。

* 4-递归构建决策树算法模型

* 算法优化

* 选择信息增益率--C4.5算法

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 293 页


*算法的停止迭代条件:

* 1-迭代次数

* 2-树的深度

* 3-树的叶子节点的个数

* 4-树的分支节点含有样本的个数

* 5-树的最大不纯度的下降

* C4.5

* Cart树---gini系数

*3-决策树剪枝

*先剪枝

*后剪枝

问题扩展

在算法的推导过程中,会大量的使用到数学的推导公式,而数学思想对于程序员来说是必不可缺的一种思想,可以
在讲解的时候,概括性的给学员讲一下数学的相关知识。

信息熵数学公式(sum(pi*log(pi)))+信息增益数学公式(总体的信息熵-以A节点作为划分节点的信息熵)

结合项目中使用

此问题在项目中是没有什么体现的,面试官就是想考察一下面试者本身具有的学习能力,以此来评估该面试者的可
培养价值,以及潜力。但是机器学习算法在推荐系统项目中、以后数据挖掘工作中会起到至关重要的作用。

22.11 协同过滤算法的底层实现是什么?

问题分析:

面试官主要是对于写了推荐系统项目,并且在推荐系统中使用到基于物品的协同过滤算法和基于用户的协同过滤算
法的学员进行考核,通过对项目中算法的原理,以及细节来考证项目的可行性。

核心答案讲解:

基于用户的CF基于用户的协同过滤,

通过用户对不同内容(物品)的行为,来评测用户之间的相似性,找到“邻居”,基于这种相似性做出推荐。这种推
荐的本质是,给相似的用户推荐其他用户喜欢的内容,这就是我们经常看到的:和你类似的人还喜欢如下内容。下
面这个列子可以说明:、

需要给用户A推荐游戏,根据用户B和用户C对游戏的偏好行为,给A推荐游戏,从下表可以知道,基于对游戏的偏
好来讲,用户A跟用户C的相似度比用户跟用户B的相似度要大,所以,系统会给用户A推荐炉石传说。当然,举的
这个例子十分简单,实际上,还需要考虑的是每个用户物品的偏好程度,虽然用户A和用户C都玩过英雄联盟,但
是用户A和用户C对英雄联盟的偏好程度可能不一样,在真正的计算过程中,需要对这种偏好的程度设定一个参数,
参数的大小表明用户对物品的偏好程度的大小。根据设置或调整参数的大小,得出最后的值给用户推荐商品,这样
的推荐计算结果会更加严谨。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 294 页


基于物品的CF的原理和基于用户的CF类似,

只是在计算邻居时采用物品本身,而不是从用户的角度,即基于用户对物品的偏好找到相似的物品,然后根据用户
的历史偏好,推荐相似的物品给用户。从计算的角度来看,就是将所有用户对某个物品的偏好作为一个向量来计算
物品之间的相似度,得到物品的相似物品后,根据用户历史的偏好预测当前用户还没有表示偏好的物品,计算得到
一个排序的物品列表作为推荐。就是我们常见的:购买该商品的用户还购买了如下商品,等等就是文章开头前的啤
酒和纸尿裤的故事,

因为超市的人员发现很多男人买纸尿裤的时候会买啤酒,根据这一用户行为,纸尿裤和啤酒的相似度较高,那么在
用户购买纸尿裤的时候推荐啤酒,增加啤酒的销量。也用相同的例子来说明:可以从下表看出,用户B和用户C有一
个共同的特征,即选择了英雄联盟也会选炉石传说,说明这两个游戏之间相似度会比较高,那么会当用户A选择了
英雄联盟,系统会把炉石传说也推荐给他。但是同时要注意的一点,这种情况也是属于比较理想化的一种,物品和
物品之间的相似度可能不一样,也需要调整参数,这里不进行深入的探讨了。

问题扩展

该算法的在实现的过程中,在计算两者的相似度的时候,使用了数学中的:“欧几里德距离”,“皮尔逊相关系数”等
数学相关的知识。同时也可以进行两者之间的使用的一个对比回答,回答面试官为什么我们选择这种,而不选择另
外的一种。在非社交网络的网站中,内容内在的联系是很重要的推荐原则,它比基于相似用户的推荐原则更加有效。
比如在购书网站上,当你看一本书的时候,推荐引擎会给你推荐相关的书籍,这个推荐的重要性远远超过了网站首
页对该用户的综合推荐。可以看到,在这种情况下,Item CF 的推荐成为了引导用户浏览的重要手段。同时 Item CF
便于为推荐做出解释,在一个非社交网络的网站中,给某个用户推荐一本书,同时给出的解释是某某和你有相似兴
趣的人也看了这本书,这很难让用户信服,因为用户可能根本不认识那个人;但如果解释说是因为这本书和你以前
看的某本书相似,用户可能就觉得合理而采纳了此推荐。

相反的,在现今很流行的社交网络站点中,User CF 是一个更不错的选择,User CF 加上社会网络信息,可以增加


用户对推荐解释的信服程度。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 295 页


结合项目中使用:

使用在大数据的推荐中的项目,项目的核心算法。

面试中:技术点是什么--原理是什么--工作中如何使用---优化

23. V10.0核心大厂算法面试题总结

23.1 题目1:给定a,b两个文件,各存放50亿个url,每个url长度为1-255字节,内存限制是4g,让你找出a,
b文件共同的url,说明思路和执行方法。

问题分析:

考官主要想针对学员的海量文件处理的思路进行考核。对一些业务场景提出疑问,考验学生的处理问题的
能力。学员应考虑到“分而治之”思想来处理文件。

核心答案讲解:

可以估计每个文件安的大小为5G×64=320G,远远大于内存限制的4G。所以不可能将其完全加载到内
存中处理。考虑采取分而治之的方法。

遍历文件a,对每个url求取hash(url)%1000,然后根据所取得的值将url分别存储到1000个小文件(记为
a0,a1,...,a999)中。这样每个小文件的大约为300M。

遍历文件b,采取和a相同的方式将url分别存储到1000小文件(记为b0,b1,...,b999)。这样处理后,所
有可能相同的url都在对应的小文件(a0vsb0,a1vsb1,...,a999vsb999)中,不对应的小文件不可能有相同的
url。然后我们只要求出1000对小文件中相同的url即可。

求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_set中。然后遍历另一个小文件
的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了。

问题扩展
如果允许有一定的错误率,可以使用 Bloom filter,4G 内存大概可以表示 340 亿 bit。将其中一个文件中
的 url 使用 Bloom filter 映射为这 340 亿 bit,然后挨个读取另外一个文件的 url,检查是否与 Bloom filter,
如果是,那么该 url 应该是共同的 url(注意会有一定的错误率)。
结合项目中使用
在大数据离线统计中,MR 的读写过程的“分而治之”的思想的理解。
在项目中读取 HDFS 上文件进行统计的时候的方法,可以使用的方法。

23.2 题目2:二叉树前序、中序、后续遍历方式(递归以及非递归)。

问题分析:’
考官主要考察的是学员对于基础的树结构的掌握程度。其中树里面最基础的二叉的一个遍历的一个考核,学员需要
能掌握手写其代码,理解其遍历的思想,以及二叉树的实现。
核心答案讲解:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 296 页


//代码在二叉树遍历代码
递归:
1、先序遍历(先遍历根节点,在遍历左节点,最后遍历右节点)
2、中序遍历(先遍历左节点,在遍历中节点,最后遍历右节点)
3、后序遍历(先遍历左节点,在遍历右节点,最后遍历根节点)
非递归 (非递归遍历二叉树必须考虑到栈的应用)
1. 先序遍历
a)访问之节点,并把结点 node 入栈。当前结点置为左孩子;
b)推断结点 node 是否为空,若为空。则取出栈顶结点并出栈,将右孩子置为当前结点;否则反复 a)步直到当前
结点为空或者栈为空(能够发现栈中的结点就是为了访问右孩子才存储的)
2. 中序排列
同样的道理。仅仅只是訪问的顺序移到出栈时
3. 后续排列
对于任一结点 P,将其入栈,然后沿其左子树一直往下搜索。直到搜索到没有左孩子的结点,此时该结点出如今栈
顶,可是此时不能将其出栈并訪问,因此其右孩子还为被訪问。
所以接下来依照同样的规则对其右子树进行同样的处理,当訪问完其右孩子时。该结点又出如今栈顶,此时能够将
其出栈并訪问。这样就保证了正确的訪问顺序。能够看出,在这个过程中,每一个结点都两次出如今栈顶,仅仅有
在第二次出如今栈顶时,才干訪问它。因此须要多设置一个变量标识该结点是否是第一次出如今栈顶。

问题扩展

1、其中广度优先遍历和深度优先遍历(前、中、后序遍历)。
2、可以向学员扩展一下什么是树?树的特点有哪些?
(英文 Tree):它是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结
构性质的数据集合。它是由 n(n>=1)个有限节点组成一个具有层次关系的集合。把他叫做“树”是因为它看起来像
一颗倒挂的树,也就是树根朝上,而树叶向下的,它具有以下的特点:
1、每个节点有零个或多个子节点:
2、没有父节点的节点称为根节点:
3、每一个非根节点有且只有一个父节点
4、除了根节点外,每个子节点可以分为多个不相交的子树:
3、树的种类有哪些?
无序树、有序树(二叉树、完全二叉树、满二叉树、平衡二叉树、排序二叉树等)

结合项目中使用
先序遍历:在第一次遍历到节点时就执行操作,一般只是想遍历执行操作(或输出结果)可选用先序遍历;
中序遍历:对于二分搜索树,中序遍历的操作顺序(或输出结果顺序)是符合从小到大(或从大到小)顺序的,故
要遍历输出排序好的结果需要使用中序遍历
后序遍历:后续遍历的特点是执行操作时,肯定已经遍历过该节点的左右子节点,故适用于要进行破坏性操作的情
况,比如删除所有节点

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 297 页


23.3 题目3:求数组所有可能的子数组?

问题分析:
考官主要相对数据结构与算法基础的考核,如数组数据结构以及需要遍历数组内所有元素这类需求的常见
处理方法和快速反应能力。这道题的解题思路可以参考二叉树先序遍历
核心答案讲解:
处理这里需求常见的是递归法
递归法 :假设集合为集合 A,从集合 A 的每个元素自身分析,它只有两种状态,或是某个
子集的元素,或是不属于任何子集,所以求子集的过程就可以看成对每个元素进行“取舍”的过程。上图
中,根结点是初始状态,叶子结点是终结状态,该状态下的 8 个叶子结点就表示集合 A 的 8 个子集。第
i 层(i=1,2,3…n)表示已对前面 i-1 层做了取舍,所以这里可以用递归了。整个过程其实就是对二叉树的先
序遍历。
问题扩展
递归法是比较常见的处理此类问题方法,然而还有另一种按位对应法可以解决此类问题
按位对应法 :
集合 A={a,b,c},对于任意一个元素,在每个子集中,要么存在,要么不存在。
映射为子集: (a,b,c)
(1,1,1)->(a,b,c)
(1,1,0)->(a,b)
(1,0,1)->(a,c)
(1,0,0)->(a)
(0,1,1)->(b,c)
(0,1,0)->(b)
(0,0,1)->(c)
(0,0,0)->@(@表示空集)
与计算机中数据存储方式相似,故可以通过一个整型数与集合映射 00…00 ~ 11…11(1 表示有,0 表示无,
反之亦可),通过该整型数逐次增可遍历获取所有的数,即获取集合的相应子集。
结合项目中使用
在程序中展现所有组合的可能性时,可以使用该算法。

23.4 题目4:给定一个数和一个有序数组,求有序数组的两个数的和满足这个数?(已知这两数存在)

(1) 问题分析:考官主要相对数据结构与算法基础的考核,看到这道题目的时候就可以想到这道题肯定是跟遍历有
关系的。
(2) 核心答案讲解:
此题可以借鉴快速排序的思路,从数组的两边同时遍历数组
有一个 一个有序的整形数组,给定一个数,在数组中找出两个数的和等于这个数,并打印出来
第一种方法:将有序数组的最小值与最大值进行相加后,与给定数进行比较,如果想等,则在数组中找出
两个数之和等与给定数;如果小于给定数,则从小的位置向后移,在进行比较;如果大于给定数,则从大
的位置向前移,在进行比较。
(3) 问题扩展
如何求取这种算法的时间复杂度呢?

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 298 页


因为该程序无论如何,只会遍历一遍集合,所以该程序的时间复杂度为 O(n)
(4) 结合项目中使用
在程序中查找特定组合时,可以参考使用这种算法

23.5 题目5: 求一个数组的第二大值?

(1)问题分析:
这道题很明显就是考察面试者最基础的算法能力,思考的话就往排序的方面想就好了,如果能想到多种方法解决问
题的话,会显示出自己的基础深厚,让面试官对你印象更好
(2)核心答案讲解:
用两个变量 max,max2,其中 max 储存最大值,max2 储存第二大值
初始化的时候,将数组中的第一个元素中较大的存进 max 中,较小的存进 max2 中
然后从第三个元素(下标为 2)的元素开始,如果遇到的数比 max 大,就让 max2=max;max 等于遇到的数
一直循环,直到数组尾部
最后输出 max2
(3)问题扩展:
排序算法是非常基础却又是各家面试经常会遇到的问题,面试前应该对 9 大排序算法应该有比较初级的了解,至
少能够手写三种算法知道六种算法的名字
常见问题
你了解的排序算法大概有几种,如何实现 ?
冒泡排序,插入排序,选择排序,归并排序,快速排序,希尔排序,堆排序等
(1) 项目应用:
排序算法在项目中使用比较广泛,比如做排行榜,无序数据按照某种规格进行排序等,使用好可以大幅度减少任务
执行时间

23.6 题目9:有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M,
要求返回频数最高的100个词。

问题分析:

考官主要想针对学员的真实业务逻辑进行考核。对一些业务场景提出疑问,考验学生的处理问题的能力。
分而治之 + hash统计 + 堆/快速排序。

核心答案讲解:

顺序读文件中,对于每个词x,取hash(x)%5000,然后按照该值存到5000个小文件(记为x0,x1,...x4999)中。
这样每个文件大概是200k左右。
如果其中的有的文件超过了1M大小,还可以按照类似的方法继续往下分,直到分解得到的小文件的大小都
不超过1M。 对每个小文件,统计每个文件中出现的词以及相应的频率(可以采用trie树/hash_map等),
并取出出现频率最大的100个词(可以用含100个结点的最小堆),并把100个词及相应的频率存入文件,这
样又得到了5000个文件。下一步就是把这5000个文件进行归并(类似与归并排序)的过程了。

问题扩展

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 299 页


此题涉及到分而治之的思想以及归并排序等基础的算法思想,结合着归并排序的分而治之的思想,将学员
的基础算法能力加强。
结合项目中使用
在大数据离线统计中,MR 的读写过程的“分而治之”的思想的理解。
在项目中读取 HDFS 上文件进行统计的时候的方法,可以使用的方法。

23.7 题目10:现有海量日志数据保存在一个超级大的文件中,该文件无法直接读入内存,要求从中提取某天
出访问百度次数最多的那个IP。

问题分析:

考官主要想针对学员的真实业务逻辑进行考核。对一些业务场景提出疑问,考验学生的处理问题的能力。
分而治之 + hash统计 。

核心答案讲解:

首先是这一天,并且是访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,
最多有个2^32个IP。同样可以采用映射的方法,比如模1000,把整个大文件映射为1000个小文件,再找出
每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的
频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。

问题扩展
算法思想:分而治之+Hash
1.IP 地址最多有 2^32=4G 种取值情况,所以不能完全加载到内存中处理;
2.可以考虑采用“分而治之”的思想,按照 IP 地址的 Hash(IP)%1024 值,把海量 IP 日志分别存储到 1024 个
小文件中。这样,每个小文件最多包含 4MB 个 IP 地址;
3.对于每一个小文件,可以构建一个 IP 为 key,出现次数为 value 的 Hash map,同时记录当前出现次数
最多的那个 IP 地址;
4.可以得到 1024 个小文件中的出现次数最多的 IP,再依据常规的排序算法得到总体上出现次数最多的 IP;。
结合项目中使用
在大数据离线统计中,MR 的读写过程的“分而治之”的思想的理解。
在项目中读取 HDFS 上文件进行统计的时候的方法,可以使用的方法。

23.8 题目14:有了解过哪些机器学习的算法?

(1) 问题分析:
考官主要考察的是学员是否对于人工智能方面的感兴趣,是否有空闲时间了解过机器学习算法这方面
的知识,有则更好。

(2) 核心答案讲解:
学员需要根据自己的实际情况去回答,学员也可以自己课下空余的时间去掌握一两常用的机器学习

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 300 页


算法,以此来增加自己的筹码:
PCA 思想:
PCA 顾名思义,就是找出数据里最主要的方面,用数据里最主要的方面来代替原始数据。具体的,假如我
们的数据集是 n 维的,共有 m 个数据(x(1),x(2),...,x(m))。我们希望将这 m 个数据的维度从 n 维降到 n'维,
希望这 m 个 n'维的数据集尽可能的代表原始数据集。我们知道数据从 n 维降到 n'维肯定会有损失,但是
我们希望损失尽可能的小。

PCA 的算法步骤:
设有 m 条 n 维数据。
1)将原始数据按列组成 n 行 m 列矩阵 X
2)将 X 的每一行(代表一个属性字段)进行零均值化,即减去这一行的均值
3)求出协方差矩阵
4)求出协方差矩阵的特征值及对应的特征向量
5)将特征向量按对应特征值大小从上到下按行排列成矩阵,取前 k 行组成矩阵 P
6)即为降维到 k 维后的数据
(3) 问题扩展
在算法的推导过程中,会大量的使用到数学的推导公式,而数学思想对于程序员来说是必不可缺的一
种思想,可以在讲解的时候,概括性的给学员讲一下数学的相关知识。
(4) 结合项目中使用
此问题在项目中是没有什么体现的,面试官就是想考察一下面试者本身具有的学习能力,以此来评估
该面试者的可培养价值,以及潜力。但是机器学习算法在推荐系统项目中、以后数据挖掘工作中会起到
至关重要的作用。

23.9 题目15:协同过滤算法的底层实现是什么?

(1) 问题分析:
面试官主要是对于写了推荐系统项目,并且在推荐系统中使用到基于物品的协同过滤算法和基于用户的协同过滤算
法的学员进行考核,通过对项目中算法的原理,以及细节来考证项目的可行性。

(2) 核心答案讲解:
基于用户的 CF 基于用户的协同过滤,
通过用户对不同内容(物品)的行为,来评测用户之间的相似性,找到“邻居”,基于这种相似性做出推荐。这
种推荐的本质是,给相似的用户推荐其他用户喜欢的内容,这就是我们经常看到的:和你类似的人还喜欢如下内容。
下面这个列子可以说明:、
需要给用户 A 推荐游戏,根据用户 B 和用户 C 对游戏的偏好行为,给 A 推荐游戏,从下表可以知道,基于对
游戏的偏好来讲,用户 A 跟用户 C 的相似度比用户跟用户 B 的相似度要大,所以,系统会给用户 A 推荐炉石传说。
当然,举的这个例子十分简单,实际上,还需要考虑的是每个用户物品的偏好程度,虽然用户 A 和用户 C 都玩过
英雄联盟,但是用户 A 和用户 C 对英雄联盟的偏好程度可能不一样,在真正的计算过程中,需要对这种偏好的程
度设定一个参数,参数的大小表明用户对物品的偏好程度的大小。根据设置或调整参数的大小,得出最后的值给用
户推荐商品,这样的推荐计算结果会更加严谨。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 301 页


基于物品的 CF 的原理和基于用户的 CF 类似,
只是在计算邻居时采用物品本身,而不是从用户的角度,即基于用户对物品的偏好找到相似的物品,然后根据
用户的历史偏好,推荐相似的物品给用户。从计算的角度来看,就是将所有用户对某个物品的偏好作为一个向量来
计算物品之间的相似度,得到物品的相似物品后,根据用户历史的偏好预测当前用户还没有表示偏好的物品,计算
得到一个排序的物品列表作为推荐。就是我们常见的:购买该商品的用户还购买了如下商品,等等就是文章开头前
的啤酒和纸尿裤的故事,
因为超市的人员发现很多男人买纸尿裤的时候会买啤酒,根据这一用户行为,纸尿裤和啤酒的相似度较高,那
么在用户购买纸尿裤的时候推荐啤酒,增加啤酒的销量。也用相同的例子来说明:可以从下表看出,用户 B 和用户
C 有一个共同的特征,即选择了英雄联盟也会选炉石传说,说明这两个游戏之间相似度会比较高,那么会当用户 A
选择了英雄联盟,系统会把炉石传说也推荐给他。但是同时要注意的一点,这种情况也是属于比较理想化的一种,
物品和物品之间的相似度可能不一样,也需要调整参数,这里不进行深入的探讨了。

(3) 问题扩展
该算法的在实现的过程中,在计算两者的相似度的时候,使用了数学中的:“欧几里德距离”,“皮尔逊相关系数”
等数学相关的知识。同时也可以进行两者之间的使用的一个对比回答,回答面试官为什么我们选择这种,而不选择
另外的一种。在非社交网络的网站中,内容内在的联系是很重要的推荐原则,它比基于相似用户的推荐原则更加有
效。比如在购书网站上,当你看一本书的时候,推荐引擎会给你推荐相关的书籍,这个推荐的重要性远远超过了网
站首页对该用户的综合推荐。可以看到,在这种情况下,Item CF 的推荐成为了引导用户浏览的重要手段。同时 Item
CF 便于为推荐做出解释,在一个非社交网络的网站中,给某个用户推荐一本书,同时给出的解释是某某和你有相
似兴趣的人也看了这本书,这很难让用户信服,因为用户可能根本不认识那个人;但如果解释说是因为这本书和你
以前看的某本书相似,用户可能就觉得合理而采纳了此推荐。

相反的,在现今很流行的社交网络站点中,User CF 是一个更不错的选择,User CF 加上社会网络信息,可以增加


用户对推荐解释的信服程度。
(4) 结合项目中使用:
使用在大数据的推荐中的项目,项目的核心算法。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 302 页


北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 303 页
24. V10.0LeetCode题目精选

24.1 LeetcodeSQL实战

24.1.1 第二高的薪水

需求分析:

编写一个 SQL 查询,获取 Employee 表中第二高的薪水(Salary.

如果不存在第二高的薪水,那么查询应返回 NULL

网址:https://leetcode-cn.com/problems/second-highest-salary/

建表语句

Create table If Not Exists Employee (Id int, Salary int);

Truncate table Employee;

insert into Employee (Id, Salary) values ('1', '100');

insert into Employee (Id, Salary) values ('2', '200');

insert into Employee (Id, Salary) values ('3', '300');

如下图:

解题思路

SELECT salary

FROM employee ORDER BY salary DESC LIMIT 1,1

#思路1:limit 1,1 第一个1代表从索引1开始,第二个1代表取1条数据

//考虑:如果只有一条记录,则找不到第二条记录,会出错,因此需要使用ifnull进行判断

SELECT IFNULL(

(SELECT salary FROM employee

ORDER BY salary desc LIMIT 1,1),NULL

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 304 页


) AS secondsalary

//考虑:如果排名第一的工资有几个,那就取不到排名第二的工资了,可以对工资进行查重(最标准的答案)

SELECT IFNULL(

(SELECT DISTINCT salary FROM employee

ORDER BY salary DESC LIMIT 1,1),NULL

) AS secondsalary

#思路2:还可以用排名函数

row_number()

rank()

dense_rank()

SELECT DISTINCT salary FROM (

SELECT salary,

dense_rank() over(ORDER BY salary DESC) num

FROM employee) tmp WHERE num=2

查询结果

如果数据只有1条,返回null值:

24.1.2 有趣的电影

需求分析

某城市开了一家新的电影院,吸引了很多人过来看电影。该电影院特别注意用户体验,专门有个 LED显示板做电影
推荐,上面公布着影评和相关电影描述。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 305 页


作为该电影院的信息部主管,您需要编写一个 SQL查询,找出所有影片描述为非 boring (不无聊) 的并且 id 为奇
数 的影片,结果请按等级 rating 排列。 链接:https://leetcode-cn.com/problems/not-boring-movies

建表语句

Create table If Not Exists cinema (id int, movie varchar(255), description varchar(255), rating float(2, 1));

Truncate table cinema;

insert into cinema (id, movie, description, rating) values ('1', 'War', 'great 3D', '8.9');

insert into cinema (id, movie, description, rating) values ('2', 'Science', 'fiction', '8.5');

insert into cinema (id, movie, description, rating) values ('3', 'irish', 'boring', '6.2');

insert into cinema (id, movie, description, rating) values ('4', 'Ice song', 'Fantacy', '8.6');

insert into cinema (id, movie, description, rating) values ('5', 'House card', 'Interesting', '9.1');

+---------+-----------+--------------+-----------+

| id | movie | description | rating |

+---------+-----------+--------------+-----------+

| 1 | War | great 3D | 8.9 |

| 2 | Science | fiction | 8.5 |

| 3 | irish | boring | 6.2 |

| 4 | Ice song | Fantacy | 8.6 |

| 5 | House card| Interesting| 9.1 |

+---------+-----------+--------------+-----------+

解题思路:

#解法一:

SELECT

FROM

cinema

WHERE

id %2 != 0 #得到不能被2整除的结果 => 奇数

AND

description != 'boring' #得到不为boring的结果

ORDER BY

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 306 页


rating DESC #根据rating排序, DESC为降序

-----------------------------------------------------------

#解法二:

SELECT

FROM

cinema

WHERE

id %2 = 1

AND

description <> 'boring' #不等于号的另一种表达方法

ORDER BY

rating DESC

-----------------------------------------------------------

#优化版:

SELECT

FROM

cinema

WHERE

MOD(id,2) = 1 #使用MOD()取余函数

AND

description <> 'boring'

ORDER BY

rating DESC

-----------------------------------------------------------

24.1.3 查询结果

+---------+-----------+--------------+-----------+

| id | movie | description | rating |

+---------+-----------+--------------+-----------+

| 5 | House card| Interesting| 9.1 |

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 307 页


| 1 | War | great 3D | 8.9 |

+---------+-----------+--------------+-----------+

补充:

MOD 和 % :

(1)% 结果有正数也有负数, mod的结果也能为正数或负数

a % b = a - a/b * b

3 % -2 = 3 - (-1*-2) = 3 - 2 = 1

-3 % 2 = -3 - (-1*2) = -3 + 2 = -1

-3 % -2 = -3 - (1 * -2) = -3 + 2 = -1

//结论: % 的结果符号取决于左操作数的符号

注意不要将SQL中的MOD函数与java中的混淆

(2)MOD(a,b)返回a/b的余数, 如果b=0, 则返回a. MOD函数支持小数运算

24.2 两数之和

问题链接:https://leetcode-cn.com/problems/two-sum/

24.2.1 问题描述

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他


们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

```

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9

所以返回 [0, 1]

```

24.2.2 参考答案

class Solution {

public int[] twoSum(int[] nums, int target) {

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 308 页


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");

24.3 最长回文子串

链接:https://leetcode-cn.com/problems/longest-palindromic-substring/

24.3.1 问题描述

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

```

输入: "babad"

输出: "bab"

注意: "aba" 也是一个有效答案。

```

示例 2:

```

输入: "cbbd"

输出: "bb"

```

24.3.2 参考答案

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 309 页


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;

```

24.4 LRU缓存机制

链接:https://leetcode-cn.com/problems/lru-cache/

24.4.1 问题描述

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数


据 get 和 写入数据 put 。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 310 页


获取数据 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

```

24.4.2 参考答案

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);

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 311 页


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);

*/

24.5 爬楼梯

问题链接:https://leetcode-cn.com/problems/climbing-stairs/

24.5.1 问题描述

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

```

输入: 2

输出: 2

解释: 有两种方法可以爬到楼顶。

1. 1 阶 +1 阶

2. 2 阶

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 312 页


```

示例 2:

```

输入: 3

输出: 3

解释: 有三种方法可以爬到楼顶。

1. 1 阶 +1 阶 +1 阶

2. 1 阶 +2 阶

3. 2 阶 +1 阶

```

24.5.2 参考答案

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];

24.6 翻转二叉树

链接:https://leetcode-cn.com/problems/invert-binary-tree/

24.6.1 问题描述

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 313 页


翻转一棵二叉树。

示例:

输入:

```

/ \

2 7

/\ /\

1 36 9

```

输出:

```

/ \

7 2

/\ /\

9 63 1

```

24.6.2 参考答案

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;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 314 页


24.7 反转链表

链接:https://leetcode-cn.com/problems/reverse-linked-list/

24.7.1 问题描述

反转一个单链表。

示例:

```

输入: 1->2->3->4->5->NULL

输出: 5->4->3->2->1->NULL

```

24.7.2 参考答案

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;

24.8 有效的括号

链接:https://leetcode-cn.com/problems/valid-parentheses/

24.8.1 问题描述

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 315 页


有效字符串需满足:

1. 左括号必须用相同类型的右括号闭合。

2. 左括号必须以正确的顺序闭合。

注意空字符串可被认为是有效字符串。

示例 1:

```

输入: "()"

输出: true

```

示例 2:

```

输入: "()[]{}"

输出: true

```

示例 3:

```

输入: "(]"

输出: false

```

示例 4:

```

输入: "([)]"

输出: false

```

示例 5:

```

输入: "{[]}"

输出: true

```

24.8.2 参考答案

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 316 页


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();

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 317 页


```

24.9 编辑距离

链接:https://leetcode-cn.com/problems/edit-distance/

24.9.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')

```

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 318 页


24.9.2 参考答案

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];

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 319 页


}

25. 小试牛刀-阿里巴巴P7面试题攻关

25.1 如果要使用一次爆炸函数可以得到多个新的列吗?

可以的,使用posexplode

1、数据介绍
主要包括三列,分别是班级、姓名以及成绩,数据表名是default.classinfo。

2、单列Explode
首先来看下最基本的,我们如何把student这一列中的数据由一行变成多行。这里需要使用split和
explode,并结合lateral view实现。代码如下:

select

class,student_name

from

default.classinfo

lateral view explode(split(student,',')) t as student_name

结果如下:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 320 页


3、单列Posexplode
接下来,我们想要给每个同学来一个编号,假设编号就按姓名的顺序,此时我们要用到另一个hive
函数,叫做posexplode,代码如下:

select

class,student_index + 1 as student_index,student_name

from

default.classinfo

lateral view posexplode(split(student,',')) t as student_index,student_name;

这里select时对编号+1主要是因为编号是从0开始的,结果如下:

4、多列Explode
好了,我们继续前进。这次我们想基于两列explode,同时能够使学生和其成绩能够匹配,即期望
的效果如下:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 321 页


显然我们要对两列进行explode,先试试行不行:

select

class,student_name,student_scorefrom

default.classinfo

lateral view explode(split(student,',')) sn as student_name

lateral view explode(split(score,',')) sc as student_score

结果如下:

好像是不太行,如果我们分别对两列进行explode的话,假设每列都有三个值,那么最终会变成3 *
3 = 9行。但我们想要的结果只有三行。

此时我们可以进行两次posexplode,姓名和成绩都保留对应的序号,即使变成了9行,我们通过
where条件只保留序号相同的行即可。代码如下:

select

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 322 页


class,student_name,student_score

from

default.classinfo

lateral view posexplode(split(student,',')) sn as student_index_sn,student_name

lateral view posexplode(split(score,',')) sc as student_index_sc,student_score

where

student_index_sn = student_index_sc;

此时结果就对了:

接下来,假设我们又想对同学的成绩进行一下排名该怎么做呢?

借助rank函数啦(row_number函数对于相同的成绩也会赋予不同的排名,所以我们选择rank函数):

select

class,

student_name,

student_score,

rank() over(partition by class order by student_score desc) as student_rank

from

default.classinfo

lateral view posexplode(split(student,',')) sn as student_index_sn,student_name

lateral view posexplode(split(score,',')) sc as student_index_sc,student_score

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 323 页


where

student_index_sn = student_index_sc

order by class,student_rank;

结果符合我们预期:

25.2 Hive的锁都有哪些,有什么不同的使用场景?

发生现象:

在执行insert into或insert overwrite任务时,中途手动将程序停掉,会出现卡死情况(无法提交


MapReduce),只能执行查询操作,而drop insert操作均不可操作,无论执行多久,都会保持卡
死状态。

查看Hive的中死锁,可以使用show locks [table]来查看。

可以看到里面的那个Type下的EXCLUSIVE,这是一种互斥锁,需要解决,否则后续的查询和插入任
务都会影响。hive存在两种锁,共享锁Shared (S)和互斥锁Exclusive (X)

锁 S X

S 是(可并发执行) 否(不可并发执行)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 324 页


锁 S X

X 否 否

锁的基本机制是:

 元信息和数据的变更需要互斥锁

 数据的读取需要共享锁

触发共享锁的操作是可以并发执行的,但是触发互斥锁,那么该表和该分区就不能并发的执行作业
了。

注意:表锁和分区锁是两个不同的锁,对表解锁,对分区是无效的,分区需要单独解锁

常规解锁方法:

 unlock table 表名; -- 解锁表

 unlock table 表名 partition(dt='2014-04-01'); -- 解锁某个分区

高版本hive默认插入数据时,不能查询,因为有锁

25.3 用一条shell命令找出一个文件中出现次数最多的userid的top10

cat words.txt | sort | uniq -c | sort -k1,1nr | head -10

主要考察对sort、uniq命令的使用,相关解释如下,命令及参数的详细说明请自行通过man查
看,简单介绍下以上指令各部分的功能:

sort: 对单词进行排序

uniq -c: 显示唯一的行,并在每行行首加上本行在文件中出现的次数

sort -k1,1nr: 按照第一个字段,数值排序,且为逆序

head -10: 取前10行数据

25.4 Spark的两个大表join的话,分布式的处理内部如何实现的?

大表与大表之间的sort merge join原理:


当两个表都非常大时,spark SQL采用Sort Merge Join进行Join,这种实现方式不用将一侧数据全

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 325 页


部加载后再进行join,但需要在join前将数据排序。
首先将两张表按照join key进行了重新shuffle,保证join keys值相同的记录会被分在相同的分区。
分区后对每个分区内的数据进行排序,排序后再对相应的分区内的记录进行连接。
因为两个序列都是有序的,从头遍历,碰到key相同的就输出;如果不同,左边小就继续取左边,
反之取右边。
可以看出,无论分区有多大,Sort Merge Join都不用把某一侧的数据全部加载到内存中,而是即用
即丢,从而大大提升了大数据量下SQL JOIN的稳定性。

25.5 说一说spark的三种join机制。

Spark通常使用三种Join策略方式

 Broadcast Hash Join(BHJ)

 Shuffle Hash Join(SHJ)

 Sort Merge Join(SMJ)

Broadcast Hash Join

在数据库的常见模型中(比如星型模型或者雪花模型),表一般分为两种:事实表和维度表。维度
表一般指固定的、变动较少的表,例如联系人、物品种类等,一般数据有限。而事实表一般记录流
水,比如销售清单等,通常随着时间的增长不断膨胀。
因为join操作是对两个表中key值相同的记录进行连接,在spark SQL中,对两个表做join最直接的
方式是先根据key分区,再在每个分区中把key值相同的记录拿出来做连接操作。但这样就不可避免
地涉及到shuffle,而shuffle在spark中是比较耗时的操作,我们应该尽可能的设计spark应用使其避
免大量的shuffle。
当维度表和事实表进行join操作时,为了避免shuffle,我们可以将大小有限的维度表的全部数据分
发到每个节点上,供事实表使用。executor存储维度表的全部数据,一定程度上牺牲了空间,换取
shuffle操作大量的耗时,这在spark SQL中称为Broadcast Join.
看起来广播时一个比较理想的方案,但它有没有缺点呢?也很明显,这个方案只能用于广播较小的
表,否则数据的冗余传输就远大于shuffle的开销;另外,广播时需要将被广播的表先collect到driver
端,当频繁有广播出现时,对driver的内存也是一个考验。

进一步说明:

当小表与大表进行Join操作时,为了避免shuffle操作,将小表的所有数据分发到每个节点与大表进
行Join操作,尽管牺牲了空间,但是避免了耗时的Shuffle操作。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 326 页


表需要broadcast,那么必须小于spark.sql.autoBroadcastJoinThreshold配置的值(默认是10M),
或者明确添加broadcast join hint。

1. base table不能broadcast,例如在left outer join中,仅仅right表可以broadcast

2. 这种算法仅仅用来broadcast小表,否则数据的传输可能比shuffle操作成本高

3. broadcast也需要到driver,如果有太多的broadcast,对driver内存也是压力

Shuffle Hash Join

由于spark是一个分布式的计算引擎,可以通过分区的形式将大批量的数据划分成n份较小的数据集
进行并行计算。这种思想应用到join上便是shuffle hash join了。利用key相同必然分区相同的原理,
spark SQL将较大表的join分而治之,先将表划分成n个分区,再对两个表中相对应分区的数据分别
进行hash join。
shuffle hash join分为两步:
对两张表分别按照join keys进行分区,即shuffle,目的是为了让有相同join keys值的记录分到对应
的分区中。
对对应分区中的数据进行join,此处先将小表分区构造一张hash表,然后根据大表分区中记录的join
keys值拿出来进行匹配。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 327 页


Sort Merge Join

上面两种发现适应一定大小的表,但是当两张表足够大时,上面方法对内存造成很大压力,这是因
为当两张表做Hash Join时,其中一张表必须完成加载到内存中。

当两张表很大时,Spark SQL使用一种新的算法来做Join操作,叫做Sort Merge Join,这种算法并


不会加载所有的数据,然后开始Hash Join,而是在Join之前进行数据排序。

首先将两张表按照join key进行了重新shuffle,保证join keys值相同的记录会被分在相同的分区。


分区后对每个分区内的数据进行排序,排序后再对相应的分区内的记录进行连接。
因为两个序列都是有序的,从头遍历,碰到key相同的就输出;如果不同,左边小就继续取左边,
反之取右边。
可以看出,无论分区有多大,Sort Merge Join都不用把某一侧的数据全部加载到内存中,而是即用
即丢,从而大大提升了大数据量下SQL JOIN的稳定性。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 328 页


25.6 Flink关闭后状态端数据恢复得慢怎么办?

(1)选用合理的state数据结构 和 statebackend

(2)并行度合理设置

25.7 Flink怎么实时增加表或结果表字段?

结果表新增字段就跟Flink只插入指定字段到结果表的步骤差不多

 首先我们向结果表上增加字段

 我们新建一个Sink表包含新增字段和主键

 建立作业执行,根据主键将该字段的存量数据同步到结果表

需要注意的是每个 Task 执行都需要分配一定的CU(CPU+内存),所以如果可以跟之前的Task


合并的话,会大幅减少Task数量并降低硬件要求。合并的过程差不多:

 按照之前的Sink表新建并增加新增字段

 按照之前的Task SQL新建并增加新增字段的同步

 将新Task启动,等存量数据同步完成后将原Task关闭。期间保证线上数据仍然可以实时同步,
支持原有业务

或者sink到hbase hbase支持列横向扩展

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 329 页


25.8 Flink只插入指定字段到结果表

Flink SQL的Sink表只支持全字段数据插入,不支持指定字段数据插入和更新操作,那后面结果表增
加字段如何处理?

首先我们需要给Sink指定主键,如果输出存储是声明了主键(primary key)的数据库(例如,
RDS/ES/HBASE等),数据流的输出结果有以下2种情况:

 如果根据主键查询的数据在数据库中不存在,则会将该数据插入数据库。

 如果根据主键查询的数据在数据库中存在,则会根据主键更新数据。

这里跟mysql数据库的for update效果一样,所以我们可以使用这个特性进行分批次插入。解决了
两个问题:

结果表有几百个甚至上千个字段的话,可以拆分成多个sql进行插入和更新数据。

结果表随着业务发展需要增加字段的话,可以根据此特性更新新字段的存量数据。

大SQL拆分

这里简单举个例子,我们的源数据表如下:

CREATE TABLE `user_source` (

`user_id` string,

`user_name` STRING,

`mobile` STRING,

`create_time` timestamp,

`update_time` timestamp

) with (

'connector' = 'mysql-cdc',

'debezium.snapshot.locking.mode' = 'none',

'hostname' = 'localhost',

'port' = '3306',

'username' = 'root',

'password' = '123456',

'database-name' = 'source',

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 330 页


'table-name' = 'user'

Sink表结构如下,这里我们指定userId为主键:

CREATE TABLE `user_sink` (

userId STRING,

userName STRING,

mobile STRING,

createTime TIMESTAMP,

updateTime TIMESTAMP,

PRIMARY KEY (userId) NOT ENFORCED

) with (

'connector' = 'elasticsearch-7',

'hosts' = 'localhost:9200',

'index' = 'user',

'username' ='root',

'password' ='123456'

我们原本的Task任务应该是这样的:

INSERT INTO user_sink

SELECT

`user_id` as userId,

`user_name` as userName,

`mobile` as mobile,

`create_time` as createTime,

`update_time` as updateTime

FROM user_source;

这是大家经常普遍遇到的情况,假设我们这里的ES的Index不是5个字段,而是500个字段那会怎么
样呢?如果我们将500个字段写在同一个SQL中,这个SQL得多大?

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 331 页


做拆分SQL的第一步就是将Sink表进行拆分:

CREATE TABLE `user_sink_1` (

userId STRING,

createTime TIMESTAMP,

updateTime TIMESTAMP,

PRIMARY KEY (userId) NOT ENFORCED

) with (

'connector' = 'elasticsearch-7',

'hosts' = 'localhost:9200',

'index' = 'user',

'username' ='root',

'password' ='123456'

CREATE TABLE `user_sink_2` (

userId STRING,

userName STRING,

mobile STRING

PRIMARY KEY (userId) NOT ENFORCED

) with (

'connector' = 'elasticsearch-7',

'hosts' = 'localhost:9200',

'index' = 'user',

'username' ='root',

'password' ='123456'

然后我们就可以将同步的SQL根据Sink进行拆分:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 332 页


INSERT INTO user_sink_1

SELECT

`user_id` as userId

`create_time` as createTime,

`update_time` as updateTime

FROM user_source;

INSERT INTO user_sink_2

SELECT

`user_id` as userId,

`user_name` as userName,

`mobile` as mobile

FROM user_source;

插入数据到 user_sink_1 表同步到ES增加该数据,在插入数据到 user_sink_2 表同步数据到ES就会


更加主键做 for update 操作,将 user_name 和 mobile 字段根据 user_id 进行更新。

25.9 Flink怎么删掉数据(数据太多了要删掉一些)?

编程过程当中,可以通过调用DataStream API的evictor()方法【可选方法】传入相应的Evictor对进
入WindowFunction前后的数据进行剔除处理,默认的Evictors都是在WindowFunction计算之前对
数据进行剔除处理的。Flink本身实现了三种Evictor,其中有CountEvictor,DeltaEvictor和
TimeEvictor。

25.9.1 CountEvictor

 定义:在窗口中保持固定数量的数据,将超过指定大小的数据在窗口计算之前剔除。

 demo:仅保留窗口中10条数据

.evictor(CountEvictor.of(10)).

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 333 页


25.9.2 TimeEvictor

概念:通过指定时间间隔,将当前窗口中最新元素的时间减去Interval,然后将小于该结果的数据
全部剔除,其本质是将具有最新时间的数据选择出来,删除过时的数据。

demo:仅保留最新10ms内数据

.evictor(TimeEvictor.of(Time.milliseconds(10)))

25.9.3 DeltaEvictor

描述:通过定义DeltaFunction和指定threshold,并计算Windows中的元素与最新元素之间的Delt
大小,如果超过了threshold则将当前数据元素剔除。

demo:剔除值差大于1的元素。

timeWindow(Time.seconds(10)).evictor(DeltaEvictor.of(1, new
DeltaFunction<Tuple2<String, Long>>() {

@Override

public double getDelta(Tuple2<String, Long> oldDataPoint, Tuple2<String,


Long> newDataPoint) {

return oldDataPoint.f1-newDataPoint.f1;

})).

25.9.4 自定义Evictor

可以通过实现Evictor接口且覆写evictBefore和evictAfter方法完成自定义Evictor,从而满足自身的
业务场景。

25.10 并发开启30个sqoop(或者是sqoop开启30个业务)怎么办?问的是怎么开启 。或
者是开启后失败了怎么办?

例如 从mysql数据库中导入到hive中可以执行

sqoop import --connect jdbc:mysql://localhost/gamewave --username root --password 123456


--table log --hive-import -m 1

其中-m 1 参数代表的含义是使用多少个并行,这个参数的值是1,说明没有开启并行功能。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 334 页


将m参数的数值调为5或者更大,Sqoop就会开启5个进程,同时进行数据的导入操作。

注意:mysql数据库的表中需要有个主键,如果没有主键的话需要手动选取一个合适的拆分字段。

sqoop import --connect jdbc:mysql://localhost/gamewave --username root --password 123456


--table log --hive-import -m 5 --split-by uid --where "rdate='2012-03-39'"

如果不能满足需求的话,则使用

sqoop import --connect jdbc:mysql://localhost/gamewave --username root --password 123456


--table log --hive-import -m 1 --where 'logtime<10:00:00'

sqoop import --connect jdbc:mysql://localhost/gamewave --username root --password 123456


--table log --hive-import -m 1 --where 'logtime>=10:00:00'

场景2:

解决sqoop导入hive表失败问题

用sqoop从关系型数据库(mysql、oracle)将数据导入hive时,当同时导入多个不同数据库,同名
表时,需要指定--target-dir参数,不然会导致任务失败,因为sqoop导入数据时,默认会在hdfs上
根据表名创建一个临时目录,这样相同表名在hdfs上的路径就冲突了。

因此这种情况下需要指定临时存放数据目录或路径,

方式一:

指定临时目录,

--target-dir dbName_tableName

比如hive用户,当导入数据时会在hdfs上创建/user/hive/dbName_tableName目录

方式二:

指定绝对路径,

--target-dir /sqoop/dbName_tableName

25.11 DWS层数据(或者是数仓数据)怎么和业务数据保证一致?

定时调度同步业务数据到数仓,遇到字段数据更新需要使用拉链表

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 335 页


25.12 数仓同步更新策略

引出拉链表

25.12.1 渐变维(SCD)

25.12.1.1 什么是渐变维

维度可以根据变化剧烈程度主要分为无变化维度、缓慢变化维度和剧烈变化维度。例如一个人
的相关信息,身份证号、姓名和性别等信息数据属于不变的部分,政治面貌和婚姻状态属于缓慢变
化部分,而工作经历、工作单位和培训经历等在某种程度上属于急剧变化字段。

大多数维度表随时间的迁移是缓慢变化的。比如增加了新的产品,或者产品的ID号码修改了,
或者产品增加了一个新的属性,此时,维度表就会被修改或者增加新的记录行。这样,在设计维度
和使用维度的过程中,就要考虑到缓慢变化维度的处理。

缓慢渐变维,即维度中的属性可能会随着时间发生改变,比如包含用户住址Address的
DimCustomer维度,用户的住址可能会发生改变,进而影响业务统计精度,DimCustomer维度就
是缓慢渐变维(SCD)。

SCD有三种分类,我们这里以顾客表为例来进行说明:

假设在第一次从业务数据库中加载了一批数据到数据仓库中,当时业务数据库有这样的一条顾
客的信息。

顾客 BIWORK ,居住在北京,目前是一名 BI 的开发工程师。假设 BIWORK 因为北京空气


质量 PM2.5 等原因从北京搬到了三亚。那么这条信息在业务数据库中应该被更新了。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 336 页


那么当下次从业务数据库中抽取这类信息的时候,数据仓库又应该如何处理呢?

我们假设在数据仓库中实现了与业务数据库之间的同步,数据仓库中也直接将词条数据修改更
新。后来我们创建报表做一些简单的数据统计分析,这时在数据仓库中所有对顾客 BIWORK 的销
售都指向了 BIWORK 新的所在地 - 城市三亚,但是实际上 BIWORK 在之前所有的购买都发生在
BIWORK 居住在北京的时候。

通过这个简单的例子,描述了因一些基本信息的更改可能会引起数据归纳和分析出现的问题。

25.12.1.2 SCD1(缓慢渐变类型1)

通过更新维度记录直接覆盖已存在的值。不维护记录的历史。一般用于修改错误的数据。

在数据仓库中,我们可以保持业务数据和数据仓库中的数据始终处于一致。可以在 Customer
维度中使用来自业务数据库中的 Business Key - CustomerID 来追踪业务数据的变化,一旦发生变
化那么就将旧的业务数据覆盖重写。

DW 中的记录根据业务数据库中的 CustomerID 获取了最新的 City 信息,直接更新到 DW


中。

25.12.1.3 SCD2(缓慢渐变类型2)

在源数据发生变化时,给维度记录建立一个新的“版本”记录,从而维护维度历史。SCD2不删除、
不修改已存在的数据。SCD2也叫拉链表。

当然在数据仓库中更多是对相对静态的历史数据进行数据的汇总和分析,因此会尽可能的维护
来自业务系统中的历史数据,使系统能够真正捕获到这种历史数据的变化。以上面的例子来说,可
能需要分析的结果是 BIWORK 在 2012年的时候购买额度整体平稳,但是从2013年开始购买额度
减少了,出现的原因可能与所在的城市有关系,在北京的门店可能比在三亚的门店相对要多一些。

像这种情况,就不能很简单在数据仓库中将 BIWORK 当前所在城市直接更新,通过起始时间


来标识,Valid To(封链时间)为 NULL 的标识当前数据,也可以用2999,3000,9999等等比较
大的年份。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 337 页


北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 338 页
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 339 页
25.12.1.4 SCD3(缓慢渐变类型3)

实际上SCD1 and 2 可以满足大多数需求了,但是仍然有其它的解决方案,比如说 SCD3。


SCD3希望只维护更少的历史记录。

比如说把要维护的历史字段新增一列,然后每次只更新 Current Column 和 Previous


Column。这样,只保存了最近两次的历史记录。但是如果要维护的字段比较多,就比较麻烦,因
为要更多的 Current 和 Previous 字段。所以 SCD3 用的还是没有 SCD1 和 SCD2 那么普遍。
它只适用于数据的存储空间不足并且用户接受有限维度历史的情况。

25.13 HiveSql OOM怎么办?

场景1:Hive插入多个分区时OOM故障解决记录

一、故障情景

基于Hive的数据仓库中需要做一张累积快照表,记录了客户发生各个行为的具体日期,比如激活日
期、注册日期、申请日期、创建订单日期等等。

这张表需要以激活日期作为分区时间,便于业务查询。

激活日期将近500个日期,在一次性将所有数据全量插入目标分区的时候所有reduce报OOM。

二、解决过程

首先增加reduce端的内存,set mapreduce.reduce.java.opts = -Xmx3072m;set


mapreduce.reduce.memory.mb = 3072;最后将内存设置到集群最高内存,仍然报OOM。

再分析源数据的分类,发现源数据存在以前的测试数据,这样会造成有的分区日期只有一条数据,
有的分区天数有几十万数据,数据存在严重的倾斜。

解决方法有两种。

第一种在HQL末尾加上distribute by和sort by,或者cluster by。如下:


insert overwrite table loan_f_milestone partition(day)
select *,
to_date(atv_tim)
from loan_f_milestone_tmp12
distribute by to_date(atv_tim)
sort by to_date(atv_tim)
--cluster by to_date(atv_tim)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 340 页


distribute by按照指定的字段将数据划分到不同的输出reduce中,可以保证每个reduce处理的数据
范围不重叠,每个分区内的数据是没有排序的。

sort by保证一个reduce内的数据按照指定字段排序。

cluster by除了有distribute by的功能,还有sort by的功能,所以最终的结果是每个reduce处理的数


据范围不重叠,而且每个reduce内的数据按照指定字段排序,而且可以做到全局排序。

所以第二种方式是设置set hive.optimize.sort.dynamic.partition=true;会将动态分区字段全局排
序。

场景2:

执行hive定时任务,发现有个定时任务报如下错误,Error: Java heap space.

(1) 查看hadoop日志发现,实际上有4个map没有执行成功,而reduce就没有执行,说明调度
平台显示的日志信息不准确。进入对应的4个map中查看日志,发现真实报内存溢出错误

(2)原来是有4个map出现内存溢出问题,上图查看实际使用值为1575879256≈1.47g,执行失败。这
种情况我们分析,有可能是map的切片设置的太大,而系统给每个map可以分配的最大内存设置的
太小,所有造成内存溢出。

通过set mapreduce.map.memory.mb;查看可知

set mapreduce.map.memory.mb=2048.所以每个map可以使用的内存完全够用。

通过hive中查询,原来系统给堆内存设置的大小是1536Mb,即1.5G,而实际中执行切片设置
的过大,造成计算该切片所需要的堆内存为1.47g,尽快比1.5g小,但是jvm本身就需要运行就需要内
存以及其他消耗,造成堆内存溢出。

hive> set mapred.child.java.opts;

mapred.child.java.opts=-Xmx1536m -Xms1536m -Xmn256m -XX:SurvivorRatio=6


-XX:MaxPermSize=128m -XX:+PrintGCDetails

-XX:+PrintGCTimeStamps -XX:+UseParNewGC -XX:+UseConcMarkSweepGC


-XX:CMSInitiatingOccupancyFraction=80 -XX:GCTimeLimit=90

-XX:GCHeapFreeLimit=10 -XX:ParallelGCThreads=8

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 341 页


解决方式:1.将系统设置的set mapred.max.split.size = 300000000;我们原先的最大分片设置成
200000000,降低切片大小. 这样的坏处是会产生更多的map去执行。

2.将上面集群的heap.size设置的更大些,比如2048(2g).设置方式如下:

1.写到配置文件里,比如设置成2G

<property>

<name >mapred.child.java.opts</name>

<value>-Xmx2048m </value>

</property>

2.当然也可以直接配置,直接在查询出来的值中修改

任务启动的jvm参数,默认值-Xmx200m,建议值-XX:-UseGCOverheadLimit -Xms512m
-Xmx2048m -verbose:gc -Xloggc:/tmp/@taskid@.gc

可以直接修改

当然以上的配置信息,最好都直接写到每个hive定时任务里面即可,不用配置到集群里固定信息,
这样能更好地利用集群资源。

其他解决方案:

1.报内存不足发生的阶段:

Map阶段

Shuffle阶段

Reduce阶段

2.oom解决方式

2.1 内存不足发生在map阶段

一般存在MapJoin才会出现这种OOM。通过设置参数set hive.auto.convert.join = false转成


reduce端的Common Join。其次如果是common join报oom一般就是切片太大了,尤其注意hdfs
显示的大小是压缩后大小,如果切片设置的太大,解压后处理很容易撑爆内存。这个时候通过如下
参数调小map输入量

set mapred.max.split.size=256000000

set mapred.min.split.size=10000000

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 342 页


set mapred.min.split.size.per.node=8000000 --每个节点处理的最小split

set mapred.min.split.size.per.rack=8000000 --每个机架处理的最小slit.

2.2 内存不足发生在shuffle阶段

这种一般是因为由于map的输出较大,但shuffle阶段选择的是拷贝map输出到内存导致。降低
单个shuffle能够消耗的内存占reduce所有内存的比例(set
mapreduce.reduce.shuffle.memory.limit.percent=0.10),使得shuffle阶段拷贝map输出时选择落

3.3 内存不足发生在reduce阶段

单个reduce处理数据量过大,通过设置参数mapred.reduce.tasks 或mapreduce.job.reduces 修改
reduce个数,或者通过

set hive.exec.reducers.bytes.per.reducer=300000000 --减小每个reduce聚合的数据量

来以进一步分散压力。但如果存在数据倾斜的情况,单纯修改reduce个数没有用,需要对应的处理,

————————————————

25.14 Hbase增加字段phionex能看到么?

方法1:可以直接在phionex中直接建立增加字段后的表结构,从Hbase或其他介质中导入数据即可。

方法2:需要重新映射Hbase表数据

25.15 怎样将 mysql 的数据导入到 hbase 中?

(1)问题分析:考察对sqoop基本运用

(2)核心问题讲解

MYSQL 到 HBASE

bin/sqoop import

--connect jdbc:mysql://192.168.66.4:3306/networkmanagement \

--username sendi \

--password 1234 \

--table alarm_result \

--hbase-table sqoop_test \

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 343 页


--column-family info \

--hbase-row-key id \

--hbase-create-table \

-m 412345678910

–hbase-table 指定要导入的表名称

–column-family 指定列族

–hbase-row-key 指定哪个输入列用作行键

–hbase-create-table 创建缺少的HBase表

导出没有主键的表

可以使用两种方式:

* –split-by 指定切分的字段

* -m 1 : 设置只使用一个map进行数据迁移

过滤条件

–where “age>18” 匹配条件

–columns “name,age” 选择要导入的指定列

–query ‘select * from people where age>18 and $CONDITIONS’: sql语句查询的结果集

不能 –table 一起使用

需要指定 –target-dir 路径

当数据库中字符为空时的处理

–null-non-string ‘0’ 当不是字符串的数据为空的时候,用 0 替换

–null-string ‘string’ 当字符串为空的时候,使用string 字符替换

提高传输速度

–direct 提高数据库到hadoop的传输速度

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 344 页


支持的数据库类型与版本:

* myslq 5.0 以上

* oracle 10.2.0 以上

(3)问题扩展

Sqoop如何实现增量导入?

增量导入对应,首先需要知监控那一列,这列要从哪个值开始增量

check-column id 用来指定一些列

这些被指定的列的类型不能使任意字符类型,如char、varchar等类型都是不可以的,常用的是指定
主键id.

–check-column 可以去指定多个列

last-value 10 从哪个值开始增量

incremental 增量的模式

append id 是获取大于某一列的某个值。

lastmodified “2016-12-15 15:47:30” 获取某个时间后修改的所有数据

–append 附加模式

–merge-key id 合并模式

注意:增量导入不能与 –delete-target-dir 一起使用,还有必须指定增量的模式

26. 小试牛刀-百度T3面试题攻关

26.1 请你谈谈Hive内部表和外部表的区别

答:内部表是由Hive自身管理,外部表数据佑HDFS管理,删除内部表会直接删除元数据及存储数

据,删除外部表仅仅会删除元数据,HDFS上的文件并不会被删除。

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 345 页


26.2 请你谈谈数据倾斜解决方法

答 :如果是join引起的数据倾斜的话 由于key值为空或为异常记录 就随机给key赋一个值

如果是一个大表和一个小表join的话可以考虑mapjoin避免数据倾斜,如果是group by 引起的数据

倾斜的话 开启map之后使用combiner。

26.3 谈谈你对分区的理解

答:为了对表进行合理的管理以及提高查询效率,所以进行分区。这样查找数据就不需要进行

全表扫描了,提高了查询效率。

26.4 SQL考核1

表a有三个变量,分别是id、staytime、day

id staytime day

1 300 20210101

2 250 20210101

3 400 20210101

4 100 20210101

1 200 20210102

3 120 20210102

4 230 20210102

1 90 20210103

2 480 20210103

3 240 20210103

1 120 20210104

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 346 页


2 450 20210104

4 180 20210104

写Hive将每个人的staytime按时间先后顺序拼接在一起,某一天没有数据就用0代替,结果如
下所示:

id staytimes

1 300_200_90_120

2 250_0_480_450

3 400_120_240_0

4 100_230_0_180

26.4.1 准备工作

create database db_baidu;

use db_baidu;

-- 创建表
create table tb_shop(
id int,
staytime int,
day string
)
row format delimited fields terminated by '\t'
;

-- 加载原始数据
load data local inpath '/export/data/shop.txt'
overwrite into table tb_shop;

26.4.2 路径

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 347 页


-- 第一步: 将去重以后的id作为临时表

-- 第二步: 将去重以后的日期 作为临时表

-- 第三步: 将去重以后的用户id 和 去重以后的日期 记录组合 作为临时表

-- 第四步: 将用户编号和日期排列组合的表 和 原始表进行 左关联查询, 将关联不上 staytime的值置为 0

-- 第五步: 将结果4 进行 行转列, 形成最终结果

26.4.3 实现

-- 第一步: 将去重以后的id作为临时表
create table tb_shop_id as
select id from tb_shop group by id; -- 28s

-- 第二步: 将去重以后的日期 作为临时表


create table tb_shop_day as
select day
from tb_shop
group by day;

-- 第三步: 将去重以后的用户id 和 去重以后的日期 记录组合 作为临时表


create table tb_shop_id_day as
select t1.id, t2.day

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 348 页


from tb_shop_id t1, tb_shop_day t2; -- 42s

-- 第四步: 将用户编号和日期排列组合的表 和 原始表进行 左关联查询, 将关联不上 staytime的


值置为 0
select t1.id, t1.day, nvl(t2.staytime, 0) staytime
from tb_shop_id_day t1
left join tb_shop t2 on t1.day=t2.day and t1.id=t2.id
order by t1.id asc, t1.day asc
;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 349 页


-- 第五步: 将结果4 进行 行转列, 形成最终结果
select t3.id, concat_ws('_', collect_list(cast(staytime as string))) staytimes
from (select t1.id, t1.day, nvl(t2.staytime, 0) staytime
from tb_shop_id_day t1
left join tb_shop t2 on t1.day=t2.day and t1.id=t2.id
order by t1.id asc, t1.day asc) t3
group by t3.id;

26.5 SQL考核2

表b有两个变量,写Hive求每个同学的平均成绩

id class_score

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 350 页


1 chinese_80,math_90,english_75

2 chinese_85,math_70

3 math_80,english_65

4 chinese_89,math_70,english_72

5 chinese_80,english_80

结果展示如下表:

id avg_score

1 81.67

2 77.50

3 72.50

4 77.00

5 80.00

26.5.1 准备工作

create database db_baidu_2;

use db_baidu_2;

-- 创建表
create table tb_score(
id int,
class_score string
)
row format delimited fields terminated by '\t';

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 351 页


-- 加载数据
load data local inpath '/export/data/score.txt'
overwrite into table tb_score;

26.5.2 路径

-- 第一步: 将 class_score 转化成map

-- 第二步: 取出将分数取出来组成数组

-- 第三步: 将学生的分数数组 拆开

-- 第四步: 按照学号分组 且 求平均分

26.5.3 实现

-- 第一步: 将 class_score 转化成map


select
id, class_score,
str_to_map(class_score, ',', '_')
from tb_score;

-- 第二步: 取出将分数取出来组成数组
select
*,
map_values(str_to_map(class_score, ',', '_'))
from tb_score;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 352 页


-- 第三步: 将学生的分数数组 拆开
select
id,
map_values(str_to_map(class_score, ',', '_')),
cast(element as int) score
from tb_score
lateral view explode(map_values(str_to_map(class_score, ',', '_'))) b as element;

-- 第四步: 按照学号分组 且 求平均分


select t1.id, cast(avg(score) as decimal(10, 2)) avg_score
from (select
id,
cast(element as int) score
from tb_score
lateral view explode(map_values(str_to_map(class_score, ',', '_'))) b as element) t1
group by t1.id;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 353 页


26.6 算法考核

输入一个列表,返回其中所有元素的组合,其中每个组合按元素大小排序。

例如,输入 [1, 2, 3] ,返回 [ [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3] ]

Import java.util.ArrayList;
import java.util.Comparator;

public class HelloJava {


public static void main(String[] args) {
ArrayList<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
System.out.println(getList(integers));
}

public static ArrayList<ArrayList> getList(ArrayList<Integer> list){


ArrayList<ArrayList> arrayLists = new ArrayList<>();
if (list.size() >= 1) {
ArrayList<Integer> ints = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
for (int j = i; j < list.size(); j++) {
ArrayList<Integer> ints1 = new ArrayList<>();
ints1.add(list.get(i));
if (!ints1.contains(list.get(j))) {
ints1.add(list.get(j));
}
arrayLists.add(ints1);
}
ints.add(list.get(i));
if (!arrayLists.contains(ints)) {
arrayLists.add(ints);
}
}

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 354 页


}
arrayLists.sort(new Comparator<ArrayList>() {
@Override
public int compare(ArrayList o1, ArrayList o2) {

if (o1.size() >= o2.size()) {


return 1;
}
return -1;
}
});
return arrayLists;
}
}

26.7 大数据技术考核

26.7.1 如何保证Hive中数据的质量

(1)问题分析

考官想考面试者在工作中的具体工作经验。

(1)核心问题讲解

首先,数据出现质量问题有哪些原因或者情况?

其次,针对这些原因,制定清洗策略。

一般的数据质量出现问题的有:无效,重复,缺失,不一致,错误值,格式出错,业务逻辑规
则有问题,抽取数据程序有错等,另外还有就是统计口径不一致,也会导致看到的数据不是想要的。

根据这些情况,如何清洗?人工,还是编写程序?这个依据数据量大小及挖掘系统要求看吧。
如果出现这类型的错误很多,一定要写程序自动清洗,如果只是小量的不影响的,可以忽略不计。

(2)问题扩展

企业不同的时期业务系统处理方式上逐步优化产生的数据差异:

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 355 页


产生原因:企业在不同的发展时间,系统处理会有所差异,特别是二开比较多的公司

解决方案:

A.后续规范的数据与前面不规范的数据,看是否可以通过相对应的关系,进行整理统一;

B.如果上述都不能处理的话,我想还是对前面的一些数据进行分开统计分析,否则两者不一样
统计了来会误导业务人员

以前在一通讯行业工作的时候,原来在联通新用户(存费送机、购机送费、单开户)、老用户
等等以前都是通过一个或几个字段的状态标志进行区别,后来业务发展,发现这样太复杂,后来做
了一个政策层级的分类,统一规范。在处理前面数据的时候,对以前的数据进行修复处理,以保证
与后续的数据统计方式一致。否则区别两个统计方式。

因为实际业务过程中无法规范而产生的数据质量问题:

问题举例:在一服装制造行业工作的时候,来统计产品的实际工时,因为是A产品完工、B产
品新生产,在这一交接阶段,同时进行生产,无法正确的统计实际的生产工时,这是正常的实际情
况。

解决方案:后与业务部门沟通,将当天的实际工时根据当天完工产品的理论工价来按比例分配,
这样对统计分析虽然会有不真实的情况,但也是能相对真实。

所以碰到问题的时候,可以是否可以折中处理,只要不完全违背统计分析的原则,还要以考虑
相应的处理方式。

26.7.2 Hive数据仓库的设计,项目中分了几层,每层有什么意义

(1)问题分析

考官考的是公司中数仓的真实使用场景,和面试者的理解。

(2)核心问题讲解

数据仓库的数据来源于不同的源数据,并提供多样的数据应用,数据自下而上流入数据仓库后向上
层开放应用,而数据仓库只是中间集成化数据管理的一个平台。
源数据层(ODS):此层数据无任何更改,直接沿用外围系统数据结构和数据,不对外开放;为临
时存储层,是接口数据的临时存储区域,为后一步的数据处理做准备。
数据仓库层(DW):也称为细节层,DW 层的数据应该是一致的、准确的、干净的数据,即对源
系统数据进行了清洗(去除了杂质)后的数据。
数据应用层(DA 或 APP):前端应用直接读取的数据源;根据报表、专题分析需求而计算生成
的数据。
数据仓库从各数据源获取数据及在数据仓库内的数据转换和流动都可以认为是 ETL(抽取 Extra,
转化 Transfer, 装载 Load)的过程,ETL 是数据仓库的流水线,也可以认为是数据仓库的血液,
它维系着数据仓库中数据的新陈代谢,

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 356 页


而数据仓库日常的管理和维护工作的大部分精力就是保持 ETL 的正常和稳定。为什么要对数据仓
库分层?
用空间换时间,通过大量的预处理来提升应用系统的用户体验(效率),因此数据仓库会存在大量
冗余的数据;不分层的话,如果源业务系统的业务规则发生变化将会影响整个数据清洗过程,工作
量巨大。
通过数据分层管理可以简化数据清洗的过程,因为把原来一步的工作分到了多个步骤去完成,相当
于把一个复杂的工作拆成了多个简单的工作,把一个大的黑盒变成了一个白盒,每一层的处理逻辑
都相对简单和容易理解,这样我们比较容易保证每一个步骤的正确性,当数据发生错误的时候,往
往我们只需要局部调整某个步骤即可。

(3)问题扩展

数据仓库元数据管理

元数据(Meta Date),主要记录数据仓库中模型的定义、各层级间的映射关系、监控数据仓库的
数据状态及 ETL 的任务运行状态。一般会通过元数据资料库(Metadata Repository)来统一地存
储和管理元数据,其主要目的是使数据仓库的设计、部署、操作和管理能达成协同和一致。

元数据是数据仓库管理系统的重要组成部分,元数据管理是企业级数据仓库中的关键组件,贯穿数
据仓库构建的整个过程,直接影响着数据仓库的构建、使用和维护。

26.7.3 Hive数据仓库中的建模方式,为什么选择这种建模方式

(1)问题分析

考官主要是为了考察面试人员对于业务的熟悉和理解程度,单讲业务建模类型是相对容易的但是切
合业务进行建模就值得我们进行思考了;

(2)核心问题讲解

Hive作为数据仓库,同关系型数据库开发过程类似,都需要先进行建模,所谓建模,就是对表之间
指定关系方式。建模在hive中大致分为星型、雪花型和星座型。要对建模深入理解,首先需要对hive
数仓中的集中表概念进行界定。hive中的表从形态上分内部表、外部表、桶表、分区表。在数据逻
辑上划分为维度表和事实表。维度表等价于我们常说的字典表。事实表就是字典表之外的数据表。

1.1 星型

多张维度表,一张事实表,维度表之间没有关系。查询性能要好些,存储有冗余的。星型模型使用
的比较多。

1.2 雪花型

雪花型是星型建模的扩展,维度表之间有关系。存储减少冗余,查询性能有损失,需要多级连接。
和星型模型的共性就是只有一张是事实表。

1.3 星座型

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 357 页


星座型也是星型模型的扩展,存在多张事实表。

(4)结合项目中使用

比如说某个业务系统的信息没有提供汇总表,而是新增一种新的业务,就需要新增一张数据表。我
们统计业务量的时候,就要拿大几十张数据表进行关联,报表执行效率很差,后面就直接在数据仓
库上帮源系统做了张新业务的汇总表,提高了后续报表的加工效率,也屏蔽了新增业务品种时对报
表的影响。

这里我们可以结合我们能书仓管设计的基本特性来考虑,这里考虑的就是分层的概念:

1. 清晰数据结构

2. 数据血缘追踪

3. 减少重复开发

4. 吧复杂问题简单化

5. 屏蔽原始数据的异常

6. 屏蔽业务的影响,不必改一次业务就需要重新接入

这里我们就可以依照这些概念,我们的哪些基本数据分到数仓层,再结合我们的业务需求逐层构建
各个业务上的集市层;可以有效减少我们后期运营维护的各项成本;

26.7.4 数据仓库如何同步,使用什么工具,根据什么进行实时同步

(1)问题分析

考官主要的考核点是想要了解我们,是否做过数据迁移工作,如果做过业务数据库和数据仓库进行
数据迁移的工作,那么我们基本都会接触到增量数据的迁移问题,因为我们不可能一直以人工的形
式为这么多数据总手动迁移的工作;

(2)核心问题讲解

这里我们借助开源工具sqoop就可以进行数据的迁移同步工作,不过随着数据量级的不断提升就会
考虑实时的增量数据该要如何处理,这里可以采用canal 解析mysql binlog日志,可以打印解析
message生成sql,集成kafka提供生产,供订阅同步数据;形成同步操作的过程,以达到数据同步
的问题;

(3)问题扩展

事实上,在生产环境中,系统可能会定期从与业务相关的关系型数据库向Hadoop导入数据,导入
数仓后进行后续离线分析。故我们此时不可能再将所有数据重新导一遍,此时我们就需要增量数据
导入这一模式了。

增量数据导入分两种,一是基于递增列的增量数据导入(Append方式)。二是基于时间列的增量

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 358 页


数据导入(LastModified方式)。

26.7.5 根据什么对Hive表进行分桶分区,为什么

(1)问题分析

考官主要是考察你在日常开发中是否都注重了表查询的效率问题,还有对数据的一些处理方案,依
照这些考察你是否具备一名基本大数据人员的基本能力;

(2)核心问题讲解

分区

在Hive Select查询中一般会扫描整个表内容,会消耗很多时间做没必要的工作。有时候只需要扫描
表中关心的一部分数据,因此建表时引入了partition概念。分区表指的是在创建表时指定的partition
的分区空间。

Hive可以对数据按照某列或者某些列进行分区管理,所谓分区我们可以拿下面的例子进行解释。
当前互联网应用每天都要存储大量的日志文件,几G、几十G甚至更大都是有可能。存储日志,其
中必然有个属性是日志产生的日期。在产生分区时,就可以按照日志产生的日期列进行划分。把每
一天的日志当作一个分区。
将数据组织成分区,主要可以提高数据的查询速度。至于用户存储的每一条记录到底放到哪个分区,
由用户决定。即用户在加载数据的时候必须显示的指定该部分数据放到哪个分区。

分桶

对于每一个表(table)或者分区, Hive可以进一步组织成桶,也就是说桶是更为细粒度的数据范
围划分。Hive也是 针对某一列进行桶的组织。Hive采用对列值哈希,然后除以桶的个数求余的方
式决定该条记录存放在哪个桶当中。

把表(或者分区)组织成桶(Bucket)有两个理由:

1)获得更高的查询处理效率。桶为表加上了额外的结构,Hive 在处理有些查询时能利用这个结构。
具体而言,连接两个在(包含连接列的)相同列上划分了桶的表,可以使用 Map 端连接(Map-side
join)高效的实现。比如JOIN操作。对于JOIN操作两个表有一个相同的列,如果对这两个表都进行
了桶操作。那么将保存相同列值的桶进行JOIN操作就可以,可以大大较少JOIN的数据量。

2)使取样(sampling)更高效。在处理大规模数据集时,在开发和修改查询的阶段,如果能在数
据集的一小部分数据上试运行查询,会带来很多方便。

(3)问题扩展

如何创建分桶分区?

1 create table student(id INT, age INT, name STRING)

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 359 页


2 partitioned by(stat_date STRING)

3 clustered by(id) sorted by(age) into 2 buckets

4 row format delimited fields terminated by ',';

6 create table student1(id INT, age INT, name STRING)

7 partitioned by(stat_date STRING)

8 clustered by(id) sorted by(age) into 2 buckets

9 row format delimited fields terminated by ',';

(4)结合项目中使用

其实对于数据体量清晰且对于数据业务了解清楚后依照基本规范选择进行分区还是分桶即可;

北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090 第 360 页

You might also like