《数据密集型应用系统设计》笔记十一:第十一章 流处理系统

1. 流处理的思想

在第十章讨论的批处理始终存在一个重要的假设:输入是有界的,是已知的有限大小,所以批处理知道何时读完它们。
批处理的局限有二:
一是:必须人为地将数据划分为固定时间段的数据块,例如以天为单位,按天处理数据,或者以h为单位,按小时处理数据。而在现实中,很多场景中,数据都是无限的,而且是随着时间推移逐渐到达,除非业务停止,否则过程不会结束,因此数据集不会以人为可分的形式“完成”。
二是:批处理需要等待,无论是输入数据集的准备,还是输出的产生,都无法做到即时处理,有很大的延迟。很多时候,人的耐心是有限的,等待会显著影响使用体验。

由于批处理的局限,流处理应运而生,这是一种:无界的、持续增量处理的方式。在每秒结束时(甚至是更小粒度,持续不断地)处理每秒的数据,完全放弃固定的时间片,每当有事件就开始处理。

2. 发送事件流

2.1 流的来源:事件

  1. 在批处理的世界中,作业的输入和输出是文件。在流处理的世界中,作业的输入和输出是事件,这个事件可能是用户活动、传感器以及数据库的写操作等。它们本质上是一样的。事件可以被编码为文本字符串或json,或者某种二进制形式。通过这种编码方式,可以保存事件,还可以通过网络将事件发送到另一个节点以进行处理。

  2. 在批处理中,文件被写入一次,然后可能被多个作业读取。类似地,在流处理中,事件由生产者(发布者)生成一次,然后可能由多个消费者(订阅者)处理。

  3. 在批处理的文件系统中,文件名标识一组相关记录;在流系统中,相关的事件通常被组合成主题或流。

2.2 流的传输:消息系统

原则上,通过文件或数据库也可以连接生产者和消费者:生产者将其生成的每个事件写入数据存储,并且每个消费者定期轮询数据存储以检查自上次运行以来出现的事件。这其实正是批处理在每天结束时处理一天数据的过程

但是,在延迟时间较短的情况下进行连续处理时,轮询的代价会变得很大。轮询的次数越多,返回新事件的请求的百分比越低,开销越大。所以,当新事件出现时,最好采用通知机制。

通知机制的常见方法是:消息系统。消息系统一般采用发布/订阅模式:发布者发送包含事件的消息,然后该消息被推送给一个或多个订阅者。

发布/订阅模式

对于发布/订阅模式,不同系统采用的方法各有不同,关于他们的区分在于两点设计的不同:

(1)如果发布者发送消息的速度比订阅者能处理的快,会发生什么?
有三种选择:系统丢弃消息;将消息缓存在队列中;激活背压(流量控制)
(2)如果节点崩溃或者暂时离线,是否有消息丢失?
持久化?——获得了可靠性,牺牲了性能。可能需要写入磁盘或结合复制方案,这都是有成本的。
接受丢失?——获得了性能,牺牲了可靠性。可以获得更高的吞吐量和更低的延迟,但是有可能出现数据错乱。

消息系统的常见方式

  1. 直接消息传递
    很多消息系统将发布者直接连接到订阅者,而不通过中间节点。这种设计的缺点是只能支持有限的容错:它要求发布者和订阅者必须同时在线,才能检测并重传在网络中丢失的数据包。如果订阅者处于离线,可能会遗漏掉线时发送的消息。

  2. 消息代理
    针对直接传递的问题,最好的替代方法是:消息代理。消息代理实质上是一种针对处理消息流而优化的数据库。它作为服务器运行,发布者和订阅者作为客户端连接到它。发布者将消息写入代理,订阅者从代理读取和接收消息。

    通过将数据集中到代理,这些系统可以更容易适应不断变化的客户端:

    (1)一方面,针对消息丢失,这个问题被转移到了代理。一些消息代理只将消息保存在内存,而另一些可以将其写入磁盘,以便在代理崩溃的情况下不会丢失消息。
    (2)另一方面,基于灵活配置,对于速度慢的订阅者,允许无限队列。

    消息代理和数据库的区别:

    (1)消息代理一般在消息成功传递给订阅者后,会自动删除消息,而数据库不会。因此,消息代理不适合长期的数据存储。
    (2)数据库支持任意的查询,消息代理不能支持任意的查询。
    (3)数据库在另一个客户端修改后,不会通知其它客户端。而消息代理,当数据发生变化时,会通知所有订阅者。

