【Flink】Flink 的八种分区策略(源码解读)

Flink 的八种分区策略(源码解读)

  • 1.继承关系图
    • 1.1 接口:ChannelSelector
    • 1.2 抽象类:StreamPartitioner
    • 1.3 继承关系图
  • 2.分区策略
    • 2.1 GlobalPartitioner
    • 2.2 ShufflePartitioner
    • 2.3 BroadcastPartitioner
    • 2.4 RebalancePartitioner
    • 2.5 RescalePartitioner
    • 2.6 ForwardPartitioner
    • 2.7 KeyGroupStreamPartitioner
    • 2.8 CustomPartitionerWrapper

Flink 包含 8 种分区策略,这 8 种分区策略(分区器)分别如下面所示,本文将从源码的角度解读每个分区器的实现方式。

在这里插入图片描述

  • GlobalPartitioner
  • ShufflePartitioner
  • RebalancePartitioner
  • RescalePartitioner
  • BroadcastPartitioner
  • ForwardPartitioner
  • KeyGroupStreamPartitioner
  • CustomPartitionerWrapper

1.继承关系图

1.1 接口:ChannelSelector

public interface ChannelSelector<T extends IOReadableWritable> {

    /**
     * 初始化channels数量,channel可以理解为下游Operator的某个实例(并行算子的某个subtask).
     */
    void setup(int numberOfChannels);

    /**
     *根据当前的record以及Channel总数,
     *决定应将record发送到下游哪个Channel。
     *不同的分区策略会实现不同的该方法。
     */
    int selectChannel(T record);

    /**
    *是否以广播的形式发送到下游所有的算子实例
     */
    boolean isBroadcast();
}

1.2 抽象类:StreamPartitioner

public abstract class StreamPartitioner<T> implements
        ChannelSelector<SerializationDelegate<StreamRecord<T>>>, Serializable {
    private static final long serialVersionUID = 1L;

    protected int numberOfChannels;

    @Override
    public void setup(int numberOfChannels) {
        this.numberOfChannels = numberOfChannels;
    }

    @Override
    public boolean isBroadcast() {
        return false;
    }

    public abstract StreamPartitioner<T> copy();
}

1.3 继承关系图

在这里插入图片描述

2.分区策略

2.1 GlobalPartitioner

该分区器会将所有的数据都发送到下游的某个算子实例(subtask id = 0)。

在这里插入图片描述

/**
 * 发送所有的数据到下游算子的第一个task(ID = 0)
 * @param <T>
 */
@Internal
public class GlobalPartitioner<T> extends StreamPartitioner<T> {
    private static final long serialVersionUID = 1L;

    @Override
    public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
        //只返回0,即只发送给下游算子的第一个task
        return 0;
    }

    @Override
    public StreamPartitioner<T> copy() {
        return this;
    }

    @Override
    public String toString() {
        return "GLOBAL";
    }
}

2.2 ShufflePartitioner

随机选择一个下游算子实例进行发送。

在这里插入图片描述

/**
 * 随机的选择一个channel进行发送
 * @param <T>
 */
@Internal
public class ShufflePartitioner<T> extends StreamPartitioner<T> {
    private static final long serialVersionUID = 1L;

    private Random random = new Random();

    @Override
    public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
        //产生[0,numberOfChannels)伪随机数,随机发送到下游的某个task
        return random.nextInt(numberOfChannels);
    }

    @Override
    public StreamPartitioner<T> copy() {
        return new ShufflePartitioner<T>();
    }

    @Override
    public String toString() {
        return "SHUFFLE";
    }
}

2.3 BroadcastPartitioner

发送到下游所有的算子实例。

在这里插入图片描述

/**
 * 发送到所有的channel
 */
@Internal
public class BroadcastPartitioner<T> extends StreamPartitioner<T> {
    private static final long serialVersionUID = 1L;
    /**
     * Broadcast模式是直接发送到下游的所有task,所以不需要通过下面的方法选择发送的通道
     */
    @Override
    public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
        throw new UnsupportedOperationException("Broadcast partitioner does not support select channels.");
    }

    @Override
    public boolean isBroadcast() {
        return true;
    }

    @Override
    public StreamPartitioner<T> copy() {
        return this;
    }

    @Override
    public String toString() {
        return "BROADCAST";
    }
}

