0. Intro

在微服务架构里,传输层可以说是最重要的一部分,哪怕一个微服务不需要call其他微服务,也需要提供接口给其他微服务调用。

而且传输层应该是跟service无关相对独立的,这样才可以支持多种协议而且不需要修改业务代码就可以轻松切换。

在传输层需要考虑的问题主要有:

传输方式:

  • http
  • rpc
  • message bus(mq,broker等)

调用方式:

  • 同步
  • 异步

1. 传输方式

其实各种传输方式的差距并不是很巨大,或者说,很难有一个特别明确的分界线应该如何选择传输协议。

http相对其他两种方式的主要优点在于简单,不需要特殊框架就可以call,大部分语言都会内置http库,无论是客户端还是服务端相对来说实现起来都比较轻松。

主要缺点在于性能往往优势不大,但是在http2里已经优化很多了(毕竟grpc的底层就是http2),而且http协议本身也可以看做是一种rpc。

rpc和message bus两者都需要使用单独的client lib,这两者之间有一个很重要的问题就在于后者可以更好的灵活调度。

我们假设有A服务需要调用B服务,同时存在10个B服务作为集群,那么如果简单的使用rpc协议,例如grpc这种,我们就需要自己在A服务上区分要调用哪个B服务来处理,而使用message bus(thrift,nats,rabbitmq等)也都可以实现类似rpc的效果,而且可以更灵活的处理负载均衡等问题。

2. 同步 vs 异步

同步的方式很像常用的func call,我们调用一个方法,我们等待他的返回,如果出错就重试或者报错。这种在逻辑上并不难懂,同步的主要缺点在于这种方式增加了服务间的耦合。

比如服务A调用服务B,必须等待服务B的返回,这种情况下如果B出错,或者测试,或者单独更新B的时候,都容易影响到A的效果。

同步最常见的使用场景就是load data,需要其他服务配合来组装返回的数据。

异步的方式会减少服务间的依赖,发送出消息之后就可以继续执行。异步的缺点主要在于会增加整体系统的复杂度,而且对调试来说也更复杂,因为你想知道一个服务会影响到几个服务会变得更加困难。

异步有两种主要的使用场景,一种是pub/sub模式,比如说我一个订单支出成功,我可能要发出一个order.paid的消息,可能同时有对账,财务,订单,库存微服务都订阅了这个消息。

另一种模式是background job,比如说我异步发送一个邮件,email.send,可能同时有10个邮件发送的服务在运行中,只要有一个收到即可。

两种模式也经常混合在一起使用,比如说第一个例子里订单微服务可能有几十台的集群。

还有一种模式介于两者之间,还是用发送邮件举例,调用发送邮件的服务可能并不关心邮件的发送结果,是否发送失败,邮件服务器是否故障,然而从整个系统来说,可能需要追踪这个结果,这种时候可以用同步的方式由发送者来确认发送结果,也可以用异步同时用message bus来确保如果失败继续retry的逻辑。

3. 约定

在demo里面,使用json作为主要的传输格式,请求json格式如下:

{
  "track_id":"aaaa-bbbb-cccc-dddd",
  "method":"show",
  "payload":{
    "id":1
  }
}

返回的json格式如下:

// 正常时
{
  "data":{"id":1, "content":"Good for u"}
}
// 错误时
{
  "err":"the post doesn't exist."
}

主要会实现http协议和nats作为message bus的两种传输方式,其他的传输方式实现方法基本都差不多。

4. 其他

在微服务这种分布式架构中,做容错是很重要的一件事,比如说网络延迟,服务挂掉,都可能会引发系统性的崩盘,在之后会单独介绍一下Circuit Breaker的模式来增加系统的健壮性。

另外就是虽然我们的微服务支持http协议,但是这种http往往并不直接对外公开,所有对外公开的接口应该统一通过api-gateway做处理,后面也会单独介绍api-gateway模式。