流计算学习之(一):流计算的发展

传统的数据架构

公司一般会采用不同的应用来满足商业的需要,例如企业资源规划系统(ERP)、客户关系管理软件(CRM),以及Web应用等。所有这些系统都是典型将数据处理(程序本身)和数据存储(事务型数据库系统)分成独立的层次,如下图所示。

在这里插入图片描述

这些程序经常需要连接到外部的服务或者面向用户,并且需要持续地处理事件,如订单、邮件、网站的点击。当一个事件被处理,程序会读取其状态,或通过远程的数据库系统来运行一个事务完成状态的更新。一般来说,一个数据库系统会服务于多个应用,这些应用有时候甚至会访问相同的数据库或者表。

当程序需要进行扩展时这种设计就容易出问题。因为可能有多个程序运行在相同的数据集合上以及共享相同的基础设施,改变表结构或者扩展一个数据库系统需要非常关注影响分析。近年解决这种紧密捆绑的程序的方式是微服务设计模式。微服务被设计为小型、自包含和独立的应用程序。多个应用之间通过统一的接口如RESTful HTTP(或RPC)进行通信,以此与其他的微服务进行交互。因为微服务之间是严格解耦的,并且只能通过良好的接口进行通信,所以每个微服务都可以通过自定义的技术栈(包括编程语言、库包、数据存储)来实现。微服务本身以及所需的软件和服务通常都是打包部署在独立的容器中。下图描述了这种微服务架构。

在这里插入图片描述

一般数据存储在公司的多种的事务型数据库系统当中,并为公司业务的种种方面提供有价值的视角。例如,订单处理系统的数据可以用来分析销量随着时间的增长情况,找到延迟发货的因素,甚至预测未来的销量以便调整库存。然而,这些事务型的数据通常被隔离在不同的数据库系统当中,如果可以将其联合在一起进行分析,那将会发挥更大的价值。此外,常常需要将这些数据转换成通用的格式。

这种情况下采用的是IT系统中的一个常用组件叫做数据仓库,而不是在事务型数据库上直接进行分析查询。数据仓库是一种专门针对于分析型查询负载的数据库系统。一般之前由事务性数据库系统管理的数据需要复制到数据仓库中。而这个从拷贝数据到数据仓库的过程被称为抽取(extract)——转换(transform)——加载(load),即ETL。一个ETL过程包括从事务性数据库中抽取数据,转换为通用的形式(如验证、标准化值、编码、去重、模式转换等),并最终将其导入到分析型数据库中。整个ETL过程是相当复杂的,且通常要求复杂的技术解决方案来实现性能要求。为了保证数据仓库中的数据是最新的,ETL的过程一般是周期运行的。

一旦数据导入到了数据仓库当中,就可以被查询和分析。通常来说,在数据仓库中会有2种查询形式。第一种是周期的报告查询,主要用来商业相关的统计如收入、用户增长、产量。将这些度量整合在报告中有利于评估业务的状况。第二种是即席(ad-hoc)查询,一般用于回答具体的问题,以及支持关键业务决策。这两种查询都是在数据仓库中以批处理的方式执行的,即查询的输入是完全准备好的,而查询在计算结果返回后就终止。下图展示了这种架构。

在这里插入图片描述

直到Apache Hadoop的兴起之前,专门的分析型数据库系统和数据仓库是针对数据分析负载的主要解决方案。然而,随着Hadoop的日益普及,一些公司认识到不少有价值的数据不在他们的数据分析流程当中。通常,这些数据要么是非结构化(即不严格遵循关系模型)的,要么过于庞大而无法使用有效的成本将其存储在关系型数据库系统中。今天Apache Hadoop生态系统的组件已经是许多企业和公司的IT系统当中不可或缺的一部分。大量的数据如日志文件、社交媒体、以及网络点击日志被写入到Hadoop的分布式文件系统(HDFS)或者其他的大容量数据存储(如Apache HBase,可以用较小的成本提供海量存储)中,而非将所有数据插入到关系型数据库中。存储在此类存储系统当中的数据,可以被一些SQL-on-Hadoop的引擎所访问,例如Apache Hive,or Apache Impala。然而,Hadoop生态系统的存储系统和执行引擎也使得这些架构的整体运行方式与传统数据仓库基本保持一致,即数据被周期性地抽取并加载到数据存储中,且以批处理的方式进行周期性的处理或者即席查询。

有状态的流处理