2.4 RebalancePartitioner

通过循环的方式依次发送到下游的 task

在这里插入图片描述

/**
 *通过循环的方式依次发送到下游的task
 * @param <T>
 */
@Internal
public class RebalancePartitioner<T> extends StreamPartitioner<T> {
    private static final long serialVersionUID = 1L;

    private int nextChannelToSendTo;

    @Override
    public void setup(int numberOfChannels) {
        super.setup(numberOfChannels);
        //初始化channel的id,返回[0,numberOfChannels)的伪随机数
        nextChannelToSendTo = ThreadLocalRandom.current().nextInt(numberOfChannels);
    }

    @Override
    public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
        //循环依次发送到下游的task,比如:nextChannelToSendTo初始值为0,numberOfChannels(下游算子的实例个数,并行度)值为2
        //则第一次发送到ID = 1的task,第二次发送到ID = 0的task,第三次发送到ID = 1的task上...依次类推
        nextChannelToSendTo = (nextChannelToSendTo + 1) % numberOfChannels;
        return nextChannelToSendTo;
    }

    public StreamPartitioner<T> copy() {
        return this;
    }

    @Override
    public String toString() {
        return "REBALANCE";
    }
}

2.5 RescalePartitioner

基于上下游 Operator 的并行度,将记录以循环的方式输出到下游 Operator 的每个实例。

举例:

  • 上游并行度是 2,下游是 4,则上游一个并行度以循环的方式将记录输出到下游的两个并行度上;上游另一个并行度以循环的方式将记录输出到下游另两个并行度上。
  • 若上游并行度是 4,下游并行度是 2,则上游两个并行度将记录输出到下游一个并行度上;上游另两个并行度将记录输出到下游另一个并行度上。

在这里插入图片描述

@Internal
public class RescalePartitioner<T> extends StreamPartitioner<T> {
    private static final long serialVersionUID = 1L;

    private int nextChannelToSendTo = -1;

    @Override
    public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
        if (++nextChannelToSendTo >= numberOfChannels) {
            nextChannelToSendTo = 0;
        }
        return nextChannelToSendTo;
    }

    public StreamPartitioner<T> copy() {
        return this;
    }

    @Override
    public String toString() {
        return "RESCALE";
    }
}

Flink 中的执行图可以分成四层:StreamGraphJobGraphExecutionGraph物理执行图

  • StreamGraph:是根据用户通过 Stream API 编写的代码生成的最初的图。用来表示程序的拓扑结构。
  • JobGraph:StreamGraph 经过优化后生成了 JobGraph,提交给 JobManager 的数据结构。主要的优化为,将多个符合条件的节点 chain 在一起作为一个节点,这样可以减少数据在节点之间流动所需要的序列化 / 反序列化 / 传输消耗。
  • ExecutionGraph:JobManager 根据 JobGraph 生成 ExecutionGraph。ExecutionGraph 是 JobGraph 的并行化版本,是调度层最核心的数据结构。
  • 物理执行图:JobManager 根据 ExecutionGraph 对 Job 进行调度后,在各个 TaskManager 上部署 Task 后形成的 “”,并不是一个具体的数据结构。

StreamingJobGraphGenerator 就是 StreamGraph 转换为 JobGraph。在这个类中,把 ForwardPartitionerRescalePartitioner 列为 POINTWISE 分配模式,其他的为 ALL_TO_ALL 分配模式。代码如下:

if (partitioner instanceof ForwardPartitioner || partitioner instanceof RescalePartitioner) {
            jobEdge = downStreamVertex.connectNewDataSetAsInput(
                headVertex,
                // 上游算子(生产端)的实例(subtask)连接下游算子(消费端)的一个或者多个实例(subtask)
                DistributionPattern.POINTWISE,
                resultPartitionType);
        } else {
            jobEdge = downStreamVertex.connectNewDataSetAsInput(
                headVertex,
                // 上游算子(生产端)的实例(subtask)连接下游算子(消费端)的所有实例(subtask)
                DistributionPattern.ALL_TO_ALL,
                resultPartitionType);
        }

