当我们跨多个微服务进行内部通讯的时候,异步消息和事件驱动至关重要。我们可能需要在不同的边界上下文中进行域模型的更新。
我们举个例子,比如 eShop 这个项目中,Ording 服务在下单的时候要和 Catelog 服务进行通讯进行库存的扣减操作,这个时候我们就需要一种方式来做这个事情,并且能够在发生故障的时候也能正常工作,也就说需要进行基于异步消息和最终一致性的通讯方式。
当使用基于消息的通讯方式的时候,进程中是采用的异步的方式通讯的。客户端向某个服务发送消息,如果这个消息需要回复,那么另一个服务会向客户端发送一个不同的消息,并且客户端会认为该消息不会立即被接收到,并且不存在响应,这就是一种基于消息的通讯方式。
消息由标题(name 或者 title)和内容(Body)共同构成。消息通常会通过一些异步协议进行发送(如AMQP,kafka协议)。
异步消息通讯有两种:一种是单接收者(端到端),另外一种是多接收者(广播)。
如果有同学对消息队列比较了解的话,这就是消息队列的两种典型使用方式。
基于消息的单接收者
单接收者也就是说是点到点的通讯,将消息使用队列等方式从一点发送的另外一点,并且该消息仅会被处理(消费)一次。这中间一个特殊情况就是,当队列在尝试从故障中恢复时候,有可能会多次发送相同的消息,客户端必须实现幂等性以便能够处理相同的消息一次。
单接收器消息通讯的方式适用于将异步命令从一个微服务发送到另一个微服务。如下图:
一旦开始使用了基于消息的通讯,你应该避免将基于消息的通讯和同步的HTTP通讯混合起来。
注意:当command来到客户端应用程序时候,它们可以实现为HTTP的同步命令。当你需要更高的可扩展性或者你业务流程中已经使用了基于消息的方式时,那么你就应该使用基于消息的通讯方式。
基于消息的多接收者
多接收者是消息通讯中一种更加灵活的方式,你可能还需要使用 发布/订阅 这种机制,以便于接收来自发送方或者其他微服务或者外部应用程序的消息。 这样,将来可以添加更多的其他消费者用户,而无需修改发送方的服务代码。
当你使用发布/订阅这种通讯方式的时候,在发送端和订阅端你也许会用到事件总线的接口。
异步事件驱动通讯
当使用异步事件驱动通信时, 一个微服务当域模型发生更新时,会发布一个集成事件,然后另外一个微服务可能需要关注这个事件,比如 eShop 中,当 product catelog 微服务发生一个价格变动的时候。另外的微服务需要订阅这个事件,这样就可以以异步的方式来接收这个事件。然后当事件触发的时候,订阅端就可以更新自己的 Domain Model,从而集成发送端的事件。 事件总线(Event Bus)可以设计为一个抽象类或接口,集成API 订阅或取消订阅事件和发布事件。事件总线还可以有一个或多个实现基于任何进程间消息传递代理,像一个消息队列或服务总线支持异步通信和发布/订阅模型。
如果事件驱动中集成了最终一致性,那么用户应该清楚这种行为,客户端用户及其业务必须显式地拥抱最终一致性并且意识到在许多情况下这种业务没有任何问题。
你可以跨越多个微服务来集成事件驱动,这些服务之间拥有最终一致性。 一个最终一致性的“事务”可能是由多个分布式的事件操作组成的一个集合。在每一个事件中,相关的微服务都在更新自己的领域实体并且发布另外一个需要集成的事件到Eventbus中。
很重要的一点是 , 你可能需要多个微服务订阅一个事件。因此, 您可以使用基于事件驱动的发布/订阅消息模式的消息通讯, 如下图所示。这种发布/订阅的机制不是微服务独有的。它类似于DDD中边界上下文之间的通讯方式, 或者类似于CQRS架构中的从写库更新数据到读库的这种模式。它最终的目标是在整个分布式系统多个数据源之间的保持最终一致性。
你将实现基于消息的事件驱动通信协议。AMQP可以帮助实现可靠的排队通信。
当您使用事件总线时,您可能希望使用的是抽象级别的东西(如Eventbus interface),它使用类似于 RabbitMQ 或服务总线(如Azure Service Bus及 Topic)来作为底层,然后提供相关API。或者,您可能希望使用更高级别的服务总线,如NServiceBus,MassTransit或Brighter来作为Eventbus和发布/订阅系统。
关于生产环境中的消息通讯技术
在消息通讯技术中,实现抽象级别的事件总线是存在不同的级别的。例如,像RabbitMQ 和Azure Event Bus这样的产品比其他产品(如NServiceBus,MassTransit或Brighter)级别就更低一些,NServiceBus这些他们可能基于底层的这些之上,当然后者也更加的重量级。
但是很多时候,我们可能学习这些重量级的东西需要花费很多的成本,而且我们也用不到那么重量级的东西,正如在eShopOnContainers示例中所做的那样,在Docker容器上运行的RabbitMQ之上的简单实现可能就足够了。
但是,在生产系统中对于需要可扩展性的关键性任务,您可能需要进行评估一下。 为了使分布式应用程序开发更容易的并且提供高级抽象的功能,我们建议您评估其他商业和开源的服务总线,如NServiceBus,MassTransit和Bright。当然,您可以在像RabbitMQ和Docker这样的低级技术的基础上构建自己的服务总线功能。但是,这种工作对于企业应用来说可能花费的太多。
异步消息解决方案
到这里,我们会发现,我们真的是太需要这么样一个组件来帮助我们实现这些东西了,既能提供高抽象级别的API帮助我们简化操作,又能轻量级并且容易学习和集成到项目中,并且能够帮助我们解决分布式事务中的一致性问题,如果是开源免费的,那就更好了。