异地多活架构设计-概论

近期工作中要做一些系统的质量工作,那么多活肯定是一个避不开的事情,借此机会,系统梳理下异地多活的架构设计,为接下来的服务质量做一些准备。

0 预设

本文预设读到的同学是对服务器开发领域有一定的认知的,对一些比较基础的认知类的东西,不会去做太多的科普。

1 几个概念解释

  • 冷备:备份全量数据,平时不支撑业务需求,即不接受服务通信请求,只有在主机房出现故障的时候才会切换到备用机房,通常切换完成后提供的也是有损服务

    冷备的方案主要有下面几点问题:

    1. 成本高,平常不跑业务,干晾着。
    2. 因为平时不接收流量,真的出问题了,你敢把流量切过去吗? 我反正还是有点怂的。
    3. 接2,即使你敢切,切换到冷备系统,也不一定能成功把业务接管过来,说不定根本就跑不起来。
    4. 接3,运气再好一点,冷备系统有能力接管,在接收流量后能马上正常提供服务。可能整个切换动作花费的时间也会比较长。
  • 活:是相对于冷备而言的,在日常的业务中要有流量,做业务支撑。

2 为什么要做多活架构

服务是怎么一步步从单实例到异地多机房多活的呢? 以下单纯从研发角度,不考虑跨国业务,也先不考虑数据同步的问题,简单推演以下

1
2
3
4
5
6
7
8
9
10
11
12
A:服务部署好了,你可以发个请求试试
B:biubiubiu,一切正常,不错。after a moment,啊,请求超时了。
A:我查一下,啊,服务挂了,就部署了1个实例。**等我再部署几个一起收请求**,不全挂就行了。
B:biubiubiu,一切正常,不错。after a moment,啊,请求又超时了。
A:我查一下,卧槽,机器硬盘坏了,**我找多几台机器(同一个机房),每个机器都部署几个服务实例**,机器不全挂就行了。
B:biubiubiu,一切正常,不错。after a moment,啊,请求又超时了。
A:好羞愧啊,我去查一下,啊,机房停电了,我去**另一个机房**也部署下服务吧,总不能2个机房一起挂吧。
B:biubiubiu,一切正常,不错。after a moment,啊,请求又超时了。
A:卧槽,这几天珠三角下雨把**深圳和广州2个机房(距离太近)**都淹了,我在北京机房也部署个服务去。这样请求耗时一点,但是起码不会珠三角跟北京一起出问题吧。
B:biubiubiu,一切正常,不错。after a moment,啊,请求又超时了。
A:艹,深圳和广州的机房空调坏了,北京机房停电了,md认命吧,我们去买个彩票吧。
B:原来后台做个数据的增删改查这么麻烦,还是我app挪按钮好一点。

现在基本都清楚为啥要做异地多活了吧。那么异地多活,除了解决以上问题,还能解决其他哪些问题呢?

  • 业务一致性、灾难恢复的诉求,不把鸡蛋放在一个篮子里, 避免由于一个集群或机房的不稳定导致业务出现问题。
  • 在系统扩展、容灾恢复、更新发布时做到数据无损,且服务持续可用
  • 有些业务应用可能是面向分布在全球的终端用户,那么为了减少服务之间的访问延时,这些业务应用服务就需要被部署在全球相应的地域; 与此同时,有些特定业务,由于它的特殊性,需要满足本地数据合规要求,也需要将这一些应用服务部署在对应的地域中。例如前段时间tiktok美国版的纠纷,就是川大统领要求美国人的数据必须放在美国。

那么异地多活架构是不是就是万金油呢?没啥缺点吗?有的!

  • 只是极力减少服务不可用的概率,并无法完全杜绝。
  • 异地多活,架构设计上要考虑很多东西,系统复杂度指数级增加,需要平衡好系统设计复杂度跟服务可用率之间的取舍。
    • 跨机房调用,服务延时问题
    • 用户数据多机房同步问题
    • 用户数据拆分到多机房的策略问题
    • 还有各种乱七八糟其他问题

3 常见多机房架构设计

3.1 阶段1:同城双机房: 服务间调用和数据访问会有跨机房流量

设计要点

  • 关键在于搭建高速网络将两个机房连接起来,达到近似一个本地机房的效果。架构设计上可以将两个机房当作本地1个机房来设计

    同城异区或邻近城市的两个机房距离一般就是几十千米,网络传输速度几乎和同一个机房相同,降低了系统设计的复杂度、成本。

  • 服务在2个机房部署热备,数据库主备也部署到不同的机房。只是解决了机房容量不足的问题
  • 会存在跨机房调用(包括跨机房rpc调用和跨机房db访问)
    1. 服务无状态,每一次RPC调用都有50%的概率跨机房。
    2. 数据库主库只能位于一个机房,所以50%的数据库访问也是跨机房的。