消息传递模式

(1)负载均衡式:每条消息都只传递给其中一个订阅者,所有订阅者可以共享主题中处理消息的工作。
(2)扇出式:每条消息都传递给所有订阅者。扇出允许几个独立的订阅者各自“收听”相同的消息广播,而不会相互影响。

订阅者可能随时会崩溃,为了确保消息不会丢失,消息代理使用确认:客户端必须在处理完消息后显式地告诉代理,以便代理可以将其从队列中移除。如果代理没有收到确认,则认为消息未处理。在负载均衡模式下,重新传递给另一个订阅者。在扇出模式下,重新传递给未确认订阅者。

2.3 分区日志

前面在数据库和消息代理的区别中已经分析到了,两个各有优劣:

(1)数据库和文件系统采取持久化存储方法:在明确选择删除之前,任何写入数据库或文件的内容都是永久保存。这保证了可靠性,批处理是可靠的,可以反复运行它们,而没有损坏输入的风险。并且即使添加了新的客户端,也可以读取以前任意写入的数据。只是牺牲了性能。
(2)消息代理在消息订阅成功后,自动删除:为了保证低延迟,基于瞬间的消息传递思维,一旦消息订阅成功,就从代理中自动删除消息。在负载均衡模式的消息系统,无法再次接收到消息,因此无法再次运行同一个订阅者并期望得到相同的结果。
如果将新的订阅者添加到消息系统,通常只会开始接受在它注册后发送的消息,任何以前的消息已经消失,无法恢复了。

能否将这两者混合使用,将优点结合起来?——这就是日志消息代理的思想。

基于日志的消息代理

日志是磁盘上一个仅支持追加式修改记录的序列。我们使用相同的结构来实现消息代理:发布者通过将消息追加到日志的末尾来发送消息,订阅者通过依次读取日志来接收消息。如果订阅者读到日志的末尾,它就开始等待消息被追加的通知。

为了突破单个磁盘所能提供的带宽吞吐的上限,可以对日志进行分区,使每一个分区成为单独的日志,并且可以独立于其它分区读取和写入。然后可以将主题定义为一组分区,他们都携带相同类型的消息。

在每个分区,代理为每个消息分配一个单调递增的序列号,因为分区只能追加,所以分区内的消息是完全有序的。

日志消息代理 VS 传统消息系统

日志消息代理的模式:扇出式。为了保证多个消费者可以独立地读取日志而不受影响,读取消息不会将其从日志中删除。
日志消息代理的负载均衡:将整个分区分配给订阅者组中的节点,而不是将单个消息分配给订阅者。

日志消息代理的缺陷:

(1)订阅限制:因为同一分区的消息将被传递给同一个节点,所以订阅一个主题的节点数最多等于该主题中的日志分区数。
(2)连锁反应:因为分区内有序,如果某个消息处理缓慢,会阻塞该分区中的后续消息的处理。

适合场景:

(1)在消息处理代价很高,希望在逐个消息的基础上并行处理,而且消息排序不重要的情况下,JMS/AMQP类型的消息代理更可取。
(2)在消息吞吐量高,消息顺序很重要的情况下,基于日志的消息系统更好。

订阅者偏移量与重新处理信息
在日志消息代理中,订阅者偏移量具有十分重要的作用。顺序读取一个分区可以很容易判断哪些消息已经被处理:所有偏移量小于订阅者者当前偏移量的消息都已经被处理,所有更大偏移量的消息还没被看到。因此,代理不需要跟踪每条消息的确认,只需要定期记录订阅者的偏移量。

