在微服务架构中,可能会有几十甚至上百个微服务。在对外提供的某一项功能中,可能需要同时或依次调用这些服务。因此保证微服务之间通信的稳定性是十分必要的。

难点

  • 弹性
  • 网络通信可能会失败
    一个服务实例会由于各种各样的原因导致异常,可能是部署实例节点异常,可能是硬件异常,可能是网络的不稳定,也有可能是服务本身的异常。
  • 解决方法
  • 重试
    正如字面意思表达的那样,如果调用某个服务接口失败了,可以再次向该接口发起请求。但这里要注意的是,连续向同一失败的服务发起请求可能反而会加剧该服务所在实例的压力,所有的请求都占用了一部分CPU、内存以及数据库连接,最终导致实例的崩溃。常见的方案是限制重试的次数,或是增加重试之间的时间间隔。
  • 熔断
    老式的电路中往往会有保险丝保护整个电路系统(现代电路中常见的是空气开关,但总体设计思路是一致的),一旦电流过大,保险丝温度升高达到熔点,保险丝熔断,导致整个电路断路,从而保护用电器安全。在API网关中也可以实现一个类似保险丝的开关,一旦检测到某个服务的压力过大,即可适时阻止继续向该服务发起请求,直到服务恢复正常。
  • 负载均衡
    为了保证服务的可用性,同一个服务往往会同时部署在多个服务上,为了均衡每个实例的请求压力,我们需要一个负载均衡方案。
  • 分布式追踪
    尽管每一项服务都会记录自己的日志,但如果不把这些日志聚合在一起,其实也很难利用这些单独的日志分析问题。
  • 服务版本
    每当你部署新版本的服务时,你都得确保不会因为其他服务仍然在调用你的旧版本接口而导致异常。甚至有的时候你可能想同时提供多个版本的服务。
  • TSL加密和TLS鉴权
    对于成熟的服务来说,通讯加密和鉴权是必不可少的安全环节。

服务间的通信常见有两种方式:一种是更易于理解的同步消息,调用方会同步等待服务返回结果。另一种是异步消息队列如RabbitMQ、Kafka等中间件,我们只需要将消息发送给消息中间件,不需要等待服务返回结果。

异步消息

优势

  • 降低耦合
    由于调用方把请求发送给消息中间件,而不是直接调用服务,因此每个服务之间可以完全独立。
  • 多重订阅
    利用消息中间件我们可以实现发布订阅模式,多个服务可以同时订阅某一个消息。比如用户下单消息可能同时需要支付服务,库存服务,订单服务订阅消息。
  • 错误隔离
    由于调用方不再同步等待请求返回结果,因此即使服务发生异常,也不需要调用方处理。
  • 响应度
    由于调用方不再同步等待请求返回结果,因此调用方发送消息成功后即可马上返回客户端,降低响应时间。
  • 负载管理
    消息中间件作为承载消息的管道,完全可以作为服务的缓冲池,即使消息产生的速度再快,服务方也可以完全按照自己的处理速率处理消息。
  • 工作流
    消息中间件可以在工作流的每一个步骤间设置检查点,管理工作流的流向,处理结果。

难点

  • 消息中间件耦合
    虽然调用方不再与服务方耦合,然而调用方和服务方却都和消息中间件耦合在一起。
  • 延迟
    如果消息堆积在消息中间件,整个端到端的服务延迟必然会增加。
  • 费用
    在消息处理速率不断增长的同时,可能也会带来服务成本的不断提高,尤其是当你使用云服务厂商提供的消息中间件服务时。
  • 复杂度
    引入更多的中间件必然带来整个架构复杂度的加深
  • 重复消息
  • 消除重复消息
  • 幂等操作
  • 请求响应语义[1]
  • 需要另一个消息队列

  • 协同请求和响应消息

  • 吞吐

  • 队列语义[1]成为处理异步消息的瓶颈

  • 至少需要一次入队操作

  • 至少需要一次出队操作

  • 队列基础设施中往往需要某种锁才能实现队列语义

  • 如果使用的是云厂商提供的队列服务的话,更是增加了网络通信的延迟

  • 有时甚至包含复杂的批处理消息

  • 可以考虑使用事件流代替队列

[1]: 队列语义

  • 消费语义
  • 最多消费一次
  • 最少消费一次
  • 恰好消费一次
  • 投递语义
  • 最多投递一次

  • 最少投递一次

  • 恰好投递一次