2.6 ForwardPartitioner

发送到下游对应的第一个 task,保证上下游算子并行度一致,即上游算子与下游算子是 1 : 1 1:1 1:1 的关系。

在这里插入图片描述

/**
 * 发送到下游对应的第一个task
 * @param <T>
 */
@Internal
public class ForwardPartitioner<T> extends StreamPartitioner<T> {
    private static final long serialVersionUID = 1L;

    @Override
    public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
        return 0;
    }

    public StreamPartitioner<T> copy() {
        return this;
    }

    @Override
    public String toString() {
        return "FORWARD";
    }
}

在上下游的算子没有指定分区器的情况下,如果上下游的算子并行度一致,则使用 ForwardPartitioner,否则使用 RebalancePartitioner,对于 ForwardPartitioner,必须保证上下游算子并行度一致,否则会抛出异常。

//在上下游的算子没有指定分区器的情况下,如果上下游的算子并行度一致,则使用ForwardPartitioner,否则使用RebalancePartitioner
if (partitioner == null && upstreamNode.getParallelism() == downstreamNode.getParallelism()) {
    partitioner = new ForwardPartitioner<Object>();
} else if (partitioner == null) {
    partitioner = new RebalancePartitioner<Object>();
}

if (partitioner instanceof ForwardPartitioner) {
    //如果上下游的并行度不一致,会抛出异常
    if (upstreamNode.getParallelism() != downstreamNode.getParallelism()) {
        throw new UnsupportedOperationException("Forward partitioning does not allow " +
            "change of parallelism. Upstream operation: " + upstreamNode + " parallelism: " + upstreamNode.getParallelism() +
            ", downstream operation: " + downstreamNode + " parallelism: " + downstreamNode.getParallelism() +
            " You must use another partitioning strategy, such as broadcast, rebalance, shuffle or global.");
    }
}

2.7 KeyGroupStreamPartitioner

根据 key 的分组索引选择发送到相对应的下游 subtask

在这里插入图片描述

  • org.apache.flink.streaming.runtime.partitioner.KeyGroupStreamPartitioner
/**
 * 根据key的分组索引选择发送到相对应的下游subtask
 * @param <T>
 * @param <K>
 */
@Internal
public class KeyGroupStreamPartitioner<T, K> extends StreamPartitioner<T> implements ConfigurableStreamPartitioner {
...

    @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);
        }
        //调用KeyGroupRangeAssignment类的assignKeyToParallelOperator方法,代码如下所示
        return KeyGroupRangeAssignment.assignKeyToParallelOperator(key, maxParallelism, numberOfChannels);
    }
...
}
  • org.apache.flink.runtime.state.KeyGroupRangeAssignment
public final class KeyGroupRangeAssignment {
...

    /**
     * 根据key分配一个并行算子实例的索引,该索引即为该key要发送的下游算子实例的路由信息,
     * 即该key发送到哪一个task
     */
    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));
    }

    /**
     *根据key分配一个分组id(keyGroupId)
     */
    public static int assignToKeyGroup(Object key, int maxParallelism) {
        Preconditions.checkNotNull(key, "Assigned key must not be null!");
        //获取key的hashcode
        return computeKeyGroupForKeyHash(key.hashCode(), maxParallelism);
    }

    /**
     * 根据key分配一个分组id(keyGroupId),
     */
    public static int computeKeyGroupForKeyHash(int keyHash, int maxParallelism) {

        //与maxParallelism取余,获取keyGroupId
        return MathUtils.murmurHash(keyHash) % maxParallelism;
    }

    //计算分区index,即该key group应该发送到下游的哪一个算子实例
    public static int computeOperatorIndexForKeyGroup(int maxParallelism, int parallelism, int keyGroupId) {
        return keyGroupId * parallelism / maxParallelism;
    }
...

2.8 CustomPartitionerWrapper

通过 Partitioner 实例的 Partition 方法(自定义的)将记录输出到下游。

public class CustomPartitionerWrapper<K, T> extends StreamPartitioner<T> {
    private static final long serialVersionUID = 1L;

    Partitioner<K> partitioner;
    KeySelector<T, K> keySelector;

