2015年我们的团队已基于MQ实现了服务的纵向切分,实现了统一网关,多语言服务接入。正在这时微服务的概念也开始火起来了。

现在把这一套东西分享出来。

纵向切分的目标

  • 热部署。服务可插拔,发布一个新的服务只需开启进程即可。
  • 服务隔离。故障局部化,取决于服务切分的颗粒。
  • 兼容升级。多版本服务共同存在,确定客户端所有老版本都不再使用的情况下,下线老版服务。
  • 多语言支持。我们内部的进程有Golang,Python和php。
  • 多实例。相同服务可以开启多个实例,增加吞吐量,实现灾难恢复,提高容错能力。

架构图

架构图

架构图的核心部分其实就是用MQ实现的RCP。业务进程做为RPC的server,gateway做为RPC的client。

gateway(用Go实现)

一、gateway是由很多个http服务组成的,通过nginx代理做负载均衡(也可以使用Haproxy,lvs之类),gateway只在架构之初需要编写,当它上线后不再需要更改升级和重新部署(除非是有bug),它是一个通用的部分,业务无关的(但是也可以做一些类似token认证,sign检查之类的事情,这些事情比较通用固定)。

下面是一个客户端请求gateway的示例:

1
2
3
4
5
6
curl -X POST \
https://api.appfan.cc/gateway \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/x-www-form-urlencoded' \
  -H 'postman-token: 63785695-5596-3ba0-fbbd-df132fb17877' \
  -d 'module=wxapp&version=1.0&method=WxappManager.SessionKey&bizContent=%7B%22Code%22%3A%22d3JmZGZkc2Y%3D%22%7D'    

POST参数:

1
2
3
4
module:wxapp
version:1.0
method:WxappManager.SessionKey
bizContent:{"Code":"d3JmZGZkc2Y="}

module:表示要访问的业务对应的后台进程。 version:表示服务的版本,在兼容升级中会用到。 method:表示业务中具体的API方法。 bizContent:表示业务API的参数,是一个JSON字符串。 上面例子,是调用微信小程序模块1.0版本,生成一个SessionKey,换取要用到的Code为"d3JmZGZkc2Y=”

二、gateway和MQ之间的通信

gateway有一个接收消息的线程从进程开启后就马上起来。还有很多处理请求的线程不断的创建和结束。

当gateway收到client上的HTTP请求后,在当前处理线程会发送一个消息到MQ上。

队列为module和version拼接而成,如"server_wxapp_1.0”,

消息体的内容有,method、bizContent、gateway的回应队列名、当前处理线程标识。

当前处理线程进入等待状态,直到另一个进程把结果告诉它,然后把结果写入到HTTP的响应

如果12s后还是等不到结果,会将一个响应超时的错误写入到HTTP的响应

同时gateway的另一个线程一直在等待MQ的回应消息,他收到的消息是不同的处理进程需要的结果,这个线程类似一个死循环(这个线程存在的原因是MQ的消息是异步过程,而http请求是同步的)。

收到的消息包含的内容为:业务进程处理的结果、刚才处理线程的标识。

一但收到消息,马上通过线程通信将结果发送给标识指定的线程

通过下面的时序图了解一次http请求的过程。 时序图

业务进程

每个业务进程都有一个固定的队列名,比如"server_wxapp_1.0”。

相同的业务进程可以启动多个实例。多个实例订阅同一个队列,天然形成了一种竞争的关系,达到了负载均衡的作用。

在修复了bug或业务改动后。我们可以先开启新的进程,然后杀掉到老的进程,这样服务就没有真空期了,实现了平滑升级。

我们可让两个不同版本的进程同时存在,比如"server_wxapp_1.0"和"server_wxapp_1.1"同时存在,表示一部分老客户端可以正常使用老的API,新的客户端可以使用新的API。通过这种方式很轻松就可以实现灰度发布。

而这些进程的开启、关闭和故障,并不影响gateway的正常动行,只会影响到本模块、本版本的API。所以在灾难发生时,大部分情况都是服务降级的过程,部分功能暂停(这取决于模块拆分的科学性)。

因为RabbitMQ本身支持不同的开发语言,所以业务进程可以用这些不同的编程语言来开发。

其它

日志功能,通过MQ的队列数据旁听可以监控所有服务器之间消息的内容。方便做日志的统一管理和分析。

gateway超时请求线下排查。对于超时的情况,可以会造成数据的不一致情况,运维过程中要找到这些超时记录,分析原因,并快速修复问题,不允许慢的业务进程存在(要远小于12s)。

RabbitMQ rpc代码参考,http://www.rabbitmq.com/tutorials/tutorial-six-go.html