大部分事件驱动型微服务至少遵循以下 3 个步骤。
- 01. 从输入事件流中消费事件。
- 02. 处理事件。
- 03. 生产任何所需的输出事件。
还有一些事件驱动型微服务从同步的“请求–响应”交互中得到它们的输入事件,第 13 章将详细介绍。本章仅介绍从事件流中获取事件的微服务。
在来源于流的事件驱动型微服务中,微服务实例会创建一个生产者客户端和一个消费者客户端,并将其自身注册到任何必要的消费者组(如果适用的话)中。微服务开始一个循环以使消费者客户端轮询获得新的事件,当事件到来时处理它们并发送任何所需的输出事件。这个工作流如下面的伪代码所示。(你的实现肯定会因所用的开发语言、流处理框架、事件代理的选择以及其他技术因素而不同。)
processEvent 函数相当有趣。这是事件处理工作真正完成的地方,主要是应用程序业务逻辑以及确定要发出哪些事件(如果有的话)。最好将此处理函数视为微服务处理拓扑的入口点。从这里开始,数据驱动的模式会对数据进行转换并处理以满足界限上下文的业务需求。
构建无状态拓扑
构建微服务拓扑需要以事件驱动方式进行思考,因为代码会在消息到达消费者输入端时响应执行。微服务的拓扑结构本质上是对事件执行的一系列操作。它需要选择必要的过滤器、路由器、转换、物化、聚合和执行业务逻辑所需的其他函数。熟悉函数式编程和大数据 MapReduce 风格框架的人对此会很熟悉。对其他人来说,这可能是一个新概念。
考虑图 5-1 所示的拓扑。该拓扑每次只消费一个事件,并且根据阶段 1 和阶段 2 的转换逻辑对事件进行处理。
键 A 和键 C 的事件都遍历了整个拓扑。它们的值都大于 10.0,这让它们通过了阶段 1,而阶段 2 只需移除事件值的小数部分。键 B 的事件被过滤了,因为它不满足阶段 1 的条件。
转换
转换会对单个事件进行处理并发出 0 个或更多输出事件。如你所猜测的那样,转换提供了需要做转换的大部分业务逻辑操作。根据操作的不同,可能需要对事件进行再分区(稍后将详细介绍)。通用的转换包括但不限于以下几项。
过滤
如果满足必要的条件,则传播事件,发出 0 个或 1 个事件。
映射
更改事件的键或值,只发出 1 个事件。请注意,如果更改键,则可能需要重新划分事件以确保数据位置正确。
映射值
只更改事件的值,而不更改键,只发出 1 个事件,不需要重新划分事件。
自定义转换
应用自定义逻辑、查找状态,甚至与其他系统同步通信。
分流与合流
消费者应用程序需要对事件流进行分流,即对事件应用逻辑运算,然后根据结果将其输出到新流。一个相对常见的场景是消费接踵而来的事件并根据特定属性(例如,国家/地区、时区、来源、产品或任何数量的特性)决定将它们路由到何处。第二种常见场景是将结果发送到不同的输出事件流,比如在处理错误时将事件输出到死信流,而不是完全丢弃它们。
应用程序还需要合并流,它消费来自多个输入流的事件,可能以某种有意义的方式进行处理,然后输出到单个输出流。需要将多个流合并为一个流的场景并不太多,因为微服务通常需要从尽可能多的输入流中消费以实现其业务逻辑。第 6 章将讨论如何以一致和可复制的顺序消费并处理来自多个输入流的事件。
如果最终要合并事件流,请定义一个新的表示合并事件流领域的统一 schema。如果这个领域没有意义,那么最好不要合并流,而是重新考虑系统设计。