简述
异地多活的概念以及为什么要做异地多活这里就不进行概述了。概念性的很多,像什么同城双活、两地三中心、三地五中心等等概念。如果有对这些容灾架构模式感兴趣的可以阅读下这篇文章进行了解:《浅谈业务级灾备的架构模式》。
阅读本篇文章之前,我们先明确一下背景,这样大家后续在看的时候就不会产生困惑。
1.1 机房划分
得物多活改造一期目前有两个机房,分别是机房 A 和机房 B。文章中大部分图中都会有标识,这就说明是两个不同的机房。
A 机房我们定义为中心机房,也就是多活上线之前正在使用的机房。如果说到中心机房那指的就是 A 机房。另一个 B 机房,在描述的时候可能会说成单元机房,那指的就是 B 机房。
1.2 单元化
单元化简单点我们直接就可以认为是一个机房,在这个单元内能够完成业务的闭环。比如说用户进入 APP,浏览商品,选择商品确认订单,下单,支付,查看订单信息,这整个流程都在一个单元中能够完成,并且数据也是存储在这个单元里面。
做单元化无非就两个原因,容灾和提高系统并发能力。但是也得考虑机房建设的规模和技术,硬件等投入的成本。具体的就不多讲了,大家大概理解了就行。
2. 改造点
了解改造点之前我们先来看下目前单机房的现状是什么样子,才能更好的帮助大家去理解为什么要做这些改造。
如上图所示,客户端的请求进来会先到 SLB(负载均衡),然后到我们内部的网关,通过网关再分发到具体的业务服务。业务服务会依赖 Redis, Mysql, MQ, Nacos 等中间件。
既然做异地多活,那么必然是在不同地区有不同的机房,比如中心机房,单元机房。所以我们要实现的效果如下图所示:
大家看上面这张图可能会感觉很简单,其实也就是一些常用的中间件,再多一个机房部署罢了,这有什么难度。如果你这样想我只能说一句:格局小了啊。
2.1 流量调度
用户的请求,从客户端发出,这个用户的请求该到哪个机房,这是我们要改造的第一个点。
没做多活之前,域名会解析到一个机房内,做了多活后,域名会随机解析到不同的机房中。如果按照这种随机的方式是肯定有问题的,对于服务的调用是无所谓的,因为没有状态。但是服务内部依赖的存储是有状态的呀。
我们是电商业务,用户在中心机房下了一个单,然后跳转到订单详情,这个时候请求到了单元机房,底层数据同步有延迟,一访问报个错:订单不存在。 用户当场就懵了,钱都付了,订单没了。
所以针对同一个用户,尽可能在一个机房内完成业务闭环。为了解决流量调度的问题,我们基于 OpenResty 二次开发出了 DLB 流量网关,DLB 会对接多活控制中心,能够知道当前访问的用户是属于哪个机房,如果用户不属于当前机房,DLB 会直接将请求路由到该用户所属机房内的 DLB。
如果每次都随机到固定的机房,再通过 DLB 去校正,必然会存在跨机房请求,耗时加长。所以在这块我们也是结合客户端做了一些优化,在 DLB 校正请求后,我们会将用户对应的机房 IP 直接通过 Header 响应给客户端。这样下次请求的时候,客户端就可以直接通过这个 IP 访问。
如果用户当前访问的机房挂了,客户端需要降级成之前的域名访问方式,通过 DNS 解析到存活的机房。
2.2 RPC 框架
当用户的请求达到了单元机房内,理论上后续所有的操作都是在单元机房完成。前面我们也提到了,用户的请求尽量在一个机房内完成闭环,只是尽量,没有说全部。
这是因为有的业务场景不适合划分单元,比如库存扣减。所以在我们的划分里面,有一个机房是中心机房,那些不做多活的业务只会部署在中心机房里面,那么库存扣减的时候就需要跨机房调用。
请求在中心机房,怎么知道单元机房的服务信息?所以我们的注册中心(Nacos)要做双向同步,这样才能拿到所有机房的服务信息。
当我们的注册信息采用双向复制后,对于中心服务,直接跨机房调用。对于单元服务会存在多个机房的服务信息,如果不进行控制,则会出现调用其他机房的情况,所以 RPC 框架要进行改造。
2.2.1 定义路由类型
-
默认路由
请求到中心机房,会优先调用中心机房内的服务,如果中心机房无此服务,则调用单元机房的服务,如果单元机房没有此服务则直接报错。
-
单元路由
请求到单元机房,那么说明此用户的流量规则是在单元机房,接下来所有的 RPC 调用都只会调用单元机房内的服务,没有服务则报错。
-
中心路由
请求到单元机房,那么直接调用中心机房的服务,中心机房没有服务则报错。请求到中心机房,那么就本机房调用。
2.2.2 业务改造
业务方需要对自己的接口(Java interface)进行标记是什么类型,通过 @HARoute 加在接口上面。标记完成后,在 Dubbo 接口进行注册的时候,会把路由类型放入到这个接口的元数据里面,在 Nacos 后台可以查看。后面通过 RPC 调用接口内部所有的方法都会按照标记类型进行路由。
如果标记为单元路由,目前我们内部的规范是方法的第一个参数为小写的 long buyerId,RPC 在路由的时候会根据这个值判断用户所在的机房。
路由逻辑如下: