00问题
flume问题总结
1. 数据采集flume的agent的堆内存大小
默认只有20M,在生产中是肯定不够的
一般需要给到1G
vi bin/flume-ng
搜索 Xmx , 并修改
2. channel阻塞
启动flume之前,积压的数据过多,
启动flume后,source读得很快,而sink写hdfs速度有限,会导致反压
反压从下游传递到上游,上游的flume的运行日志中会不断报:channel已满,source重试
这里就涉及到flume的运行监控
如果通过监控,发现channel频繁处于阻塞状态,可以通过如下措施予以改善(优化):
a. 如果资源允许,可以增加写入hdfs的agent机器数,通过负载均衡来提高整体吞吐量
b. 如果资源不允许,可以增大batchSize,来提高写入hdfs的效率
c. 如果资源不允许,可以配置数据压缩,来降低写入hdfs的数据流量
d. 如果source的数据流量不是恒定大于sink的写出速度,可以提高channel的缓存容量,来削峰
3.如果agent进程宕机,如何处理?
下游宕机:问题不大,我们配置高可用模式,会自动切换;当然,还是要告警,通知运维尽快修复;
上游宕机:问题较大,通过脚本监控进程状态,发现异常则重新拉起agent进程;并告警通知运维尽快查明原因并修复;
4. flume采集到的数据如果是经过压缩的, 需要在创建文件时以.gz结尾, 防止在导入到hive中时出错
Linux把程序运行在后台的命令写法及含义
将程序挂起
- 执行中的命令使用 ctrl+Z 程序挂起
- jobs 查看挂起的任务
- fg 任务编号放行, 程序从之前的地方继续执行
想要将数据真正的放在后台运行
- 在程序后面加 &
- 输出到控制台的东西依然会将数据打印到输出台
- 想要结束进程需要使用 ps -ef | grep “名字” 找到任务进程号 使用kill -9 杀死
想要使程序不将输出打印在控制台
sh xxx.sh 1>/root/std.log 2>/root/err.log &
-
1 代表标准输出 2 代表错误输出
-
会将程序输出的数据和错误信息打印到文件中 并且在后台运行
-
如果打印的东西不需要, 可以将输出打印到 /dev/null 中 写进去不保存消失了
sh xxx.sh 1>/dev/null 2>&1 &
-
一般后台运行的程序当前会话窗口关闭可能会将后台运行的程序关闭
-
加 nohup 会话关闭不会将后台的程序关闭
nohup sh xxx.sh 1>/dev/null 2>&1 &
-
ls -lR |grep “^-”|wc -l 统计 当前目录及子目录下文件数量
-
ls -l |grep “^-”|wc -l 统计 当前目录下文件数量
-
ls -lR |grep “^d”|wc -l 统计 当前目录及子目录下文件夹数量
-
ls -l |grep “^d”|wc -l 统计 当前目录下文件夹数量
java.long.Integer cannot be cast to [B
java.long.Integer cannot be cast to [B
不能将整数转换成字节数组
原因
自身bug
hive底层使用向量化引擎来提高效率,但是向量化引擎对paquuet支持不完善,所以报错
hive里面对orc支持最完善
解决方法 : 关闭向量化引擎
Hive的向量化执行引擎,对parquet的支持尚不完善,如果报向量化相关的错误,可关闭向量化执行:
set hive.vectorized.execution.enabled=true ; //开启
set hive.vectorized.execution.enabled=false ; //关闭
Hive 执行sql报 code2 错误
1.集群时间不同步
2.设置运行模式
set mapreduce.framework.name=local; 本地
set mapreduce.framework.name=yarn; yarn
3.MapReduce Job程序内存溢出
修改 MR 内存
vi /opt/apps/hadoop-3.1.1/etc/hadoop/hadoop-env.sh
修改内容
export HADOOP_CLIENT_OPTS="-Xmx1g ${HADOOP_CLIENT_OPTS}"
hive报错
java.lang.IllegalArgumentException: Required AM memory (2048+384 MB) is above the max threshold (2048 MB) of this cluster! Please check the values of 'yarn.scheduler.maximum-allocation-mb' and/or 'yarn.nodemanager.resource.memory-mb'.
解决方法
vi /opt/apps/hadoop/etc/hadoop/yarn-site.xml 加
yarn.app.mapreduce.am.resource.mb=4
yarn.nodemanager.resource.memory-mb=8
yarn.scheduler.maximum-allocation-mb=4g
sqoop导入数据到hive中
报错
2021-01-23 23:18:06,565 ERROR hive.HiveConfig: Could not load org.apache.hadoop.hive.conf.HiveConf. Make sure HIVE_CONF_DIR is set correctly.
2021-01-23 23:18:06,565 ERROR tool.ImportTool: Import failed: java.io.IOException: java.lang.ClassNotFoundException: org.apache.hadoop.hive.conf.HiveConf
解决方法
需要将hive 中的 hive-common-3.1.2.jar 发至 /opt/apps/sqoop-1.4.7/lib/一份
hbase
读写数据流程
HBASE调优
https://mp.weixin.qq.com/s/yEPn_ClKidLc0-WtCspNug
预分region 建表时使用 split 字段
优化rowkey 等长 唯一性 热点数据加盐
建立二级索引 使用拦截器来进行设计
rowkey的设计
- 等长, 一般不超过16字节
- 具有唯一性
- 散列 : 加盐, 可以加随机数 不用rowkey根据随机生成的前缀分到不同的region上, 避免热点问题
- 常用来查询的字段放在前面
hbase region划分
-
一般在建表时进行region的划分, 使用split 关键字进行操作, 例
hbase> create ‘ns1:t1’, ‘f1’, SPLITS => [‘10’, ‘20’, ‘30’, ‘40’]
墓碑标记
- HDFS中不支持随意修改数据 , 所有的操作都使写操作 , 会将delete等操作写到一个新的hfile文件中形成一个墓碑标记
- 在查询数据时 , 会在两种表中都查到对对应的数据 , 但是看到第二种表中的delete标记,就不会及那个数据返回 但是原先的数据还保存在hfile文件中 .
hive
oneData
从设计、开发、部署和使用层面,避免指标的重复和冗余建设,保障数据口径的规范和统一,最终实现数据资产全链路关联、提供标准数据输出以及建立统一的数据公共层。
数据建模
- 一般的数据建模分两种
- 一种是自上而下 也就是从需求出发 根据业务需求 对接运营人员 整理所需要的各种指标 根据这些指标然后自上而下寻找可以得出这些指标的事实表 维度表等 着这样一步一步捋清楚 然后进行分析开发
- 自下而上 从数据源出发 根据我们之前埋点的数据分析能够计算的各种指标 和运营人员对接 沟通所需要的指标 然后进行分析
- 关于大数据开发 , 阿里提出过oneData标准, 里面提出了建模时指标统一规范定义 对数据开发的帮助其实是很有帮助的 因为之前开发的时候可能遇到两个表join的问题 , 比如按id进行join id这个字段有int 类型的也有String 类型的 数据类型不统一 , 进行join的话默认是按 int类型来进行join 所有的String类型字段就会进入同一个reduce中 导致数据倾斜 解决办法就是使用函数 cast( id as int )
列式存储
相较于行式存储,列式存储的查询速度非常快。
数据可三范式
- 第一范式 : 有主建 , 原子性, 列不可再分
- 第二范式 : 在第一范式的基础上 每一列必须和主键存在依赖关系
- 第三范式 : 在第二范式的基础上 两张表要进行连接的使用外键进行连接
hive内部表外部表区别
未被external修饰的是内部表(managed table),被external修饰的为外部表(external table);
区别:
- 内部表数据由Hive自身管理,外部表数据由HDFS管理;
- 内部表数据存储的位置是hive.metastore.warehouse.dir(默认:/user/hive/warehouse),外部表数据的存储位置由自己制定;
- 删除内部表会直接删除元数据(metadata)及存储数据;删除外部表仅仅会删除元数据,HDFS上的文件并不会被删除;
- 对内部表的修改会将修改直接同步给元数据,而对外部表的表结构和分区进行修改,则需要修复(MS.K REPAIR TABLE table_name;)
hive执行计划
explain会把查询语句转化成stage组成的序列,主要由三方面组成:
- 查询的抽象语法树
- plane中展示各个stage的依赖情况
- 每个阶段的具体描述:描述具体来说就是显示出对应的操作算子和与之操作的对应的数据,例如查询算子,filter算子,fetch算子 等。
执行计划 :
解析器将sql字符串转化成抽象的语法树,遍历语法树生成逻辑执行计划,优化器对执行计划进行优化,然后执行器将优化后的逻辑计划翻译成mapreduce任务。
Hive中的常用调优手段有哪些?
- 设置数据的压缩格式与存储格式 一般是parquet + snappy 对比正常的txt文件 大小缩小了大概1/4
- 合理使用分区表, 分桶表 分桶表优化了jion操作 partitioned by clustered by
- 参数调优 jvm重用, 小文件数量多 , 对应的task数量多 开启jvm重用可以节省大量的时间
- 设置map端和reduce进行小文件合并 开启map端聚合 , 开启hive 内置的数据倾斜优化机制
- sql优化 使用union all 而不是union union 会去重 将count(distinct) 修改为子查询 先group by 在count(1)
- 调节数据倾斜
hive数据倾斜
原因:数据倾斜的本质就是数据分配的不均匀 某个reduce的数据输入量远远大于其他reduce数据的输入量
- key分布不均匀 热点key集中在一起
- 业务数据本身的特性
- 建表时考虑不周
- 某些SQL语句本身就有数据倾斜
- group by 时会将group by 相同的的字段都拉至reduce端的同一个节点进行进行聚合 当其中每一组的数据量过大时 数据倾斜 慎用!
解决方法
-
map 端join
select /*+ MAPJOIN(a) */ a.c1, b.c1 ,b.c2 from a join b where a.c1 = b.c1;
-
不同数据类型关联也会产生数据倾斜
- 例如注册表中ID字段为int类型,登录表中ID字段即有string类型,也有int类型。当按照ID字段进行两表之间的join操作时,默认的Hash操作会按int类型的ID来进行分配,这样会导致所有string类型ID的记录统统都分配到一个Reduce里面去!!!
- 解决方法:把数字类型转换成字符串类型
on haha.ID = cast(xixi.ID as string)
-
数据出现错误有很多 null 值时, 所有为null的字段都会进入同一个reduce中 可以加随机数
-
参数调节
set hive.map.aggr=true // map端聚合,降低传给reduce的数据量
set hive.groupby.skewindata=true // 开启hive内置的数倾优化机制 -
细化维度 先计算省市区 在进一步计算省市
-
少量的热点数据造成的数据倾斜可以将该字段的数据提取出去进行处理 处理之后再将其和之前的进行union
hive小文件解决
产生原因
- 读取的数据源就是大量的小文件
- 动态分区插入数据,会产生大量的小文件,从而导致map数量剧增
- 使用动态分区时插入数据时如果按一个字段进行分区 , 这个字段的数量为10 个 最后一个map就会产生10个小文件
- 假如一共有100个map 最后就会产生1000个小文件
- +distribute可以使数据在reduce阶段按指定的字段进行分区 较少小文件的产生
- Reduce/Task数量较多,最终落地的文件数量和Reduce/Task的个数是一样的
影响 :
- 小文件过多, 对应的mapper的任务数量就越多, 每个Mapper都会对应启动一个JVM线程来运行,每个Mapper执行数据很少、个数多,导致占用资源多,甚至这些任务的初始化可能比执行的时间还要多,严重影响性能,这个问题可以通过开启JVM重用来解决;
- 小文件过多, 都保存在hdfs中, 文件的元数据信息过多, 都存储在内存中,
解决 :
-
尽量不使用动态分区 , 使用动态分区时加distribute by rank() 是数据均匀的发送至reduce端
insert overwrite table temp.wlb_tmp_smallfile partition(dt)
select * from process_table
DISTRIBUTE BY rank(); -
开启hive自动合并小文件的机制 设置map端和reduce端的输出进行合并
//设置map端输出进行合并,默认为true
set hive.merge.mapfiles = true
//设置reduce端输出进行合并,默认为false
set hive.merge.mapredfiles = true -
开启 jvm 重用
拉链表
拉链表意义:既能记录数据的历史状态,又可以最大程度的节省存储空间
缓慢变换维适合做成拉链表
比如我们在查询用户活跃度分析做了一个拉链表 , 记录了用户最近一个月的活跃状态
分区分桶的区别,为什么要分区
合理的使用分区, 分桶 , 可以提高查询效率 尽量不要使用动态分区, 会造成小文件过多 + distribute by
分区表 : 分文件夹管理 字段相同的数据会进入同一个分区
分桶表:分文件管理 更细粒度的划分 以某一个字段进行分桶 , 会计算字段的hash值 / 分桶数量决定进入那个桶中 clustered by ( id ) into 3 buckets
想要实现两张大表之间的join 可以将两个大表之间的连接字段分桶 此时的join就是按桶来进行join的 一个桶一个join 不过两张表之间的分桶数量必须是倍数关系
- 分区表:细化数据管理,缩小mapreduce程序 需要扫描的数据量。
- 分桶表:提高join查询的效率,在一份数据会被经常用来做连接查询的时候建立分桶,分桶字段就是连接字段;提高采样的效率。
- 分桶表进行抽样,效率高。
select * from stu_buck TABLESAMPLE(BUCKET x OUT OF y)
分桶数 / y = 抽取多少个桶的数据
x 表示从那个桶开始抽取 312-=0 2
00
- 分桶表进行抽样,效率高。
有了分区为什么还要分桶?
- 获得更高的查询处理效率。提高join的效率
- 使取样( sampling)更高效。在处理大规模数据集时,在开发和修改査询的阶段,如果能在数据集的一小部分数据上试运行查询,会带来很多方便。
hive 中sort by order by distribute by cluster by
-
order by 全局排序 字段为null 会在最前面
-
sort by 分区内数据进行排序
-
distribute by 按指定的字段将数据进行分区 要写在sort by之前
-
cluster by : 当distribute by和sorts by字段相同时,可以使用cluster by方式, 只能时升序不能时降序
select * from emp distribute by empno sort by empno desc;
select * from emp cluster by empno; --查询结果相同
hive常用函数
-
增加分区
ALTER TABLE event_app_log ADD IF NOT EXISTS PARTITION (dt =‘2021-01-18’);
-
cast( id as String) 类型抓换
-
nvl( a, b):空字段赋值 如果a不为null a a 为 null b
-
case when then else end :模式匹配
-
concat(字符串,字符串…):对字符串进行连接
-
concat_ws(分隔符,数组arr…)
-
COALESCE(a1,a2,…,an) 返回a1,a2,…,an中遇到的第一个不为NULL的值
now() --获取当前时间
select electtrunc(current_date(),‘MM’) --获取本月的第一天
select to_date(string timestamp) --返回时间字符串的日期部分,例如:to_date(“1970-01-01 00:00:00”)=“1970-01-01”;
select current_timeStamp --显示当前时间
select unix_timeStamp() --以二进制显示当前时间
select from_unixtime(1610726473) --将二进制的时间转为为 2021-01-15 16:01:13格式
select from_unixtime(1610726473,‘yyyy-MM-dd’) --可以设置显示的格式
select hour (from_unixtime(1610726473)) --提取小时
select day(from_unixtime(1610726473)) --提取当前日
自定义函数
- UDF:一对一 实现userDefaultedAggregateFuncation
- UDAF:多对一
- UDTF:一对多
多维分析中的常用操作(上钻 … ):
数据立方体中最常见的五大操作:切片Slice,切块Dice,旋转Pivot,上卷(也叫向上钻取)Roll-up,下钻Drill-down
- 下钻Drill-down:向下从更细分的粒度(维度)来探索分析数据,如按照时间维度,按照天粒度来分析数据
- 改变维的层次,变换分析的粒度。从上层降到下一层,或者说是将汇总数据拆分到更细节的数据。比如通过对2010年第二季度的总销售数据进行钻取来查看2010年第二季度4、5、6每个月的消费数据,当然也可以钻取浙江省来查看杭州市、宁波市、温州市……这些城市的销售数据。
- 上卷Roll-up: 向上从更粗的粒度(维度)来探索分析数据,比如时间维度,按照季度来分析数据
- 钻取的逆操作,即从细粒度数据向高层的聚合,如将江苏省、上海市和浙江省的销售数据进行汇总来查看江浙沪地区的销售数据。
- 切片Slice: 查询某个维度等于某个指定值的数据集 比如按照产品种类等于电子产品的维度 来分析数据
- 选择维中特定的值进行分析,比如只选择电子产品的销售数据,或者2010年第二季度的数据。
- 切块Dice: 查询某个维度等于某几个指定值的数据集
- 选择维中特定区间的数据或者某批特定值进行分析,比如选择2010年第一季度到2010年第二季度的销售数据,或者是电子产品和日用品的销售数据。
- 旋转Pivot:即维的位置的互换,就像是二维表的行列转换,如通过旋转实现产品维和地域维的互换。 旋转 变换维度展现顺序
hive的集合数据类型
- map : 映射(键值对) 使用[key]
- array:数组 使用 [索引]
- struct:结构体 使用 .
宽表的优缺点
优点 :
- 提高查询性能
- 快速响应
- 方便使用,降低使用成本
- 提高用户满意度
缺点 :
- 数据大量冗余
- 改造成本大
sql
查询出每门课都大于80分的学生姓名
select
name
from table_A
group by name
having min(score)>80
如果还查详细信息,则使用上述作为条件 name in () 查询出所有数据记录的信息
查询语文成绩最大的学生
with tmp as(
select
name,
kecheng,
rank() over(partition by kecheng order by fenshu desc) as rn
from table_A) t
select
name
from tmp
where kecheng='语文' and rn=1
查询语文成绩最大的学生(子查询解法)
select
A.name
from
A
where A.kecheng = '语文'
and A.fenshu = (
select max(A.fenshu) 最大值
from A
WHERE A.kecheng = '语文'
);
查询在没有成绩的学生姓名
select
b.name
from Table_B b
left join table_A a on a.name=b.name
where a.name is null
spark
RDD的特点!!!
- 有一些列连续的分区,task的并行度由分区数决定
- 函数作用在分区上
- rdd和rdd之间存在依赖关系,通过shuffle划分宽依赖和窄依赖
- (可选)k-v类型的RDD shuffle的分区器默认为hashpartitioner
- (可选)如果从HDFS上读取数据,会有一个最优位置,即将Executor中的Task调度到数据所在的节点上,要求Worker和DataNode部署在同一节点或OnYarn,通过访问NameNode获取数据块位置信息
spark数据本地化
spark在driver上,对application的每一个stage的task,进行分配之前都会计算出每个task要计算的是哪个分片数据,RDD的某个partition;spark的task分配算法,优先会希望每个task正好分配到它要计算的数据所在的节点,这样就不用在网络间传输数据;但是,如果节点的计算资源和计算能力都满了,那么task就没有机会分配到它数据所在的节点。这种时候,spark会等待一段时间,默认是3s,到最后,实在等不了,就会选择一个比较差的本地化级别,比如将task分配到离计算数据所在节点教近的节点。
数据本地化级别如下:
- PROCESS_LOCAL:进程本地化,代码和数据在同一进程中,也就是在同一个executor中;计算数据的task由executor执行,数据在executor的BlockManager中性能最好
- NODE_LOCAL:节点本地化,代码和数据在同一个节点中;比如说,数据作为一个HDFS block块,task在节点上某个executor中运行;或者说数据和task在一个节点上的不同executor中;数据需要在进程间进行传输
- NO_PREF:对于task来说,数据从哪里获取都一样,没有好坏之分
- RACK_LOCAL:机架本地化,数据和task在一个机架的两个节点上;数据需要通过网络在节点之间进行传输
- ANY:数据和task可能在集群中的任何地方,而且不在一个机架中,性能最差
优化 :
- 通过调节数据本地化等待时长。即设定spark.locality.wait,默认是3s,适当提升时长,再在log或webUI中查看是否提升了数据本地化级别。具体代码是
new SparkConf().set(“spark.locality.wait”,“10”) - 最后还要看整个spark作业的运行时间有没有缩短,如果提升了本地化级别,但因为大量的等待时长,spark作业的运行时间反而增加,那就不要调节了。
spark On yarn 执行流程
- 客户端(Client)向Yarn的ResourceManager申请资源,ResourceManager向Client返回一个 ApplicationID,
- Client向HDFS上传数据(三个包:执行程序的jar 包、Spark的jar包、配置文件)
- ResourceManager选择一个资源充足的NodeManager启动ApplicationMaster
- NodeManager接收到ResourceManager的命令,通过RPC请求从HDFS上下载数据包 , 生成 ApplicationMaster进程
- ApplicationMaster向ResourceManager(中的ResourceScheduler)申请资源, ResourceScheduler找到符合条件的NodeManager,将他们的信息返回给ApplicationMaster
- ApplicationMaster向选出来的NodeManager发送执行消息,NodeManager根据信息从HDFS中 下载依赖数据,并开启Executor进程,并向Driver反向注册
- Driver生成DAG,拆分Task,并将Task发送给Executor执行
spark和MR的区别
Hadoop的MapReduce作为第一代分布式大数据计算引擎,在设计之初,受当时计算机硬件条件所限(内存、磁盘、cpu等), 为了能够计算海量数据,需要将中间结果保存到HDFS中,那么就要频繁读写HDFS从而使得网络IO和磁盘IO成为性能瓶颈。Spark可以将中间结果写到本地磁盘或将中间cache到内存中,节省了大量的网络IO和磁盘IO开销。并且Spark使用更先进的DAG任务调度思想,可以将多个计算逻辑构建成一个有向无环图,并且还会将DAG先进行优化后再生成物理执行计划,同时 Spark也支持数据缓存在内存中的计算。性能比Hadoop MapReduce快100倍。即便是不将数据cache到内存中,其速度也是MapReduce10 倍以上。
spark中宽依赖和窄依赖的区别
算子和算子之前存在依赖关系, 根据shuffle来进行划分
宽依赖 : 有shuffle的产生 是划分stage 的依据 一个父RDD对应多个子RDD 的分区
窄依赖 : 没有shuffle的产生 ,多个算子会被合并到一个Task中,即在一个pipeline中 一个父RDD分区对应一个子RDD的分区
repartition和 coalesce的区别和适用场景
分区数量少变多 repartition 多变少 coalesce 可以不进行shuffle
- 都是对RDD进行重新分区的操作
- 一般增大分区数量时使用repartition 因为增大分区数量一定进行shuffle repartition 底层掉用的是coalesce shuffle 位true, 而coalesce可以选择是否进行shuffle 减小分区数量可以不进行shuffle 所以选择 coalesce
- repartition:
- 底层调用的是coalesce,shuffle=true,一定shuffle
- 适用场景:分区由少变多,或者对一些不是键值对的RDD中进行重新分区的话,适用repartition
- coalesce:
- shuffle可以设置为true或者false,可以shuffle也可以不shuffle
- 使用场景:分区减少的话可以使用coalesce,把shuffle设置为false,就可以不进行shuffle
spark on yarn cluster 和 client 区别
- cluster 的driver端运行在集群其中一个节点上
- client 的driver端运行在提交任务的机器上
spark 内存管理
spark内存分对内内存和堆外内存 统一采用动态内存管理机制
- 堆内内存
- executor storage 之前位动态抢占策略 executor 优先级更高
- execution内存 存放计算过程中的临时数据
- storage 保存spark中cache的数据
- user memory 用户自定义的数据结构以及spark内部的元数据等, 例如RDD的依赖等
- 预留内存 300M
- 堆外内存
- execution
- storage
spark在进行shuffle时采用的是netty框架, 默认会去申请堆外内存, 如果内存不足会导致内存溢出
spark流程
- 程序调用action算子底层调用run方法,
- 构建DAG(触发一次action会生成一个DAG) – 是对多个RDD 转换过程和依赖关系的描述
- 切分stage(根据shufflle来进行切分, 没有shuffle多个算子可以合并在一起形成一个pipeline), 生成taskset,
- 划分task(shufflemaptask 和resultTask ) ---- task spark 任务最小的执行单元 有属性( 从哪里读数据 ) 有方法 ( 如何计算 )
- 调度到executor中执行
spark优化
- 避免创建重复的RDD, 尽可能的复用同一个RDD
比如要对一个RDD进行map操作后还要对它进行reduce操作时, 那么我们只需要创建一次, 不需要重复创建
val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt")
rdd1.map(...)
val rdd2 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt")
rdd2.reduce(...)
更改为 :
val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt")
rdd1.map(...)
rdd1.reduce(...)
-
对多次使用的RDD进行持久化, 并选择kryo的作为序列化方式
对需要多次使用的RDD进行持久化, 使用cache , persist, 以后在每个RDD操作时, 都会直接从内存或磁盘中取数据而不会在从头计算默认, cache 默认是放在内存中, 底层调用的是persist , persist可以选择其他的存储方式, 一般的话都是使用 MEMORY_AND_DISK_SER, 同时可以指定序列化方式, 一般选择kryo.
选择kryo序列化方式时自定义函数需要进行注册, 相比java 性能提高了10 倍左右
// 设置序列化器为KryoSerializer。
conf.set(“spark.serializer”, “org.apache.spark.serializer.KryoSerializer”) -
尽可能避免使用shuffer类算子
在进行shuffer时, 需要进行大量的IO操作和网络数据的传输, 会占用大量的资源 , 因此我们可以尽可能的避免进行shuffer类操作.
方式 : 使用广播变量broadcast实现map端jion, 避免shuffer, 同时可以防止数据倾斜的产生 -
使用高性能的算子
使用reduceByKey 代替 groupByKey, reducebykey 会进行局部聚合, groupbykey不会进行局部聚合
使用mappartitions 代替 map
使用foreach 代替foreachaprtitions
在filter之后在进行进行coalesce , 在数据进行filter操作后, 每个分区内数据都变得可能不是太多, 在调用coalsece操作减少分区的数量, 可以对性能有一定的提升 -
jvm 调优 堆外内存调节
conf spark.yarn.executor.memoryOverhead=2048 -
合理的分配资源
./bin/spark-submit \
–master yarn-cluster
–num-executors 100 \ executors总数量
–executor-memory 6G \ 每个executors内存大小
–executor-cores 4 \ 每个executors核数量
–driver-memory 1G \
–conf spark.default.parallelism=1000 \ 设置task数量
–conf spark.storage.memoryFraction=0.5 \
–conf spark.shuffle.memoryFraction=0.3
–conf spark.shuffle.file.buffer=64k
spark Shuffle 调优
- 设置 spark.shuffle.file.buffer : 该参数用于设置shuffle write task的BufferedOutputStream的buffer缓冲区大小
- 设置每次shuffer拉取数据的大小 时间间隔 …
- 调节spark 堆外内存的大小, shuffle部分使用了netty框架进行网络传输, 会申请堆外内存, reduce拉取数据时后去申请堆外内存, 内存不足会导致内存溢出
spqrk数据倾斜
原理 :
在进行shuffle的时候,必须将各个节点上相同的key拉取到某个节点上的一个task来进行处理,比如按照key进行聚合或join等操作。此时如果某个key对应的数据量特别大的话,就会发生数据倾斜
原因 :
- 在shuffer是某个key特别多导致数据倾斜
- 有时候内存溢出也是数据倾斜导致的
解决方法 :
- 如果导致数据倾斜的是Hive表。该Hive表中的数据本身很不均匀,可以通过Hive来进行数据预处理,spark作业中针对的数据源就是预处理后的Hive表。由于数据已经预先进行过聚合或join操作了,在Spar中就不需要使用原先的shuffle类算子执行这类操作了
- 广播变量 实现 map端jion, 进而完全规避掉shuffle类的操作,彻底避免数据倾斜的发生和出现
- 提高shuffer的并行度, 例reduceByKey(1000)
- 局部聚合和全局聚合 将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被一个task处理的数据分散到多个task上去做局部聚合,进而解决单个task处理数据量过多的问题。接着去除掉随机前缀,再次进行全局聚合,就可以得到最终的结果。
- 过滤掉少量导致数据倾斜的key
spark内存溢出
-
map过程产生了大量的对象导致内存溢出
解决方法 : 调用repartition 方法增加分区, 减少每个分区数据量的大小 -
数据不平衡, 数据倾斜导致内存溢出,
repartition -
调用coalesce方法导致内存溢出, 原先由100个task 现在coalesce(10)
-
Executor 堆外内存的配置需要在spark-submit 脚本里配置, Executor堆外内存配置:
conf spark.yarn.executor.memoryOverhead=2048
spark的三种join
- Braodcast Hash Join 一张较小的表和一张大表进行join
- 小表广播出去的同时会映射成一种hash的结构, 更容易Join
- Sort Merge Join 两张大的表之间的join
- 将数据按相同的规则分区并进行排序, 排序的目的就是一条数据来了到要进行join的数据里面找不至于从第一条找到最后一条
- 排序的作用就是为了查询时更快一些 , 查找时还可以使用二分查找法
- 开启参数 spark.sql.crossJoin.enabled=true
- Shuffle Hash Join 一张小表和一张大表之间的join 或 两张小表之间的join
- 大表join一个相对小的表 , 按相同的分区规则进行shuffle 例 hashpartition key的hash值相同的数据就会进入同一个分区, 再将小表构建成一个hash的样子, 进行join
shuffle的分类
- 分为hashshuffle和sortshuffle
- hashshuffle:
- 使用在spark 1.2以前,会产生大量的中间磁盘文件,大量的磁盘IO操作影响性能
- m 个map task ,r 个reduce task,会产生m*r个小文件
- 优化:可以共享buffer。c 个core ,r 个reduce task ,产生c*r个小文件
- sortshuffle:
- 产生较多的临时小文件,最后会把临时小文件merge成一个文件,下一个stage的shuffle read task拉取数据时,只需要根据索引文件读取文件的部分数据即可
- 临时小文件合并成一个文件时,也会产生一个索引文件,索引文件中记录着分区信息和票偏移量
- m 个map task,会产生2*m个小文件
- bypass机制:有条件的sort,当shuffle reduce task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值(默认200)时,会触发bypass机制,不进行sort,如果所求数据不需要排序,就可以设置此参数
spark , hadoop ,flink 三种checkpoint 的区别
- hadoop 的checkpiont机制是为了将hdfs 的元数据信息( FSimage ) 定期和日志文件合并, 然后持久化到磁盘中, 保证hdfs中元数据信息的安全
- spark 是将计算的中间结果保存在hdfs中 , 使用前必须指定checkpoint 的目录
- Flink 的checkpoint 是容错机制最核心的功能 , 默认是无线重启 它可以将Flink中各种算子的状态定期进行快照, 然后保存至statebackend中
- statebackend 默认保存在内存中 可以设置保存在外部的存储系统中
sort by 和 sort by key
DateSet 和 RDD 和 DataFrame之间的转换关系
TopN计算
老师和学科
- 先按学科和老师进行聚合 reduceByKey
- 按学科进行分组 group by
- 将每一组的数据进行排序
序列化反序列化实现
java序列化是指把java对象转换为字节序列的过程,而java反序列化是指把字节序列恢复为java对象的过程
spark序列化问题
- 闭包现象
函数的内部引用了一个外部的变量, 而这个变量没有实现序列化接口, 无法进行序列化 , 导致task无法发送至executor端 - task已经发送至executor端, 但是使用一个自定义的bean来接收结果, 结果在保存至磁盘时无法序列化.
yarn的资源调度策略
- FIFO 使用一个队列,先进先出,
-
问题 : 造成任务阻塞
-
- 公平调度 : 使用多个任务队列,
-
问题 : 造成资源浪费 ,有的队列没有任务却占有内存
-
- 资源调度 : 多个队列 ,如果一个队列里没有任务,那么他的运算资源会被其他队列抢占,当有任务时可以再去抢占.
Flink
kafka的优势
- 高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒;
- 可扩展性:kafka集群支持热扩展;
- 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失;
- 容错性:允许集群中节点故障(若副本数量为n,则允许n-1个节点故障);
- 高并发:支持数千个客户端同时读写。
窗口的分类
Window就是一种将无限数据集切分成多个有限数据集并对每一个有限数据集分别进行处理的手段。Window本质上是将数据流按照一定的规则,逻辑地切分成很多个有限大小的“bucket”桶,这样就可以对每一个在“桶里面”的有限的数据依次地进行计算
keyBy之前调用 windowAll keyBy之后调用 window
- 按条数划分窗口 可以实现增量聚合 也可以实现全量聚合 ( 需要使用window的apply方法 )
- keyBy之前 countWindowAll
- keyBy之后 countWindow
- 按时间划分窗口 ( 区别 : 划分窗口时传入的时间语义不一样)
- 滚动窗口 ( Tumbilg window )
- 滑动窗口 ( Sliding window )
- 会话窗口 (Session window )
- 使用EventTime作为划分时间窗口的标准需要提前定义WaterMark ( 触发窗口的唯一标准 ) — > [ 最大事件事件 - 延迟事件 ]
如果想要历史累加的结果 , 而不是每个窗口触发一次然后输出对应窗口的数据 可以使用reduce方法 第一函数进行窗口内数据的累加 第二个函数将数据累加在状态中输出
三种时间语义
问题:Flink 三种时间语义是什么,分别说出应用场景?
解答:
- Event Time:这是实际应用最常见的时间语义,指的是事件触发产生的时间,往往跟watermark结合使用
- Processing Time:即数据被Operator处理时的系统时间。适用场景:没有事件时间的情况下,或者对实时性要求超高的情况
- Ingestion Time:数据从消息中间件读取,进入到Flink的source中对应的系统时间。适用场景:存在多个 Source Operator 的情况下,每个 Source Operator 可以使用自己本地系统时钟指派 Ingestion Time。后续基于时间相关的各种操作, 都会使用数据记录中的 Ingestion Time
Flink的状态
- 问题:说一下 Flink 状态机制?
解答:Flink实时计算程序为了保证计算过程中,出现异常可以容错,就要将中间的计算结果数据存储起来,这些中间数据就叫做State。Flink 内置的很多算子,包括源 source,数据存储 sink 都是有状态的。在 Flink 中,状态始终与特定算子相关联。Flink 会以 checkpoint 的形式对各个任务的状态进行快照,用于保证故障恢复时的状态一致性。Flink 通过状态后端来管理状态 和 checkpoint 的存储,状态后端也可以有不同的配置选择。 - state的分类
- operatorState
- listState
- keyedState
- valueState
- ListState
- mapState
- operatorState
- 使用状态必须创建一个状态描述器
- 状态数据默认stateBackend中 默认存储在内存中也可以设置保存在外部存储系统中 当时使用rocksDB来保存
- RocksDB是一种特殊的StateBackend,在状态量非常大的情况下,能够显著提高checkpoint 的效率;
- RocksDB支持增量写入
- RocksDB对保存的状态中的单条数据的大小有限制:不能超过2G
savePoint 和 checkPoint 区别
- checkPoint flink会自动创建的保存状态的目录
- 在任务结束后会自动将状态数据删除 也可以设置任务cacal后不删除状态数据
env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)
- 在任务结束后会自动将状态数据删除 也可以设置任务cacal后不删除状态数据
- savePoint 人为停止的时候创建一个目录用来保存状态
- 可以使用命名 flink stop jobid -p 保存路径 可以将任务停掉 并将状态数据保存在指定的目录
Flink Checkpoint和Savepoint对比:
- 概念:Checkpoint 是 自动容错机制 ,Savepoint 程序全局状态镜像 。
- 目的: Checkpoint 是程序自动容错,快速恢复 。Savepoint是 程序修改后继续从状态恢复,程序升级等。
- 用户交互 : Checkpoint 是 Flink 系统行为 。 Savepoint是自行用户触发。
- 状态文件保留策略: Checkpoint默认程序删除 ,可以设置CheckpointConfig中的参数进行保留 。Savepoint会一直保存,除非用户删除 。
定时器
定时器可以在process方法中自定义 将多条数据攒到一起 延迟触发
- 注册定时器需要重写ontime方法 定时器触发会执行ontime方法中
- 同一个时间定义了多个定时器 定时器指挥触发一次
双流Join
- 将其中一个流使用broadcast 进行广播 , 使用connect进行关联 , 然后调用方法处理
- 使用cogroup 或 join 进行双流join cogroup 可以实现left join rght join 等 需要使用 . where ( ) .equals ( ) … 划窗口进行join
- Interval Join 区间连接
- 假设有A、B两个流,Interval Join可以根据B流中的一条消息的timestamp,在A流上确定一个区间,使用B流上的这个消息到A流上的这个区间中进行join;
- 会将两个流进行keyBy 按keyBy字段join
- interval join不能进行左外连接和右外连接等操作
Flink资源组的配置
在Flink中 相同资源组的算子可以允许在同一个 solt 槽内运行 , 不同资源组的算子会被分配到不同的solt槽内 , 从而实现solt分离, Flink默认的资源组的名称为default
在不设置资源组的名称下 , 会自动跟随上一个资源组的名称
我们可以通过设置资源组的名称来达到让某一个算子独享一个solt的目的 --> [ slotSharingGroup ]
someStream.filter(...).slotSharingGroup("name");
reblance
kafka每个分区的数据会轮询分发下游
Flink背压机制
- 产生数据流的速度如果过快,而下游的算子消费不过来的话,会产生背压。
解决方法:背压的监控可以使用 Flink Web UI(localhost:8081) 来可视化监控,一旦报警就能知道。一般情况下背压问题的产生可能是由于 sink 这个 操作符没有优化好,做一下 优化就可以了。比如如果是写入 ElasticSearch, 那么可以改成批量写入,可以调 大 ElasticSearch 队列的大小等等策略。
- watermark 水位线的最大延迟时间这个参数, 如果设置的过大,可能会造成内存的压力 。
解决方法:设置最大延迟时间小一些 ,然后把迟到元素发送到侧输出流中去。 晚一点更新结果。
- 滑动窗口的长度如果过长,而滑动距离很短的话,flink的性能会下降的很厉害
解决方法:通过时间分片的方法,将每个元素只存入一个“ 重叠窗口”,这样就可以减少窗口处理中状态的写入。
kafka的ack机制
ack机制可以保证数据的可靠性和持久性
在数据发送至kafka的时候,每次发送消息都会有一个确认反馈机制,确保消息正常的能够被收到。
设置发送数据是否需要服务端的反馈,有三个值 0, 1, -1
- 0 : 当ack=0 当消息给到kafka立马给producer响应(这种方法很不安全)
- 1 : producer只要收到一个分区副本成功写入的通知就认为推送消息成功了。这里有一个地方需要注意,这个副本必须是leader副本。只有leader副本成功写入了,producer才会认为消息发送成功。
- -1 : 当ack=-1或者是all 所有follwer都同步到了消息,才响应(这种方法就安全)
Flink yarn session 和 yarn per-job
- yarn session : bin/yarn-session -jm jobmanager 内存 -tm taskmanager内存 -s 槽数量 可以通过一个jobmanager在Web UI 中启动多个job , 可以让多个job使用同一个jobmanager
- yarn per-job: bin/flink run -m yarn-cluster -c 主类名 -p 并行度 -yjm jobmanager内存 -ytm taskmanager内存 -ys 一个容器的槽的数量
- 如果要跑一个实时的任务, 里面有大量的状态, 要使用yarn session 模式还是 per-job模式 ?
答 : 使用 yarn per-job模式 因为 job 里面有大量的状态, jobmanager 要单独为一个job服务, 不能让一个jobmanager为多个job服务, job里面的大量的状态需要checkPoint , 使用yarn-session模式 jobmanager压力很大
Flink优化
- 使用kryo序列化方式
- rocksDB 作为StateBackEnd
- 对状态数据设置过期时间, 超过一定时间没有触发 就将之前的状态数据清除掉
- 使用布隆过滤器 + 状态 来实现去重的操作
Flink基本概念
一个job可以启动一到多个taskmanager , 由任务的并行度和容器里面槽的数量决定
一个槽里可以有多个task, 一个task由多个subtask组成
FlinkOnYarn执行流程
- 客户端向 Yarn ResourceManager提交任务,并向HDFS上传Flink的Jar包和配置,
- ResourceManager根据任务分配Container容器并通知对应的NodeManager启动ApplicationMaster,
- ApplicationMaster启动后加载Flink的Jar包和配置构建环境,然后启动JobManager,
- ApplicationMaster向ResourceManager申请资源启动TaskManager,
- ResourceManager分配Container资源后,由ApplicationMaster通知容器所在节点的NodeManager启动TaskManager,
- NodeManager加载Flink的Jar包和配置构建环境并启动TaskManager,
- TaskManager启动后向JobManager发送心跳包,并等待JobManager向其分配任务。
kafka 实现 ExectlyOnce
kafka --> kafka
分两步提交
-
jobmanger 会协调每个TaskManager进行checkPoint操作, checkPoint 默认数据保存在StateBackend中, 默认StateBackend是内存级别的, 也可以选择保存在外部存储系统(hdfs)中 .
-
当chegkpoint启动时,JobManager会通过RPC的方式将检查点分界线barrier发送到各个算子中, barrier会在算子间传递下去, 只要算子接收到barrier, 就会将算子当前的状态做快照然后持久化到stateBackEnd中 .
-
当所有的subtask都完成了checkPoint操作, 会向jobManager进行ack响应 .
-
sink中的状态是这段时间的产生数据, 它会将这段时间的数据作为state保存到stateBackend中, 如果checkPoint成功但是在commit(提交事务)失败, 这样子虽然数据已经在kafka中了但是属于脏数据, 不可以读, 这样在任务重启时, 会从stateBackend中偏移量以及之前的数据取出来, 偏移量已经是最新的了, 但是数据还没有提交成, 那么他就会将之前未提交成功的数据取出在提交 .jobManager集齐了所有subtask的应答 , 会向实现了checkPointListener的subtask(也就是sink)发送RPC消息, 让其提交事务 .
source sink都可以保存状态 , source 可以记录偏移量 , sink可以记录在这段时间产生的数据
exactly-once 的保证
问题:如果下级存储不支持事务,Flink 怎么保证 exactly-once?
解答:端到端的 exactly-once 对 sink 要求比较高,具体实现主要有幂等写入和 事务性写入两种方式。幂等写入的场景依赖于业务逻辑,更常见的是用事务性写入。而事务性写入又有预写日志(WAL)和两阶段提交(2PC)两种方式。
如果外部系统不支持事务,那么可以用预写日志的方式,把结果数据先当成状态保存,然后在收到 checkpoint 完成的通知时,一次性写入 sink 系统。
为什么用 Flink
问题:为什么使用 Flink 替代 Spark?
解答:主要考虑的是 flink 的低延迟、高吞吐量和对流式数据应用场景更好的支持;另外,flink 可以很好地处理乱序数据,而且可以保证 exactly-once 的状态一致性。
checkpoint 的理解
问题:如何理解Flink的checkpoint
解答:Checkpoint是Flink实现容错机制最核心的功能,默认是无限循环, 它能够根据配置周期性地基于Stream中各个Operator/task的状态来生成快照,从而将这些状态数据定期持久化存储在stateBackend中,当Flink程序一旦意外崩溃时,重新运行程序时可以有选择地从这些快照进行恢复,从而修正因为故障带来的程序数据异常。他可以存在内存,文件系统,或者 RocksDB。
海量 key 去重
问题:怎么去重?考虑一个实时场景:双十一场景,滑动窗口长度为 1 小时, 滑动距离为 10 秒钟,亿级用户,怎样计算 UV?
解答:使用类似于 scala 的 set 数据结构或者 redis 的 set 显然是不行的, 因为可能有上亿个 Key,内存放不下。所以可以考虑使用布隆过滤器(Bloom Filter) 来去重。
checkpoint 与 spark 比较
问题:Flink 的 checkpoint 机制对比 spark 有什么不同和优势?
解答: spark streaming 的 checkpoint 仅仅是针对 driver 的故障恢复做了数据和元数据的checkpoint。而 flink 的 checkpoint 机制要复杂了很多,它采用的是轻量级的分布式快照,实现了每个算子的快照,及流动中的数据的快照。
Flink实时计算为了能够容错,可以将中间数据(state)定期保存到起来,这种定期触发保存中间结果的机制叫CheckPointing。CheckPointing是周期执行的。
watermark 机制 水位线
事件时间 - 窗口延迟时间 ? > 窗口结束时间 窗口触发 : < 窗口结束时间 窗口不触发
问题:请详细解释一下 Flink 的 Watermark 机制。
解答: 是Flik中一种延迟触发窗口的机制,通常更EventTime结合使用. 在使用 EventTime 处理数据的时候可能会遇到数据乱序的问题,流处理从 Event(事 件)产生,流经 Source,再到 Operator,这中间需要一定的时间。虽然大部分情况下,传输到 Operator 的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络延迟等原因而导致乱序的产生,特别是使用 Kafka 的时候,多个分区之间的数据无法保证有序。因此, 在进行 Window 计算的时候,不能无限期地等下去,必须要有个机制来保证在特定的时间后, 必须触发 Window 进行计算,这个特别的机制就是 Watermark(水位线)。Watermark是用于处理乱序事件的。
在 Flink 的窗口处理过程中,如果确定全部数据到达,就可以对 Window 的所有数据做窗口计算操作(如汇总、分组等),如果数据没有全部到达,则继续等待该窗口中的数据全部到达才开始处理。这种情况下就需要用到水位线(WaterMarks)机制,它能够衡量数据处理进度(表达数据到达的完整性),保证事件数据(全部)到达 Flink 系统,或者在乱序及延迟到达时,也能够像预期一样计算出正确并且连续的结果。
数据高峰的处理
问题:Flink 程序在面对数据高峰期时如何处理?
解答:使用大容量的 Kafka 把数据先放到消息队列里面作为数据源,再使用 Flink 进行消费,不过这样会影响到一点实时性。
java
HashMap
JDK8之后,如果哈希表单向链表中元素超过8个,那么单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6个,会重新把红黑树变成单向链表数据结构。.
HashMpa 底层是一个红黑树结构 或 数组+链表 的结构
- map.put ( k , v ) 实现原理
- 将 k v 封装到一个node对象中
- 底层会调用 k 的hashCode() 方法获得 k 的hash 值 将其转换成数组对应的下标
- 如果下标位置没有任何元素 就将node添加到这个位置
- 如果下标上有链表 就会那 k 和链表上的每一个 k 进行equals 如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
- map.get(k)实现原理
- 先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
- 通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。
- 如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。
- 如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,
- get方法最终返回这个要找的value。
- 为何随机增删、查询效率都很高的原因是?
原因:增删是在链表上完成的,而查询只需扫描部分,则效率高。
HashMap集合的key,会先后调用两个方法,hashCode and equals方法,这这两个方法都需要重写。