• 第十一章


    第十一章 流处理系统

    “流”是指随着时间的推移而持续可用的数据。
    本章,我们将把事件流视为一种数据管理机制: 一种无界的、持续增量处理的方式。

    发送事件流

    通过文件或数据库也可以连接生产者和消费者 : 生产者将其生成的每个事件写入数据存储, 井且每个消费者定期轮询数据存储以检查自上次运行以来出现的事件。这实际上正是批处理在每天结束时处理一天的数据的过程。但是,当新事件出现时 ,最好通知消费者。数据库传统上无怯很好地支持这种通知机制,因此 ,开发了专门的工具用来提供事件通知。

    消息系统

    向消费者通知新事件的常见方陆是使用消息系统。
    在这种发布/订阅模式中,不同的系统采取了不用的方法,为了区分这些系统,提出以下两个问题对区分很有帮助 :

    • 如果生产者发送消息的速度比消费者所能处理的快,会发生什么?
    • 如果节点崩溃或者暂时离线 , 是否会有消息丢失?

    生产者与消费者之间的直接消息传递

    许多消息系统将生产者直接连接到消费者,而不通过中间节点,这些直接悄息传递方式运行效果不错,但是它们通常都要求应用程序代码意识消息丢失的可能性。

    消息代理

    一种广泛使用的替代方能是通过悄息代理(也称为消息队列)发送消息,消息代理实质上是一种针对处理消息流而优化的数据库
    一些消息代理只将消息保存在内存中,而另一些消息代理(取决于配置)将其写入磁盘,以便在代理崩愤的情况下不会丢失消息 。

    消息代理与数据库对比

    • 数据库通常会保留数据直到被明确要求删除,而大多数悄息代理在消息成功传递给悄费者时就自动删除消息。这样的消息代理不适合长期的数据存储。
    • 由于消息代理很快删除了消息, 多数消息系统会假定当前工作集相当小,即队列很短。
    • 数据库通常支持二级索引和各种搜索数据的方式 ,而消息代理通常支持某种方式订阅匹配特定模式的主题。
    • 查询数据库肘 ,结果通常基于数据的时间点快照。消息代理不支持任意的查询,但是当数据发生变化时 ( 即新消息可用时),它们会通知客户端。

    多个消费者

    当多个消费者读取罔一个主题中的消息时,有两种主要的消息传递模式:

    • 负载均衡式:每一条消息都只被传递给其中 一个消费者 ,所以悄 费者可以共享主题中处理消息的工作 。
    • 扇出式:每条消息都被传递给所有的消费者。

    这两种模式可以组合使用。

    确认和重新传递

    消费者可能会随时崩溃,为了确保消息不会丢失,消息代理使用确认:客户端必须在处理完悄息后显式地告诉代理,以便代理可以将其从队列中移除。
    即使消息代理试图保留消息的顺序,负载均衡与重新传递的组合也不可避免地导致消息被重新排序。 为了避免此问题,可以为每个消费者使用单独的队列(即不使用负载均衡功能)。

    分区日志

    日志消息代理:将数据库的持久存储方居与消息传递的低延迟功能相结合

    基于日志的消息存储

    日志是磁盘上一个仅支持追加式修改记录的序列。我们可以使用相同的结构来实现消息代理:生产者通过将消息追加到日志的末尾来发
    送消息,消费者通过依次读取日志来接收消息。为了突破单个磁盘所能提供的带宽吞吐的上限,可以对日志进行分区,因为分区只能追加,所以分区内的消息是完全有序的。不同分区之间则没有顺序保证。

    对比日志与传统消息系统

    因为多个消费者可以独立地读取日志而不会相互影响,读取消息不会将其从日志中删除,因此基于日志的方住很自然地支持扇出式消息传递。
    在消息吞吐量高的情况下,每个消息处理速度快 ,消息顺序又很重要的情况下, 基于 日志的方怯工作得很好。

    消费者偏移量

    代理不需要跟踪每条消息的确认,只需要定期记录消费者的偏移量。减少的记录开销以及可以使用批处理和流水线操作的机会有助于提高基于 日志的系统的吞吐量 。

    磁盘空间使用

    为了回收磁盘空间,日志实际上是被分割成段,并且不时地将旧 段删除或归档保存。
    日志实现了 一个有限大小的缓冲区,当缓冲区变搞时,旧的消息就被丢弃,该缓冲区也被称为循环缓冲区或环形缓冲区。

    当消费者跟不上生产者时

    基于日志的方住是一种缓冲形式,它具有较大但固定大小的缓冲区
    如果消费者落后得太多,代理会有效地丢弃缓冲区容量不能容纳的旧消息,由于缓冲区很大,因此有足够的时间让操作员修复缓慢的消费者。
    即使消费者确实落后太多,并且开始丢失信息,也只有该消费者受到影响;它不会中断其他悄费者的服务。

    重新处理信息

    除了消费者的任何输出之外,处理的唯一副作用是消费者偏移量前移了。但是偏移量在消费者的控制之下,因此在必要时可以轻松地对其进行操作。

    数据库与流

    复制日志是数据库写入事件的流,由主节点在处理事务时生成。从节点将写入流应用于他们自己的数据库副本,从而最终得到相同数据的准确副本。

    保持系统同步

    由于相同或相关的数据出现在多个不同的地方,因此他们需要保持相互同步,这种同步通常 由 ETL进程执行,一般通过获取数据库的完整副本,对其进行转换并将其批量加载到数据仓库中一换句话说,就是批处理 。
    如果定期的完整数据库转储过于缓慢,有时使用的替代方居是双重写入,其中程序代码在数据更改时显式地写入每个系统 。
    但是双重写入会引发并发问题和容错问题。造成两个系统不一致。

    变更数据捕获

    最近,人们对变更数据捕获(CDC)越来越感兴趣 。 CDC记录了写入数据库的所有更改,井以可复制到其他系统的形式来提取数据 。
    例如,可以捕获数据库中的更改并不断将相同的更改应用于搜索索引 。

    实现变更数据捕获

    我们可以调用日志消费者的派生数据,存储在搜索索引和数据仓库中的数据只是记录系统中数据的另一个视图。
    像消息代理一样,变更数据捕获通常是异步的:记录数据库系统不会在提交更改之前等待应用于消费者。这种设计具有的操作上的优势是,添加缓慢的消费者不会对记录系统造成太大影响,但是它的缺点是所有复制滞后导致的问题在这里全部适用

    初始快照

    构建新的全文索引需要整个数据库的完整副本,仅仅应用最近更改的日志还不够,因为它会丢失最近未更新的项目。因此,如果没有完整的日志历史记录, 则需要从一致的快照开始,

    日志压缩

    如果只能保留有限的日志历史记录,则每次需要添加新的报生数据系统时都需要执行快照过程。
    存储引擎定期查找具有相同 key的日志记录,丢弃所有的重复项,井且只保留每个 key的最新的更新。这个压缩和合井的过程是在后台运行的。

    对变更流的API支持

    越来越 多 的数据库开始支持将变更流作为标准接口,而不是那些典型的改进和反向工程的CDC努力。

    事件溯源

    与变更数据捕获类似, 事件溯橱涉及到将所有对应用程序状态的更改保存为更改事件的日志。最大的区别在于事件湖晾在不同抽象层次上应用了这个想法:

    • 在变更数据捕获中,应用程序以数据可变方式来操纵数据库,例如 自由地更新和删除记录。
    • 在事件溯源中,应用程序逻辑是基于写入事件日志的不可变事件构建的。

    事件溯惊使得随着时间的推移去演化应用程序变得更加容易 ,通过更容易理解事情发生的原因 , 以及防范应用程序错误来帮助调试。

    从事件日志导出当前状态

    使用事件溯源的应用程序需要记录事件的日志,井将其转换为适合向用户显示的状态
    使用事件溯源的应用程序通常有一些机制来保存从导出 的 当前状态的快照,因此它们不需要重复处理全部的日志 。

    命令和事件

    事件溯源的哲学是小心的区分事件和命令。 当来自 用户的请求第一次到达时,它最初是一个命令 :此时它可能仍然会失败,例如因为违反了某些完整性条件。应用程序必须首先验证它是否可以执行该命令。如果验证成功并且命令被接受, 它将变成一个持久且不可变的事件。

    状态,流与不可变性

    批处理受益于其输入文件的不变性,所以可以在现有的输入文件上运行实验处理作业,而不用担心损坏它们 。
    事务日志记录了对数据库所做的所有更改。 高速追加是更改日志的唯一方位。

    不变事件的优势

    如果意外地部署了将错误数据写入数据库的错误代码,井且代码能够破坏性地覆盖数据,恢复将变得更加的困难。通过不可变事件的追加日志,诊断问题和从问题中恢复就要容易得多。
    不可变的事件还会捕获更多的信息,而不仅仅是当前的状态。

    相同的事件日志派生多个视图

    通过从不变事件 日志中分离可变状态,可以从相同的事件日志派生出多个面向读取的表示方式。
    将数据写入形式与读取形式分开,并允许多个不同的读取视图,可以获得很大的灵活性 。这个想告有时被称为命令查询责任分离

    并发控制

    事件捕获和变更数据捕获的最大缺点是事件日志的消费者通常是异步的 , 所以用户可能会写入日志,然后从日志派生的视图中读取 , 却发现这些写操作还没有反映在读取视图中 。
    一种解决方案是同步执行读取视图的更新,并将事件追加到日志中。

    不变性的限制

    不变的历史数据可能变得过于庞大,碎片化也可能成为一个问题, 并且压缩和垃圾回 收的性能对于运维的健壮性变得至关重要
    由于管理方面的原因需要删除数据,尽管这些数据都是不可变的。

    流处理

    有了流之后,可以用它来做什么,即怎么处理它:

    • 可以将事件 中的数据写入数据库、缓存、搜索索引或者类似的存储系统,然后被其他客户端查询。
    • 可以通过某种方式将事件推送给用户,例如通过发送电子邮件警报或推送通知,或者将事件以流的方式传输到实时仪表板进行可视化。
    • 可以处理一个或多个输入流以产生一个或多个输出流。数据流可能会先经过由几个这样的处理阶段组成的流水线,最终在输出端结束

    流与批量作业的一个关键区别是,流不会结束。因此不能使用排序合并join,容错机制也必须改变。

    流处理的适用场景

    流处理长期以来一直被用于监控目的,即希望在发生某些特定事件时收到警报。

    复杂事件处理

    复杂事件处理(Complex Event Processing, CEP)是20世纪90年代为分析事件流而发展的一种方法,尤其适用需要搜索特定的事件模式
    通常情况下,数据库会持久存储数据井将查询视为暂时的:当 查询到来时-,数据库搜索与查询匹配的数据,然后在查询完成时忘记它 。 CEP引擎则反转了这些角色 :查询是长期存储的,来自输入流的事件不断流过他们以匹配事件模式

    流分析

    流分析往往不太关心找到特定的事件序列 , 而更 多地面向大量事件的累计效果和统计指标

    维护物化视图

    对某个数据集导出一个特定的试图以便高效查询,并在底层数据更改时自动更新该导出视图
    在事件溯惊中,应用状态通过应用事件日志来维护,这里的应用状态也是一种物化视图 。

    在流上搜索

    传统的搜索引擎首先索引文档,然后在索引 上运行查询。相比之下,搜索流则是反过来:查询条件先保存下来,所有文档流过查询条件,可以针对每个查询来测试每个文档。为了优化,可以对查询和文档都进行索引,从而缩小匹配的查询集合。

    流的时间问题

    流处理系统经常需要和时间打交道,尤其是在用于分析目的时,这些分析通常使用时间窗口。

    事件时间与处理时间

    发生事件处理滞后的原因有很多,而且,消息延迟还可能导致消息的不可预知的排序 。混淆事件时 间与处理时间会导致错误的结果。

    了解什么时候准备就绪

    如果基于事件发生时间而定义窗口,面临一个棘手的问题是,{尔无能确定什么时候能收到特定窗口内的所有事件,或者是否还有一些事件尚未到来。

    你用谁的时钟?

    当事件可能在系统中的多个点缓冲时,为事件分配时间戳就比较困难。
    为了调整不正确的设备时钟,一种方法是记录三个时间戳:

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

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

    窗口类型

    有以下几种常见的窗口类型:

    • 轮转窗口:翻擦窗口的长度是固定的,每个事件都属于一个窗口。
      如果有一个 1分钟的翻悔窗口,则所有时间戳在10:03:00和 10:03:59之间的事件都会被分组到一个窗口中, 10:04:00和 10:04:59之间的事件被分组到下一个窗口
    • 跳跃窗口:跳跃窗口也具有固定长度,但允许窗口重叠以提供一些平滑过渡。
      一个5分钟窗口,设定跳跃( hop )值为 1 分钟则包含 10:03:00 至 10:07:59之间的事件,而下一个窗口将包括 10:04:00至 10:08:59之间的事件
    • 滑动窗口:滑动窗口包含在彼此的某个间隔内发生的所有事件。
      例如, 一个5分钊1的滑动窗口将包括 10:03:39和 10:08:12的事件,因为它们相距不到 5分钟,滑动窗口可以通过保留按时间排序的事件缓冲区井且在从窗口过期时移除旧事件来实现。
    • 会话窗口:与其他窗口类型不同,会话窗口没有固定的持续时间 。
      它是通过将同一用户在时间上紧密相关的所有事件分组在一起而定义的, 一旦用户在一段时间内处于非活动状态,则窗口结束。

    流式join

    我们来区分三种不同类型的join:流和流join,流和表join和表和表join

    流和流join(窗口join)

    流处理操作需要维护状态 :例如 ,在最后一小时发生的所有事件(基于会话ID索引)。无论什么时候发生搜索事件或单击事件,都会将其添加到适当的索引,并且流处理还检查另一个索引,以查看同一个会话ID的另一个事件是否已到达。如果有匹配的事件,则发出一个派生事件来表明哪个搜索结果发生了单击 。如果搜索事件过期而没有匹配到单击事件,则发出一个报生事件表明哪些搜索结果未被单击。

    流和表join

    要执行此join ,流处理过程需要一次查看一个活动事件,在数据库中查找事件的用户ID ,然后将该概要信息添加到活动事件 中。数据库查询可以通过查询远程数据库来实现。
    另一种方也是将数据库副本加载到流处理器中,以便在本地进行查询而无需经过网络往返。

    表和表join (物化视图维护)

    我们需要一个时间线缓存:这是一种基于每个用户的“收件箱”,在发送推文的时候就将其写入其中,因此读取时间线就只需要一次查询。
    要在流处理中实现这种缓存维护,需要用于推文(发送和删除)和关注关系(关注和取消关注)的事件流。流过程需要维护一个包含每个用户的关注者集合的数据库, 以便知道当一个新的推文到达时需要更新哪些时间线

    join 的时间侬赖性

    这里描述的三种类型的join (流-流,流-表和表-表)有很多相同之处 :它们都需要流处理系统根据一个join输入来维护某些状态(例如搜索和单击事件,用户资料或关注列表),并在来自另一个join输入的消息上查询该状态 。

    流处理的容错

    在使输出结果可见之前等待某个任务完成是不可行的,由于流是无限的,因此几乎永远无法完成这个任务。

    微批处理和校验点

    一种解决方案是将流分解成多个小块,井像小型批处理一样处理每个块。这种方法被称为微批处理
    微批处理隐含地设置了与批处理大小相等的轮转窗口(基于处理时间而不是事件时间) 。任何需要更大窗口的作业都需要显式地将状态从一个微批处理保留至下一个微批处理 。

    幕等性

    容等操作是可以多次执行的操作,并且它与只执行一次操作具有相同的效果。
    即使操作不是天生具备幕等性,往往也可以使用一些额外的元数据使其变得罪等。

    故障后重建状态

    任何需要状态的流处理,比如基于窗口的聚合(诸如计数器,平均值和直方图)以及表和索引的join操作,者ri必须确保在故障发生后状态可以恢复 。
    一种选择是将状态保存在远程存储中井采取复制 ,然而为每个消息去查询远程数据库可能会很慢。另一种方也是将状态在本地保存,井定期进行复制 。之后,当流处理器从故障中恢复时,新任务可以读取副本的状态、井且在不丢失数据的情况下恢复处理。
    在某些情况下,甚至可能不需要复制状态,而是从输入流开始重建。

  • 相关阅读:
    LeetCode–打印从1到最大的n位数
    常用十大算法(十)— 踏棋盘算法
    常用十大算法(九)— 弗洛伊德算法
    常用十大算法(八)— 迪杰斯特拉算法
    LeetCode–组合
    LeetCode–组合总和
    5513. 连接所有点的最小费用 kruskal
    152. 乘积最大子数组 dp
    1567. 乘积为正数的最长子数组长度 dp
    5481. 得到目标数组的最少函数调用次数. 位运算
  • 原文地址:https://www.cnblogs.com/aojun/p/15336057.html
Copyright © 2020-2023  润新知