流式计算与Flink架构介绍|青训营笔记

448 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的第1天。

一、为什么选择Flink进行流式计算

1、大数据解决方案发展史

1)Hadoop:分布式、Map-Reduce、离线计算

2)Spark:批处理、流处理(Spark Streaming 解决方案:把数据切成微批,比如每2秒一个批)、SQL高阶API、内存迭代计算

3)Flink:流计算、实时、流批一体、Streaming(流)/Batch(批) SQL

2、为什么需要流式计算

大数据的实时性可以给一些业务带来更大的价值,比如根据用户行为数据实时推荐内容、监测业务系统的健康状态、监测异常交易行为。

批式计算可以简单理解为“需要等一批数据到齐之后,才开始处理”。由于批式计算不是实时计算,因此也称它为“离线计算”。批式计算处理的数据集是静态数据集。计算时长以小时、天为单位。

由于大数据实时性的需求,大数据计算架构模式也从批式计算转变为流式计算。

流式计算可以理解为数据(水)通过水管源源不断流向水龙头,在水管中先对数据进行基本的处理,处理后的数据流向业务下游,直到业务处理完成。数据源不断更新,流式计算作业24小时不断运行。

3、为什么选择Flink

Everything is a stream. 对于有边界的数据集(比如批数据集)、无边界的数据集(比如消息队列)都能在Flink里被抽象成流。得益于这种底层抽象,使得Flink能够处理各种数据。而Flink的SQL支持,能够促进普及使用,降低业务维护成本。

Flink能够保证每个数据被处理一次,延迟性在毫秒级(Spark Streaming延迟在秒级),能够吞吐的数据量大,提供了内置的对于状态一致性的处理,重启后可以完整恢复中间状态。

1)流式计算框架对比

image-20220727104442764.png

2)Flink社区的开源生态

Flink的开源生态优秀。Flink是个计算框架,本身没有数据存储的能力,但是能够和大多数数据存储引擎(消息队列:Kafka、RocketMQ、RabbitMQ;Key-Value型:Redis;离线数据源:HDFS、Hive;OLAP相关:Clikhouse)进行比较好的集成。

image-20220727104830562.png

二、Flink架构

1、Flink的分层架构

1)SDK层

①SQL相关的API ②DataStream(JAVA)相关的API ③Python相关的API

2)执行引擎层(Runtime层)

①翻译:将SDK层的描述翻译为DAG(有向无环图),用于描述数据处理的逻辑。

②调度:将DAG转化为分布式环境下的TASK,将TASK分配到不同的worker节点(也叫TaskManager,TM)执行,TASK之间通过Shuffle Service 进行数据交换

3)状态存储层

负责存储算子的状态信息。如果有状态相关的处理,可将状态数据存储在Flink State Backend里。

4)资源管理层

支持将Flink部署在不同环境中,如Linux、Windows、K8S、Yarn...

image-20220727105039644.png

2、Flink整体架构

1)一个Flink集群,主要包含JM、TM两个核心组件

①JobManager(JM):负责整个任务的协调工作,包括:调度Task;触发协调Tsak做Checkpoint;协调容错恢复...

②TaskManager(TM):负责执行一个DataFlow Graph的各个Task以及data streams的buffer(缓冲)和数据交换

image-20220727104939944.png

代码写在Client端,将用户代码转换为Dataflow graph(可理解为DAG逻辑执行图),传给JobManager。JM将逻辑执行图转化为物理执行图,并将物理执行图相关的Task分配到TM端。

2)JobManger的架构与职责

image-20220727111147997.png

Dispatcher接收Client端提交的job,为这些job拉起JobMaster,如果JobMaster挂掉之后,Dispatcher可以恢复作业。

JobMaster负责管理一个job的整个生命周期,会向ResourceManager申请slot(插槽,一个插槽能放定量的task)。

ResourceManager负责slot资源的管理和调度。收到JobMaster的slot申请之后,会调用(假设运行在K8S上)K8S的API去拉起一些资源(TM)。

TM被拉起后,会向ResourceManager发起注册。JobMaster收到ResourceManager批准的资源后,把Task部署在对应的worker节点(也叫TM)上。

3、Flink作业示例

流式的wordCount:从kafka中读取一个实时数据流,每10秒统计一次单词出现次数,输出到下游系统里。DataStream实现代码如下:

/*Source*/
DataStream<String> lines = env.addSource(new FlinkKafkaConsumer<>(...));
/*
1、如果想要创建一个Flink作业,必须先声明环境变量env。比如我想从卡夫卡中读一个数据源,所以用“env.addSource()”。
2、FlinkKafkaConsumer是用户使用Kafka作为Source进行编程的入口,用户可以指明卡夫卡的地址/topic是哪个,它就会从卡夫卡中读取(也叫消费)相关的数据。
3、DataStream<String>的“String”意味着处理后的格式是String。
*//*Transformation解析*/
DataStream<Event> envents = lines.map((line) -> parse(line));
/*
1、map代表对数据集进行一对一的解析处理
2、map((line) -> parse(line))代表每读一行,就对这行调用parse
3、parse可以按空格拆分单词
4、<Event>:单词拆分为Event的格式
*//*Transformation*/
DataStream<Statistics> stats = events
    keyBy(event -> event.id)//逻辑地将一个流拆分成不相连的分区,每个分区包含相同key的元素。在内部通过哈希分区实现的。
    timeWindow(Time.seconds(10))//设计了一个10s的窗口
    apply(new MyWindowAggregationFunction());//实现统计单词频次的功能/*Sink*/
stats.addSink(new BucketingSink(path));//把数据写入下游系统

1)业务逻辑图(condensed view)

image-20220727155851113.png

2)业务执行图(parallelized view)

假设sink算子的并发配置为1,其余算子的并发为2。

map后的一个单词需要使用keyBy去做哈希,根据单词对应的哈希值,有可能传到keyBy[1]或者keyBy[2]。

3)业务执行优化

为了更高效地分布式执行,Flink会尽可能地将不同的operator链接(chain)在一起形成Task。这样每个Task可以在一个线程中执行,内部叫做OperatorChain,如下图的source和map算子可以chain在一起。

image-20220727193456558.png

比如Source[1]之后可以直接做map[1],不需要像keyBy一样在[1]、[2]间进行hash shuffle,因此可以将Source和map链接(chain)在一起。也就是说,在物理执行上,Source[1]读完数据后,map[1]紧接着做解析,在一个线程中串行执行。

相比原本Source[1]一个线程、map[1]一个线程,减少了线程的切换、数据的序列化反序列化操作。如果结合Flink执行机制的话,chain能够减少TM在数据缓冲区的交换,减少了延迟。

image-20220727200720915.png

最后将Task调度到具体TM中的slot执行,一个slot只能运行同一个Task的subTask(比如Source和map是subtask)。

一个TM(可以理解为一个进程)里有几个slot(可以理解为几个线程)是用户可以自定义的,每个slot是一个单独的线程(Thread)在执行。

三、个人总结

重点需要了解:

1、数据流是如何在Flink 客户端、JobManager、TaskManager之间流动的

2、JobManager、TaskManager的架构

3、OperatorChain的优点