同城跨机房专线访问的耗时在数毫秒级,图中用暗黄色线表示。如果微服务架构下的服务众多,每次完整的调用链有个几十次rpc调用,那么这部分的延迟加起来也很可观了。

服务间调用和数据访问有跨机房

注意,这个多机房是在同城的(同一个城市,或者临近的两个城市),地理位置很近。

综合考虑系统设计复杂度,机房成本,故障发生概率,同城多机房架构是应对多机房级别故障最优架构

3.2 阶段2:只有数据访问会跨机房

依靠不同服务注册中心,将应用层逻辑隔离开。请求按照预设的负载均衡规则进入其中一个机房,接下来所有的请求调用都在这个机房内处理完。但是,由于数据库主库只在其中一边,所以这个阶段仍然没解决一半数据访问跨机房的问题。

只有数据访问跨机房

3.3 阶段3:两地三中心

在金融系统中广泛应用的容灾部署模式,性价比较高。

这是当前看最主流的灾备方案,数据中心1和数据中心2在同城作为生产级的机房,当用户访问的时候随机访问到数据中心A或B。之所以随便访问,因为A和B会同步做数据复制,所以两边的数据是完全一样的。但是因为是同步复制的,所以只能在同城去做两个数据中心,否则太远的话同步复制的延时会太长。

  • 两个生产级的数据中心1和2,必须在同一个城市,或者在距离很近的另外一个城市,但是距离是有要求的.
  • 异地灾备机房距离数据库主节点距离过远,数据访问耗时过长
  • 异地灾备节点数据不是实时一致的(因为远),在同城的主备机房可用的情况下,都不会选择让异地备机房直接提供服务。

其实这个异地机房就是最前面说的冷备,那么那些冷备机房的问题,它都有。

  • 当数据中心1和2出问题的时候,不敢流量切往异地的备份数据中心,敢切但用不起来,能用起来但切换时间久。
  • 异地备份中心因为不对外提供服务,所以整个资源会处于浪费状态,属于浪费了,成本比较高。
  • 在两地三中心中,数据是单点写,单点的压力会比较高。

常说的两地三中心架构

从以上来看,阶段1的方案是性价比最高的,阶段2次之,阶段3里的异地冷备,平时就是浪费,不差钱就搞一个,最主流的方案。

4 异地多活架构设计

异地多活一般是指在不同城市建立独立的数据中心,此处的异地,要跟现有的机房位置相隔足够远,例如北京和上海。在其中一个城市或邻近城市出现极端灾难的时候,另一个距离很远的城市的机房仍然可用,从而实现服务的高可用。

4.1 设计要点

异地多活架构有一些天生的缺陷

  • 网络环境复杂,延迟不确定,大致 50ms -> 1s
  • 距离远,搭建专用网络通道复杂度和成本高
  • 因为距离远,肯定会出现数据不一致,需要保证数据短时间不一致时能够提供服务
  • 要求数据高度一致的服务做异地多活要做单元化设计(会另外写一篇来讲单元化设计)

异地多活模式适用于对数据一致性要求不高的场景,例如

  • 用户登录,数据不一致时重新登录即可。
  • 新闻网站,A用户看到的内容跟B用户看到的内容有一些延迟,没啥问题,尤其是现在看新闻基本都是千人千面基于算法推荐的,就更不容易产生用户体验的下降问题。
  • 微博网站,即使丢失一点微博或评论数据也影响不大。

异地多活架构的实践,关键在于数据不一致的情况下,业务不受影响或者影响很小,这从逻辑的角度上来说其实是矛盾的。对数据一致性要求高的场景,异地多活也可以做,但是设计就要复杂一点,架构设计的主要目的就是为了解决这个矛盾。

4.2 常见设计误区

  • 每个业务都想要异地多活
  • 想要做到 100% 可用,从99.99%到100%,要付出的代价很大很大的哦。

    我们要忍受这一小部分用户或者业务上的损失,否则本来想为了保证最后的0.01%的可用性,做个完美方案,结果却发现99.99%也保不住了。

  • 只使用存储系统自己的同步方案(例如使用MySQL的时候,只使用MySQL自身提供的主从同步),没有其他并行的数据同步方案。
  • 数据想要做到实时一致性(有的时候,最终一致性也是很好的方案。毕竟实时一致性跟最终一致性的复杂度也是不可同日而语的)。

以上这些误区,都是因为想把系统做到完美,但是所有的设计其实都是有一定的取舍,有舍才有得,我们要的是最大性价比的方案,在系统设计复杂度和系统可用性两方面找到一个平衡。当然,这个平衡点,不同的业务会有很大区别,例如(微博和支付业务)。同一个业务,随着业务的发展,平衡点也可能需要不断地去调整。

