抖音技术能力大揭密!钜惠大礼、深度体验,尽在火山引擎增长沙龙,就等你来! 立即报名>> 了解详情
写点什么

事件流如何提高应用程序的扩展性、可靠性和可维护性

2015 年 3 月 27 日

关于事件流处理,在不同的场景中有不同的概念。有人称之为流处理,有人称之为事件溯源或 CQRS,还有人称之为“复杂事件处理(Complex Event Processing)”。不管名称是什么,它们的基本原则都是一样的。Martin Kleppmann 是 Apache Samza 的贡献者。在本文中,我们将跟随他的思路深入理解这些概念,以便帮助我们设计更好的系统。

“流处理(stream processing)”源于LinkedIn 构建大规模数据系统的经验,并在开源项目 Apache Kafka 和 Apache Samza 中实现。Martin 以 Google Analytics 为例具体介绍了这一概念。Google Analytics 是一小段 JavaScript 代码,可以追踪哪个访问者访问了哪个网页。然后,系统管理员可以研究这些数据,并按照时间段、URL 等划分这些数据。为了实现这个目的,每次用户访问一个页面时,就需要记录一个事件来反映这个事实。页面访问事件可能是(图 1)这样的结构:

(图 1)

每个事件都是包含上述信息的一个简单不变的事实。它只简单地记录已发生的事情。然后,我们就可以从这些页面访问事件中生成图形仪表板。通常来说,这些事件可以使用(图 2)所示的其中一种方式存储:

(图2)

选项(a):在每个事件进来的时候将其存储,并把它们全部转存到一个大型的数据库、数据仓库或Hadoop 集群中。在需要时,就可以在数据集上执行查询。这个过程会扫描所有事件,或者至少是某个大型的数据子集,并动态地完成聚合。

选项(b):如果每个事件都存储数据量太大的话,可以选择存储事件的聚合结果。比如,如果要记录某个事件的发生次数,那么就可以在这个事件进来时将计数器加1。我们还可以将多个计数器保存在 OLAP 立方中。有了 OLAP 立方,当需要查找一个 URL 在某一天的访问量时,直接读取相应 URL 和日期组合的计数器就可以了。这样就只需要读取一个值,而不需要扫描一个很长的事件列表。

选项(a)的好处是,存储原始事件数据可以最大化分析的灵活性。比如,可以跟踪某个人以什么顺序访问了哪些页面,采用选项(b)就无法实现。这种分析对于一些离线处理任务非常重要,比如训练一个推荐系统。在这种应用场景下,最好是保存原始事件。

不过,选项(b)也有它的用途,尤其是需要实时决策或响应的时候。比如,为了防止别人破坏网站,可能需要引入一个访问频率限制,在一个小时内一个特定的 IP 只允许请求 100 次;如果客户端超出这个限制,就阻塞它。这时,通过原始数据存储实现效率将非常低下,因为系统需要不断地重新扫描事件历史才能确定某个人是否超出了限制。而针对每个 IP 每个时间窗口维护一个计数器将会更高效。总之,存储原始事件和存储聚合结果都是有用的,只不过应用场景不同。

对于选项(b),在最简单的情况下,可以让 Web 服务器直接更新聚合结果。这时,可以将计数器保存在像 memcached 或 Redis 这样具有原子增量操作的缓存中。每次 Web 服务器处理一个请求,就直接向缓存发送一条增量命令。更复杂一点,可以引入事件流(如图 3),或者消息队列,或者事件日志。流上的事件与(图 1)中 PageViewEvent 记录相同。

(图 3)

这种架构的好处是,同样的事件数据可以供多个消费者使用,不同的消费者完成不同的任务,非常灵活和易于扩展。

“事件溯源(Event sourcing)”是一个同流处理类似的概念,只不过它出自领域驱动设计社区。它关注数据在数据库中的存储结构。这里将以电商网站的购物车为例:

(图 4)

如果用户 123 将产品 999 的数量改成了 3,那么系统将通过 UPDATE 操作实现数据修改:

(图 5)

不过,按照事件溯源的思想,这不是一个好的数据库设计方式,因为它没有记录购物车每次变化的信息,即丢失了历史操作信息。因此,在用户 123 初次添加产品 999 的时候,系统应该记录 AddToCart 事件;当用户改变主意想买 3 个 999 时,系统接着记录 UpdateCartQuantity 事件。总之,用户对购物车的每次操作都记为一个单独的事件。这就是事件溯源的本质:将每次写操作记为一个不可变事件,而不是对数据库执行破坏性写入。

