实时数据处理是运行以现代技术为导向的业务的基础方面。客户比以往任何时候都想要更快的结果,并且会在获得更快结果的丝毫机会上背叛。因此,如今的组织都在不断地寻求减少响应的毫秒数。
实时处理接管了之前使用批处理处理的大部分方面。实时处理需要对传入的数据流执行业务逻辑。这与将数据存储在数据库中然后执行分析查询的传统方式形成鲜明对比。此类应用程序无法承受先将数据加载到传统数据库然后再执行查询所涉及的延迟。这为流式数据库奠定了基础。流式数据库是可以接收高速数据并在移动中处理它们的数据存储,而无需混合使用传统数据库。它们不是传统数据库的直接替代品,但擅长处理高速数据。本文将涵盖流式数据库的四个关键设计原则和保证。
了解流式数据库
流式数据库是可以实时收集和处理传入的一系列数据点(即数据流)的数据库。传统数据库存储数据并期望用户执行查询以根据最新数据获取结果。在实时处理是关键标准的现代世界中,等待查询不是一种选择。相反,查询必须连续运行并始终返回最新数据。流式数据库促进了这一点。
在流式数据库的情况下,查询不会被执行而是被注册,因为执行永远不会完成。它们运行无限长的时间,对传入的更新数据做出反应。应用程序还可以及时查询以了解数据如何随时间变化。
与传统数据库相比,流式数据库在撰写本文时完成所有工作。实现这一目标面临着许多挑战。其一,人们对传统数据库的期望最低限度的持久性和正确性。当数据始终处于动态状态时,保持这种持久性和正确性需要复杂的设计。然后是使用户能够查询动态数据的挑战。SQL 长期以来一直是所有查询要求的标准。流式数据库自然也支持 SQL,但是当数据总是在移动时,实现窗口、聚合等结构很复杂。
持久查询是对移动数据进行操作的查询。它们无限期地运行并不断产生输出行。无休止的查询对更新逻辑提出了独特的挑战。关键问题是关于用改进的查询替换查询时的行为——它是对到那时到达的所有数据进行操作,还是只对下一组数据进行操作?第一种操作模式的名称是backfill,后者是exactly-once processing。要实现 exactly-once 处理,执行引擎必须有一个本地存储。有时,查询可以提供给其他数据流。这样的操作称为级联模式。
现在概念已经清楚了,让我们花点时间了解流式数据库的架构细节。流式数据库通常构建在基于生产者-消费者范式工作的流处理系统之上。生产者是创建事件的实体。消费者消费事件并处理它们。通常将事件分组为主题的逻辑分区,以方便业务逻辑的实现。
它们之间有一个经纪人,负责确保生产者和消费者所需的流和格式转换的可靠性。Broker通常分布在一个分布式平台上,以保证高可用性和健壮性。流查询引擎驻留在处理平台之上。还存在将 SQL 查询转换为流处理逻辑的 SQL 抽象层。将所有内容拼接在一起,架构如下所示。
现在我们了解了流式数据库和持久查询的概念,让我们花一些时间了解它们的典型用例。
物联网平台
物联网平台处理从世界各地的设备推送的大量事件。他们需要根据实时处理生成警报,并在反应时间方面有严格的 SLA。IoT 平台还需要永久存储所有接收到的事件,并需要基于窗口的流数据聚合以进行分析。流式数据库和持久查询非常适合这里。
事件溯源
事件溯源是一种范例,其中根据随时间发生的事件而不是实体的最终状态来执行应用程序逻辑。这有助于提高应用程序的持久性和可靠性,因为可以通过回复事件随时重新创建应用程序状态。这在审计跟踪是强制性要求的情况下很有用。
点击流分析
点击流分析平台处理作为应用程序使用的一部分生成的点击事件。来自点击事件的数据有时会直接输入机器学习模型,为客户提供推荐和建议。持续事件和点击流的实时处理是运行电子商务等业务的重要组成部分。
交易系统
交易系统每秒处理数百万个交易请求,并将它们与需求和供应方程式进行匹配以结算交易。在这种情况下,审计跟踪是一项强制性要求,即使是最轻微的延误也会给相关各方造成巨大的经济损失。
欺诈检测系统
欺诈检测系统一旦检测到与典型的欺诈开始非常匹配的场景,就需要立即采取行动。他们还必须记录触发警报和事件发生后的后续事件。考虑一个金融系统欺诈检测系统,它根据合法所有者的消费模式检测欺诈。它需要实时将事件的特征提供给欺诈检测模型,并在标记可能的违规行为时立即采取行动。实时数据库是实现此类用例的绝佳解决方案。
IT系统监控
集中监控帮助组织保持其 IT 系统始终运行流式数据库通常用于从系统收集日志并在满足特定条件时生成警报。通过事件存储生成的实时警报和审计跟踪是可观察系统实施的关键要素。
流式数据库市场并不拥挤,只有少数数据库经得起考验来处理生产工作负载。Kafka、Materialise、Memgraph 等是一些稳定的。选择一个适合用例的工具需要仔细比较它们的特性和用例剖析。
现在让我们将注意力转移到学习流式数据库背后的关键数据库设计原则和保证上。
4个关键的流式数据库设计原则和保证
数据库系统的完整性通常用数据库是否符合 ACID 标准来表示。ACID 投诉构成了良好数据库设计原则的基础。ACID 合规性代表原子性、一致性、隔离性和持久性。原子性是指保证逻辑操作的一组语句部分在其中一条语句出错的情况下优雅地失败。这样的一组语句称为事务。在流式数据库的早期,事务支持和原子性常常缺失。但是较新的版本确实支持事务。
一致性是指遵守数据库强制执行的规则,如唯一键约束、外键约束等。如果结果状态不遵守这些规则,一致的数据库将恢复事务。隔离是单独事务执行的概念,这样一个事务不会影响另一个事务。这使得事务的并行执行成为可能。像 KSQLDB 这样的数据库支持查询的强一致性和并行执行。耐用性是指从故障点恢复。该架构的分布式特性确保了现代流式数据库的强大持久性。
在流式数据库的情况下,保证是指处理事件的保证。由于数据是不断移动的,因此很难保证事件处理的顺序或避免重复处理。确保所有数据只处理一次是一项昂贵的操作,并且需要状态存储和确认。同样,在分布式应用程序的情况下,确保消息以与接收消息相同的顺序进行处理需要复杂的体系结构。
现在让我们看看核心设计原则以及它们是如何在流式数据库中实现的。
1.自动恢复
对于流式数据库,自动恢复是最关键的数据库设计原则之一。流式数据库用于高度监管的领域,如医疗保健、金融系统等。在这些领域,没有任何借口可以失败,并且事故可能导致巨大的金钱损失甚至生命损失。想象一下作为医疗保健物联网平台的一部分集成的流式数据库。传感器监测患者的重要参数并将它们发送到云端托管的流式数据库。这样的系统永远不会宕机,基于阈值生成警报的查询应该无限期地运行。
由于流式数据库不会出现故障,因此它们通常基于分布式架构来实现。基于节点集群设计的流式数据库提供了很好的容错能力,因为即使少数节点出现故障,系统的其余部分仍然可以接受查询。这种容错必须在开发周期的早期纳入流式数据库的设计中。现在让我们看看流式数据库自动恢复中涉及的关键活动。
分布式流式数据库中的自动恢复涉及以下活动:
- 故障检测
- 重新平衡和智能路由
- 最终恢复
故障检测是自动恢复的第一步。系统必须有足够的自我意识来检测故障情况,以便它可以采取必要的步骤进行恢复。在分布式系统中,故障检测通常是通过心跳机制来完成的。心跳是一个周期性的轻量级消息,节点发送给集群的每个节点或集群的主节点。这让其他人知道它还活着。如果没有收到来自节点的心跳消息,系统会认为它已经死亡并启动恢复程序。心跳消息的大小、其中捆绑的信息以及心跳消息的频率对于优化资源很重要。非常频繁的心跳有助于更早地检测到故障,但它也会耗费处理时间并产生开销。
当分布式系统中发生节点故障时,该节点拥有的资源必须重新平衡到其他节点。分布式系统使用受控复制来确保即使在一些节点出现故障时数据也不会丢失。一旦一个节点出现故障,系统会确保数据在其他节点之间重新平衡,并尽可能保持复制策略以降低数据丢失的风险。
要维护高可用性流式数据库,仅在部分故障期间进行重新平衡是不够的。由于在流式数据库的情况下查询始终在运行,因此系统需要确保它们保持运行。这就是智能路由的用武之地。智能路由有助于确保查询保持运行并返回结果。使用故障节点上的资源的查询被无缝地路由到其他节点。这需要仔细设计,并且是流式数据库基本要求的一部分。
最终恢复涉及恢复在事件中丢失的状态存储。状态存储需要确保系统满足用户配置的约束恰好一次、至多一次或至少处理保证。分布式数据库通常使用无限日志作为事实来源。他们还使用单独的主题,将时间偏移量作为恢复机制。如果发生故障,此时间偏移主题可用于重新创建事件的时间线。
2.恰好一次语义
对于流式数据库,故障情况下的自动恢复是不够的。与传统数据库设计不同,流式数据库设计应确保在故障期间丢失的结果不影响下游消费者。实现这一点有几个方面。首先,系统需要保证没有记录漏处理。这可以通过重新处理所有记录来完成,但存在风险。其一,没有充分考虑的重新处理可能导致记录被处理不止一次。这会导致不准确的结果。例如,考虑相同的医疗保健 IOT 平台,其中警报是决定生命的警报。重复处理会导致重复警报,从而造成资源浪费。重复处理也会导致聚合结果,如平均值、百分位计算等。
根据要求,有时,事件处理中的一些错误可能是可以接受的。流式数据库定义了不同的消息处理保证以支持具有不同需求的用例。可以使用三种类型的消息保证——至多一次、至少一次和恰好一次。Utmost once 保证定义了消息永远不会被处理超过一次的情况,但有时可能会错过处理。至少一次保证定义了允许重复处理但不接受丢失记录的情况。
Exactly once 语义保证一条消息只被处理一次,并且结果足够准确,以至于消费者不会注意到失败事件。让我们借助图表来探讨这个概念。
假设系统正在处理按顺序发送的消息。为了表示,消息在此处从 1 开始排序。处理器接收消息,根据逻辑进行转换或聚合,传递给消费者。在上图中,绿色表示尚未处理的消息,红色表示已处理的消息。每次处理消息时,处理器都会使用偏移量更新状态存储。这是为了在发生故障时启用恢复。现在假设处理器在处理第二条消息后发生错误并崩溃。当处理器恢复时,它必须从 3 而不是从消息 2 重新开始处理。它应该避免对状态存储进行重复更新或将消息 2 的结果再次提供给消费者。
换句话说,系统从结果的角度和系统间通信的角度都掩盖了故障。这需要生产者、消息系统和消费者根据约定的合同进行合作。消息传递确认是可以帮助流式数据库完成此操作的最简单的保证。合约应该能够承受代理故障、生产者与代理之间的通信故障,甚至是消费者故障。
3.处理故障记录
良好的数据库设计将处理无序记录视为一个关键方面。由于多种原因,流式数据库会遇到乱序记录。原因包括网络延迟、生产者不可靠、时钟不同步等。由于它们用于高度敏感的应用程序,如金融和医疗保健,处理顺序非常重要,流式数据库必须优雅地处理它们。为了更好地理解这个问题,让我们考虑同一个物联网医疗保健平台的例子。假设由于短暂的互联网连接失败,其中一台设备在短时间内无法发送数据。当它恢复时,它从恢复时间开始发送数据。一段时间后,设备中的固件发送了之前未能发送的其余数据。
为了更好地了解上下文,让我们现在使用图表来解释这个问题。下图有一个生产者,它发送的消息的编号从 1 开始。绿色块代表尚未发送的事件,红色块代表此处已发送的事件。当消息。1,2, 3 按时间戳顺序到达;一切安好。但是,如果其中一条消息在途中或从源本身延迟,则会导致流媒体平台的结果损坏。在这种情况下,消息1和3早于2到达。流媒体平台在1和3之后收到2,但必须保证下游消费者按实际顺序收到消息。
如果乱序记录没有得到妥善处理,处理记录的流式数据库将向下游系统提供不准确的结果。例如,假设消息表示一个温度值,并且有一个持久查询可以找到最后一分钟的平均温度。延迟的温度值会导致错误的平均值。流式数据库以两种方法处理这些情况。第一种方法涉及一个配置参数,该参数定义处理器在每个微批处理开始之前等待无序记录到达的时间量。微批次是一组记录,它们是实时查询的一部分。微批处理的概念是持久查询的基础。
解决乱序记录的第二种方法是允许处理器更新已经计算的结果。这需要与消费者达成协议。这个概念与事务的概念直接冲突,因此在实现时需要仔细设计。这仅适用于下游消费者即使以不同顺序接收输入也能够产生相同输出的情况。例如,如果下游消费者的输出是一个允许修改的表,那么流式数据库就可以使用这种策略。
4. 一致的查询结果
实现流式数据库通常基于分布式架构。传统的数据库设计原则将原子性和一致性视为良好数据库设计的关键支柱。但是在流式数据库的情况下,由于分布式的特性,很难实现写入的一致性。考虑一个使用复制分区概念并部署在节点集群上的流式数据库。收入流将根据存储模式进入不同的分区或节点。为确保真正的写入一致性,需要确保只有在所有分区都反映成功时才确认流。当有多个消息作为逻辑事务的一部分时,这很困难。
确保写入一致性的困难也会影响到读取一致性。只有读取一致,才有可能返回一致的查询结果。考虑充当多个持久查询的数据源的流,该查询根据不同的业务逻辑聚合流。流式数据库必须确保两个查询都作用于单一的真实来源,并且查询的结果反映不冲突的值。这是一个极其困难的命题,因为为了处理乱序的记录,大多数数据库都以持续更新的模式运行,经常会改变它们之前计算的结果。在串联查询的情况下,此类更新会在不同时间滴入下游流,并且一些派生查询状态可能反映不同的源数据状态。
有两种方法可以解决查询结果的一致性问题。第一种方法通过确保在来自该流的所有查询完成之前不确认写入来解决这个问题。这对生产者而言是昂贵的。因为为了满足 exact once 处理要求,大多数流式数据库依赖于代理和生产者之间的确认。如果仅在查询完成后才确认所有写入,那么生产者将被蒙在鼓里的时间更长。这种方法是 block-on-write 方法。
解决一致性的第二种方法是在查询引擎级别进行。在这里,查询引擎延迟需要强一致性保证的特定查询的结果,直到所有写入都得到确认。这比第一种方法更便宜,因为输入流的写入性能不受影响。这种方法不是延迟确认作为查询基础的输入流,而是在查询级别运行。因此,如果查询不需要强一致性保证,则该查询的结果将比依赖于相同输入流的其他查询更早发出。因此,不同的查询根据其一致性配置对同一输入流的不同版本进行操作。
流式数据库必须在数据的陈旧性和一致性级别之间取得平衡,以优化性能。提高一致性级别可能会导致处理速度降低,反之亦然。
结论
流式数据库是实时处理应用程序的基础。它们不是传统数据库的替代品,而是有助于满足需要对永无止境的数据流进行始终在线处理的独特需求。设计流式数据库是一项复杂的任务,因为在处理流式数据时存在一些限制。实现读取一致性、处理乱序数据、确保恰好一次处理和自动恢复是设计流式数据库时考虑的典型设计原则。