文章目录
1. 定义
当进行聚合运算时(Group By/KeyBy + Agg),如果聚合所使用的key存在热点,则会导致数据倾斜。如统计某日各个省份的车流量,则负责运算北京、上海等一线城市的count subtask节点则会成为热点,处理数据的压力会比较大。
1.1 危害
1.1.1 任务卡死
keyBy 或 rebalance 下游的算子,如果单个 subtask 存在热点并完全卡死,会把整个 Flink 任务卡死。看如下示例:
如下图所示,上游每个 Subtask 中会有 3 个 resultSubPartition,连接下游算子的 3 个 subtask。下游每个 subtask 会有 2 个 InputChannel,连接上游算子的 2 个 subtask。Local BufferPool为subtask中的ResultSubpartition/InputChannel所共用,在正常运行过程中如果没有反压,所有的 buffer pool 是用不完的。
一旦subtask B0变成热点,则会引起反压,依次产生如下问题:
1、Subtask B0 内的 A0 和 A1 两个 InputChannel 会被占满;Subtask B0 公共的 BufferPool 中可申请到的空间也被占满
2、Subtask A0 和 A1 的 B0 ResultSubPartition 被占满;Subtask A0 和 A1 公共的 BufferPool 中可申请到的空间也被占满
3、如图2所示,Subtask A0 的主线程会从上游读取数据消费,按照数据的 KeyBy 规则,将数据发送到 B0、B1、B2 三个 ResultSubpartition 中;可以看到,如果 B0 这个ResultSubpartition占满了,且 B0 在公共的 Local BufferPool 中可申请到的空间也被占满。现在有一条数据被keyby后发往B0,但是现在 B0 这个ResultSubpartition 没有空间了,所以主线程就会卡在申请 buffer 上,直到可以再申请到 buffer。
Subtask A0 的主线程被卡住,则不会往下游的任何subtask发送数据了,如图1所示,下游的Subtask B1和Subtask B2不再接收新数据。整个任务处于瘫痪状态。
Flink的数据传播机制参考下图:
1.1.2 checkpoint时间变长
checkpoint barrier也是一种特殊的数据,如果整个任务中各个可用buffer变少,则checkpoint barrier的传输也会因为找不到可用buffer而降低速度;由于checkpoint barrier的对齐机制,会造成当前checkpoint的barrier迟迟无法对齐,进而超时
1.1.3 state变大
对于有两个以上输入管道的 Operator,存在checkpoint barrier对齐机制,接受到较快的输入管道的 barrier 后,它后面数据会被缓存起来但不处理,直到较慢的输入管道的 barrier 也到达,这些被缓存的数据会被放到state 里面,导致 checkpoint 变大
2. 解决办法
2.1 修改分区策略
2.1.1 目标
让不需要shuffle的两个算子间进行shuffle,打乱数据,从而避免数据倾斜
2.1.2 手段
在Flink任务提交后,经常可以看到web ui中的一些算子之间采用的分区策略是forward
,在该分区策略下很可能会存在数据倾斜现象。如以下情况:
某kafka topic统计每个省份的车次,针对每个省份都有一个partition,共计36个partition,同时设有36个source算子,36个flatmap算子。由于source和flatmap满足one-to-one
关系,且并行度相同,则Flink默认会采用forward
这个分区策略来关联source和flatmap这两个算子。
Flink默认设置forward
分区策略有两个条件:
- 两个算子满足
one-to-one
关系 - 两个算子并行度相同
此时,北京和上海对应的flatmap
算子必然会出现热点数据,由于source
到flatmap
算子之间并不需要有特定的对应关系,因此可以采用不同的分区策略来将数据打乱,让不同省份的车流数据落到所有的flatmap
算子,消除数据倾斜。
因此,我们只需要破坏forward
分区策略的条件即可
- 修改两个算子的并行度
- 强行设定分区策略,代码如下:
dataStream.rebalance();
2.2 两阶段聚合
所谓两阶段聚合,即在需要shuffle的两个算子之间,再加一层算子
2.2.1 目标
-
<