浅谈流式计算

大部分和大数据有过交集的工程师,应该都对流式计算(Streaming Processing)有所了解。 我对流式计算接触有限,只是在2016年的时候写过几个Spark Streaming的Job,以及测试过Apache Flink的一些基础用法。 好在计算机从业者的思维是一层一层的抽象,虽然抽象的方向不一样,但是不同的技术之间总是有很多共通的地方。 这里我把所接触到的和流式计算相关的零碎信息,抽象成构建Streaming Pipeline需要思考的三个问题。 如果把流式计算所要处理的Event/Message/Record比作水,这三个问题可以描述为水的顺流、汇集和回流。

顺流

对于批处理(Batch Processing),输入的大小是很好估算的。 批处理的输入就像是一桶水,在处理这桶水之前,你是知道这桶水有多大的,至少可以知道这个桶多大。 甚至如果比较幸运的话,桶上还会贴着记录密度、各类物质的浓度等的标签。 流式计算的输入是一个水流,水的流速是不稳定的。 除了一些先验性的知识和历史的统计外,程序本身是很难知道未来一段时间水的流速的。

这种流速的不确定性,需要工程师去考虑两个问题:弹性伸缩动态切片。 不同的流速需要的资源是不一样的,如何根据流速去动态的调整资源是一个很大的挑战,一个可能的思路是把需要资源多的操作依赖于可伸缩的外部系统去实现。 另外,有时候我们希望流式处理的每个并发任务所处理的输入大小是比较固定的,这种情况下流速增大的话就需要提高并发度。 在某一个节点增加并发度,就意味着上游节点需要改变原有的切片数和切片方式。

现阶段大多数情况下这两个问题很少被考虑到,主要有二个原因。 其一、我猜想很多情况下大家还是会把资源调节到能够处理最大流速;其二、很多流式处理是用来做数据分析的,如果处理有延迟,或需要改一下参数重启,都是可以接受的。

汇集

流式计算的一个需求是把数据汇集到有限的数据集中,就像是把源源不断的水流收集到各个桶中。 其中的一个原因是数据分析的需求,例如很多时候,人们关心的是“过去五分钟一共有多少次点击”、“过去一分钟最大的一笔订单是多少”之类的问题。 这种需求就是要对一个有限的数据集进行统计分析。 而从技术出发,像排序、聚合等数据操作,也都只是在有限的数据集上清晰的定义和成熟的算法。

这种有限的数据集也被称作是窗口。窗口可以从多个纬度定义,最常见的一个纬度就是时间,而从时间定义窗口时,需要考虑选择事件时间(Event Time)还是处理时间(Process Time)。 另外一个常用的纬度就是用户标志,它通常和时间组合在一起,用来定义会话(Session)这样的窗口。

事件的延迟乱序会给我们汇集数据带来很大的挑战,这两个问题本身都不好解决。即便在非常理想的应用场景下,由于网络原因,数据的延迟仍然不可避免和无法估计。 事件的保序更是分布式系统中的最大挑战之一。 所以作为流式计算的设计者,更应该考虑的问题是怎么去得到一个近似值,什么时候去关闭并提交一个窗口,什么的情况下值得我们重新提交一个窗口。

回流

流式计算如何容错。容错的本质在于记录状态从状态中恢复。记录和存储一致的状态相信有很多解决方案了,有些流式处理的框架也原生支持。 如何设计这些状态以及如何从不同的状态中恢复,是需要考虑的核心问题。

我们可以认为流式计算的各个节点都是一个Operation, 而Source和Sink是特殊的Operation。 当尝试从状态中恢复时,Operation需要知道应该采取哪些措施,这些措施通常是删除部分未全局提交的数据,或者重新发送一些事件,这种行为就好比是回流

很多时候,读和写的地方都不支持“回流”,这就需要在应用端定义Source和Sink的Operation时去弥补,比如去缓存部分数据,或者是去把加过去的值再减回来。