并行度
并行度
Spark基于 MapReduce 架构的思想是“数据不动代码动”,
那么 Flink 就类似“代码不动数据流动”,原因就在于流式数据本身是连续到来的、我们不会同时传输所有数据,这其实是更符合数据流本身特点的处理方式
怎样实现数据并行呢?
其实也很简单,我们把一个算子操作,“复制”多份到多个节点,数据来了之后就可以到其中任意一个执行。这样一来,一个算子任务就被拆分成了多个并行的“子任务”(subtasks),再将它们分发到不同节点,就真正实现了并行计算
在 Flink 执行过程中,每一个算子(operator)可以包含一个或多个子任务(operator subtask),这些子任务在 不同的线程、不同的物理机或不同的容器中 完全独立地执行
一个特定算子的子任务(subtask)的个数被称之为其并行度(parallelism)。这样,包含并行子任务的数据流,就是并行数据流,它需要多个分区(stream partition)来分配并行任务。一般情况下,一个流程序的并行度,可以认为就是其所有算子中最大的并行度。一个程序中,不同的算子可能具有不同的并行度
并行度设置方法
-
对于一个算子,首先看在代码中是否单独指定了它的并行度,这个特定的设置优先级最高,会覆盖后面所有的设置
stream.map(word -> Tuple2.of(word, 1L)).setParallelism(2); -
如果没有单独设置,那么采用当前代码中执行环境全局设置的并行度
env.setParallelism(2); -
如果代码中完全没有设置,那么采用提交时-p 参数指定的并行度
bin/flink run –p 2 –c com.atguigu.wc.StreamWordCount ./FlinkTutorial-1.0-SNAPSHOT.jar -
如果提交时也未指定-p 参数,那么采用集群配置文件
flink-conf.yaml中的默认并行度parallelism.default: 2
任务槽(Task Slots)
Flink 中每一个 worker(也就是 TaskManager)都是一个 JVM 进程,它可以启动多个独立的线程,来并行执行多个子任务(subtask)
TaskManager 的计算资源是有限的,并行的任务越多,每个线程的资源就会越少
那一个 TaskManager 到底能并行处理多少个任务呢?为了控制并发量,我们需要在 TaskManager 上对每个任务运行所占用的资源做出明确的划分,这就是所谓的 任务槽(task slots)
在配置文件中配置:
taskmanager.numberOfTaskSlots: 8
Flink 中的计算资源通过 Task Slot 来定义。每个 task slot 代表了 TaskManager 的一个固定大小的资源子集,例如,一个拥有3个slot的 TaskManager,会将其管理的内存平均分成三份分给各个 slot
将资源 slot 化意味着来自不同job的task不会为了内存而竞争,而是每个task都拥有一定数量的内存储备
需要注意的是,这里不会涉及到CPU的隔离,slot目前仅仅用来隔离task的内存
通过调整 slot 的数量,我们就可以控制子任务之间的隔离级别,如果在同一个 JVM 进程中运行的任务,将共享 TCP 连接和心跳消息,也可能共享数据集和数据结构,这就减少了每个任务的运行开销,在降低隔离级别的同时提升了性能
默认情况下, Flink 是允许子任务共享 slot 的,只要属于同一个作业,那么对于不同任务节点的并行子任务,就可以放到同一个 slot 上执行
于是最终结果就变成了:每个任务节点的并行子任务一字排开,占据不同的 slot;而不同的任务节点的子任务可以共享 slot。一个 slot 中,可以将程序处理的所有任务都放在这里执行,我们把它叫作保存了整个作业的运行管道(pipeline)
Flink出于分布式执行的目的,将operator的subtask链接在一起形成task(类似spark中的管道),每个task在一个线程中执行,将operators链接成task是非常有效的优化:它可以减少线程与线程间的切换和数据缓冲的开销,并在降低延迟的同时提高整体吞吐量
分区
数据分区在 Flink 中叫作 Partition
本质上来说,分布式计算就是把一个作业切分成子任务 Task, 将不同的数据交给不同的 Task 计算
在分布式存储中, Partition 分区的概念就是把数据集切分成块,每一块数据存储在不同的机器上。同样 ,对于分布式计算引擎,也需要将数据切分,交给位于不同物理节点上的Task计算
分区接口
StreamPartitioner是 Flink 中的数据流分区抽象接口,决定了在实际运行中的数据流分发模式, 将数据切分交给 Task 计算,每个Task 负责计算一部分数据流
所有的数据分区器都实现了ChannelSelector 接口,该接口中定义了负载均衡选择行为
ChannelSelector接口定义如下:
public interfaceChannelSelector<T extends IOReadablewritable> {
//下游可选 Channel 的数量
void setup (intnumberOfChannels);
//选路方法
int selectChannel (T record);
//是否向下游广播
boolean isBroadcast();
}
在该接口中可以看到,每一个分区器都知道下游通道数量,该通道在一次作业运行中是固定的,除非修改作业的并行度,否则该值不会改变

状态
对于传统的事务型处理架构,这些状态数据是保存在数据库中的;
而对于实时流处理,状态是保存在本地内存,并通过分布式扩展来提高吞吐量
在 Flink 中,每一个算子任务都可以设置并行度,从而可以在不同的 slot 上并行运行多个实例,我们把它们叫作“并行子任务”
而状态既然在内存中,那么就可以认为是子任务实例上的一个本地变量,即槽所在内存中的一个本地变量,能够被任务的业务逻辑访问和修改

浙公网安备 33010602011771号