微服务通信原则:智能终端和哑管道

大部分公司迁移到微服务架构面临的一个挑战是如何实现微服务之间的通信。

在过去单体架构中,各个组件都在同一个进程中运行,相互通信只是相互的函数的调用而已。但是在微服务环境中,组件之间是由服务器硬性边界分隔了,一般位于不同的VM或JVM或服务器中,相互之间需要通过端口进行通信。

解决微服务间通信问题的最好方法就是遵循Martin Fowler 2014年定义微服务的论文中的“智能端点和哑管道”原则。

本文阐述了Martin Fowler的这个微服务通信概念,并解释了如何遵循“哑管”范式构建微服务通信的现代技术。


微服务通信的类型
微服务之间有两种主要的通信形式:
1. 请求-响应:一个服务通过发出指定请求来调用另一个服务,这种方式通常用于存储或检索数据。该服务然后等待响应:得到某个响应资源或响应确认。

2.观察者:基于事件的隐式调用,其中一个服务发布事件,其他一个或多个正在监视该事件的观察者通过异步运行逻辑来响应该事件。

为了说明这两种通信类型,以社交媒体应用程序的用户注册流程为例。

当用户注册创建账户时,客户端向API端点发出POST请求,向后端API发送其基本概要信息,如名称,密码,电子邮件地址等。由于这是微服务架构,因此这个请求需要调用几个后端微服务来处理底层的功能。

有一个核心用户元数据服务,是用于存储基本的配置文件信息,还有一个密码服务,用于密码明文存储,以供用户登录后进行比较。还有其他一些微服务在后台运行以响应用户注册:比如发送一封电子邮件要求用户点击一个链接来验证他们的电子邮件地址,还有一种微服务是搜索朋友以便将这新注册的用户推荐给他们。

最初的两种请求是保存配置文件信息和密码,这毫无疑问是明确使用请求 - 响应通信方式。这种方式是同步堵塞式的,客户端会一直堵塞等待数据都被保留在后端以后返回200 OK响应。但是,客户端是不需要等待验证电子邮件被发送出去以后或推荐朋友以后才返回响应。对于这两个功能,最好使用观察者模式,即后端服务监视用户注册事件并异步触发。

了解何时何地使用请求 - 响应模型与观察者模型是设计有效的微服务通信的关键。


反模式:集中式服务总线
建立一个复杂的集中式通信总线,在其中根据逻辑进行路由和转发消息是架构设计中的陷阱,这样做往往会导致很多问题。相反,微服务更倾向于采取分散的方法:微服务使用哑管(非智能,不具备智能路由等高级功能的消息系统)来从一个端点到另一个端点获取消息。


乍看起来,中央总线的图形不如在微服务产生的许多直接连接通信的网络可怕。但是,重要的是要考虑在中央总线里其实还是存在相同的连接。只是所有这些通信路径现在都嵌入到一个单体组件中了。这种中央单体总线会是的系统变得过于复杂,并且从性能角度和工程角度都可能成为瓶颈或单点风险。

分散的微服务通信使开发团队能够在架构的不同边界上进行并行工作,同时不会破坏其他组件。如果每个微服务将其他服务视为外部资源,而不必区分内部服务和外部资源,则意味着每个微服务都封装了自己的格式化输入/输出逻辑。这使得团队可以独立添加功能或修改现有功能,而无需修改中央总线。缩放扩展微服务通信也是分散的,以便每个服务可以有自己的负载平衡器和缩放逻辑。

请求 - 响应模型
微服务之间的请求 - 响应通信用于一个服务发送请求并期望返回资源或确认响应。
实现这种模式的最基本的方法是使用HTTP,最好遵循REST原则。两个微服务之间的标准HTTP通信管道通常如下所示:

原始服务 --> Http负载平衡器 ---->后端服务

在这种方法中,有一个简单的负载均衡器可以位于服务通信中间,原始服务可以向负载均衡器发出HTTP请求,负载平衡器可以将该请求转发到后端微服务的其中任何一个实例。

但是,在某些情况下,服务之间的流量非常高,或者开发工程团队希望尽可能减少微服务之间的延迟。在这种情况下,他们可能采用胖客户端负载平衡。这种方法通常使用诸如Consul,Eureka或ZooKeeper之类的系统来跟踪微服务实例及其IP地址的集合。然后,发起的微服务可以直接向需要与之通话的后台服务实例发出请求。

Consul维护一个解析到不同后端微服务实例的DNS记录,以便一个服务可以在没有负载均衡器情况下直接与另一个服务对话,。

值得注意的框架是GRPC,它已经成为多语言应用程序的强大武器。GRPC可以使用外部负载均衡器进行操作,类似于上面的HTTP方法,也可以使用胖客户端负载均衡器。然而,GRPC的突出特点是它将通信负载转换成一种称为协议缓冲区protocol buffers的通用格式。

protocol buffers允许后端服务将通信负载序列化为有效的二进制格式以便通过线路传输,然后将其反序列化为适合其特定运行时的对象模型。

观察者模型
观察者模型对于扩展微服务至关重要。并不是每个通讯都需要回应或确认。事实上,在许多长时间的工作流程中,至少有一些逻辑实现应该是完全异步和非阻塞的。

分发这种类型的工作负载的标准方式是使用代理服务来传递消息,理想的是实现一个队列。RabbitMQ,ZeroMQ,Kafka甚至Redis Pub / Sub都可以用作哑管道,它们允许微服务发布事件,同时允许其他微服务订阅他们需要感兴趣的事件。

这种方法的巨大优势在于,发布服务方不需要知道事件订阅了多少用户,或者这些订阅者做什么事情来响应自己的事件。在消费者发生故障的情况下,大多数队列系统具有重试/重新发送功能以确保消息最终被处理。生产者只需“发送然后忘记”即可,相信消息代理的队列将确保消息最终到达正确的消费者。即使所有消费者都忙于无法立即回应事件,队列仍然会持久保存,直到消费者准备好处理事件。

观察者模型的另一个好处是微服务系统的未来可扩展性。一旦在生产者服务中实现了事件广播,新的消费者类型可以在事件发生之后被添加和订阅,而不需要改变生产者。例如,在本文开头的社交媒体应用程序中,有两位消费者订阅了用户注册事件:电子邮件验证服务和朋友推荐服务。工程师可以轻松地添加第三个服务,回复用户注册事件,通过电子邮件发送所有通信录中名单有该新用户的哪些人群,让他们知道他们的联系人刚刚注册。这项新功能完全不需要对原来的核心功能用户注册服务进行任何更改,从而消除了增加新功能可能会破坏关键的用户注册功能的风险。

观察者模型是微服务部署中非常强大的工具,不实现观察者通信的微服务架就无法发挥其全部潜力。

结论
当你接受架构和逻辑分离的概念以后,智能端点和哑管的微服务原理就很容易理解。尽管使用“哑管”,微服务还是可以实现基本的消息传递,也就不需要集中式的中央服务总线。相反,微服务应该利用那些在请求响应和观察者通信中都是傻瓜式管道或称哑管的中间件系统。


Microservice Principles: Smart Endpoints and Dumb