有一个重要的观察结论是:几乎所有的数据都是由事件流创建的。比如用户在网站及移动APP上的交互、订单配置、服务器日志、传感器测量。所有这些数据都是事件流。其实,很难找到一次性就生成了有限且完整的数据的这么一个例子。有状态的流处理是处理无界数据流的一种程序设计模式,它对于公司IT系统的很多不同的用例都是有用的。在我们讨论它的用例之前,我们简要解释下什么是有状态的流处理以及它是如何运转的。

如果一个处理事件流的程序,不仅仅是要执行一次次的记录的转换,一般都是需要有状态的,即能够存储和访问中间数据。一个程序接收到一个事件时,它可能执行任何涉及从状态中读取或者写入数据的计算。一般来说,状态可以在很多不同的地方进行存储和访问,其中包括程序变量、本地文件、嵌入式或外部数据库。Apache Flink的状态存储在本地内存或者一个嵌入式数据库,而非一个远程的数据库。因为Flink是一个分布式的系统,在程序或者机器发生故障时,本地的状态需要被保护以防失败从而避免数据丢失。Flink通过定期将程序状态的一致性检查点写入到远程的持久化存储中来保证这一点。下图展示了一个有状态的Flink程序。

在这里插入图片描述

有状态的流处理程序经常从事件日志中提取到来的事件。一条事件日志会被存储分发到不同的事件流当中。一旦事件被写入到一个持久化的、只添加(append-only)的日志当中,便意味着事件的写入顺序是不能改变的。写入事件日志的流可能会被相同甚至不同的消费者读取多次。正是由于日志的只添加(append-only)的特性,发布给所有消费者的事件都是相同的顺序。目前市面上的事件日志系统,有一些是开源的比如最广泛使用的Apache Kafka,也有一些是云计算厂商提供的集成服务。

将Flink上运行的有状态的流处理程序和事件日志联系起来是一件很有意思的事情,这其中有几点原因。在这个架构中事件日志充当一个可信的数据源,因为它存储了输入的事件并且可以以确定的顺序进行重放。为了防止故障,Flink通过将状态恢复到之前获取的检查点以及重置事件日志的读取位置,来恢复一个有状态的流处理程序。这样程序会重放事件日志中的输入事件,直到事件流的当前位置。这项技术被用于故障恢复,同时也可以用于程序更新、修复BUG、修复之前提交的结果、迁移程序到其他集群,以及使用不同的版本进行A/B测试。

如前所述,有状态的流处理是一种通用且灵活的设计模式,可以用来处理很多不同的案例。下面我们列出三种常见的使用有状态的流处理程序:

  1. 事件驱动程序
  2. 数据管道程序
  3. 数据分析程序

接着会给出实际的应用案例。这里将它们视为不同的模式,是为了强调有状态的流处理的通用性。然而,实际中的案例组合了不止一种特性,这也更加说明了这种程序设计模型的灵活性。

事件驱动程序

事件驱动程序是一种有状态的流处理程序,它接收事件流并且在接收到的事件上应用业务逻辑。根据业务逻辑,一个事件驱动程序可能触发某些操作,例如向一个可能被其他事件驱动程序消费的一个输出型(outgoing)的事件流里进行发送警报或者邮件,甚至写入一些事件。

事件驱动程序的典型案例有:

  • 实时推荐,如推荐产品给浏览电商网站的客户
  • 模式检测或者复杂事件处理(CEP),如信用卡交易中的欺诈检测
  • 异常检测,如网络入侵检测

事件驱动程序是前面讨论的微服务的一种演进。它们彼此之间通过事件日志进行通讯而非REST方式的调用,并且将应用数据写入到本地状态而非读写外部数据存储(如一个事务数据库或者KV存储)。下图大体描述了一个由事件驱动的流处理程序组成的服务架构。

在这里插入图片描述

事件驱动程序其实是一种很有意思的设计模型,因为相比存储计算分离的传统架构以及流行的微服务,它提供了非常多的优点。如本地状态读取,即从内存或者本地磁盘读取,相比从远程数据源读写有更优秀的性能。伸缩和容错不需要特别考虑,因为这些已经由流处理器接管。最后,通过使用事件日志作为输入源,程序的输入完整性可以被可靠地存储,并可以确定地进行重放。这一点非常吸引人,尤其结合Flink的保存点的特性,我们可以重置程序的状态到先前的一致保存点。通过重置一个程序的状态和重放输入数据,就有可能修复一个程序的bug以及解决它的负面影响、部署一个程序的新版本而不丢失它的状态、进行假设分析或者A/B测试。

事件驱动的应用程序对运行它们的流处理器有很高的要求。商业逻辑受限于它所能控制的状态和时间,这方面决定于流处理器的API、提供的状态原语、支持事件-时间处理的质量。此外仅仅一次(exactly-once)的状态一致性和扩展程序的能力都是基本要求。Apache Flink满足所有这些要求,可以说是事件驱动程序的良好选择。

