发布于 

微服务:服务间如何通信?

在微服务架构中,会将一个完整的应用程序拆分成一组服务。这些服务之间需要经过协作,通过接口调用,才能组成一个完整的应用。

不同的服务部署在不同的机器上,或者同一个机器的多个容器中,进程间的通信就不可避免了,也变得非常重要。

按种类来分,进程间的通信方式有很多种,比如远程过程调用的 RESTful API 和 gRPC 、基于消息机制的异步方式。

  • RESTful API :现在前后端分离比较流行,RESTful API 大家应该都比较熟悉。REST 是一种使用 HTTP 协议的进程间通信机制,一般使用 Json 来传递数据;
  • gRPC :是一个高性能、开源和通用的 RPC 框架,基于ProtoBuf(Protocol Buffers) 序列化协议开发,且支持众多开发语言。面向服务端和移动端,基于 HTTP/2 设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特;
  • 异步消息:使用消息中间件来实现,比如 RabbitMQ、Kafka 等。

按照交互方式来分,会有同步、异步。

  • 同步:客户端向服务端发起请求、等待服务端响应,等待的过程会造成阻塞;
  • 异步:客户端向服务端发起请求,服务端立即响应,不会造成阻塞,比如说消息队列的发布、订阅方式。

这里有几个概念需要统一下语言:接口、客户端、服务端

  • 接口:如果使用的是消息机制,那么接口就是有消息通道、类型和消息格式组成的;如果是基于 HTTP ,则是有 URL、HTTP 动词和请求响应的格式来组成;也可以是提供一组方法的类;
  • 客户端、服务端:说起客户端,第一印象容易想到浏览器、移动 APP 这种,这里的客户端是指在调用和被调用的调用方,服务端就是被调用方。而接口的定义是由服务端来进行定义。

在前后端分离之前的单体应用中,当接口方法有变化时,编译下代码就知道哪些地方应该要调整,或者在 IDE 中进行接口方法引用的查找,也能很容易处理。

在微服务中,不同的服务可能是不同的团队来进行开发,服务端接口的修改、滚动发布等都需要有很好的兼容性和可用性。

一种方式是在接口中向下兼容,但时间越长代码就会变得越复杂,比如:

  • 添加一些控制性的参数;
  • 接口方法新添加的参数需要给默认值;
  • 返回值可能也需要进行特殊处理。

另一种方式就是不兼容,所有的客户端需要进行代码的调整来适应新的接口。在客户端代码还没有完全调整完之前,新老接口需要共存,共存有两种方式:

  • 使用 URL 地址中添加版本号,比如:/api/v1/xxx , /api/v2/xxx ;
  • 在请求头或消息体中添加版本号,接口方法中根据版本号来进行适配,调用对应版本的逻辑然后返回给客户端。

所以,一个设计良好的接口可以在暴露有用功能的同时隐藏实现细节,对于细节,可以进行扩展,修改,并不会影响到客户端的调用,这就要求在接口设计之前,需要先进行定义,经过多轮评审后再进行编码实现。

好的设计自带防腐层。

因为客户端和服务端是互相独立的,服务端有时在特定时间内无法完全响应客户端的请求,可能是自己本身有故障,也可能是超过了负载。这时,客户端请求就会被阻塞,无限地等待。

有几种方式可以来解决这个问题:

  • 设置超时:在等待请求响应时,不要无限阻塞,设置一个超时时间,超过时间就返回;
  • 根据负载能力,限制客户端请求的数量,超过上限,后面的请求直接返回失败;
  • 对客户端请求进行监控,如果失败的比例超过阈值,就进行熔断,让调用立即失败。

现在有一些成熟的框架可以方便进行熔断的处理,比如:.NET 中的 Polly、Spring Cloud 中的 Sentinel、Hystrix 。

在传统软件中,经常使用环境变量和配置文件来进行一些静态地址的配置,而在部署在云端的分布式微服务程序中,地址是动态的,那客户端怎么能找到这些地址呢?就需要用到服务发现。

服务发现就是客户端不再依赖一个静态的固定地址去寻找服务端,而是根据一个路由名称在服务注册表去寻找服务端地址,服务端部署后会将地址写入服务注册表。

在微服务框架中,也有相关的框架来实现服务发现,比如:.NET 中的 consul 、Spring Cloud 中的 Eureka、Nacos 等。

对于实时性要求不高的场景,可以采用异步消息的方式来实现。比如删除数据时,需要删除数据中对应的附件信息、各种操作的日志记录、流程流转中需要发送消息通知等。

使用异步消息有下面几个好处:

  • 不需要知道是接收方的地址,只需要将消息发出去就行,发送方和接收方充分解耦;
  • 消息的消费者可以是一个,也可以是多个,处理速度不够,能方便横向扩展;
  • 消息中间件在发送方和接收方中间起到一个缓冲的作用。

现在流行的开源中间件有 RabbitMQ、ActiveMQ、RokcetMQ、Kafka 等,选择这些中间件时需要考虑:

  • 支持的编程语言;
  • 支持的消息标准;
  • 是否支持持久化?
  • 延迟是否在接受范围之内?
  • 消息是否能支持顺序?

很多工作流引擎就是使用消息驱动的机制,流程在流转过程中需要保证消息是顺序处理了,否则流程数据可能就错乱了,如何在保证消息顺序处理的情况下又能横向进行扩展,这是一个挑战。在 Kafka 中可以使用分片的方式进行解决。

上面介绍的是服务间通信的一些常用方式,了解了基本逻辑,在具体实践时,无论是使用 .NET 技术栈还是 Java 技术栈来做微服务,就都不是什么难事了。

希望本文对您有所帮助!