流处理架构
以Apache Flink为代表的第三代流处理引擎能够帮助用户实现有状态的流处理应用程序,先要搞清楚有状态和无状态计算指什么。
有状态 vs 无状态 计算
有状态计算指的是在计算过程中产生的中间计算结果,并且后续的计算过程需要使用,而无状态计算不会产生或存储中间计算结果,下一步计算也不会用到,像Spark或者Flink这些计算引擎会将用户应用程序转换成用有向无环图(DAG)表示的计算图,通常复杂的应用程序会由多步构成,比如Join、Group或者聚合运算,或者是以前缓存过的数据集,或者是机器学习上次模型训练参数,这些都是有状态计算,Flink对于这些中间结果的存储使用内存或本地存储提高性能和传输可靠性;而典型的如监控告警针对单一输入数据按规则处理后直接输出,这些输入无状态计算。简单说就是基于单个事件的计算是无状态计算,而计算需要多条记录的是有状态计算。
继续流处理架构说,一般企业的IT系统由业务系统和数据中心构成,业务系统从传统的以数据库为中心的架构设计逐渐演变到了微服务架构,业务数据也被称为事务性数据(Transactional Data)会散落到很多事务型数据库中,例如MySQL,很多的数据分析要多个不同的数据源数据才能完成,再加上后来的非结构化数据的应用,企业开始使用Hadoop等处理技术讲数据集中存储到HDFS中,利用sql-on-hadoop技术,如Spark、Impala统一分析,而将数据采集-清洗-分析的过程称为ETL,这种模式就是“批处理”。
“批处理”有一些明显的缺点:
- 延迟高,通常完成分析是小时级甚至是天级
- 数据有界,分析的是一批固定的数据集,实际情况却是数据在源源不断的产生
相比“批处理”,流处理引擎不需要周期性出发,对于持续的数据流,可以低延迟的合并计算更新结果
流处理架构到目前发展到了第三代,第一代关注低延迟,形成了Lambda体系架构(Batch layer、Speed layer、Serving layer数据可靠、准确、实时的分层处理架构,图1),第二代改进了故障保证,保证每天记录只对结果贡献一次,第三代修复了结果对事件到达的时间和顺序的依赖关系,与exactly-once failure语义结合是第一个能够计算一致和准确结果的开源流处理器,Flink属于第三代(图2)。
图1 Lambda架构
图2 Flink架构
Flink流处理
主要特性
- Flink提供了流处理、批处理、结构化数据处理、机器学习、CEP等多种数据处理方式,并且提供了层次化API,从底层到高级的API方便应用开发
- Flink实现了exactly-once state consistency语义
- Flink为常用的存储系统(如Apache Kafka、Apache Cassandra、ElasticSearch、JDBC和分布式文件系统(HDFS和S3))提供了连接器
- Flink支持事件时间event-time和处理事件processing-time语义
- 毫秒级延迟、每秒能够处理数百万个事件、应用可并行运行,不同于Spark Streaming的微批处理,当然Spark Streaming的Structred Streaming不是微批模式的,其与Flink类似
核心概念
时间处理
时间(Time)
Flink定义了三类时间,图3说明了3类时间产生的位置:
- 事件时间(Event Time):事件实际发生的时间,通常是数据中携带的业务时间。
- 处理时间(Processing Time):事件被处理的时间。
- 进入时间(Ingestion Time):事件进入流处理框架的时间。
图3 Flink定义的三类时间
事件时间和处理时间比较常用,可以在应用程序中显示的定义使用哪个时间处理事件,当采用事件时间时存在乱序的问题,可以通过水印WaterMark机制处理。
窗口(Window)
Flink中使用窗口对流式数据切分,有三种类型的窗口,很好理解,直接看图:
- 滚动窗口 (Tumbling Window, 无重叠)
- 滑动窗口 (Sliding Window, 有重叠)
- 会话窗口 (Session Window, 活动间隙)
图4 滚动窗口
图5 滑动窗口
图6 会话窗口
水印(WaterMarker)
先要搞明白水印是干嘛的,流式处理中数据按照时间顺序从上游系统实时的发送到Flink中进行处理,正常情况下前一个事件和后一个事件的事件时间是递增的,但是有可能因为网络等原因造成前一事件比后一事件晚到,这样会造成在这一窗口触发计算的时候漏过了这条信息,而水印的作用就是给窗口的计算时间一个缓冲期,让窗口的计算再延迟一些发生,延迟多少要由用户根据自己系统的经验判断,现在窗口的计算时间变成了窗口结束时间+水印时间,再比这个时间晚到的数据本次计算就不算了,可以选择丢弃或者再次进行计算并更新之前的窗口计算结果。借一个别的地方的图来说明比较形象。
- 消息无延迟到达
数据源第13秒产生2条消息,第16秒产生1条消息
- 消息延迟到达
由于网络阻塞,第13秒钟有一条消息延迟6s到达
- 无水印时运行聚合运算
窗口1发生错误,因为消息延迟到达,少计算了一个事件
- 添加水印后的聚合运算
水印设置为当前时间-5s,延迟的事件重新落入window1中,窗口2也没有问题。水印时间需要根据当前系统进行评估。
- 触发器
很好理解,定义何时启动应用程序处理窗口内数据,由于总会有迟到的数据,并且早到的数据会一直等待直到达到最大延迟,无法尽快看到结果,触发器可以解决水印带来的问题。触发器可以是周期性触发器或者定量触发。
一致性
数据一致性上包括三种语义:
- 最多一次(At-most-once):表示事物成功发送一次,处理是否成功不管,最多计算成功一次
- 至少一次(At-least-once):事件至少被处理一次,对于失败重发的事件可能重复计算,需要应用自己处理
- 精确一次(Exactly-once):无论是否发生失败,事件只会被处理一次。
Flink实现的是Exactly-once语义,采用了两阶段提交的方式,这个需要sink端也能够支持消息的事务处理(例如Kafka),在Operator计算借用了Checkpoint机制,所有并行算子的计算完成快照成功生成后向sink进行预提交,然后sink接收到这次请求后再进行sink端的提交,例如Kafka采用的是写入临时文件的方式,提交后再删除临时文件的方式,中间产生任何问题都可以用Checkpoint点进行回滚。借用下网上的图,还是很清晰的。
图6 预提交阶段
图7 提交阶段
算子
这部分如果学习过Spark的RDD算子那么很好理解,和Spark类似,Flink的算子包括:
- Map、Flatmap
- Filter
- KeyBy、Reduce
- fold
- join、coGroup
- connect、split
- SortPartition
- Distinct
- Union
- 广播变量、文件缓存
检查点(Checkpoint)
检查点是Flink实现Exactly-Once语义的重要保障,Checkpoint可以定期的将Operator的数据进行快照存储,Checkpoint会定期的生成barriers插入到数据流中随数据一起传播,在多输入流的场景中由于barriers到达的时间可能不同,所以需要对齐,barriers是广播的,对于没有到达的数据流的barriers当前数据流的数据会被缓存,等到所有barriers都到达时再存储为快照,存储下来的快照称为State
连接器(Connector)
Source和Sink连接外部数据源的组件称为连接器(Connector)。Flink内置了很多连接器,对一致性语义的支持不同,见下表:
- Source
Apache Kafka :Exactly-once
RabbitMQ:Exactly-once
Collections:Exactly-once
Files:Exactly-once
Sockets:At-most-once
- Sink
HDFS:Exactly-once
ElasticSearch:At-least-once
Kafka:Exactly-once
Cassandra:Exactly-once
Files:At-least-once
Sockets:At-least-once
Standard Output:At-least-once
Redis:At-least-once
总结
以上内容对Flink流程处重要内容进行了简单的梳理,结合资料和自己的理解概括说明下,后续计划对这些部分展开详细的说明,希望这篇文章可以帮助理解流式处理的原理,如果有问题请多多包涵,也欢迎探讨!