你的Flink数据重分区又设置错了?Flink重分区算子详细解析

a9a4288e3e4d74e97b320b5c66d06c8b.png全网最全大数据面试提升手册!

一、背景说明

目前Flink包含8个重分区算子,对应8个分区器(7个官方定义及1个自定义),均继承与父类StreamPartitioner。

RebalancePartitioner RescalePartitioner KeyGroupStreamPartitioner GlobalPartitioner ShufflePartitioner ForwardPartitioner CustomPartitionerWrapper BroadcastPartitioner

二、各分区器说明

1. 概览图
a2ef23c717469e4137840d51b835fef0.png
2. RebalancePartitioner

Partitioner that distributes the data equally by cycling through the output channels.

rebalance()算子是真正意义上的轮询操作,上游数据轮询下发到下游算子,注意与broadcast()算子的区别,上图颜色点代表两者数据分发的区别。

private int nextChannelToSendTo;

// 下游channel选择器,第一个数据是随机选择下游其中一个channel
@Override
public void setup(int numberOfChannels) {
    super.setup(numberOfChannels);
    nextChannelToSendTo = ThreadLocalRandom.current().nextInt(numberOfChannels);
}
// 后续+1取模的方式开始轮询下发
@Override
public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
    nextChannelToSendTo = (nextChannelToSendTo + 1) % numberOfChannels;
    return nextChannelToSendTo;
}
// 分发模式为 ALL_TO_ALL
@Override
public boolean isPointwise() { return false; }

FLink 将任务的执行计划分为 StreamGraph–>JobGraph–>ExecutionGraph,其中的StreamingJobGraphGenerator类用以实现将StreamGraph转化为JobGraph,在该类中会调用分区器的isPointwise()方法实现分发模式的选择 :POINTWISE / ALL_TO_ALL。

JobEdge jobEdge;
if (partitioner.isPointwise()) {
    jobEdge =
            downStreamVertex.connectNewDataSetAsInput(
                    headVertex, DistributionPattern.POINTWISE, resultPartitionType);
} else {
    jobEdge =
            downStreamVertex.connectNewDataSetAsInput(
                    headVertex, DistributionPattern.ALL_TO_ALL, resultPartitionType);
}
3. RescalePartitioner

The subset of downstream operations to which the upstream operation sends elements depends on the degree of parallelism of both the upstream and downstream operation. For example, if the upstream operation has parallelism 2 and the downstream operation has parallelism 4, then one upstream operation would distribute elements to two downstream operations while the other upstream operation would distribute to the other two downstream operations. If, on the other hand, the downstream operation has parallelism 2 while the upstream operation has parallelism 4 then two upstream operations will distribute to one downstream operation while the other two upstream operations will distribute to the other downstream operations. In cases where the different parallelisms are not multiples of each other one or several downstream operations will have a differing number of inputs from upstream operations.

根据源码里面的注释可知道,rescale的上下游交互取决于他们的并行度,上游为2下游为4,则一个上游对应两个下游,上游为4下游为2,则两个上游对应一个下游。如若是不同倍数的并行度,则下游会有不同数量的输入。

  • 区别于rebalance有两点,轮询从下游第一个分区开始以及是点对点分发模式。

  • rescale可以增加数据本地处理,减少了网络io性能更高,但数据均衡性不如rebalance。
    private int nextChannelToSendTo = -1;

// 下游channel选择器,从0开始轮询
@Override
public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
    if (++nextChannelToSendTo >= numberOfChannels) {
        nextChannelToSendTo = 0;
    }
    return nextChannelToSendTo;
}
// 分发模式 POINTWISE 点到点,一个下游只会有一个输入
@Override
public boolean isPointwise() { return true; }
4. GlobalPartitioner

Partitioner that sends all elements to the downstream operator with subtask ID=0.

如源码注释所写,所有上游数据下发到下游第一个分区。

// 下游channel选择器,均返回0
@Override
public int selectChannel(SerializationDelegate<StreamRecord<T>> record) { return 0;}

// 分发模式为 ALL_TO_ALL
@Override
public boolean isPointwise() { return false;}
5. ForwardPartitioner

Partitioner that forwards elements only to the locally running downstream operation.

仅将元素转发到本地运行的下游操作的分区器。

// 下游channel选择器,均返回0
@Override
public int selectChannel(SerializationDelegate<StreamRecord<T>> record) { return 0;}

// 分发模式 POINTWISE 点到点,一个下游只会有一个输入
@Override
public boolean isPointwise() { return true;}

与global一样的channel选择方法,区别在于isPointwise()方法为点到点。因此实现了下游仅有一个输入,通过概览图可以清晰看到两者区别。

6. BroadcastPartitioner

Partitioner that selects all the output channels.

上游数据会分发给下游所有分区,故源码里面也提示了不支持select channel。

/**
* Note: Broadcast mode could be handled directly for all the output channels in record writer,
* so it is no need to select channels via this method.
*/
@Override
public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
    throw new UnsupportedOperationException(
            "Broadcast partitioner does not support select channels.");
}
7. KeyGroupStreamPartitioner

Partitioner selects the target channel based on the key group index.

总结下来就是,按照分区键根据hashCode()一次哈希,再murmurHash(keyHash)二次哈希,按照最大并行度(默认128)取模生成keyGroupId,最后根据keyGroupId * parallelism / maxParallelism 得出下游分区index,作为数据分发的依据。

