微服务架构的进程间通信(RPC)
1. 进程间通信(RPC)
在单体式应用中,各个模块之间的调用是通过编程语言级别的方法或者函数来实现的。但是一个基于微服务的分布式应用是运行在多台机器上的。
一般来说,每个服务实例都是一个进程。因此,如下图所示,服务之间的交互必须通过进程间通信(RPC)来实现。
2. 客户端与微服务的交互模式
交互模式可以从两个维度进行归类。(1)第一个维度是一对一还是一对多:
- 一对一:每个客户端请求有一个服务实例来响应。
- 一对多:每个客户端请求有多个服务实例来响应。
(2)第二个维度是这些交互式同步还是异步:
- 同步模式:客户端请求需要服务端即时响应,甚至可能由于等待而阻塞。
- 异步模式:客户端请求不会阻塞进程,服务端的响应可以是非即时的。
(3)一对一的交互模式有以下几种方式:
- 请求/响应:一个客户端向服务器端发起请求,等待响应。客户端期望此响应即时到达。在一个基于线程的应用中,等待过程可能造成线程阻塞。
- 通知(也就是常说的单向请求):一个客户端请求发送到服务端,但是并不期望服务端响应。
- 请求/异步响应:客户端发送请求到服务端,服务端异步响应请求。客户端不会阻塞,而且被设计成默认响应不会立刻到达。
(4)一对多的交互模式有以下几种方式:
- 发布/订阅模式:客户端发布通知消息,被零个或者多个感兴趣的服务消费。
- 发布/异步响应模式:客户端发布请求消息,然后等待从感兴趣服务发回的响应。
下表显示了不同交互模式:
每个服务都是以上这些模式的组合,对某些服务,一个RPC机制就足够了;而对另外一些服务则需要多种RPC机制组合。下图展示了在一个打车服务请求中服务之间是如何通信的。
上图中的服务通信使用了通知、请求/响应、发布/订阅等方式。例如,乘客通过移动端给『行程管理服务』发送通知,希望申请一次出租服务。『行程管理服务』发送请求/响应消息给『乘客服务』以确认乘客账号是有效的。紧接着创建此次行程,并用发布/订阅交互模式通知其他服务,包括定位可用司机的调度服务。
3.客户端与服务端接口API
不管选择了什么样的RPC机制,重要的是使用某种交互式定义语言(IDL)来精确定义一个服务的接口API。接口API的定义实质上依赖于选择哪种RPC。如果使用消息机制,API则由消息频道(channel)和消息类型构成;如果选择使用HTTP机制,API则由URL和请求、响应格式构成。API的变化是不可避免的,微小的改变可以和之前版本兼容。比如,你可能只是为某个请求和响应添加了一个属性。这时,客户端使用旧版API应该也能和新版本一起工作。但是有时候,API需要进行大规模的改动,并且可能与之前版本不兼容。因为你不可能强制让所有的客户端立即升级,所以支持老版本客户端的服务还需要再运行一段时间。如果你正在使用基于基于HTTP机制的RPC,例如REST,一种解决方案是把版本号嵌入到URL中。每个服务都可能同时处理多个版本的API。或者,你可以部署多个实例,每个实例负责处理一个版本的请求。
4.容错处理
分布式系统中部分失败是普遍存在的问题。因为客户端和服务端是都是独立的进程,一个服务端有可能因为故障或者维护而停止服务,或者此服务因为过载而停止或者反应很慢。假设推荐服务无法响应请求,那客户端就会由于等待响应而阻塞,这不仅会给客户带来很差的体验,而且在很多应用中还会占用很多资源,比如线程,以至于到最后由于等待响应被阻塞的客户端越来越多,线程资源被耗费完了。如下图所示:
Netfilix Hystrix提供了一个比较好的解决方案,具体的应对措施包括:
- 网络超时:当等待响应时,不要无限期的阻塞,而是采用超时策略。使用超时策略可以确保资源不会无限期的占用。
- 限制请求的次数:可以为客户端对某特定服务的请求设置一个访问上限。如果请求已达上限,就要立刻终止请求服务。
- 断路器模式(Circuit Breaker Pattern):记录成功和失败请求的数量。如果失效率超过一个阈值,触发断路器使得后续的请求立刻失败。如果大量的请求失败,就可能是这个服务不可用,再发请求也无意义。在一个失效期后,客户端可以再试,如果成功,关闭此断路器。
- 提供回滚:当一个请求失败后可以进行回滚逻辑。例如,返回缓存数据或者一个系统默认值。
5.RPC实现技术
服务之间的通信采用同步的请求/响应模式,可以选择基于HTTP的REST或者Thrift。服务之间的通信采用异步的、基于消息的通信模式,可以选择AMQP或者STOMP。大量开源消息中间件可供选择,比如RabbitMQ、Apache Kafka、Apache ActiveMQ和NSQ。消息格式可以选择基于文本的,比如 JSON和XML;二进制格式(效率更高)的,比如Avro和Protocol Buffer。
5.1 采用异步的,基于消息的通信模式
下图展示了打车软件如何使用消息发布/订阅:
行程管理服务在发布-订阅channel内创建一个行程消息,并通知调度服务有一个新的行程请求,调度服务发现一个可用的司机然后向发布-订阅channel写入司机建议消息(Driver Proposed message)来通知其他服务。
5.2 采用同步的,基于请求/响应的通信模式
下图展示了打车软件如何使用REST:
乘客通过移动端向行程管理服务的/trips资源提交了一个POST请求。行程管理服务收到请求之后,会发送一个GET请求到乘客管理服务以获取乘客信息。当确认乘客信息之后,紧接着会创建一个行程,并向移动端返回201状态码响应。
使用基于HTTP的协议的好处:
- HTTP非常简单并且大家都很熟悉。
- 可以使用浏览器扩展(比如Postman)或者curl之类的命令行来测试API。
- 内置支持请求/响应模式的通信。
- HTTP对防火墙友好。
- 不需要中间代理,简化了系统架构。
使用基于HTTP的协议的不足之处:
- 只支持请求/响应模式交互。可以使用HTTP通知,但是服务端必须一直发送HTTP响应才行。
- 因为客户端和服务端直接通信(没有代理或者buffer机制),在交互期间必须都在线。
- 客户端必须知道每个服务实例的URL。客户端必须使用服务实例发现机制。