    public CustomPartitionerWrapper(Partitioner<K> partitioner, KeySelector<T, K> keySelector) {
        this.partitioner = partitioner;
        this.keySelector = keySelector;
    }

    @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(), e);
        }
		//实现Partitioner接口,重写partition方法
        return partitioner.partition(key, numberOfChannels);
    }

    @Override
    public StreamPartitioner<T> copy() {
        return this;
    }

    @Override
    public String toString() {
        return "CUSTOM";
    }
}

比如:

public class CustomPartitioner implements Partitioner<String> {
      // key: 根据key的值来分区
      // numPartitions: 下游算子并行度
      @Override
      public int partition(String key, int numPartitions) {
         return key.length() % numPartitions;//在此处定义分区策略
      }
  }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/444269.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HTML 学习笔记(五)超链接

HYperText 超文是用超链接的方式&#xff0c;将不同空间的文字信息组合在一起的网状文其就像一个桥梁&#xff0c;建立了不同页面中的联系&#xff0c;实现了访问不同网站中页面的功能 <!DOCTYPE html> <html lang"en"><head><meta charset&qu…

深度学习+感知机

深度学习感知机 1感知机总结 2多层感知机1XOR2激活函数3多类分类总结 3代码实现 1感知机 是个很简单的模型,是个二分类的问题。 感知机&#xff08;perceptron&#xff09;是Frank Rosenblatt在1957年提出的一种人工神经网络&#xff0c;被视为一种最简单形式的前馈神经网络&…

【C语言】深入理解指针(进阶篇)

一、数组名的理解 数组名就是地址&#xff0c;而且是数组首元素的地址。 任务&#xff1a;运行以下代码&#xff0c;看数组名是否是地址。 #include <stdio.h> int main() {int arr[] { 1,2,3,4,5,6,7,8,9,0 };printf("&arr[0] %p\n", &arr[0]);pri…

[Java安全入门]三.CC1链

1.前言 Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库&#xff0c;它提供了很多强大的数据结构类型和实现了各种集合工具类。Commons Collections触发反序列化漏洞构造的链叫做cc链&#xff0c;构造方式多种&#xff0c;这里先学习cc1链…

Dubbo-记录

1.概念 Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题&#xff0c;官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力&#xff0c; 利用 Dubbo 提供的丰富服务治理…

C语言--函数指针变量和函数指针数组的区别(详解)

函数指针变量 函数指针变量的作用 函数指针变量是指向函数的指针&#xff0c;它可以用来存储函数的地址&#xff0c;并且可以通过该指针调用相应的函数。函数指针变量的作用主要有以下几个方面&#xff1a; 回调函数&#xff1a;函数指针变量可以作为参数传递给其他函数&…

【数仓】通过Flume+kafka采集日志数据存储到Hadoop

相关文章 【数仓】基本概念、知识普及、核心技术【数仓】数据分层概念以及相关逻辑【数仓】Hadoop软件安装及使用&#xff08;集群配置&#xff09;【数仓】Hadoop集群配置常用参数说明【数仓】zookeeper软件安装及集群配置【数仓】kafka软件安装及集群配置【数仓】flume软件安…

【深度学习笔记】6_10 双向循环神经网络bi-rnn

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 6.10 双向循环神经网络 之前介绍的循环神经网络模型都是假设当前时间步是由前面的较早时间步的序列决定的&#xff0c;因此它们都将信…

【Leetcode打卡】递归回溯