// 核心逻辑,其中最大并行度由系统定义,DEFAULT_LOWER_BOUND_MAX_PARALLELISM = 1 << 7 为128
public KeyedStream(
        DataStream<T> dataStream,
        KeySelector<T, KEY> keySelector,
        TypeInformation<KEY> keyType) {
    this(
            dataStream,
            new PartitionTransformation<>(
                    dataStream.getTransformation(),
                    new KeyGroupStreamPartitioner<>(
                            keySelector,
                            StreamGraphGenerator.DEFAULT_LOWER_BOUND_MAX_PARALLELISM)),
            keySelector,
            keyType);
}
  • KeyGroupStreamPartitioner

// key为分组键,maxParallelism由系统定义默认128,numberOfChannels为用户定义并行度
@Override
public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
    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);
}
  • KeyGroupRangeAssignment

public static int assignKeyToParallelOperator(Object key, int maxParallelism, int parallelism) {
    Preconditions.checkNotNull(key, "Assigned key must not be null!");
    return computeOperatorIndexForKeyGroup(
            maxParallelism, parallelism, assignToKeyGroup(key, maxParallelism));
}

// 第一次hash
public static int assignToKeyGroup(Object key, int maxParallelism) {
    Preconditions.checkNotNull(key, "Assigned key must not be null!");
    return computeKeyGroupForKeyHash(key.hashCode(), maxParallelism);
}

// 第二次hash(murmurhash)
public static int computeKeyGroupForKeyHash(int keyHash, int maxParallelism) {
    return MathUtils.murmurHash(keyHash) % maxParallelism;
}

// 根据公式获取目标下游分区index
public static int computeOperatorIndexForKeyGroup(
        int maxParallelism, int parallelism, int keyGroupId) {
    return keyGroupId * parallelism / maxParallelism;
}
8. ShufflePartitioner

Partitioner that distributes the data equally by selecting one output channel randomly.

shuffle()算子按Random()方法随机选择下游分区。

// 随机分发
private Random random = new Random();

@Override
public int selectChannel(SerializationDelegate<StreamRecord<T>> record) { return random.nextInt(numberOfChannels);}

@Override
public boolean isPointwise() { return false;}
9. CustomPartitionerWrapper

Partitions a DataStream on the key returned by the selector, using a custom partitioner. This method takes the key selector to get the key to partition on, and a partitioner that accepts the key type.

partitionCustom()方法顾名思义就是就是自定义分区器,其中主要是重写里面两个方法Partitioner(定义分区行为)及KeySelector(定义key)

public <K> DataStream<T> partitionCustom(
        Partitioner<K> partitioner, KeySelector<T, K> keySelector) {
    return setConnectionType(
            new CustomPartitionerWrapper<>(clean(partitioner), clean(keySelector)));
}

如果这个文章对你有帮助,不要忘记 「在看」 「点赞」 「收藏」 三连啊喂!

38d8cc28e041dfa0c5b94bde14a7482b.png

f1214a2c46b3420d4a412d5105aad189.jpeg

2022年全网首发|大数据专家级技能模型与学习指南(胜天半子篇)

互联网最坏的时代可能真的来了

我在B站读大学,大数据专业

我们在学习Flink的时候,到底在学习什么?

193篇文章暴揍Flink,这个合集你需要关注一下

Flink生产环境TOP难题与优化,阿里巴巴藏经阁YYDS

Flink CDC我吃定了耶稣也留不住他!| Flink CDC线上问题小盘点

我们在学习Spark的时候,到底在学习什么?

在所有Spark模块中,我愿称SparkSQL为最强!

硬刚Hive | 4万字基础调优面试小总结

数据治理方法论和实践小百科全书

标签体系下的用户画像建设小指南

4万字长文 | ClickHouse基础&实践&调优全视角解析

【面试&个人成长】2021年过半,社招和校招的经验之谈

大数据方向另一个十年开启 |《硬刚系列》第一版完结

我写过的关于成长/面试/职场进阶的文章

当我们在学习Hive的时候在学习什么?「硬刚Hive续集」

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flink 中的算子可以分为三类:转换算子(Transformation)、数据算子(Source)、数据算子(Sink)。 1. 转换算子 转换算子用于对数据进行转换和处理,常用的算子有: - map:对每条数据进行一定的处理,将其转换为另一种形式输出。 - flatMap:与 map 类似,但是可以输出多个结果。 - filter:对数据进行过滤,只保留符合条件的数据。 - keyBy:按照指定的 key 进行分组,将具有相同 key数据分到同一个分区中。 - reduce:对分组后的数据进行聚合计算,输出一个结果。 - sum/min/max:对分组后的数据进行求和/最大值/最小值计算,输出一个结果。 - window:将数据按照时间窗口进行分组,进行聚合计算。 - join:将两个流的数据按照指定的条件进行连接,输出一个新的流。 - union:将两个流合并成一个流。 - coMap/coFlatMap:将两个流的数据进行合并处理,输出一个新的流。 2. 数据算子 数据算子用于从外部数据源中读取数据,常用的算子有: - socketTextStream:从指定的 Socket 地址读取文本数据。 - readTextFile:从指定的文件路径读取文本数据。 - readCsvFile:从指定的 CSV 文件路径读取 CSV 数据。 - addSource:从自定义数据源中读取数据。 3. 数据算子 数据算子用于将数据写入到外部系统中,常用的算子有: - print:将数据打印到控制台。 - writeAsText:将数据以文本形式写入到指定的文件路径。 - writeAsCsv:将数据以 CSV 形式写入到指定的文件路径。 - addSink:将数据写入到自定义的数据汇中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值