如果订阅者节点失败,则订阅者组中的另一个节点将被分配到失败的订阅者分区,并以最后记录的偏移量开始使用消息。如果订阅者已经处理了后续的消息,但还没有记录他们的偏移量,那么在重新启动后,这些消息将被再次处理。

3. 数据库与流

将对数据库的写入视为流也是有用的:可以捕获变更日志。包括隐式地通过变更数据捕获或显式地通过事件溯源两种方法。通过日志压缩,可以保留数据的完整副本。

将数据库表示为流为高效集成系统提供了更多的机会。通过使用变更日志并将其应用到派生系统,可以不断更新搜索索引、缓存和分析系统等派生数据系统。甚至可以全新做起,将所有数据从始至终都视为变更日志(过分了!老千层饼了)从而构建全新的视图。

4. 流处理

前面已经讨论了流的来源(用户活动事件,传感器以及数据库的写操作),讨论了流的传输(直接消息传递和消息代理,以及目前广为使用的两种消息代理:AMQP/JMS风格的消息代理和基于日志的消息代理)

那么,有了流之后,可以用它做什么?大体上有三种选择:

(1)可以将事件中的数据写入数据库、缓存、搜索索引或类似的存储系统,然后被其它客户端查询。这是保持数据库与系统其它部分同步的好方法。
(2)可以通过某种方式将事件推送给用户,例如发生邮件警报或推送通知,或者将事件以流的方式传输到实时仪表板进行可视化。此时,人是流的最终订阅者。
(3)可以处理一个或多个输入流以产生一个或多个输出流。这也是我们研究的重点:处理流以产生其他派生流。

流作业和批量作业在各个方面都很类似,但是关键区别在于:流不会结束。这种区别带来的差别在于:

(1)排序失去意义。无法对无界数据集进行排序,因此不能使用排序-合并join。
(2)容错机制改变。批量作业不会主动删除消息,可以简单地重新执行失败任务。而流处理不行,它会自动删除消息,在崩溃失败后重新开始基本不可能。

4.1 流处理的适用场景

  1. 长期以来,流处理一直被用于监控目的,即希望在发生某些特定事件时收到警报。
  2. 复杂事件处理(Complex Event Processing,CEP)。与正则表达式支持在字符串中搜索特定字符模式的方法类似,CEP允许制定规则,从而可以在流中搜索特定模式的事件。
    传统的搜索引擎首先索引文档,然后在索引上运行查询。CEP则是反过来,查询条件先保存下来,然后文档流过查询条件。CEP系统通常使用像SQL这样的高级声明式查询语言或图形化用户界面,来描述应该检测到的事件模式。
  3. 流分析。通过在固定时间间隔内进行聚合运算,获得一系列的统计指标,用于数据分析。常见的storm,streaming,flink等,都支持这种聚合分析。
  4. 维护物化视图。对某个数据集导出一个特定的视图以便高效查询,并在底层数据更改时自动更新该导出视图。比如,将所有数据从始至终都视为变更日志,从而构建全新的视图。比如,kafka的日志压缩功能。原则上,任何流处理器都可以维护物化视图,但这种永久维护事件的需求和面向分析的流处理框架目的背道而驰,所以,一般不用。

4.2 流的时间问题

事件时间与处理时间

事件时间:事件实际发生的时间
处理时间:处理节点上的本地系统时钟

批处理中,由于需要按时分批处理,批处理会查看每个事件嵌入的时间戳(事件时间),处理过程花费的时间相较于事件的总时间,级别不够。并且,处理运行的时间与事件实际发生的时间无关,所以查看运行批处理机器的系统时钟没有意义。在事件中使用时间戳可以使得处理过程是确定性的。