数据管道和实时ETL

如今的IT体系中包括了许多不同的数据存储,比如关系型和专用型的数据库系统、事件日志、分布式文件系统、内存缓存、搜索索引。这些系统使用不同的形式和数据结构来存储数据,从而提供特定场景的良好性能。一个组织的数据一般会存储上述多种系统当中。例如,网上商店的商品信息可以存储在事务型的数据库、Web缓存以及搜索索引中。这里需要进行数据复制,数据存储之间需要保持同步。

传统ETL周期任务的做法是将数据在多个存储系统之间移动,这明显不能最快的反映变化。相反,常见做法是将所有变更写入一个事件日志,从而作为一个可信的数据源。事件日志将所有变更发布给消费者,并将所有更新合并到目标数据存储中。实际场景中,所有更新在合并之前需要被处理。例如,这些数据需要规范化、使用额外的数据进行关联和丰富,以及预聚合,所有这些转换通常就是由所谓的ETL进行处理。

低延迟地进行数据的提取、转换和插入是有状态的流处理程序的另一个常见的场景。这种程序一般称为数据管道。此外数据管道还要求能够在短时间内处理大量的数据,即支持高吞吐和程序的扩展能力。作为操作数据管道的流处理器,应该具有多种源和接收器的连接器,用于读写各种存储系统及格式。同样,Flink提供了操作数据管道的所有特性以及各种连接器。

流分析

之前我们介绍了数据分析管道的通用架构。ETL任务将数据周期性地导入到数据存储,然后进行即席查询和调度查询等。对于批处理来说,无论是基于数据仓库还是Hadoop生态的组件,基本的操作模式都是几乎类似的。虽然很多年来这种周期性地将数据导入到数据分析系统的方式都是很先进的,但是它有个明显的缺点。

很显然,这种ETL任务和报告查询的周期性会导致很大的延迟。由于调度的间隔,可能需要几个小时甚至几天才能将数据点包含在报告中。某种程度上来说,使用数据管道程序将数据导入到数据存储可以减少延迟。然而,即使使用持续的ETL,在查询处理事件之前也会有一定的延迟。在过去,对于数据分析,即使有几个小时甚至几天的延迟也是可以接受的,因为对于最新意见和结论的迅速响应并不能产生明显的优势。然而这一点在过去十年里发生了巨大的改变。随着接入系统的快速的数字化,使得实时收集数据并迅速响应成为可能,例如适应环境的调整、用户体验的个性化等。在线零售商能够向正在浏览产品的用户进行商品推荐,移动游戏可以给用户发放虚拟礼品让他们留在游戏中,或者在合适的时机提供游戏内购买,厂商可以监控机器的行为并触发相应的维护操作,以减少产品不可用的机会。所有这些案例要求实时收集数据、低延迟地进行分析、对结果快速响应。而传统面向批处理的架构不能处理这些案例。

毫无疑问,使用有状态的流处理来构建低延迟地分析管道是一项明智之举。现在不必等待周期的调度,流分析程序会持续地收集事件流,在极低的延迟下进对后来的事件进行合并从而保证结果一直在更新。这有点类似于数据库系统中更新物化视图的视图维护技术。通常情况下,流处理程序会将结果存储在支持高效更新的外部数据存储中,比如数据库或者KV存储。另外,Flink还提供了一个特性叫做可查询的状态(queryable state),该特性允许用户将应用程序的状态作为key-lookup表公开,并让外部应用程序可访问。流分析程序实时更新的结果可以用于仪表板之类的应用,如图所示。

在这里插入图片描述

流分析程序可以在极短的时间内将一个事件合并到流分析的结果当中,除此之外还有一个小小的优点。传统的分析管道包括多个独立的组件如一个ETL过程、一个存储系统,在使用基于Hadoop的环境下也还是需要一个数据的处理器和调度来触发任务及查询。这些组件需要谨慎地进行组合,而错误处理和失败恢复也极具有挑战性。

而相反,一个运行有状态的流处理程序的流处理器会负责所有处理步骤,包括事件提取、持续计算(包括状态维护)、结果更新。而且,流处理器具有仅仅一次的状态一致性保证,从而支持故障恢复,并且能够调整程序的并行度。此外支持流分析要求还有事件处理中的顺序正确、结果明确,以及在短时间内处理大量数据,即高吞吐。Flink对于这些疑问交复了一份满意的答卷。

流分析程序常见的用例有:

  • 监控手机网络的质量
  • 分析移动应用中的用户行为
  • 消费者数据的即席分析