(图 6)

可以发现,它同流处理的例子(关于 Google Analytics)一样:(a)存储原始事件;(b)存储聚合结果。

通过进一步思考可以观察到,(a)是理想的数据写入形式,只需要将事件追加到日志尾部,而不需要更新多个不同的表。这对数据库而言是一种最简单、最快速的写入方式。另一方面,(b)是理想的数据读取形式。比如,在用户想知道购物车中有什么的时候,他并不会关心购物车中产品的变化历史,所以直接读取聚合结果会获得最好的性能。

(图 7)

为了帮助我们更深入的理解上述概念,Martin 又分别举了 Twitter、Facebook 和 Wikipedia 的例子。本文就不一一赘述了,感兴趣的读者可以查看原文

现在,让我们回到有关事件流的讨论。不管是流处理,还是事件溯源,只要有了事件流,就可以完成以下工作:

  • 获取所有的原始事件(也许还要做一点转换),然后将它们加载到一个大型的数据仓库中供分析人员使用;
  • 更新全文搜索索引,使用户可以搜索最新数据;
  • 更新缓存,使系统可以从快速缓存中读取数据,并保证缓存中的数据是最新的;
  • 通过对事件流进行处理创建一个新的事件流,然后将后者作为另一个系统的输入。

与传统的数据库使用方法相比,采用类似事件溯源的方法是一个重大的变革。这项变革带来了如下好处:

  • 松耦合——数据读写使用不同的数据库模式,读取的数据经由写入的数据转换而来,应用程序不同部分之间的耦合度降低了;
  • 读写性能——规范化(写入快)和非规范化(读取快)的争论源于数据读写使用同一模式的假设,如果数据读写使用不同的数据库模式,读写速度都会得到提升;
  • 扩展性——因为事件流是一种简单的抽象,而且允许开发人员将应用程序分解成流的生产者和消费者,所以很容易跨机器并行和扩展;
  • 灵活性——原始事件简单、明确,“模式迁移”不会造成多大影响;而向用户展示数据要复杂得多,但如果有一个转换过程可以实现从原始事件到缓存内容的转换,那么当需要新的用户界面时,只需要使用新的逻辑构建新的缓存;
  • 错误场景——原始事件是不变的事实,如果系统出现问题,那么开发人员总是可以用相同的顺序将事件重放。

这里需要注意,实际上,数据库写操作通常都有一个类似事件的不变性,大部分数据库都有的“写前日志(write-ahead log)”本质上就是一个写操作的事件流,虽然在不同的数据库中实现形式可能不同,如PostgreSQL、InnoDB 和Oracle 中的MVCC 机制,CouchDB、Datomic 和LMDB 中的追加式B 树。

接下来,Martin 介绍了如何在应用程序层面上使用事件流。

他用的比较多的是Apache Kafka 和Apache Samza。前者是一个消息代理,就像一个发布- 订阅消息队列,一秒钟可以处理包含数百万条消息的事件流,并将它们永久存储到磁盘上及跨机器复制。后者是与Kafka 搭配使用的处理过程,开发人员可以用它编写代码,消费输入流,生产输出流。

(图8)

除了Samza 之外,开发人员还可以选择 Storm Spark Streaming 这两种最流行的流处理框架。关于它们之间的区别,感兴趣的读者可以查看 Samza 文档。这些分布式流处理框架均源于互联网公司。它们都关注底层的一些事情:如何将流处理扩展到多台机器;如何将 Job 部署到集群;如何处理故障;如何在多租户环境下实现可靠的性能。它们像 MapReduce 更多一些,而像数据库更少一些。

相比之下,还有一些面向流处理的高级语言,如复杂事件处理(CEP)。使用 CEP,可以编写查询或规则来匹配满足特定模式的事件。这些查询或规则与 SQL 查询类似,只不过 CEP 引擎会不断的查找事件流来匹配查询,并在匹配成功时发送通知。这对于欺诈检测或业务流程监控非常有用。

