随着信息技术的快速发展,计算机的存储、网络数据传递速度和计算能力都得到了长足的进步。在大数据、5G和物联网等高新技术的加持下,逐步兴起了一种新的密集型流数据应用。
流数据一般具有如下特点:
数据连续,实时产生,无结束边界。
数据本身可以携带时间标签。
数据到达顺序可能和产生时间不一致。
数据量大,数据规模可以达亿级别。
数据二次处理代价高昂,不存储全量数据。
由于流数据的以上特征,基于传统的批数据处理模式不太适合流计算场景。如果将传感器的数据先保存到数据库中,再利用SQL进行数据分析,这个时效性就比较差。另外,如果将全部数据都进行持久化存储,那么需要的存储空间将非常大,成本也会非常高。
一般来说,流处理应用使用延迟和吞吐量这两个指标来表示性能水平。其中延迟表示处理事件所需的时间。而吞吐量是衡量流处理应用计算能力的指标,它代表每个单位时间里,流处理应用最大可以处理事件的数量。
在流处理应用中,通过分布式并行计算,来完成低延迟和高吞吐二者之间的平衡。对于一个流计算系统来说,一般具备如下的特征:
延迟低,几毫秒到几秒之间。
高吞吐,可以处理大量的事件数据。
分布式,可以动态扩容。
可靠性,计算过程状态可保存,可从故障中恢复。
为了解决流数据计算的若干问题,必须提出一套可行的流处理模型。业界中,把Google公司的Dataflow模型比作现代流数据计算的基石。Google公司在2015年发表了一篇关于Dataflow模型的论文“The dataflow model: a practical approach to balancing correctness, latency, and cost in massive-scale, unbounded, out-of-order data processing”,它提供了一种统一流处理和批处理的系统框架。
Dataflow模型对于无序的流数据,提供了一套基于事件时间(Event Time)、水位线(Watermark)和延迟处理的机制,从而实现窗口(Window)聚合计算的能力,以实现流数据计算的正确性、高吞吐和延迟这三者之间的平衡。
由于很多系统都是分布式部署的,各个系统之间的数据通过网络进行传输,那么数据在采集和传输过程中,不可避免会产生数据乱序和延迟到达的情况。换句话说,流处理系统在对数据流进行处理时,其接收到的数据次序很有可能与数据产生的原始次序不同。
举例来说,假设张三、李四和王五,这3人从徐州出差到北京,张三早上7点50分从公司出发,坐K打头的火车去北京;李四8点30分从公司出发,坐G打头的高铁去北京;而王五9点15分从公司出发,坐飞机去北京。那么实际到达北京的可能为顺序为:王五、李四、张三。
因此,正确和高效地对乱序流数据进行处理,才能保证整个流数据计算的正确性。在流数据计算领域,关于时间有两个非常重要的概念:
(1)事件时间(Event Time)
数据产生时从原设备获取的时间戳,比如传感器产生的气体浓度数据,事件时间则是传感器记录某一个数据瞬间的时间戳。用事件时间作为时间属性的好处是同样的数据输入,多次运行的结果是一致的。
(2)处理时间(Processing Time)
流数据中某个事件被流处理程序处理时所记录的时间戳。由于流数据场景下,产生数据的设备和处理数据的设备可能是分布式的,因此不同设备的时间应该进行同步。通常情况下,处理时间比事件时间晚一些,用处理时间作为时间属性会导致同样的数据输入,多次运行的结果是不一致的。
Dataflow模型在无界流数据处理过程中,对重点考虑的4个难题给出了有效的解决思路:
(1)需要产出什么结果
这个要根据实际业务需求,用户自行进行设计和实现。由于这部分流处理框架不能提前预置,但需要提供良好的编程接口,以实现灵活的数据处理自定义功能。
(2)计算什么时间的数据
窗口模型(Window Model)实现基于时间属性对数据进行窗口操作的目的。它可以将无界的数据按照时间属性划分为一个一个有限的数据集合,从而实现在窗口中对有限数据进行分组和聚合等操作。
(3)在什么时候触发计算
触发模型(Trigger Model)能够将数据结果与事件的时间属性或事件数量进行关联,解决了作业应该在什么时候触发的问题。另外,可以结合水位线来解决事件数据乱序到达带来的计算问题。
水位线从本质上来说,也是一个时间戳。按照约定,水位线T就表示窗口已经接收到所有t <=T的数据。其他t > T的数据都将被视为迟到,而对于迟到数据的处理,则需要采用增量更新模型。
注意,水位线T的确定是一个难题,另外单靠水位线机制也不能确保100%可靠。
增量更新模型支持不同的策略:
(1)丢弃
当窗口已经触发计算后,就不会继续存储窗口当中的数据,所有超过水位线T的迟到数据将直接丢弃。
(2)累积
当窗口已经触发计算后,会在一定时间内继续存储窗口当中的数据,超过水位线T的迟到数据在该时间内仍然可以进入窗口进行处理。这个额外的等待时间就是允许迟到时间(Allowed Lateness)。
(3)累积与回撤
在累积策略的基础上,可以对上一次窗口操作的结果进行回撤修改,再输出新的计算结果。对于某些下游操作而言,如果不进行撤回修正的话,当前窗口的计算结果可能就不正确。
在流处理应用系统中,一个流计算作业的内部计算过程可以用数据流图进行描述。它给出了流数据如何在不同算子之间进行流转的示意,通常表示为一个具有流转方向的有向无环图(DAG)。
数据流图中,有数据源、数据处理算子和数据输出。其中图中的节点称为算子,连接不同节点的线代表数据之间的依赖性,也给出了数据流转的方向。算子是流处理应用当中最基本的功能单元,代表相关的业务处理逻辑。
数据流图有逻辑数据流图和物理数据流图之分。以大数据领域常见的单词计数(Word Count)为例,下面给出一个逻辑数据流图示意图。
逻辑数据流图一般以一种更加简练和宏观的角度来对流数据处理过程进行描述。它往往并不完全代表实际的物理执行情况。对于一个分布式流处理引擎来说,它会将逻辑数据流图转换为物理数据流图,来调度内部任务的执行。
在逻辑数据流图中节点表示算子,同一个算子可以有多个并行实例来实现并发计算。在物理数据流图中,节点是任务。其中的拆分、映射和分组求和算子有两个并行算子实例(任务),每个算子实例对输入数据的部分数据进行处理。下面是物理数据流图示意图。
分布式流处理引擎为了实现低延迟和高吞吐计算,会在数据并行和计算并行这两个维度进行优化。其中的数据并行需要将数据按照一定的规则拆分成不同的分区数据,不同分区的数据可以在不同的计算机上进行处理,这样就可以实现高吞吐量。
另外,在内存中对数据进行并行计算,同时利用当前计算机的多核特征,可以更好地利用计算机资源,实现数据处理的低延迟。
这就类似于一个工程项目,通过制定项目任务甘特图,可以将项目划分为不同的子任务,每个子任务虽然有前后关系,但是有些子任务是可以并行处理的,通过安排足够的资源(人、财和物)实现任务并行,就可以降低整个项目的工期。
注意,数据的横向拆分一般通过Key来实现分区,而纵向的拆分通过窗口机制来实现。
最后介绍一下物理数据流图中的数据分配策略,它表示一个流计算引擎如何将流数据分配到不同的任务节点上执行。
常见的数据分配策略如下:
(1)转发分配策略
这种数据分配策略将流数据从一个任务节点直接分配到下一个任务节点。为了提升数据流转效率,流处理引擎会自动将同一台物理计算机上,不同两个任务之间的数据交换方式采用前向分配策略,从而避免网络通信。
(2)基于Key分配策略
对于数据分区来说,最常用的就是按照Key进行数据分配,使用这种策略对数据进行分区,能够保证同一Key的数据由同一任务进行计算。
(3)随机分配策略
随机策略将数据随机的分配到下游的并行任务中去,以实现负载均衡的目的,从而充分利用集群中的不同节点进行数据并行处理。
(4)广播分配策略
这种数据分配策略往往会涉及网络数据传输和数据拷贝,延迟相对比较大,因此代价比较高。它会将上一个任务节点中的所有数据,发送到下一个算子中所有并行的任务节点上。
流处理的本质就是一种高效的增量数据处理机制,流处理系统可以在每接收到一个事件数据后,就进行逻辑处理。
一个流处理应用也会包含如下3个部分:
(1)流数据源
流数据源是一个与外部系统进行交互的接口,它可以从外部系统获取到原始的数据。流数据源种类繁多,比如HDFS文件系统或数据库。
(2)流数据转换
从数据源获取流数据后,内部就需要根据业务逻辑对数据流进行转换操作。一般来说,这些转换会将一个输入数据流转换成一个新的数据流。下面是几种常见的流数据转换示意图。
另外,还有一种转换操作为滚动聚合,比如计算流数据当中的某个字段的和。聚合操作是有状态(State)的,每次迭代计算时,都需要将上一次累积的计算中间值和当前的值进行计算,并更新上一次的聚合值。下面是求和聚合流数据转换示意图。
(3)流数据输出
流计算引擎从数据源获取数据,经过转换操作对数据进行处理后,需要将计算结果进行输出,以供外部系统进行使用。比如将可燃气体浓度传感器中的数据作为数据源,经过过滤操作算子处理,过滤出浓度大于0.97这个阈值的事件数据,并将过滤后的数据流写入到外部系统中,如消息队列,或者写入数据库中。
在流数据上的操作,除了支持常规的转换操作和滚动聚合操作外(一个事件数据到达就会触发计算,延迟低),还支持基于窗口的操作,它会接收并缓冲一定量的数据后才会触发相应的计算逻辑。
基于窗口上的求和操作,程序只对窗口中的有界数据集进行求和操作,而不是全部的历史数据。窗口操作一般以时间属性来划分窗口。
窗口有不同的类型,一般分为3种:
(1)滚动窗口(Tumbling Window)
滚动窗口是将无界的流数据,按照固定大小进行拆分成不同的窗口,不同窗口中的事件数据没有交叉。当某个事件数据到达时,如果满足窗口触发规则,则会触发计算机制,将窗口内全部数据进行逻辑处理,并给出结果。
滚动窗口分为基于数量的滚动窗口和基于时间的滚动窗口。基于数量的滚动窗口以事件数据个数为窗口划分规则,比如一个大小为3的基于数量的滚动窗口,每当窗口中缓冲到3个元素时,即会触发窗口计算。下面是基于数量的滚动窗口示意图。
从图可知,第一个窗口包含3个元素,依次为2、1和3。当元素3到达窗口时,就会触发第一个窗口的计算。一般来说,窗口计算完成后,会释放该窗口相关的资源(当然也可以不销毁),并创建一个新的窗口来接收新的元素。
另外,还有一个基于时间的滚动窗口,这个窗口划分以时间为依据,比如每隔10分钟划分一个窗口。这个时间窗口是一个左闭右开的窗口,比如[10:10,10:20)。下面是基于时间的滚动窗口示意图。
从图可以看出,每隔10分钟划分的窗口里面的事件数据个数可能是不同的,有的是2个元素,有的是3个元素。但是当时间到达某个窗口的尾部时,就会触发该窗口。比如其中的一种窗口触发机制是:当10:20之后的某条数据到达时,即触发[10:10,10:20)窗口的计算。
(2)滑动窗口(Sliding Window)
滑动窗口有两个参数,一个是窗口大小,一个是滑动大小。当滑动大小等于窗口大小时,就是滑动窗口。滑动窗口将事件数据分配到固定大小的窗口中,但不同窗口中的元素可能有交叉,即一个元素可能同时属于多个窗口。
滑动窗口可以分为基于数量的滑动窗口和基于时间的滑动窗口。下面给出一个窗口大小为3,滑动大小为2,基于数量的滑动窗口示意图。
从图可以看出,窗口W1包含2个元素,而W2窗口包含3个元素,且与W1窗口有一个共同的元素1。只要有2个元素到达某个窗口,就会触发该滑动窗口。
同样地,还有基于时间的滑动窗口,比如大小为10分钟,滑动大小为5分钟的滑动窗口。下面给出一个窗口大小为3秒,滑动大小为2秒的滑动窗口示意图。
从图可知, 在999 毫秒的时候, 会触发第一个窗口W1[-2000,1000)的计算。此时该窗口中只有一个元素2。当在1秒的时候,创建了一个新的窗口W1[0,3000),即从1000毫秒滑动了2秒(2000毫秒),当时间轴到达2999毫秒时,会触发W1窗口,此时窗口中的元素为4个,即5、1、3和2。
(3)会话窗口(Session Window)
除了滚动窗口和滑动窗口外,还有一种窗口类型,即会话窗口。在某些场景下,会话窗口非常好用,而且这些场景用滑动窗口和滚动窗口实现起来非常难。
会话窗口用一个时间间隙阈值来区分不同的窗口。比如,一个Web应用,在服务器端会维护一个Session ID,当用户在网页上不进行相关操作时,超过服务器设定的会话超时时间,则此Session ID失效。会话窗口的基本原理类似。
从图可知,当事件数据时间间隔超过一定的时间阈值(session gap)时,就会划分不同的窗口。