关于Flink有一点本书没有提到,但是值得一说的就是Flink也支持流上的SQL分析查询。已经有些公司基于Flink SQL搭建流分析服务,来用于内部使用或者提供外部的付费用户。

开源流处理框架的演进

数据流处理并非是一项新技术。最早的研究原型和商业产品可以追溯到20世纪90年代末期。然而流处理技术的普及在近年来由于成熟的开源流处理器的可用性得到了很大的推动。在今天,开源的分布式流处理器为许多企业的关键业务上的应用提供支持,这些应用横跨不同的行业,如(在线)零售、社交媒体、电信、游戏和银行等。之所有开源软件成为这一领域的先驱,主要有以下两点原因。1)开源的流处理器人人皆可尝试和使用;2)由于许多开源社区的努力,流处理器的扩展性能得到快速发展和成熟。

仅仅Apache基金会就有十几个与流处理相关的项目。而同时新的分布式流处理项目不断地进入开源的舞台并挑战着最优秀的特性。而常常这些新起之秀的一些特性也被之前的流处理器所采用。而且,开源项目的成员也在不断贡献新的特性以满足实际案例的需要。就这样,开源社区不断提升项目的能力,甚至拓展了流处理的技术边界。接下来我们简要看看开源流处理技术的诞生以及今天的模样。

第一代的分布式流处理器着重关注毫秒级延迟的事件处理,同时保证在失败的情况下不丢失事件。这些系统一般只提供很低级别的接口,而且没有内置支持流结果的准确性和一致性,而决定于时间和事件到来的顺序。而即使数据在失败的情况下没有丢失,那么有可能会被处理不止一次。相比批处理保证结果的准确性来说,第一代的开源流处理器牺牲了部分的准确性来换取更低的延迟。此时的数据处理系统要么提供速度要么保证准确性,这也导致了称为Lambda设计架构的产生,如图所示。

在这里插入图片描述

Lambda架构通过一个低延迟处理器负责的速度层来增强了传统的批处理架构。进入Lambda架构的数据首先会被流处理器收集,然后被写入HDFS之类的批处理存储中。流处理几乎实时地计算出可能不准确的结果并将结果写入速度层的表(即上图的Speed Table)中。等到数据写入到批处理存储后再由批处理器进行周期性的处理,并且将准确的结果写入到批处理的表(即上图的Batch Table)中,此时将相应的Speed Table中不准确的数据丢掉。这样最后应用程序通过一个服务层——将Speed Table中最新的近似准确的数据与Batch Table中较早的但是准确的数据合并起来,从而获取到最终的结果。Lambda架构其实旨在解决传统批处理架构的高延迟性。然而,这种架构有明显的缺陷。首先,它要求对于两个独立的提供不同API的处理系统实现语义相同的应用逻辑。第二点,流处理器计算的最新的结果不是准确的而仅仅是近似的。第三点,Lambda架构搭建和维护困难。这么一个架构需要同时具备一个流处理器、一个批处理器、一个流处理的存储、一个批处理存储,以及从批处理器中抽取数据的工具,同时还要调度批处理任务。

在第一代的基础上进行改进,第二代开源的流处理器提供更好的容错,确保在发生故障的时候仅仅处理一次。另外,编程接口也不再是低级的API操作,而是带有更多内置类型的高级API。然而,一些对于高吞吐和更好的容错,是以将延迟从毫秒增加到秒为代价的。而且结果仍然决定于时间和事件到达的顺序,即结果不仅取决于数据,而决定于外部条件比如硬件利用率等。

第三代开源的流处理器解决了依赖于时间和事件到达顺序的问题。这一代系统结合仅仅一次的失败语义,在开源流处理器中首次有能力计算出一致且准确的结果。这些系统不仅仅可以计算实时数据,并且可以像处理实时数据一样来处理历史数据。另外一个进步是消除了延迟与吞吐之间的矛盾。以往的流处理器要么提供高吞吐要么提供低延迟,而第三代流处理器能够两者兼顾。这一代流处理器的出现让Lambda架构成为历史。

并且可以像处理实时数据一样来处理历史数据。另外一个进步是消除了延迟与吞吐之间的矛盾。以往的流处理器要么提供高吞吐要么提供低延迟,而第三代流处理器能够两者兼顾。这一代流处理器的出现让Lambda架构成为历史。

除了截至到目前我们讨论的这些系统的属性(如容错、性能、结果准确性)之外,流处理器还在不断添加新的特性。由于流处理器一般要求7X24小时的最小宕机时间,因而许多流处理器增加了一些特性比如高可用配置、与资源管理器的紧密集成如YARN或者Kubernets,以及动态扩展流应用程序的能力。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值