还有一个相关概念是在流上进行全文搜索。它是说,在流上事先注册一个查询,当有事件匹配查询时发送通知。这里有一些与此相关的试验性工作。以下是其它一些与流处理相关的概念:

  • Actor 框架——像 Akka、Orleans 和 Erlang OTP 等框架也是基于不可变事件的流。不过,它们更多的是一种并发机制,而不是数据管理机制;
  • “响应式(Reactive)”——它似乎是一个定义松散的概念集合,像函数响应式编程,主要是将事件流提供给用户界面使用;
  • 变更数据捕获(CDC)——按照我们熟悉的方式使用数据库,但要将任何插入、更新和删除操作抽取到一个数据变更事件流中。

感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流。

2015 年 3 月 27 日 03:463823
用户头像

发布了 1008 篇内容, 共 322.2 次阅读, 收获喜欢 293 次。

关注

评论

发布
暂无评论
发现更多内容

实现高性能MySQL,深入探索数据库索引

奔着腾讯去

数据库 数据库事务 innodb 索引 MySQL 高可用

新垣结衣嫁了个“非典型性”程序员

小智

程序员 软件开发 日本

网易云课堂 Service Worker 运用与实践

有道技术团队

Service Worker

520到了,吟湿几首

花花

520 520单身福利 520 单身福利

云小课 | 玩转HiLens Studio之手机实时视频流调试代码

华为云开发者社区

华为 华为HiLens HiLens Studio EI智能体 实时视频

iPhone如何拍摄惊人的照片

懒得勤快

浅析决策树的生长和剪枝

华为云开发者社区

数据 决策树 预测模型 剪枝 过拟合

只有程序猿才能看懂的520内涵表白

三掌柜

520 520单身福利 520 单身福利

省钱、省时、省力的音视频通信服务

anyRTC开发者

音视频 WebRTC 云服务 RTC

程序员兼职网站推荐~

MY

PCB天线无线模组如何布局摆放?

不脱发的程序猿

物联网 嵌入式设计 PCB天线无线模组 无线模组布局摆放 PCB产品

人人矿场提供真实稳定算力,形成全球分布式算力供给网络

DT极客

面向服务体系结构的领域驱动设计

信码由缰

DDD

张一鸣退隐江湖

池建强

字节跳动 张一鸣

浅谈虚拟偶像背后的舞蹈生成

行者AI

人工智能

仅需几行代码轻松实现第一人称行走

森友小锘

3D可视化 前端可视化 数字孪生

TypeScript 开发环境搭建

Emperor_LawD

typescript ts 520单身福利 520 单身福利

520特辑丨码神VS爱神:盘点程序员的四大男友力,你偏爱哪一种?

华为云开发者社区

程序员 代码 520 男朋友 男友力

Patract走进Substrate Seminar,介绍Wasm合约工具套件

Patract

智能合约 rust polkadot Patract Wasm

细节爆炸!阿里架构师总结出:共计23版块Java架构师“成长笔记”

Java架构追梦

Java 阿里巴巴 架构 面试 成长笔记

程序员应该多久跳一次槽?怎样跳槽才是正确的跳槽?

Java架构师迁哥

大厂面试题之计算机网络重点篇(附答案)

linux大本营

c++ Linux 网络协议 udp TCP/IP

视频分割修整功哪一款视频剪辑软件更好用?

奈奈的杂社

短视频 视频剪辑 视频处理 视频制作

项目开发中ARM单片机芯片分类及选型

不脱发的程序猿

嵌入式 ARM单片机 ARM芯片分类及选型 单片机选型

GO语言平均薪资为什么比Java高?

Java架构师迁哥

华云大咖说 | 华云超融合在论文期刊行业的应用实践

华云数据

嵌入式系统降低功耗的设计技术

不脱发的程序猿

嵌入式设计 嵌入式系统 低功耗

商业落地页端到端性能优化实践

百度Geek说

前端

OCR性能优化:从认识BiLSTM网络结构开始

华为云开发者社区

OCR Seq2Seq BiLSTM 网络结构 OCR网络

Go sync.Pool 浅析

HHFCodeRv

go

【LeetCode】前K个高频单词Java题解

HQ数字卡

算法 LeetCode 5月日更

Study Go: From Zero to Hero

Study Go: From Zero to Hero

事件流如何提高应用程序的扩展性、可靠性和可维护性-InfoQ