在介绍流分区器前,为方便大家理解,我们解释下文中会用到的Flink几个关键名词概念。
Parallelism:Flink中Parallelism表示并行度,假设source算子的parallelism=2,集群有2个TaskManager,每个TaskMananger有一个slot,那么source算子会同时在2个TaskManager中运行。
Operator:一个算子,比如map算子就是Operator
UpStream:上游流
DownStream:下游流
文中的例子upStream为source算子,downStream为map算子。现在回到分区器内容中来,假设parallelism=2,当source[0]中数据T需要emit给map时,给map[0]或者map[1]哪个最优?
最优分区器评估标准:
本地化和数据均衡跟鱼和熊掌不能兼得一个道理,在数据倾斜不是很夸张的提前下优先选择本地化策略。
所有分区器都实现了StreamPartitioner接口selectChannel方法,返回选择的Channel。(Channel指map[0]或者map[1]实际运行所在的进程)
在selectChannel方法
int selectChannel(T record);
方法名 |
入参 |
出参 |
selectChannel |
需要emit到downStream的数据 |
数据被分配到Channel |
本地化策略
ForwardPartitioner是完全本地化分区器,没有跨节点数据传输,最优选择的分区器。RescalePartitioner是局部本地化分区器,其它都是全局分区器。
假设source--->map两个算子各自的并行度决定可使用的分区器。使用本地化分区器的算子都可组成OperatorChain
本地化分区器,数据传输不会出现跨节点,upStream、downStream会组成一个OperatorChain,这是最优的Partitioner策略。
@Override
public int selectChannel(SerializationDelegate
return 0;
}
出度为1,只有一个Channel
采用轮询路由规则,局部分区策略,个人理解是介于本地化分区策略和全局化分区策略之间。
路由规则:
@Override
public int selectChannel(SerializationDelegate
if (++nextChannelToSendTo >= numberOfChannels) {
nextChannelToSendTo = 0;
}
return nextChannelToSendTo;
}
自增变量nextChannelToSendTo决定数据选择channel的路由规则,当大于等于最大Channels数值时重置为0,在尽可能减少跨节点传输数据的情况下确保数据负载均衡。
全局分区器,数据只会路由到Partitioner为0的DownStream节点。
@Override
public int selectChannel(SerializationDelegate
return 0;
}
当所有数据要做串行处理的时,这是一个不错的选择。
广播分区器,会把同一份数据路由给downStream所有的节点,这相当于对数据进行了复制,复制份数由setParallelism决定。
适用场景:数据字典,配置项类的数据通过广播分区策略传播到downStream每个节点
键-组流分区器,基于消息键的hash值做简单的计算决定Channel,属于全局分区器。
数据路由物理图:
@Override
public int selectChannel(SerializationDelegate
K key;
try {
key = keySelector.getKey(record.getInstance().getValue());
} catch (Exception e) {
throw new RuntimeException("Could not extract key from " + record.getInstance().getValue(), e);
}
return KeyGroupRangeAssignment.assignKeyToParallelOperator(key, maxParallelism, numberOfChannels);
}
算法:Murmurhash(key)%maxParallelism*parallelism/maxParallelism
缺点:当某个键的消息比较多时,会出现数据倾斜,解决数据倾斜的方法一般是对键加前缀打散处理,对数据聚合后再去掉前缀还原。
现在看重头戏,如何确定出度的数量。
再平衡分区器,启动时随机选择一个Channel,然后使用取模算法选择Channel,达到了轮询效果
@Override
public int selectChannel(SerializationDelegate
nextChannelToSendTo = (nextChannelToSendTo + 1) % numberOfChannels;
return nextChannelToSendTo;
}
数据均衡上表现最好的一个分区器,在均衡上比ShufflePartitioner更出色,最大的缺点是数据跨节点传输量过大
混洗分区器,随机的方式把数据打散,数据均衡上也是表现良好的,比RebalancePartitioner略差,共同的缺点就是数据跨节点传输量过大
@Override
public int selectChannel(SerializationDelegate
return random.nextInt(numberOfChannels);
}
自定义分区器包装类,用户在构建此类实例前需要实现Partitioner接口,在方法partition中定义分区器路由规则。
使用自定义分区规则:
只需在DataStream类实例方法partitionCustom传入Partitioner实现类实例即可
@Override
dataStream.partitionCustom(new Partitioner
public int partition(String key, int numPartitions){
return hash(key)%numPartitions
}
})
出度:
env.setParallelism(4)
env.addSource(…).map(…).keyBy(…)
上图中并行度为4,那么Channel也为4。