gitbook/零基础入门Spark/docs/447514.md

166 lines
14 KiB
Markdown
Raw Permalink Normal View History

2022-09-03 22:05:03 +08:00
# 31新一代流处理框架Batch mode和Continuous mode哪家强
你好,我是吴磊。
在上一讲我们通过“流动的Word Count”示例初步结识了Structured Streaming并学习了流处理开发三要素也就是Source、流处理引擎与Sink。
![图片](https://static001.geekbang.org/resource/image/35/5a/35cd34dfa43a3a9c52f538e002e5905a.jpg?wh=1920x562)
今天这一讲让我们把目光集中到Structured Streaming也就是流处理引擎本身。Structured Streaming与Spark MLlib并列是Spark重要的子框架之一。值得一提的是Structured Streaming天然能够享受Spark SQL提供的处理能力与执行性能同时也能与其他子框架无缝衔接。因此基于Structured Streaming这个新一代框架开发的流处理应用天然具备优良的执行性能与良好的扩展性。
知己知彼百战百胜。想要灵活应对不同的实时计算需求我们就要先了解Structured Streaming的计算模型长啥样搞清楚它如何应对容错、保持数据一致性。我们先从计算模型说起。
## 计算模型
当数据像水流一样源源不断地流进Structured Streaming引擎的时候引擎并不会自动地依次消费并处理这些数据它需要一种叫做Trigger的机制来触发数据在引擎中的计算。
换句话说Trigger机制决定了引擎在什么时候、以怎样的方式和频率去处理接收到的数据流。Structured Streaming支持4种Trigger如下表所示。
![图片](https://static001.geekbang.org/resource/image/c2/23/c2b93ed2e7fbd9c7157443605b691f23.jpg?wh=1920x635 "Structured Streaming支持的4种Trigger")
要为流处理设置Trigger我们只需基于writeStream API调用trigger函数即可。Trigger的种类比较多一下子深入细节容易让你难以把握重点所以现在你只需要知道Structured Streaming支持种类繁多的Trigger即可。
我们先把注意力放在计算模型上面。对于流数据Structured Streaming支持两种计算模型分别是Batch mode和Continuous mode。**所谓计算模型本质上它要解决的问题就是Spark以怎样的方式来对待并处理流数据**。
这是什么意思呢没有对比就没有鉴别咱们不妨通过对比讲解Batch mode和Continuous mode来深入理解计算模型的含义。
### Batch mode
我们先来说说Batch mode所谓Batch mode它指的是Spark将连续的数据流切割为离散的数据微批Micro-batch也即小份的数据集。
形象一点说Batch mode就像是“抽刀断水”两刀之间的水量就是一个Micro-batch。而每一份Micro-batch都会触发一个Spark Job每一个Job会包含若干个Tasks。学习过基础知识与Spark SQL模块之后我们知道这些Tasks最终会交由Spark SQL与Spark Core去做优化与执行。
![图片](https://static001.geekbang.org/resource/image/0d/75/0d452a6c679f2d81299da9d9d0a52075.jpg?wh=1920x1035 "计算模型Batch mode")
在这样的计算模型下不同种类的Trigger如Default、Fixed interval以及One-time无非是在以不同的方式控制Micro-batch切割的粒度罢了。
比方说在Default Trigger下Spark会根据数据流的流入速率自行决定切割粒度无需开发者关心。而如果开发者想要对切割粒度进行人为的干预则可以使用Fixed interval Trigger来明确定义Micro-batch切割的时间周期。例如Trigger.ProcessingTime(“5 seconds”)表示的是每隔5秒钟切割一个Micro-batch。
### Continuous mode
与Batch mode不同Continuous mode并不切割数据流而是以事件/消息Event / Message为粒度用连续的方式来处理数据。这里的事件或是消息指代的是原始数据流中最细粒度的数据形式它可以是一个单词、一行文本或是一个画面帧。
以“流动的Word Count”为例Source中的事件/消息就是一个个英文单词。说到这里你可能会有疑问“在Batch mode下Structured Streaming不也是连续地创建Micro-batch吗数据同样是不丢不漏Continuous mode与Batch mode有什么本质上的区别吗
![图片](https://static001.geekbang.org/resource/image/cd/4e/cdd3867109596000f30f0fc0881b934e.jpg?wh=1920x1091 "计算模型Continuous mode")
一图胜千言对比两种计算模型的示意图我们可以轻松地发现它们之间的差异所在。在Continuous mode下Structured Streaming使用一个常驻作业Long running job来处理数据流或者说服务中的每一条消息。
那么问题来了相比每个Micro-batch触发一个作业Continuous mode选择采用常驻作业来进行服务有什么特别的收益吗或者换句话说这两种不同的计算模型各自都有哪些优劣势呢
用一句话来概括,**Batch mode吞吐量大、延迟高秒级而Continuous mode吞吐量低、延迟也更低毫秒级**。吞吐量指的是单位时间引擎处理的消息数量批量数据能够更好地利用Spark分布式计算引擎的优势因此Batch mode在吞吐量自然更胜一筹。
而要回答为什么Continuous mode能够在延迟方面表现得更加出色我们还得从Structured Streaming的容错机制说起。
## 容错机制
对于任何一个流处理引擎来说,容错都是一项必备的能力。所谓容错,它指的是,在计算过程中出现错误(作业层面、或是任务层面,等等)的时候,流处理引擎有能力恢复被中断的计算过程,同时保证数据上的不重不漏,也即保证数据处理的一致性。
从数据一致性的角度出发这种容错的能力可以划分为3种水平
* At most once最多交付一次数据存在丢失的风险
* At least once最少交付一次数据存在重复的可能
* Exactly once交付且仅交付一次数据不重不漏。
![图片](https://static001.geekbang.org/resource/image/35/5a/35cd34dfa43a3a9c52f538e002e5905a.jpg?wh=1920x562 "流处理3要素")
这里的交付指的是数据从Source到Sink的整个过程。对于同一条数据它可能会被引擎处理一次或在有作业或是任务失败的情况下多次但根据容错能力的不同计算结果最终可能会交付给Sink零次、一次或是多次。
聊完基本的容错概念之后我们再说回Structured Streaming。就Structured Streaming的容错能力来说Spark社区官方的说法是“结合幂等的SinkStructured Streaming能够提供Exactly once的容错能力”。
实际上这句话应该拆解为两部分。在数据处理上结合容错机制Structured Streaming本身能够提供“At least once”的处理能力。而结合幂等的SinkStructured Streaming可以实现端到端的“Exactly once”容错水平。
比方说应用广泛的Kafka在Producer级别提供跨会话、跨分区的幂等性。结合Kafka这样的Sink在端到端的处理过程中Structured Streaming可以实现“Exactly once”保证数据的不重不漏。
不过,在 Structured Streaming 自身的容错机制中为了在数据处理上做到“At least once”Batch mode 与 Continuous mode 这两种不同的计算模型,分别采用了不同的实现方式。而容错实现的不同,正是导致两种计算模型在延迟方面差异巨大的重要因素之一。
接下来我们就来说一说Batch mode 与 Continuous mode 分别如何做容错。
### Batch mode容错
在Batch mode下Structured Streaming利用Checkpoint机制来实现容错。在实际处理数据流中的Micro-batch之前Checkpoint机制会把该Micro-batch的元信息全部存储到开发者指定的文件系统路径比如HDFS或是Amazon S3。这样一来当出现作业或是任务失败时引擎只需要读取这些事先记录好的元信息就可以恢复数据流的“断点续传”。
要指定Checkpoint目录只需要在writeStream API的option选项中配置checkpointLocation即可。我们以上一讲的“流动的Word Count”为例代码只需要做如下修改即可。
```scala
df.writeStream
// 指定Sink为终端Console
.format("console")
 
// 指定输出选项
.option("truncate", false)
 
// 指定Checkpoint存储地址
.option("checkpointLocation", "path/to/HDFS")
 
// 指定输出模式
.outputMode("complete")
//.outputMode("update")
 
// 启动流处理应用
.start()
// 等待中断指令
.awaitTermination()
```
在Checkpoint存储目录下有几个子目录分别是offsets、sources、commits和state它们所存储的内容就是各个Micro-batch的元信息日志。对于不同子目录所记录的实际内容我把它们整理到了下面的图解中供你随时参考。
![](https://static001.geekbang.org/resource/image/44/84/440cc046557de572d598f1d4c415yy84.jpg?wh=7266x2515 "Checkpoint与WAL日志")
对于每一个Micro-batch来说在它被Structured Streaming引擎实际处理之前Checkpoint机制会先把它的元信息记录到日志文件因此这些日志文件又被称为Write Ahead LogWAL日志
换句话说当源数据流进Source之后它需要先到Checkpoint目录下进行“报道”然后才会被Structured Streaming引擎处理。毫无疑问“报道”这一步耽搁了端到端的处理延迟如下图所示。
![图片](https://static001.geekbang.org/resource/image/8b/a3/8b96555e6ff67550fe007dacccd849a3.jpg?wh=1920x859 "Batch mode下端到端延迟示意图")
除此之外由于每个Micro-batch都会触发一个Spark作业我们知道作业与任务的频繁调度会引入计算开销因此也会带来不同程度的延迟。在运行模式与容错机制的双重加持下Batch mode的延迟水平往往维持在秒这个量级在最好的情况下能达到几百毫秒左右。
### Continuous mode容错
相比Batch modeContinuous mode下的容错没那么复杂。在Continuous mode下Structured Streaming利用Epoch Marker机制来实现容错。
因为Continuous mode天然没有微批所以不会涉及到微批中的延迟到达Source中的消息可以立即被Structured Streaming引擎消费并处理。但这同时也带来一个问题那就是引擎如何把当前的处理进度做持久化从而为失败重试提供可能。
为了解决这个问题Spark引入了Epoch Marker机制。所谓Epoch Marker你可以把它理解成是水流中的“游标”这些“游标”随着水流一起流动。每个游标都是一个Epoch Marker而游标与游标之间的水量就是一个Epoch开发者可以通过如下语句来指定Epoch间隔。
```scala
writeStream.trigger(continuous = "1 second")
```
以表格中的代码为例对于Source中的数据流Structured Streaming每隔1秒就会安插一个Epoch Marker而两个Epoch Marker之间的数据就称为一个Epoch。你可能会问“Epoch Marker的概念倒是不难理解不过它有什么用呢
在引擎处理并交付数据的过程中每当遇到Epoch Marker的时候引擎都会把对应Epoch中最后一条消息的Offset写入日志从而实现容错。需要指出的是日志的写入是异步的因此这个过程不会对数据的处理造成延迟。
有意思的是对于这个日志的称呼网上往往也把它叫作Write Ahead Log。不过我觉得这么叫可能不太妥当原因在于准备写入日志的消息都已经被引擎消费并处理过了。Batch mode会先写日志、后处理数据而Continuous mode不一样它是先处理数据、然后再写日志。所以把Continuous mode的日志称作是“Write After Log”也许更合适一些。
我们还是用对比的方法来加深理解接下来我们同样通过消息到达Source与Structured Streaming引擎的时间线来示意Continuous mode下的处理延迟。
![图片](https://static001.geekbang.org/resource/image/3f/91/3f1ed32039435d657f3313a1ba9d1891.jpg?wh=1920x848 "Continuous mode下端到端延迟示意图")
可以看到消息从Source产生之后可以立即被Structured Streaming引擎消费并处理因而在延迟性方面能够得到更好的保障。而Epoch Marker则会帮助引擎识别当前最新处理的消息从而把相应的Offset记录到日志中以备失败重试。
## 重点回顾
到此为止,今天的内容就全部讲完了,我们一起来做个总结。
今天这一讲我们学习了Structured Streaming中两种不同的计算模型——Batch mode与Continuous mode。只有了解了它们各自在吞吐量、延迟性和容错等方面的特点在面对日常工作中不同的流计算场景时我们才能更好地做出选择。
在Batch mode下Structured Streaming会将数据流切割为一个个的Micro-batch。对于每一个Micro-batch引擎都会创建一个与之对应的作业并将作业交付给Spark SQL与Spark Core付诸优化与执行。
Batch mode的特点是吞吐量大但是端到端的延迟也比较高延迟往往维持在秒的量级。Batch mode的高延迟一方面来自作业调度本身一方面来自它的容错机制也就是Checkpoint机制需要预写WALWrite Ahead Log日志。
要想获得更低的处理延迟你可以采用Structured Streaming的Continuous mode计算模型。在Continuous mode下引擎会创建一个Long running job来负责消费并服务来自Source的所有消息。
在这种情况下Continuous mode天然地避开了频繁生成、调度作业而引入的计算开销。与此同时利用Epoch Marker通过先处理数据、后记录日志的方式Continuous mode进一步消除了容错带来的延迟影响。
尺有所短、寸有所长Batch mode在吞吐量上更胜一筹而Continuous mode在延迟性方面则能达到毫秒级。
不过需要特别指出的是到目前为止在Continuous mode下Structured Streaming仅支持非聚合Aggregation类操作比如map、filter、flatMap等等。而**聚合类的操作比如“流动的Word Count”中的分组计数Continuous mode暂时是不支持的这一点难免会限制Continuous mode的应用范围需要你特别注意**。
## 每课一练
Batch mode通过预写WAL日志来实现容错请你脑洞一下有没有可能参考Continuous mode中先处理数据、后记录日志的方式把Batch mode中写日志的动作也挪到数据消费与处理之后呢
欢迎你在留言区跟我交流讨论,也推荐你把这一讲的内容分享给更多朋友。