4.3 异地多活架构设计要解决的问题

  • 跨机房接口调用慢导致的延时。(其实所有的问题,就是因为距离远,但是异地的初衷就是要距离远的)

    根据业务做单元化,如交易单元,同一个业务的整个流程,尽量避免跨机房,这样来避免延时问题。(单元化另写一篇讲)

  • 数据同步慢导致的数据实时一致性问题。
  • 单元化之后导致的数据写入路由一致性问题。
  • 不同单元之间的数据同步问题。

4.4 正确的设计思路

  • 只保证核心业务的异地多活
  • 核心数据做到最终一致性不必须实时一致性,Best Effort即可,有以下一些方法
    • 可以减少数据同步量,只同步核心数据
    • 搭建高速网络
  • 避免只使用存储系统的同步功能,可以将多种手段配合存储系统的同步来使用,甚至可以不采用存储系统的同步方案,改用自己的同步方案(不同方式,尽可能走不同的网络通道)
    • 消息队列方式
    • 二次读取方式
    • 回源读取方式
    • 重新生成数据方式
  • 只保证绝大部分用户的异地多活
  • 很多业务的异地多活,是需要从业务的设计上去考虑

4.5 异地多活架构的锦上添花–地域感知的负载均衡

4.5.1 为什么要有地域感知负载均衡

  • 在跨地域部署的多集群中,各地域可能会部署本地区定制化的服务,这些场景要求服务间的访问不能随机路由,需要优先(或只能)访问本地域服务。比如
    • 不同地区的服务会有不同的显示语言或定制功能
    • 某些服务只在特定区域开放
  • 用作容灾。在多地域架构中, 往往会有一个主集群, 拥有较为充足的服务资源, 而容灾地域的资源仅保证服务基本可用。因此我们要求正常情况下,请求能由主集群提供服务,流量不要地域间随机路由。
  • 除了以上场景,若2个异地对等集群,提供服务和持有资源完全相同, 我们也应该尽量满足本地域内闭环服务, 因为跨地域的服务访问往往会有较大的延迟,用户体验差,且可能会让容错性较差的系统出现服务雪崩的风险。

4.5.2 地域负载均衡默认使用的地域优先策略

  • 最高优先级:相同地域且相同区域
  • 第二优先级:相同地域但不同区域
  • 最低优先级:不同地域

5 通常的异地多活演进

5.1 演进过程

  1. 同城的双活。
  2. 异地只读及冷备(类似两地三中心)
  3. 异地多活。在异地多活之前,最重要是同城的“双活”,双活上打了一个引号,原因在于同城双活的情况下,其实整个模式是应用层是双活的,两边的业务都有,用户访问过去都会处理请求。但是存储层都是主备的,主在A机房,备在B机房,不会同时用,可以说是伪双活,不是真正意义上的双活。

5.2 经验总结

  • 服务调用延时问题

    让操作全部在同一中心内完成,使用单元化设计
    比如用户请求路由到某个数据中心后,在淘宝上浏览商品,搜索、下单、添加购物车等操作,还包括写数据库,都在其进入的那个数据中心中完成,而不需要跨数据中心

  • 数据同步的延时问题

    核心业务的数据,要做到全国范围1s内完成骨干网数据同步
    这个也主要靠单元化设计来解决,把用户操作封闭在一个单元内完成,那个用户被sharding到的单元
    自研数据同步中间件,补充数据库自有的数据同步途径。

  • 部署规模

    还是单元化设计,将关系紧密的一组业务单元化
    流量很大的单元,通常流量会爆发性增长,这类单元,做异地部署,流量小的,就没有必要了。
    例如,对淘宝来说,只异地部署跟买家交易相关的核心业务单元,让买家整个购物过程顺畅完成,卖家对一些体验的容忍度会比买家高很多。每家公司需要根据自己业务的关注点,做出合适的选择。
    另外,在数据层自身支持跨机房服务之前,不建议部署超过两个的机房。因为异地两个机房,异地容灾的目的已经达成,且服务器规模足够大各种配套的设施也会比较健全,运维成本也相对可控。当扩展到三个点之后,新机房基础设施磨合、运维决策的成本等都会大幅增加

  • 路由一致性

    这是单元化设计后出现的问题。除了将关系紧密的一组业务单元化,还有另一个维度做单元化,就是根据用户做sharding,将不同的用户分流到不同的单元。
    买家相关的数据在写的时候,一定是要写在他所属的那个单元里。要保障这个用户从进来一直到访问服务,访问数据库,全链路的路由规则都是完全一致的。如果说某个用户本来应该进A数据中心,但是却因为路由错误,进入了B数据中心,那看到的数据可能就是错的了。造成的结果,可能是用户看到的购买列表是空的,买的东西不见了,已付款的交易变成未付款,这都是不能接受的。

  • 数据一致性

    在任何时间点,必须确保单行的数据在一个地方写,绝对不能在多个地方写。