【Leetcode打卡】递归回溯 784. 字母大小写全排列 class Solution { public:int find(string s,int pos){int ipos;while(i<s.size()){if(isalpha(s[i])){return i;}i;}return -1;}void turn(string& s,int pos){if(islower(s[pos])){s[pos]toupper(s[pos]);}else{s[po…

ChatGPT提示词工程:prompt和chatbot

ChatGPT Prompt Engineering for Developers 本文是 https://www.deeplearning.ai/short-courses/chatgpt-prompt-engineering-for-developers/ 这门课程的学习笔记。 ChatGPT提示词工程&#xff1a;prompt和chatbot 文章目录 ChatGPT Prompt Engineering for DevelopersWhat …

Linux下使用open3d进行点云可视化(.bin文件)

整个场景可视化&#xff1a; import numpy as np import open3d as o3ddef read_kitti_bin_point_cloud(bin_file):# 加载.bin文件point_cloud_np np.fromfile(bin_file, dtypenp.float32).reshape(-1, 4)# 仅使用X, Y, Z坐标&#xff0c;忽略反射率point_cloud_o3d o3d.geo…

代码训练LeetCode(6)编辑距离

代码训练(6)LeetCode之编辑距离 Author: Once Day Date: 2024年3月9日 漫漫长路&#xff0c;才刚刚开始… 全系列文章可参考专栏: 十年代码训练_Once-Day的博客-CSDN博客 参考文章: 72. 编辑距离 - 力扣&#xff08;LeetCode&#xff09;力扣 (LeetCode) 全球极客挚爱的技…

44岁「台偶一哥」成现实版「王子变青蛙」,育一子一女成人生赢家

电影《周处除三害》近日热度极高&#xff0c;男主角阮经天被大赞演技出色&#xff0c;最让人意想不到&#xff0c;因为该片在内地票房报捷&#xff0c;很多人走去恭喜另一位台湾男艺人明道&#xff0c;皆因二人出道时外貌神似&#xff0c;至今仍有不少人将两人搞混。 多年过去&…

RNN预测正弦时间点

import torch.nn as nn import torch import numpy as np import matplotlib matplotlib.use(TkAgg) from matplotlib import pyplot as plt # net nn.RNN(100,10) #100个单词&#xff0c;每个单词10个维度 # print(net._parameters.keys()) #序列时间点预测num_time_steps 50…

什么是RabbitMQ的死信队列

RabbitMQ的死信队列&#xff0c;是一种用于处理消息&#xff0c;处理失败或无法路由的消息的机制。它允许将无法被正常消费的消息重新路由到另一个队列&#xff0c;以便稍后进行进一步的处理&#xff0c;分析或排查问题。 当消息队列里面的消息出现以下几种情况时&#xff0c;就…

ChatGPT 控制机器人的基本框架

过去的一年&#xff0c;OpenAI的chatGPT将自然语言的大型语言模型&#xff08;LLM&#xff09;推向了公众的视野&#xff0c;人工智能AI如一夜春风吹遍了巴黎&#xff0c;全世界都为AI而疯狂。 OpenAI ChatGPT是一个使用人类反馈进行微调的预训练生成文本模型。不像以前的模型主…

每日一练:LeeCode-209、长度最小的子数组【滑动窗口+双指针】

每日一练&#xff1a;LeeCode-209、长度最小的子数组【滑动窗口双指针】 思路暴⼒解法滑动窗口 本文是力扣 每日一练&#xff1a;LeeCode-209、长度最小的子数组【滑动窗口双指针】 学习与理解过程&#xff0c;本文仅做学习之用&#xff0c;对本题感兴趣的小伙伴可以出门左拐 L…

《剑指 Offer》专项突破版 - 面试题 76 : 数组中第 k 大的数字(C++ 实现)

目录 详解快速排序 面试题 76 : 数组中第 k 大的数字 详解快速排序 快速排序是一种非常高效的算法&#xff0c;从其名字可以看出这种排序算法最大的特点是快。当表现良好时&#xff0c;快速排序的速度比其他主要对手&#xff08;如归并排序&#xff09;快 2 ~ 3 倍。 快速排…

力扣---腐烂的橘子

题目&#xff1a; bfs思路&#xff1a; 感觉bfs还是很容易想到的&#xff0c;首先定义一个双端队列&#xff08;队列也是可以的~&#xff09;&#xff0c;如果值为2&#xff0c;则入队列&#xff0c;我这里将队列中的元素定义为pair<int,int>。第一个int记录在数组中的位…

嵌入式驱动学习第二周——使用perf进行性能优化

前言 这篇博客来聊一聊如何使用perf进行性能优化。 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程&#xff0c;未来预计四个月将高强度更新本专栏&#xff0c;喜欢的可以关注本博主并订阅本专栏&#xff0c;一起讨论一起学习。现在关注就是老粉啦&#xff01; 目录 前言…