Flink从入门到上天系列第六篇:Flink运行时架构

Flink从入门到上天系列第六篇:Flink运行时架构

一:系统架构

1:会话模式

作业管理器(JobManager)

        JobManager是一个Flink集群中任务管理和调度的核心,是控制应用执行的主进程。也就是说,每个应用都应该被唯一的JobManager所控制执行。

        JobManger又包含3个不同的组件。

JobMaster

        JobMaster是JobManager中最核心的组件,负责处理单独的作业(Job)。所以JobMaster和具体的Job是一一对应的,多个Job可以同时运行在一个Flink集群中, 每个Job都有一个自己的JobMaster。需要注意在早期版本的Flink中,没有JobMaster的概念;而JobManager的概念范围较小,实际指的就是现在所说的JobMaster。

        

        在作业提交时,JobMaster会先接收到要执行的应用。JobMaster会把JobGraph转换成一个物理层面的数据流图,这个图被叫作“执行图”(ExecutionGraph),它包含了所有可以并发执行的任务。JobMaster会向资源管理器(ResourceManager)发出请求,申请执行任务必要的资源。一旦它获取到了足够的资源,就会将执行图分发到真正运行它们的TaskManager上。

        而在运行过程中,JobMaster会负责所有需要中央协调的操作,比如说检查点(checkpoints)的协调。



资源管理器(ResourceManager)

        ResourceManager主要负责资源的分配和管理,在Flink 集群中只有一个。所谓“资源”,主要是指TaskManager的任务槽(task slots)。任务槽就是Flink集群中的资源调配单元,包含了机器用来执行计算的一组CPU和内存资源。每一个任务(Task)都需要分配到一个slot上执行。

        这里注意要把Flink内置的ResourceManager和其他资源管理平台(比如YARN)的ResourceManager区分开。



分发器(Dispatcher)

        Dispatcher主要负责提供一个REST接口,用来提交应用,并且负责为每一个新提交的作业启动一个新的JobMaster 组件。Dispatcher也会启动一个Web UI,用来方便地展示和监控作业执行的信息。Dispatcher在架构中并不是必需的,在不同的部署模式下可能会被忽略掉。



任务管理器(TaskManager)

        TaskManager是Flink中的工作进程,数据流的具体计算就是它来做的。Flink集群中必须至少有一个TaskManager;每一个TaskManager都包含了一定数量的任务槽(task slots)。Slot是资源调度的最小单位,slot的数量限制了TaskManager能够并行处理的任务数量。

        启动之后,TaskManager会向资源管理器注册它的slots;收到资源管理器的指令后,TaskManager就会将一个或者多个槽位提供给JobMaster调用,JobMaster就可以分配任务来执行了。

        在执行过程中,TaskManager可以缓冲数据,还可以跟其他运行同一应用的TaskManager交换数据。

二:核心概念

1:并行度

1:并行度概念

代码示例:

package com.dashu.day02; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.java.tuple.Tuple; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.streaming.api.datastream.DataStreamSink; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.KeyedStream; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; public class Flink01_par { // 该样例准备并行度。 public static void main(String[] args) throws Exception { // 环境准备 StreamExecutionEnvironment evn = StreamExecutionEnvironment.getExecutionEnvironment(); // 从指定网络端口中读取数据 DataStreamSource<String> socketDS = evn.socketTextStream("192.168.67.137", 8888); // 对读取的数据进行扁平化处理--向下游传递的是一个一个单词 SingleOutputStreamOperator<String> flatMapDS = socketDS.flatMap(new FlatMapFunction<String, String>() { @Override public void flatMap(String s, Collector<String> out) throws Exception { String[] s1 = s.split(" "); for (String string : s1) { // 单词向下进行传递。 out.collect(string); } } }); // 对流中数据进行类型转换,把字符串转换成Tuple2<String,Long> SingleOutputStreamOperator<Tuple2<String, Long>> mapDS = flatMapDS.map(new MapFunction<String, Tuple2<String, Long>>() { @Override public Tuple2<String, Long> map(String s) throws Exception { return Tuple2.of(s, 1L); } }); // 按照单词进行分组 KeyedStream<Tuple2<String, Long>, Tuple> keyDS = mapDS.keyBy(0); // 求和 SingleOutputStreamOperator<Tuple2<String, Long>> sumDS = keyDS.sum(1); // 打印 DataStreamSink<Tuple2<String, Long>> print = sumDS.print(); // 提交作业。 evn.execute(); } } 
        当要处理的数据量非常大时,我们可以把一个算子操作,“复制”多份到多个节点,数据来了之后就可以到其中任意一个执行。

        这样一来,一个算子任务就被拆分成了多个并行的“子任务”(subtasks),再将它们分发到不同节点,就真正实现了并行计算。

        在Flink执行过程中,每一个算子(operator)可以包含一个或多个子任务(operator subtask),这些子任务在不同的线程、不同的物理机或不同的容器中完全独立地执行。

        

        一个特定算子的子任务(subtask)的个数被称之为其并行度(parallelism)。这样,包含并行子任务的数据流,就是并行数据流,它需要多个分区(stream partition)来分配并行任务。

        一般情况下,一个流程序的并行度,可以认为就是其所有算子中最大的并行度。一个程序中,不同的算子可能具有不同的并行度。


        在Idea开发Flink程序的时候,没有显示的指定并行度。默认并行度个数是当前CPU的线程数。

 

11> (tree,1) 3> (big,1) 

       

2:并行度设置两种方式

设置并行度的两种方式:

1:代码中全局设置

2:代码中给单个算子设置并行度。优先级更高。

3:配置文件中flink-conf.yaml中parallelism.default:1 来设置并行度。

4:命令行提交作业设置并行度。-p 4  之后,我们在flink web ui 界面就可以看到并行度更新

5:UI界面上提交任务设置并行度

优先级:针对某一个算子单独设置 > 代码中全局设置 > 命令参数[UI界面] > flink-conf
package com.dashu.day02; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.java.tuple.Tuple; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.RestOptions; import org.apache.flink.streaming.api.datastream.DataStreamSink; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.KeyedStream; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; public class Flink01_par { // 该样例准备并行度。 public static void main(String[] args) throws Exception { // 环境准备 // StreamExecutionEnvironment evn = StreamExecutionEnvironment.getExecutionEnvironment(); // 这种方式是可以有WEB UI的,切换一个环境对象即可。 Configuration configuration = new Configuration(); configuration.set(RestOptions.PORT,8888); StreamExecutionEnvironment evn = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(configuration); // 在代码中以全局的方式设置并行度。 evn.setParallelism(3); // 从指定网络端口中读取数据 // socketDS并行度不论设置多少个,只有一个在干活。只能被一个消费。 // 但是不同的数据源是一样的。kafka就不是这样。因为kafka不是一个端口。 DataStreamSource<String> socketDS = evn.socketTextStream("192.168.67.137", 8888); // 对读取的数据进行扁平化处理--向下游传递的是一个一个单词 SingleOutputStreamOperator<String> flatMapDS = socketDS.flatMap(new FlatMapFunction<String, String>() { @Override public void flatMap(String s, Collector<String> out) throws Exception { String[] s1 = s.split(" "); for (String string : s1) { // 单词向下进行传递。 out.collect(string); } } }); // 对流中数据进行类型转换,把字符串转换成Tuple2<String,Long> SingleOutputStreamOperator<Tuple2<String, Long>> mapDS = flatMapDS.map(new MapFunction<String, Tuple2<String, Long>>() { @Override public Tuple2<String, Long> map(String s) throws Exception { return Tuple2.of(s, 1L); } }).setParallelism(4);//单独给某个算子制造并行度。 // 按照单词进行分组 KeyedStream<Tuple2<String, Long>, Tuple> keyDS = mapDS.keyBy(0); // 求和 SingleOutputStreamOperator<Tuple2<String, Long>> sumDS = keyDS.sum(1); // 打印 DataStreamSink<Tuple2<String, Long>> print = sumDS.print(); // 提交作业。 evn.execute(); } } 

2:算子链

1:算子链概念

        每一个算子有多个并行度的时候,每一个并行度就是一个子任务。每一个算子的子任务有肯能在不同的taskManager上执行。

        
        如果数据传输过程中相邻的算子之间,他们的并行度相同,那么一台机器上相连的算子就形成一个算子链。避免了不同机器之间的网络传输这是flink底层已经帮我们做好的。这是一个非常好的优化手段。

        
一个数据流在算子之间传输数据的形式可以是一对一(one-to-one)的直通模式,也可以是打乱的重分区模式,具体是哪一种形式,取决于算子的种类。

        一对一模式:

        这种模式下,数据流维护着分区以及元素的顺序。比如图中的source和map算子,source算子读取数据之后,可以直接发送给map算子做处理,它们之间不需要重新分区,也不需要调整数据的顺序。这就意味着map 算子的子任务,看到的元素个数和顺序跟source 算子的子任务产生的完全一样,保证着“一对一”的关系。map、filter、flatMap等算子都是这种one-to-one的对应关系。这种关系类似于Spark中的窄依赖。

        重分区模式:

        在这种模式下,数据流的分区会发生改变。比如图中的map和后面的keyBy/window算子之间,以及keyBy/window算子和Sink算子之间,都是这样的关系。



        每一个算子的子任务,会根据数据传输的策略,把数据发送到不同的下游目标任务。这些传输方式都会引起重分区的过程,这一过程类似于Spark中的shuffle。

2:合并算子链

        在Flink中,并行度相同的一对一(one to one)算子操作,可以直接链接在一起形成一个“大”的任务(task),这样原来的算子就成为了真正任务里的一部分,如下图所示。每个task会被一个线程执行。这样的技术被称为“算子链”

        算子链前提

        1:两个算子并行度想通

        2:one to one 不存在重新区和数据打乱重组。

算子链Flink提供的非常的优化手段,可以减少算子之间的网络。

3:算子链引用+禁用算子链

        flatmap和map之间并行度相同,所以合并成一个大任务,其中有三个子任务。后边sort会打乱分布,之后输出也不会重新打乱,并且分布生成,并且并行度相同,所以合并成一个大任务。
        有的时候虽然任务可以合并,但是因为数据量大,计算压力大,我们不想让他合并。应该怎么做?答案:禁用算子链

// 禁用算子链

.flatMap(word -> Tuple2.of(word, 1L)).disableChaining();



// 从当前算子开始新链

.flatMap(word -> Tuple2.of(word, 1L)).startNewChain()
 

//全局禁算子链evn.disableOperatorChaining();
public class Flink02_operatorchain { // 该样例准备并行度。 public static void main(String[] args) throws Exception { // 环境准备 // StreamExecutionEnvironment evn = StreamExecutionEnvironment.getExecutionEnvironment(); // 这种方式是可以有WEB UI的,切换一个环境对象即可。 Configuration configuration = new Configuration(); configuration.set(RestOptions.PORT,8888); StreamExecutionEnvironment evn = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(configuration); // 在代码中以全局的方式设置并行度。 evn.setParallelism(3); // 从指定网络端口中读取数据 // socketDS并行度不论设置多少个,只有一个在干活。只能被一个消费。 // 但是不同的数据源是一样的。kafka就不是这样。因为kafka不是一个端口。 DataStreamSource<String> socketDS = evn.socketTextStream("192.168.67.137", 8888); // 对读取的数据进行扁平化处理--向下游传递的是一个一个单词 SingleOutputStreamOperator<String> flatMapDS = socketDS.flatMap(new FlatMapFunction<String, String>() { @Override public void flatMap(String s, Collector<String> out) throws Exception { String[] s1 = s.split(" "); for (String string : s1) { // 单词向下进行传递。 out.collect(string); } } }).disableChaining(); // 对流中数据进行类型转换,把字符串转换成Tuple2<String,Long> 二元组。 SingleOutputStreamOperator<Tuple2<String, Long>> mapDS = flatMapDS.map(new MapFunction<String, Tuple2<String, Long>>() { @Override public Tuple2<String, Long> map(String s) throws Exception { return Tuple2.of(s, 1L); } }); // 按照单词进行分组 KeyedStream<Tuple2<String, Long>, Tuple> keyDS = mapDS.keyBy(0); // 求和 SingleOutputStreamOperator<Tuple2<String, Long>> sumDS = keyDS.sum(1); // 打印 DataStreamSink<Tuple2<String, Long>> print = sumDS.print(); // 提交作业。 evn.execute(); } } 

3:任务槽

        Flink中每一个TaskManager都是一个JVM进程,它可以启动多个独立的线程,来并行执行多个子任务

        很显然,TaskManager的计算资源是有限的,并行的任务越多,每个线程的资源就会越少。那一个TaskManager到底能并行处理多少个任务呢?为了控制并发量,我们需要在TaskManager上对每个任务运行所占用的资源做出明确的划分,这就是所谓的任务槽

        每个任务槽(task slot)其实表示了TaskManager拥有计算资源的一个固定大小的子集。这些资源就是用来独立执行一个子任务的。

        假如一个TakManager有三个slot,那么它会将管理的内存平均分成三份,每个slot独自占据一份。这样一来,我们在slot上执行一个子任务时,相当于划定了一块内存“专款专用”,就不需要跟来自其他作业的任务去竟争内存资源了。


        Flink的TaskManager负责运行任务,提供任务运行需要的资源。

        为了让TaskManager上的资源进行更加合理的分配

        TaskManager进程上,可以开启多个线程,每个线程称之为任务槽

        任务槽可以均分TaskManager上的资源。

        

        TaskManager的内存大小可以在配置文件和提交作业的时候指定。

        taskmanager.memory.process.size:1728mb

        默认情况下:TaskManager上面slot数量是1个。

        Slot的数量可以在配置文件以及提交参数中指定。

        taskmanager.numberOfTaskSlots: 1

        
        Slot是TaskManager资源分配最小单位。在实际开发中任务槽数量需要参考cpu核数一般slot数量和cpu核数关系1:1或者2:1

4:任务槽和并行度的关系

        为什么有两个Task但是就使用了一个任务槽 呢?

        接下来,我们在启动命令中设置并行度是2,这样就是有5个task,那么三个任务槽还够不够用?我们看一看。
        一共有5个task但是只使用了两个任务槽,这是为什么呢?

        这是Flink帮我们做了一些事情。
        Flink是允许子任务共享slot的。只要属于同一个作业,那么对于不同任务节点(算子)的并行子任务,就可以放到同一个slot上执行。

        所以对于第一个任务节点source→map,它的6个并行子任务必须分到不同的slot上,而第二个任务节点keyBy/window/apply的并行子任务却可以和第一个任务节点共享slot。

        当我们将资源密集型和非密集型的任务同时放到一个slot中,它们就可以自行分配对资源占用的比例,从而保证最重的活平均分配给所有的TaskManager



        slot共享另一个好处就是允许我们保存完整的作业管道。这样一来,即使某个TaskManager出现故障宕机,其他节点也可以完全不受影响,作业的任务可以继续执行。



        当然,Flink默认是允许slot共享的,如果希望某个算子对应的任务完全独占一个slot,或者只有某一部分算子共享slot,我们也可以通过设置“slot共享组”手动指定:

.map(word -> Tuple2.of(word, 1L)).slotSharingGroup("1");



        这样,只有属于同一个slot共享组的子任务,才会开启slot共享;不同组之间的任务是完全隔离的,必须分配到不同的slot上。在这种场景下,总共需要的slot数量,就是各个slot共享组最大并行度的总和。

三:作业提交流程

1:Standalone会话模式作业提交流程

2:逻辑流图/作业图/执行图/物理流图

逻辑流图(StreamGraph)

        这是根据用户通过 DataStream API编写的代码生成的最初的DAG图,用来表示程序的拓扑结构。这一步一般在客户端完成。



作业图(JobGraph)

        StreamGraph经过优化后生成的就是作业图(JobGraph),这是提交给 JobManager 的数据结构,确定了当前作业中所有任务的划分。主要的优化为:将多个符合条件的节点链接在一起合并成一个任务节点,形成算子链,这样可以减少数据交换的消耗。JobGraph一般也是在客户端生成的,在作业提交时传递给JobMaster。

        我们提交作业之后,打开Flink自带的Web UI,点击作业就能看到对应的作业图。



执行图(ExecutionGraph)

        JobMaster收到JobGraph后,会根据它来生成执行图(ExecutionGraph)。ExecutionGraph是JobGraph的并行化版本,是调度层最核心的数据结构。与JobGraph最大的区别就是按照并行度对并行子任务进行了拆分,并明确了任务间数据传输的方式。

4)物理图(Physical Graph)

        JobMaster生成执行图后,会将它分发给TaskManager;各个TaskManager会根据执行图部署任务,最终的物理执行过程也会形成一张“图”,一般就叫作物理图(Physical Graph)。这只是具体执行层面的图,并不是一个具体的数据结构。

物理图主要就是在执行图的基础上,进一步确定数据存放的位置和收发的具体方式。有了物理图,TaskManager就可以对传递来的数据进行处理计算了。

3:Yarn应用模式作业提交流程

Read more

一文详解llama.cpp:核心特性、技术原理到实用部署

目录 * 项目定位与核心特性:介绍llama.cpp是什么、核心设计哲学及主要特点。 * 核心架构与技术原理:分析其软件架构、GGML基础库、GGUF文件格式和量化技术。 * 环境部署与实践指南:提供安装部署的多种方式、基本运行方法和API服务配置。 * 进阶特性与扩展功能:介绍路由模式、工具调用、平台移植和企业级部署方案。 🎯 项目定位与核心特性 llama.cpp是一个用纯C/C++编写的开源大语言模型推理框架,最初为在本地运行Meta LLaMA模型而创建。它的核心设计哲学是极简、高效与可移植,旨在让大模型推理摆脱对GPU和复杂Python环境的依赖。 核心设计哲学 1. 极简与可移植性:纯C/C++实现意味着几乎零外部依赖,能在从云服务器到树莓派的各种设备上编译运行。 2. CPU优先优化:虽然后期加入了强大的GPU支持,但其初心是让LLM在普通CPU上高效运行,这使其在众多依赖GPU的框架中独树一帜。 3. 极致性能追求:通过底层硬件指令集优化和量化技术,实现在有限硬件上的惊人性能表现。 主要特点对比 特性维度llama.cpp典型Pyth

By Ne0inhk
Ollama Windows 安装与使用全指南:零配置本地运行 Llama、DeepSeek 等大模型,保障隐私与高效体验

Ollama Windows 安装与使用全指南:零配置本地运行 Llama、DeepSeek 等大模型,保障隐私与高效体验

Ollama Windows 安装与使用全指南:零配置本地运行 Llama、DeepSeek 等大模型,保障隐私与高效体验 * 🎯 核心摘要 * 一、环境准备与系统要求 * 二、安装 Ollama * 方法一:使用官方安装包(最简单,推荐新手) * 方法二:通过命令行安装(可选) * 三、基础使用:快速开始 * 1. 拉取并运行您的第一个模型 * 2. 常用模型管理命令 * 3. 模型选择建议 * 四、进阶应用 * 1. 使用 API 接口 * 2. 使用图形化界面(WebUI) * 五、常见问题与优化 🎯 核心摘要 Ollama 是一个开源工具,可让用户在 Windows 电脑上轻松运行 Llama、DeepSeek 等主流大语言模型。

By Ne0inhk

从 0 到 1:解决 VsCode 远程连服务器后 Github Copilot 无法使用问题

从 0 到 1:解决 VS Code 远程连服务器后 GitHub Copilot 无法使用问题 当您使用 VS Code 的远程功能(如 SSH 或容器)连接到服务器时,GitHub Copilot 可能无法正常工作,这通常是由于远程环境中的网络、扩展安装或身份验证问题导致的。我将一步步引导您解决这个问题,确保过程清晰可靠。请按照顺序操作,并测试每个步骤。 步骤 1: 确认本地 Copilot 正常工作 在开始远程连接前,先确保 Copilot 在您的本地 VS Code 中工作正常。 * 打开本地 VS Code。 * 创建一个新文件(如 test.py),输入一些代码(如 def

By Ne0inhk
HarmonyOS ArkUI 表冠事件(Digital Crown Event)全面解析与实战演示

HarmonyOS ArkUI 表冠事件(Digital Crown Event)全面解析与实战演示

文章目录 * 一、数字表冠核心概念 * 1.1 什么是数字表冠? * 1.2 表冠事件与其他输入事件对比 * 二、核心 API 详解 * 2.1 onDigitalCrown 事件接口 * 2.2 CrownEvent 完整结构 * 2.3 两种灵敏度字段详解 * 三、基础用法:表冠数据实时显示 * 3.1 可运行完整示例 * 四、列表滚动控制 * 4.1 表冠驱动列表滚动 * 五、数值调节控制器 * 5.1 音量/亮度旋钮 * 六、图片缩放控制 * 6.1 表冠驱动图片缩放 * 七、进度与时间选择器 * 7.1

By Ne0inhk