事件驱动型微服务架构的最佳实践
September 18th 2019
image本文仅做翻译
原文地址:https://hackernoon.com/best-practices-for-event-driven-microservice-architecture-e034p21lk
如果你正从事架构相关的工作,那么你可能正在努力实践微服务架构。而且,估计之前是使用 REST 作为通讯层的协议。现在,越来越多的项目正迁移到事件驱动的架构上来。让我们深入了解一下这个架构的优缺点,以及这个模式导致的一些关键设计决策和常见的反模式。
什么是事件驱动的微服务架构?
在事件驱动架构中,当一个服务执行了一个其他服务关注的操作时,这个服务就会产生一个事件——一个包含了所执行动作的记录。这个事件的作用就是让其他作为这个事件消费者的服务运行他们自己的任务。与 REST 不同,产生事件的服务不需要知道消费这个时间的服务的细节。
这里有一个简单的示例:当网站受到一个订单时,一个“下单”时间就产生了,随后被若干个微服务消费:
- 订单服务会将一个订单记录写到数据库中
- 客户服务会创建一条 客户 记录
- 支付服务则会处理支付的逻辑
事件可以通过多种方式发布。例如,它们可以发布到队列中并确保分发到对应的消费者服务,或者它们可以发布到一个 “发布/订阅”模型的处理流中,这样所有对这个事件感兴趣的服务都可以获取到这个事件。不管是哪种方式,都是 生产者 发布事件,然后 消费者 接收事件并作出相应的处理。注意在某些情况下,这两个角色也可能被称作 发布者(生产者)和 订阅者(消费者)。
为什么要使用事件驱动架构
事件驱动架构相对于 REST 方式提供了一下这些好处:
- 异步处理——基于事件的架构是非阻塞的异步处理模式。这种方式保证了一个原子任务处理完成资源就可以被释放去做其他事情,而不用维护这个原子任务与其他任务的关系。它还能可以将事件放入队列或缓冲区,这样可以防止因为消费者的原因导致生产者压力增大或被阻塞。
- 松耦合——服务之间不需要(也不应该)知道彼此的存在,或者相互依赖。当使用事件时,服务是独立运行的,服务本省并不知道其他服务的存在,包括它们的实现细节以及传输协议。在事件模型下的服务可以更容易的独立的升级、测试、部署。
- 易扩展——既然在事件驱动的架构中服务是不耦合的,并且服务都是只运行一个任务,查找哪一个服务是瓶颈,并且扩展这个服务(仅这个服务)都会很容易。
- 支持恢复——一个使用了队列的事件驱动架构可以“重发”丢失的事件。当用户需要恢复丢失的数据时这是很有价值的。
当然,事件驱动架构也会有它的缺点。在解耦的时候对过于简单的关注点进行分离会导致过度设计;它可能需要大量的前期投入;并且会导致基础设施,服务契约,使用多语言构建系统,依赖关系图的复杂性的额外增加。
或许最重要的瓶颈和挑战是数据和事务处理的管理。因为他们天然是异步的,事件驱动架构需要小心的处理服务间数据不一致,版本冲突,事件重复,并且通常不支持 ACID(原子性、一致性、隔离性、持久性) 事务,取而代之的是最终一致性 这会让定位问题和调试更加的困难。
即使有这些缺点对于企业及的微服务系统来说,事件驱动架构通常还是一个更好的选择。它在扩展性、松耦合、对 dev-ops 友好的好处大过了它的缺点。
何时使用 REST
在如下的情况下 REST 的方式会更合适:
- 你需要一个有时限的 请求/响应 的接口
- 要很方便的支持事务
- 你的 API 是公开的
- 你的项目很小(REST 很容易实现和部署)
你最重要的设计选择 – 消息框架
一旦你决定了使用消息驱动的架构,那么你就需要选择你的时间框架。事件产生和消费的方式是你系统的关键要素。有许多的框架以供选择,要做出正确的决定需要花一些时间进行研究。
你最基础的选择开始于对消息处理或流处理的选择。
消息处理
对于传统的消息处理来说,由一个组件创建消息然后分发到指定的(通常是单个)目标。消息接收组件将会接收消息并作出相应的处理,然后等待新的消息。通常,当消息到达,消息组件会单独执行一个进程。然后消息就被删除。
消息处理架构的一个典型例子就是消息队列。尽管大多数新的项目使用流处理(稍后说明)架构,但是使用消息(或者说事件)队列也是很主流的方式。消息队列通常使用一个通过代理“存储并发送”的系统,事件在代理之间进行路由,知道他们到达合适的消费者。ActiveMQ 和 RabbitMQ是两个主力的消息队列框架。这两个框架都经过了多年的实践并有稳定的社区支持。
流处理
使用流处理是另一种选择,当组件到达一个特定的状态是会发出事件。其他对这个事件感兴趣的组件会监听事件流并最初相应的处理。事件不会被指定一个特定的接受者,但是它对任何想要回去它的组件来说都是可以获得的。
在流处理的方式中,组件可以同时接收多个事件,并且基于多个流和事件进行复杂的操作。一些流包含了持久化的功能,事件可以根据需要保存足够长的时间。
使用流处理,系统可以重新生成历史事件,在事件发生之后再次产生并触发相应的处理,甚至进行滑动窗口计算(sliding-window computation)。例如,它可以从每秒事件流中计算出每分钟的平均CPU使用率。
最流行的时间处理框架是 Apache Kafka。Kafka 是在很多项目中检验过的成熟且稳定的解决方案。它可以作为一种具备工业级强度的解决方案。Kafka 有大量的用户群,一个很好的社区,和一个不断更新的工具集。
其他的选择
也有其他的框架提供消息或流的组合处理或期中一种解决方案。例如, Pulsar, Apache 支持的一个新框架,是一个开源的发布/订阅方式的消息系统同时支持消息和流两种方式,并且都具有极高的性能。Pulsar具有丰富的特性,他支持多租户和跨域发布,当然也相应的更为复杂一些。有人认为 Kafka的目标是高吞吐,而 Pulsar 的目标是低延迟。
NATS 是另一个可选项,它属于发布/订阅模式的消息系统,它使用“同步”的队列。NATS 被设计用户发送小的、高频的消息。它同时支持高吞吐和低延迟。然而,NATS 认为某种程度的数据丢失是可以接受的,性能的优化优先于数据分发的可靠。
其他的设计考量
一旦你选定了消息框架,下面这些问题就需要被考虑了:
事件源
实现一个由多个松耦合服务组合的系统是很困难的,需要独立数据存储,具有原子性的事务。 Event Sourcing模式对此会有帮助。使用事件源,更新和删除永远不会直接作用于数据;取而代之的是保存一些列的事件。
CQRS
上边提到事件源会引入一个新的问题:既然状态是基于一系列的时间,那么查询就会变得慢并且复杂。 Command Query Responsibility Segregation (CQRS) 是一个解决方案,它对插入操作和读取操作使用不同的模型。
查找事件信息
在事件驱动架构中一个最大的挑战是对服务和事件进行分类。你从哪里获得时间的说明和细节呢?事件产生的原因是什么?那个团队创建了这个事件?他们还继续维护这个事件么?
处理变更
事件的结构会改变么?你如何改变一个事件的结构而不影响到其他的服务?随着服务的增加你如何回答这些问题会变得至关重要。
要成为一个好的事件消费者需要在编码实现是考虑到事件结构的变更。成为一个号的事件生产者则意味着要意识到事件结构的变更会影响到其他的服务,并且对事件进行优良的设计并编写清晰的文档。
私有部署还是托管部署
根据你的选择的事件框架,你还需要决定你的服务是私有部署(消息代理的部署并非一件简单的事情,尤其是需要高可用的情况下),或者使用托管的服务,例如Apache Kafka on Heroku.
反模式
和大多数架构一样,一个事件驱动的架构也有它对应的反模式。下边就是一些需要小心的反模式。
过量的好东西
要小心你不要因为创建了大量的事件而感到激动。创建了过量的事件将会导致服务之间不必要的复杂性,增加开发者的认知负担,让开发和测试都更困难,并且导致消费者数量激增。不是每个方法都需要一个事件。
泛化事件
不要使用泛化的时间,不管是对于事件命名还是事件意图。你需要其他团队理解为何存在这个事件,它应该被怎样使用,以及它何时该被使用。事件应当具有一个良好的目的说明,并且被恰当的命名。如果事件有一个泛化的名字,或者使用标志位泛化事件都会让人困惑,引发问题。
复杂的依赖关系
要特别小心服务依赖于一个或多个服务造成复杂的依赖关系,或导致循环反馈。每个网络请求节点都会给原始请求增加额外的延迟,尤其是数据中心之外的 南/北 网络流量。
确定顺序,分发,副作用
事件是异步的;所以,任何基于顺序或重复的假设不仅会增加系统复杂程度,还会抵消掉很多事件驱动架构的关键优势。如果你的消费者有副作用,例如插入一个值到数据库,那么你最要不要重复之前的事件。
过早的优化
大部分产品都由小规模开始,然后随着时间的流逝而增长。虽然你梦想着将来会扩展成大型的复杂组织,但是如果你的团队很小,那么事件驱动架构带来的多余的复杂性将会减慢开发速度。相对的,考虑将你的系统设计为一个简单但是分离了关注点的架构,将来随着它的发展你可以进行切换。
期望事件驱动解决所有问题
在较低的技术水平上,不要指望事件驱动架构能够解决所有的问题。虽然这个架构肯定可以在很多方面提供改进,但是它依然无法解决很多关键的核心问题,例如缺乏自动化测试,缺乏交流,或过时的 dev-ops 实践。
更多内容
理解事前驱动架构的优缺点,以及一些通常的设计决策和挑战,让创建一个好的设计成为可能。
如果你想要学习更多的内容,可以查看 event-driven reference architecture这是我们在 Heroku 上创建的项目,它让你通过一个点击就能把应用部署到 Heroku上。这个参考的架构创建了一个虚拟的网上开发销售商店。