Flink重分区算子解析 - StreamPartitioner

一、背景说明

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

RebalancePartitioner
RescalePartitioner
KeyGroupStreamPartitioner
GlobalPartitioner
ShufflePartitioner
ForwardPartitioner
CustomPartitionerWrapper
BroadcastPartitioner

二、各分区器说明

1. 概览图

在这里插入图片描述

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)));
}

学习交流,有任何问题还请随时评论指出交流。

<think>嗯,用户问的是Flink中涉及重分区算子。首先,我得回忆一下Flink算子有哪些,特别是涉及到数据重分布的那些。重分区通常是指改变数据在并行任务之间的分布方式,这会影响任务的并行度和数据处理的效率。 首先,常见的重分区算子可能有keyBy,因为keyBy会根据指定的键将数据重新分组到不同的分区,这样相同键的数据会被发送到同一个任务实例。然后是shuffle,这个算子会随机地将数据均匀分布到下游任务,不过用户可能需要注意shuffle的具体行为。 接下来还有rebalance,这个算子用轮询的方式均匀分配数据,用来处理数据倾斜的问题。不过有时候用户可能会混淆rebalance和rescale的区别,rescale是在本地进行数据交换,可能不会跨网络,而rebalance是全局的,所以需要区分清楚。 另外,还有global,这个算子会把所有数据发送到下游的第一个任务,所以会导致全局聚合,但并行度会被强制为1。而forward则要求上下游并行度一致,数据不会重新分区,只是直接传递,所以可能不算严格的重分区算子,但用户可能想知道它的特殊情况。 还有partitionCustom,允许用户自定义分区策略,这比较灵活,但需要用户自己实现Partitioner接口。可能需要举一个例子说明。 需要确保每个算子的解释准确,同时说明它们的适用场景和可能带来的影响,比如网络开销、数据倾斜的解决等。同时,要提醒用户在使用这些算子时要注意的性能问题,比如shuffle可能带来较大的网络负载,而keyBy在窗口聚合时很常见。 另外,用户可能想知道这些算子如何影响作业的并行结构,比如改变并行度或者数据分布方式。所以需要明确每个算子对应的数据分区变化,以及上下游并行度的关系。 可能还需要提到,重分区操作通常会触发网络传输,属于比较昂贵的操作,所以在实际应用中要合理使用,避免不必要的重分区导致性能下降。例如,在数据已经按某个键分布好的情况下,再次使用keyBy可能没有必要,反而增加开销。 最后,总结一下各个算子的区别和适用场景,帮助用户根据具体需求选择合适的重分区策略。确保回答结构清晰,分点列出每个算子,并给出示例,这样用户更容易理解。</think>在 Apache Flink 中,涉及重分区算子会改变数据在并行任务间的分布方式,直接影响任务的并行结构和性能。以下是常见的重分区算子及其原理和用途: --- ### 1. **`keyBy`** - **作用**:根据指定的键(Key)对数据进行哈希分区,相同键的数据会被分配到同一分区。 - **触发条件**:需要后续聚合操作(如 `reduce`、`window`)时使用。 - **示例**: ```java DataStream<Tuple2<String, Integer>> stream = ...; KeyedStream<Tuple2<String, Integer>, String> keyedStream = stream.keyBy(value -> value.f0); ``` - **特点**: - 分区数由下游算子并行度决定。 - 可能导致数据倾斜(某些分区负载过高)。 --- ### 2. **`shuffle`** - **作用**:完全随机地将数据均匀分布到下游所有任务。 - **适用场景**:需要完全打乱数据分布时使用。 - **示例**: ```java DataStream<String> shuffled = stream.shuffle(); ``` - **特点**: - 数据分布均匀,但网络开销较大。 - 不保证任何顺序。 --- ### 3. **`rebalance`** - **作用**:以轮询(Round-Robin)方式均匀分配数据到下游任务。 - **适用场景**:解决数据倾斜问题。 - **示例**: ```java DataStream<String> rebalanced = stream.rebalance(); ``` - **特点**: - 强制全局数据平衡。 - 适合上游并行度与下游不一致的场景。 --- ### 4. **`rescale`** - **作用**:在本地 TaskManager 内部进行轮询分区,避免跨节点网络传输。 - **示例**: ```java DataStream<String> rescaled = stream.rescale(); ``` - **特点**: - 仅在本地交换数据,网络开销低。 - 要求上下游并行度是倍数关系(如上游并行度 4,下游并行度 2)。 --- ### 5. **`global`** - **作用**:将所有数据发送到下游的第一个并行任务。 - **适用场景**:全局聚合操作(如 `count()`)。 - **示例**: ```java DataStream<String> global = stream.global(); ``` - **特点**: - 下游并行度被强制为 1。 - 可能导致性能瓶颈。 --- ### 6. **`forward`** - **作用**:数据直接传递到下游同一并行任务(上下游并行度必须相同)。 - **示例**: ```java DataStream<String> forwarded = stream.forward(); ``` - **特点**: - 无网络开销,但要求严格并行度匹配。 - 适用于链式优化(Operator Chaining)。 --- ### 7. **`partitionCustom`** - **作用**:自定义分区策略。 - **示例**(按奇偶分区): ```java stream.partitionCustom( (key, numPartitions) -> key % 2, value -> value // 提取分区键 ); ``` - **特点**: - 灵活性高,需实现 `Partitioner` 接口。 - 可能引入复杂逻辑。 --- ### **重分区的影响** 1. **网络开销**:跨节点重分区(如 `shuffle`、`keyBy`)会触发网络传输。 2. **数据倾斜**:不均匀的分区策略(如 `keyBy` 键分布不均)可能导致某些任务过载。 3. **并行度变化**:重分区后下游算子的并行度决定实际分区数。 --- ### **最佳实践** - 优先使用 `keyBy` 配合聚合操作。 - 数据倾斜时尝试 `rebalance` 或自定义分区。 - 避免不必要的重分区(如连续多个 `shuffle`)。 - 通过 Flink Web UI 监控反压和数据分布。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值