微服务体系架构中, 每个微服务都是一个独立的进程. 服务之间的交互必须通过一定的进程间通讯机制来实现.
服务交互的形式
服务之间的交互可以从两个角度来考虑.
- 一种是一对多,还是一对一.
一对一: 每个客户端请求都由一个服务实例处理。
一对多: 每个请求由多个服务实例处理。 - 第二个角度是同步交互还是异步交互
同步——客户端期待服务的及时响应,甚至可能在等待时阻塞。
异步——客户机在等待响应时不会阻塞,并且响应(如果有的话)不必立即发送。
结合两种角度, 如果微服务交互形式是一对一的:
对于同步的要求, 则可以使 Request/Response 模式.
对于异步的要求, 可以是 Notification和 Request/async response 模式.
如果对于一对多:
对于同步的要求, 实际并不符合.
对于异步的要求, 可以是 Publish/Subscribe和 Request/async response 模式.
下面列举下几种形式:
- Request/Response——客户端向服务发出请求并等待响应。客户希望得到及时的响应。在基于线程的应用程序中,发出请求的线程甚至可能在等待时阻塞。
- Notification(也称为单向请求)——客户端向服务发送请求,但不期望或发送响应。
- Request/Async Response——客户端向服务发送请求,服务异步响应。客户端在等待时不会阻塞,并且在设计时假定响应可能在一段时间内不会到达。
有以下几种一到多种互动: - Publish/Subscribe——客户端发布一条通知消息,由零个或多个感兴趣的服务使用。
- Publish/Async Response——客户端发布一条请求消息,然后等待一段时间,等待感兴趣的服务的响应。
在微服务的实际应用中, 可能只使用以上一种形式, 也可能是几种形式的组合来进行.
微服务之间通信注意的问题
- API的定义
服务的API总是随着时间而变化。在单例应用程序中,更改API和更新所有调用方通常很简单。在基于微服务的应用程序中,这要困难得多,即使您的API的所有消费者都是同一应用程序中的其他服务。我们通常不能强制所有客户端与服务同步升级。此外,我们可能会逐步部署服务的新版本,以便同时运行服务的新旧版本。有一个处理这些问题的策略是很重要的。
如何处理API更改取决于更改的大小。一些更改是次要的,并且向后兼容以前的版本。例如,可以向请求或响应添加属性。设计客户端和服务以便它们遵守健壮性原则是有意义的。使用旧API的客户机应该继续使用新版本的服务。该服务为丢失的请求属性提供默认值,客户端忽略任何额外的响应属性。重要的是使用通信机制和消息传递格式, 设计之初要考虑到扩展性。
然而,有时必须对API进行重大的、不兼容的更改。由于不能强制客户端立即升级,服务必须在一段时间内支持API的旧版本。如果使用基于HTTP的机制(如REST),一种方法是在URL中嵌入版本号。每个服务实例可以同时处理多个版本。或者,可以部署不同的实例,每个实例处理特定的版本。
- 处理并发调用失败的问题
在分布式系统中存在部分故障的风险。由于客户机和服务是独立的流程,服务可能无法及时响应客户机的请求。服务可能因为故障或维护而停机。或者服务可能过载,响应请求非常慢。
Netflix描述了一个很好的方法。处理部分失败的策略包括:
-
网络超时——永远不要无限期阻塞,在等待响应时总是使用超时。使用超时可以确保资源不会无限期地占用。
-
限制未完成请求的数量——对客户端可以使用特定服务的未完成请求的数量施加上限。如果已经达到极限,那么提出额外的请求可能是没有意义的,这些尝试需要立即失败。
-
断路器模式-跟踪成功和失败请求的数量。如果错误率超过配置的阈值,则跳闸断路器,使进一步的尝试立即失败。如果大量请求失败,说明服务不可用,发送请求是没有意义的。超时一段时间后,客户端应再次尝试,如果成功,应关闭断路器。
-
提供回退—当请求失败时执行回退逻辑。例如,返回缓存的数据或默认值,如空建议集。
Netflix Hystrix是一个实现这些模式和其他模式的开源库。
4. 进程间交互的方式, 主要分为, 消息队列, REST和Thrift.
下面做一简单的介绍:
异步消息传递
使用消息传递时,流程通过异步交换消息进行通信。客户机通过发送消息向服务发出请求。如果期望服务进行响应,则通过向客户机发送单独的消息来实现。由于通信是异步的,所以客户机不会阻塞等待响应。
有许多消息传递系统可供选择。您应该选择一个支持多种编程语言的。一些消息传递系统支持标准协议,如AMQP和STOMP。其他消息传递系统具有专有的但文档化的协议。有大量的开源消息传递系统可供选择,包括RabbitMQ、Apache Kafka、Apache ActiveMQ和NSQ。在高层次上,它们都支持某种形式的消息和通道。它们都力求可靠、高性能和可伸缩。然而,每个代理的消息传递模型的详细信息存在显著差异。
REST
现在流行以RESTful风格开发api。REST是一种(几乎总是)使用HTTP的IPC机制。REST中的一个关键概念是资源,它通常表示业务对象,比如客户或产品,或者业务对象的集合。REST使用HTTP谓词操作资源,这些资源使用URL引用。例如,GET请求返回资源的表示形式,它可能是XML文档或JSON对象的形式。POST请求创建一个新资源,PUT请求更新一个资源
使用基于HTTP的协议有很多好处:
- HTTP简单而熟悉。
- 您可以使用诸如邮递员之类的扩展在浏览器中测试HTTP API,或者使用curl在命令行中测试HTTP
- API(假设使用JSON或其他文本格式)。
- 它直接支持请求/响应式通信。
- 当然,HTTP是防火墙友好的。
- 它不需要中间代理,这简化了系统的体系结构。
使用HTTP有一些缺点:
- 它只直接支持交互的请求/响应样式。您可以使用HTTP进行通知,但是服务器必须始终发送HTTP响应。
- 因为客户机和服务直接通信(没有缓冲消息的中介),所以它们必须在交换期间同时运行。
- 客户端必须知道位置(即,每个服务实例的URL)。正如在上一篇关于API网关的文章中所描述的,这在现代应用程序中是一个非常重要的问题。客户端必须使用服务发现机制来定位服务实例。
Thrift
Apache Thrift是REST的一个有趣的替代品。它是一个编写跨语言RPC客户机和服务器的框架。Thrift为定义api提供了一个C风格的IDL。使用Thrift编译器生成客户端存根和服务器端框架。编译器为各种语言生成代码,包括c++、Java、Python、PHP、Ruby、Erlang和Node.js。
Thrift接口由一个或多个服务组成。服务定义类似于Java接口。它是一组强类型方法。Thrift方法可以返回一个(可能是空的)值,也可以定义为单向方法。返回值的方法实现交互的请求/响应样式。客户端等待响应并可能抛出异常。单向方法对应于交互的通知样式。服务器不发送响应。
Thrift支持各种消息格式:JSON、二进制和压缩二进制。二进制比JSON更有效,因为它的解码速度更快。而且,顾名思义,紧凑二进制是一种高效的空间格式。当然,JSON对人类和浏览器都很友好。Thrift还为您提供了多种传输协议的选择,包括原始TCP和HTTP。原始TCP可能比HTTP更有效。然而,HTTP是防火墙、浏览器和人类友好的。
小结
微服务必须使用进程间通信机制进行通信。在设计服务如何通信时,我们需要考虑各种问题:服务如何交互、如何为每个服务指定API、如何改进API以及如何处理部分故障等.