目录
(三)Broadcast Partitioner(广播分区器)
(四)Rebalance Partitioner(重平衡分区器 - 重点关注)
(六)Custom(自定义)Partitioning(自定义分区)
在大数据处理领域,Apache Flink 凭借其强大的流处理和批处理能力备受青睐。而物理分区作为 Flink 数据处理流程中至关重要的一环,能够让用户根据实际需求灵活调配数据流向,优化数据分布,提升处理效率与性能。本文将深入探讨 Flink 的物理分区策略,包括各类内置分区器的原理、用法,以及如何自定义分区规则,并结合实战代码帮助读者更好地理解与运用。
一、Flink 物理分区策略详解
(一)Global Partitioner(全局分区器)
此分区器会将所有的数据发送到下游的某个算子实例(subtask id = 0)。这在特定场景下很有用,比如后续处理逻辑只需在单一节点集中处理数据时,能减少通信开销,但也极易造成数据倾斜,导致该特定算子实例负载过重。使用方式如下(示例代码片段示意):
DataStream<Long> globalStream = filterStream.global();
(二)Shuffle Partitioner(随机分区器)
依据均匀分布随机划分元素,确保数据被较为均衡地分发到下游各个算子实例,打散数据原有顺序,避免局部热点,适用于各分区处理逻辑无差别、需平均分配负载场景。示例:
DataStream<Long> shuffleStream = filterStream.shuffle();
(三)Broadcast Partitioner(广播分区器)
它会把上游的所有数据,复制多份发送到下游所有的算子实例,即下游每个分区都能拿到完整上游数据副本。常用于需要将某些全局配置、小数据集广播到下游所有任务并行处理的情况,像共享规则集、配置参数表等场景。用法:
DataStream<Long> broadcastStream = filterStream.broadcast();
(四)Rebalance Partitioner(重平衡分区器 - 重点关注)
通过循环的方式依次发送到下游的 task,旨在解决数据倾斜问题。当出现某一个分区数据量过大,导致处理失衡时,调用 rebalance 操作,对分区数据进行重分区,重新均匀分配负载。例如:
DataStream<Long> rebalanceStream = filterStream.rebalance();
其底层逻辑是基于轮询机制,按顺序逐个将数据分配到下游不同分区,确保每个分区获取相对均等的数据量,维持处理的均衡性与稳定性。
(五)Forward Partitioner(前向分区器)
发送到下游对应的第一个 task,不过它有严格限制,要求保证上下游算子并行度一致,即上游算子与下游算子是 1:1 的关系。在上下游的算子没有指定分区器的情况下,如果并行度匹配,默认启用此分区器,否则使用 Rebalance Partitioner。示例:
DataStream<Long> forward = streamSource.forward();
(六)Custom(自定义)Partitioning(自定义分区)
Flink 允许用户按需定制分区规则,契合复杂业务场景独特分发需求。需实现 Partitioner 接口,重写partition
方法定义元素到分区映射逻辑,如下自定义分区示例,按数据值范围分往不同分区:
import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.functions.Partitioner;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
class CustomPartitioner implements Partitioner<Long>{
@Override
public int partition(Long key, int numPartitions) {
System.out.println(numPartitions);
if(key <10000){
return 0;
}
return 1;
}
}
public class _11_自定义分区规则 {
public static void main(String[] args) throws Exception {
//1. env-准备环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
env.setParallelism(2);
DataStreamSource<Long> streamSource = env.fromSequence(1, 15000);
DataStream<Long> dataStream = streamSource.partitionCustom(new CustomPartitioner(), new KeySelector<Long, Long>() {
@Override
public Long getKey(Long value) throws Exception {
return value;
}
});
//dataStream.print();
// 每一个分区的数据量有多少
dataStream.map(new RichMapFunction<Long, Tuple2<Long,Integer>>() {
@Override
public Tuple2<Long, Integer> map(Long value) throws Exception {
long partition = getRuntimeContext().getIndexOfThisSubtask();
return Tuple2.of(partition,1);
}
}).keyBy(0).sum(1).print("前:");
DataStream<Long> rebalance = dataStream.rebalance();
rebalance.map(new RichMapFunction<Long, Tuple2<Long,Integer>>() {
@Override
public Tuple2<Long, Integer> map(Long value) throws Exception {
long partition = getRuntimeContext().getIndexOfThisSubtask();
return Tuple2.of(partition,1);
}
}).keyBy(0).sum(1).print("后:");
//5. execute-执行
env.execute();
}
}
二、查看各分区数据量方法
想洞悉各分区实际数据承载情况,可借助以下技巧。利用RichMapFunction
获取当前任务分区索引,构建包含分区标识与计数(初始设为 1,方便后续聚合统计)的二元组,再按分区标识分组聚合求和,代码如下:
import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
public class _10_物理分区策略 {
public static void main(String[] args) throws Exception {
//1. env-准备环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
// 手动设置五个分区
env.setParallelism(5);
//2. source-加载数据
DataStreamSource<Long> streamSource = env.fromSequence(1, 100000);
//DataStream<Long> ds = streamSource.global();
// 此时打印,数据都在1 分区
//ds.print();
// shuffle 将数据随机均匀分布在不同的 分区上,或者任务上。
//DataStream<Long> shuffle = streamSource.shuffle();
// broadcast 将上游的每一个分区的数据发送给下有的所有分区
//DataStream<Long> broadcast = streamSource.broadcast();
// 将数据均匀的分发给下游的分区,如果遇到数据倾斜,直接就解决了
//DataStream<Long> rebalance = streamSource.rebalance();
// 上有的数据对应下游的数据,分区数必须是 1:1才行
DataStream<Long> forward = streamSource.forward();
// streamSource.rescale();
//shuffle.print();
// 虽然打印了,但是我不知道某个分区具体有多少数据,所以我想看到某个分区,以及这个分区的数据量
forward.map(new RichMapFunction<Long, Tuple2<Long,Integer>>() {
@Override
public Tuple2<Long, Integer> map(Long value) throws Exception {
long partition = getRuntimeContext().getIndexOfThisSubtask();
return Tuple2.of(partition,1);
}
}).keyBy(0).sum(1).print();
//5. execute-执行
env.execute();
}
}
如此便能清晰知晓各分区的数据量多寡,监控数据分布状态,为分区策略调优提供依据。
三、实战代码解读
文中提供多段示例代码。PartitionerDemo
类先构建 Flink 执行环境,设并行度、加载数据后,人为制造数据不均衡(过滤掉部分小值数据),接着演示不同分区器切换使用,观察数据在各分区 “落脚” 差异;_10_物理分区策略
类着重展示各分区器基础使用及结合上述查看数据量方法剖析数据流向与分布;_11_自定义分区规则
类凸显自定义分区逻辑编写、应用,以及对比重分区前后数据量分布变化,借这些代码跑通、调试、分析,能直观把握物理分区在 Flink 数据处理流程的实战表现。
四、总结
Flink 物理分区策略丰富多样,从内置通用分区器到自定义灵活规则,为数据在算子间流转分配提供精细管控手段。合理选用分区策略可化解数据倾斜、适配业务逻辑、提升集群资源利用率与处理效能。深入理解各分区器原理、掌握实战运用与数据监控方法,是用好 Flink 处理大数据、构建高效流批处理应用的关键基石,助力开发者在大数据海洋稳健航行、挖掘数据价值。建议基于实际业务场景需求,多尝试不同分区组合配置,深挖性能优化空间。