流处理中,经常使用处理节点上的本地系统时钟(处理时间)来确定窗口,当事件发生和事件处理之间的间隔可以忽略不计时,这种方法简单,也合理。但是,如果存在明显的处理滞后,该方法不再有效。

基于事件时间定义窗口

两个问题:

  1. 事件真实发生时间如何确定?
    用户控制的设备上的时钟通常是不可信的,他可能被意外或故意设置成错误时间。相较之下,服务器收到事件的时间更加准确。为了校正设备时钟,记录三个时间戳:

根据设备时钟,记录事件发生的时间;
根据设备时钟,记录将事件发送到服务器的时间;
根据服务器时钟,记录服务器收到事件的时间。

通过第三个时间戳减去第二个时间戳,可以估计出设备时钟和服务器时钟之间的偏移量,然后将该偏移量应用于第一个时间戳,从而可以估计出事件真实发生的时间(其实是基于服务器时钟的,相对可信的时间)。

  1. 如何确定何时能收到特定窗口的所有事件?
    在一段时间没有看到任何新的事件之后,可以认为超时并宣布关闭该窗口,但由于网络中断而延迟,仍然可能发生某些事件其实还缓存在另一台计算机上。此时,我们需要能够处理在窗口已经声明完成后,才到达的滞后事件。大体上,两个选择:

(1)忽略这些滞后的事件。因为正常情况,这些滞后只是很小的一部分,可以丢弃。也可以将丢弃事件的数量进行追踪,当出现大量丢弃数据时,发出警报。
(2)发布更正。针对滞后事件的一个更新值,这可能需要回收以前的输出。(比较麻烦,不建议。)

窗口类型

一旦明确了如何确定事件的时间戳,下一步就是决定如何定义时间段即窗口了。窗口是如此重要,可以非常方便的帮助我们聚合分析。
常见的窗口类型:

(1)轮转窗口:长度是固定的,没有重叠。10:00:00-10:00:59,10:01:00-10:01:59,依次类推
(2)跳跃窗口:长度也是固定的,但允许窗口重叠以提供一些跳跃过渡。5分钟窗口,1分钟跳跃。10:00:00-10:04:59,10:01:00-10:05:59,依次类推
(3)滑动窗口;长度固定,但是边界不固定,在界限内可以任意滑动。5分钟窗口,10:01:50和10:06:10的两个事件,因为间隔在5分钟内,所有会置于同一个滑动窗口,而轮转和跳跃则不会。
(4)会话窗口:没有固定的持续时间。将同一用户在时间上紧密相关的所有事件分组在一起而定义,一旦用户在一段时间内处于非活动状态(比如,15分钟没有事件),则窗口结束。

4.3 流式join

批处理作业通过主键来join数据集,然后构建数据管道。流处理将数据管道推广为对无界数据集的增量处理,对流join也完全适用。
三种不同类型的流式join:

(1)流和流join:两个输入流都由活动事件组成,采用join来搜索在特定时间窗口(比如一小时)内发生的相关事件。
(2)流和表join:一个输入流由活动事件组成,另一个是数据库变更日志。对于活动事件,join操作用来查询数据库并输出一个包含更多信息的事件。
由于join操作不能使用无限滑动窗口,在使用join进行实时数据分析的时候,很难将流中的所有数据进行join。另一方面,即使可以将两个流中的所有数据进行join操作,但当数据体量巨大的时候,其实时性也会受到影响。为了在不影响实时性的情况下还可以进行join操作,可以使用流与表之间的join操作进行数据分析。
(3)表和表join:两个输入流都是数据库变更日志。此时,一方的每一个变化都与另一方的最新状态join,结果是对两个表之间的物化视图进行持续的更新。

4.3 流处理的容错

批处理中,我们可以简单地重新开始,并丢弃失败任务的输出。但在流处理中,由于长时间运行并持续产生输出,所以不太可能将所有输出都简单地丢弃。因此,流处理的容错需要新方案,基于微批处理、检查点、事务或幂等性写入等,可以实现更细粒度的恢复机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值