分布式系统专家 Martin Kleppmann 在他的“由内至外的数据库变革”的演讲中提出了一个激进的想法:“从关系型数据库转向不可变事件和物化视图的日志可以带来显著的收益。”他在演讲中讲解了关系型数据库的内部工作原理,以及使用这种数据库架构创建的应用程序所面临的诸多局限,这些内容会彻底改变你对数据库和事件日志的看法。
虽然我同意他列举的各条数据库局限,也认可他所提到的事件日志的好处,但我不认为用事件日志替换数据库在实践中是可行的。我认为用来彻底变革数据库的那些设计原则应该应用在更高的服务设计级别,彻底改变微服务的流。
通过这种转变,在服务中,我们可以继续使用传统数据库做最适合它们的事情——高效处理可变状态,并使事件日志以可靠的方式在服务之间传播更改。
借助充当数据库和事件日志之间连接组件的 Debezium 等框架,我们可以同时享受非常熟悉、久经考验的数据库技术以及现代化的事件日志(例如 Red Hat 的托管 Apache Kafka 服务)技术的便利。
这种变革性的思维需要有意识地在微服务中提供出站 API,以将所有相关的状态更改和领域事件从服务内部传输到外部世界。
微服务运动与事件驱动的新兴趋势的融合,就是我所说的由内至外的微服务变革(turning microservices data inside-out)。
微服务 API 类型
基于上述理念,我从微服务提供和消费的不同 API 类型的视角来研究微服务。
微服务的一种常见描述是“围绕一个业务领域构建的许多独立部署的组件,各自拥有自己的数据并通过 API 公开。”这很像是前文视频中对数据库的描述——有一个单个进出 API 的黑匣子。
数据从微服务的入站 API 流向出站 API
我认为微服务的一种更好的描述是,每个微服务都由数据流经的入站和出站 API 以及描述这些 API 的一个元 API 组成。虽然入站 API 在今天广为人知,但出站 API 用到的地方并不多,而元 API 的职责分散在了各种工具和快速发展的微服务技术上。为了让这种由内至外的方法发挥作用,我们需要让出站和元 API 成为微服务的一等构造,并围绕这些领域改进工具链和实践。
入站 API
如今所有微服务都有入站 API,它们以服务端点的形式存在。这些 API 是由外向内的,它们允许外部系统通过命令和查询直接或通过事件间接与服务交互。
入站 API 是当今微服务的常态
在实现方面,这些 API 通常是基于 REST 的,它们为同步操作提供突变或只读操作,以负载均衡网关作为前端。这些也可以实现为异步的、基于命令的交互队列,或基于事件的交互的主题。这些 API 的职责和治理很容易理解,它们构成了当今微服务 API 领域的大部分内容。
出站 API
我所说的出站 API 是源自服务内部并通向外部服务和系统的交互,其中大部分是由服务发起的查询和命令,目标是其他实体拥有的相关服务。我把源自服务内部的出站事件也放在这个类别下面。出站事件不同于针对特定端点的查询和命令,因为出站事件是由服务定义的,而没有对现有和未来可能的接收者的具体知识。虽然这种 API 具有间接的性质,我们仍然期望对于服务中发生的任何重大更改(通常由入站交互引起),能够可预测且可靠地生成这些事件。
今天,出站事件往往是在设计后期才会加上去的。它们要么是为依赖于它们的特定消费者的需求而创建的,要么是在服务生命周期的后期添加的——不是由服务所有者,而是由其他负责数据复制的团队添加。在这两种情况下,出站事件的可能用例都很狭窄,其潜力也被削弱了。
出站事件的一大挑战是为服务中发生的任何更改实现一个统一且可靠的通知机制。为了在每一个微服务和所有类型的数据库中统一应用这种方法,这里的工具必须是非侵入性的,并且对开发人员足够友好。但现实中并不存在支持这种模式的良好框架,也没有经过验证的模式、实践和标准,这些都阻碍了人们采用出站事件作为通行的顶级微服务构造。
通过更改数据捕获实现的出站事件
要实现出站事件,你可以在应用程序代码中加入更新数据库和将事件发布到消息传递系统的逻辑,但这会引发众所周知的双重写入问题。或者你可以尝试用事件日志替换传统数据库,或使用专门的事件源平台。但是,如果你认为项目中最有价值的资源是人才,以及他们久经战阵的工具和实践,那么用其他东西来替换数据库等基本组件肯定会带来重大影响。更好的方法是继续使用关系型数据库和围绕它的所有历经数十年风雨考验的工具和实践,并使用 Debezium 等连接组件来为你的数据库做一个补充(免责声明:我是 Red Hat 的 Debezium 产品经理,所以我肯定会偏爱它)。
我认为出站事件的最佳实现方法是发件箱模式,它使用单个事务来执行由服务逻辑指示的正常数据库更新,并将消息插入到同一数据库中的一个专用发件箱表中。将事务写入数据库的事务日志后,Debezium 从日志中提取发件箱消息并将其发送到 Apache Kafka。这种模式优点很多,例如“读取你自己的写入”语义,其中对服务的后续查询会返回新存储的记录,同时我们还能通过 Apache Kafka 获得可靠、异步的更改传播。Debezium 可以有选择地从数据库事务日志中捕获更改,以统一的方式将它们转换并发布到 Kafka 中,充当服务的出站事件接口。Debezium 可以作为一个库嵌入到 Java 应用程序运行时中,也可以解耦成一个边车(sidecar)。它是即插即用的组件,无论是遗留服务还是从头开始创建的新服务都可以把它加进去。这就是任何服务都需要的,基于配置的出站事件 API。
元 API
今天,元 API 负责描述入站和出站 API,并实现对它们的治理、发现和使用。它们是在围绕特定技术的孤立工具中实现的。在我的定义中,发布到 API 门户的 REST 端点的 OpenAPI 就是元 API 的一个示例。发布到模式注册表的消息主题的 AsyncAPI 也是元 API 的一个示例。Debezium 发布数据库模式更改事件(不同于数据更改事件)的模式更改主题是元 API 的又一个示例。其他工具中有各种描述数据结构的功能,服务它们的 API 都可以归类为元 API。所以在我的定义中,元 API 是允许不同利益相关者使用服务并支持其他系统使用入站和出站 API 的工件。
元 API 的职责演变
微服务的一条基本设计原则是让服务可独立更新和部署。但是今天,服务所有者之间仍然需要大量协调才能完成涉及 API 更改的升级工作。服务所有者需要更好的元 API 工具来订阅依赖服务的更新并实现及时更改。元 API 工具需要更深入地集成到开发和运营活动中,以提高敏捷性。今天的元 API 工具在整个技术栈中都是孤立的、被动的和不相关的。将来,元工具需要将服务交互的变化性质反映为事件驱动的方法,并在自动化开发和运营团队的一些常规任务方面发挥更积极的作用。
新兴趋势
出站事件的兴起
出站事件已经成为大多数现代平台的首选集成方法。大多数云服务都会发出事件。许多数据源(例如 Cockroach changefeeds、MongoDB 更改流)甚至文件系统(例如 Ceph 通知)都可以发出状态更改事件。定制的微服务在这里也不例外。发出状态更改或域事件是现代微服务统一匹配它们所连接的事件驱动系统,以便从相同的工具链和实践中受益的最自然方式。
出于多种原因,出站事件必然会成为顶级微服务设计构造。设计具有出站事件的服务有助于在应用程序现代化过程中复制数据。出站事件还能通过发件箱模式和使用非阻塞 Saga 实现跨越多个服务的复杂业务事务,来实现优雅的服务间交互。
出站事件非常适合分布式数据网格架构。在这种架构中,服务从设计之初就考虑了自己的数据消费者。数据网格声称,对于推动创新的数据,其所有权必须由负责将其数据作为产品提供的域数据所有者之间联合承担。简而言之,与其让一个中心化的数据工程团队通过 ETL 流程从每个微服务复制数据,不如让微服务与开发人员和数据工程师共同拥有,并在设计服务时一开始就让数据可用。有什么比通过 Debezium、Apache Kafka 和 Schema Registry 使用实时数据流传输出站事件更好的方法呢?
总而言之,出站事件让微服务得以符合 Unix 哲学,即“每个程序的输出成为尚未知程序的输入”。为了让你的服务迎接未来的挑战,你在设计服务时需要让数据从入站 API 流向出站 API。这让我们可以使用现代化的面向事件工具和模式统一开发和运维所有服务,并在将来解锁通过事件公开的数据的更多未知用途。
元 API 工具的融合
随着事件驱动架构的采用增长、服务演进速度不断加快,元 API 的职责和重要性也在上升。元 API 工具的作用域不再局限于同步 API,还包括了异步 API。元 API 正在持续发展,通过兼容性检查、更新通知、绑定代码生成、测试模拟等途径促进安全的模式演变,从而实现更快的开发周期。
作为服务的消费者,我想在同一处位置发现已有的端点和数据格式、API 兼容性规则、限制和服务遵守的 SLA。同时,我希望收到关于即将发生的任何更改、任何弃用、API 更新或我可能感兴趣的服务将提供的任何新 API 的通知。不仅如此,开发人员还面临着越来越快地交付代码的挑战,而现代 API 工具可以自动化模式和事件结构发现的过程。一旦一个模式被发现并添加到了注册表中,开发人员就可以快速为其语言生成代码绑定并开始在 IDE 中进行开发工作。然后,其他工具可以使用元 API 定义并生成测试和模拟(mock),并使用 Microcks 甚至 Postman 之类的东西发出伪事件来模拟负载。在运行时,元 API 中可用的上下文信息可以使让我正在运行应用程序的平台注入连接凭据,将其注册到监控工具,等等。
总体而言,元 API 正在异步交互生态系统中发挥更积极的作用,包括自动化服务所有者之间的一些协调活动、提高开发人员生产力和自动化运营团队的许多任务。为了实现这一点,包含 API 元数据、代码生成、测试刺激、环境管理的各种工具必须更好地融合、标准化和集成。
事件驱动空间的标准化
事件驱动架构(EDA)有着悠久的历史,而最近的一些推动因素,如云计算的流行、微服务架构和更快的变革步伐,都提高了 EDA 的重要性和采用率。正如 Kubernetes 及其生态系统在平台空间中发生的整合和标准化过程,围绕 Apache Kafka 的事件驱动空间中也存在着整合和社区驱动的标准化趋势。我们来看几个具体的例子。
Apache Kafka 已成为事件流的事实标准平台,就像 AWS S3 之于对象存储,Kubernetes 之于容器编排一样。Kafka 背后有一个庞大的社区、一个大型的工具和服务开源生态系统,并且可能是现代数字组织采用的最大的事件基础设施。业内存在各种各样的由专业公司和云提供商提供的自托管 Kafka 产品和托管服务,最近 Red Hat 也加入了这一行列。(Red Hat OpenShift Streams for Apache Kafka 是我参与开发的一项托管 Kafka 服务,我很想听听大家的反馈意见。)
人们经常用 Kafka 作为基于日志的消息传递 API,甚至 Pulsar、Red Panda 和 Azure 事件中心等非 Kafka 项目也提供了对它的兼容性。今天的 Kafka 不仅仅是一个第三方架构依赖。Kafka 影响了服务的设计和实现方式,决定了系统实现扩展和高可用的路径,并驱动用户基于它来实时消费数据。但 Kafka 本身就像一个没有任何 Pod 的裸 Kubernetes 平台。下面我们来看看 Kafka 生态系统中还有哪些必不可少的补充内容,并且这些内容也正在成为事实标准。
模式注册表(Schema Registry)对异步 API 来说就像是 API 管理器对于同步 API 一样重要。在许多流场景中,事件负载包含了生产者和消费者都需要理解和验证的结构化数据。模式注册表为模式文档提供了一个中央存储库和一个通用治理框架,并使应用程序能够遵守这些契约。今天市面上有很多注册表,例如 Red Hat 的 Apicurio、Aiven 的 Karapace,还有来自 Cloudera、Lenses、Confluent、Azure、AWS 等厂商的注册表。虽然模式存储库越来越受欢迎,并且围绕模式管理的功能和实践也在不断充实,但同时它们的许可限制也有很多不同。不仅如此,模式注册表往往会以 Kafka 序列化器 / 反序列化器(SerDes)、转换器和其他客户端依赖的形式泄漏到客户端应用程序中。因此人们很快意识到,需要一个开放和供应商中立的标准来切换实现。好消息是 CNCF 提出了模式注册表 API 标准提案,并且 Apicurio 和 Azure Schema Registry 等注册表已经开始遵循它了。用开源服务注册表 API 和通用治理实践作为开源 Kafka API 的补充看起来是正确的做法,我希望这个领域能有越来越多的采用和整合过程,使整个元 API 概念成为事件驱动架构的基石。
与 EDA 类似,更改数据捕获(CDC)的概念也并不新鲜。但最近围绕事件驱动系统的驱动因素,和对实时数据访问的日益增长的需求正在为事务日志驱动的事件流工具开辟道路。今天,市面上有许多闭源、点击式工具(如 Striim、HVR、Qlik)依赖同样的事务日志概念来点对点复制数据。有一些云服务,例如 AWS DMS、Oracle GoldenGate Cloud Service 和 Google Datastream 会将你的数据流式传输到它们的服务中(但反过来是不行的)。有许多数据库和键值存储也可以流式传输更改。人们越来越希望看到一种开源、供应商中立的 CDC 标准,不同供应商都可以遵循这种标准、下游的更改事件消费者也可以依赖它。
为了取得成功,这样的标准必须在供应商中立的基础上进行管理,并成为更广阔的生态系统的一部分。目前最接近这一目标的是 CNCF,它已经诞生了 AsyncAPI、CloudEvents、Schema Registry 和 Serverless Workflow 规范。目前,CDC 领域领先的开源项目是 Debezium。Debezium 得到了很多大公司的使用,嵌入到了 Google、Heroku、Confluent、Aiven、Red Hat 的云服务和多个开源项目中,并被许多我们无法知晓的专有解决方案使用。如果你正在寻找这一领域的标准,最接近事实标准的就是 Debezium。
澄清一下,谈到 CDC 标准,我并不是指数据源发出更改的 API。我的意思是说数据源和连接组件(例如 Debezium)在将数据库事务日志转换为事件时要遵循的标准约定。这包括了数据映射(从数据库字段类型到 JSON/Avro 类型)、数据结构(例如 Debezium 的 Before/After 消息结构)、快照、将表划分为主题、将主键划分为主题分区、事务划分指示符等等。如果你非常重视 CDC,使用 Debezium 将确保从数据库事务日志条目映射到跨数据源统一的 Apache Kafka 事件的语义都是一致的。
围绕 Apache Kafka 生态系统的规范和实现
CNCF 的事件驱动领域已经有一些规范正在获得关注。
AsyncAPI 是用于事件驱动应用程序的 OpenAPI 的等效实现,最近加入了 CNCF。它提供了一个规范来为你的事件驱动系统制订文档,以保持不同团队和工具之间的一致性和统一治理。
CloudEvents(也是 CNCF 的一部分)旨在将强制性元数据信息指定到可以称为一个标准信封的内容中,来消除元数据挑战。它还为多种协议的多种编程语言提供了库,从而简化了互操作性。
OpenTelemetry(另一个 CNCF 沙箱项目)标准化了跟踪信息的创建和管理工作,这些信息通过多个应用程序揭示事件的端到端路径。
CNCF Serverless Workflow 是一个供应商中立的规范,用于协调异步无状态和有状态交互。
还有我们上面讨论的 CNCF 中的服务注册表提案。
无论我们称之为标准化、社区采用还是其他什么东西,我们都不能忽视围绕事件驱动构造的整合过程,并看到一些开源项目正在崛起成为事实标准。
总结
微服务的重心一直是封装属于某个业务领域的数据,并通过尽可能少的 API 将其公开。但这种情况正在改变。离开服务的数据与进入服务的数据同样重要。在微服务中公开数据不再是事后才补上的概念。封装在高度解耦的微服务中的孤立且不可访问的数据是没多少价值的。数据的新用户和可能出现的用户需要访问可发现、可理解的实时数据。为了满足这些用户的需求,微服务必须将数据由内而外送出去,并在设计中加入可以发出数据的出站 API,和使数据消费成为自助活动的元 API。Apache Kafka、Debezium 和 Apicurio 等项目是这种架构的自然推动者,在各种开源异步规范的帮助下,它们正在成为实现面向未来的事件驱动微服务的事实选项。
作者介绍:
Bilgin Ibryam 是 Red Hat 的产品经理和前架构师,是 Apache 软件基金会的提交者和成员。他是开源布道者、博客作家、演讲者和《Kubernetes 模式》《Camel 设计模式》等书的作者。Bilgin 目前的工作重点是分布式系统、事件驱动架构和可重复的云原生应用程序开发模式与实践。可以在 Twitter 上关注 @bibryam,以获取这方面主题的未来更新内容。
原文链接:
评论