1. 异地多活介绍

异地多活在近年越来越多大型互联网公司采用的方案,几乎也是大型应用发展到一定阶段的必然选择,综合比较一下各个互联网公司的方案,会发现有很多共性的东西,也有很多差异化的东西。

1.1 什么是异地多活

异地多活一般是指在不同城市建立独立的数据中心,“活”是相对于冷备份而言的,冷备份是备份全量数据,平时不支撑业务需求,只有在主机房出现故障的时候才会切换到备用机房,而多活,是指这些机房在日常的业务中也需要走流量,做业务支撑。冷备份的主要问题是成本高,不跑业务,当主机房出问题的时候,也不一定能成功把业务接管过来。

CAP原则分布式架构设计无论怎样都绕不开CAP原则,C一致性 A可用性 P分区容错性,分区容错性是必不可少的,没有分区容错性就相当于退化成了单机系统,所以实际上架构设计是在一致性和可用性一个天平上的两端做衡量。为什么强一致性和高可用性是不能同时满足?假如需要满足强一致性,就需要写入一条数据的时候,扩散到分布式系统里面的每一台机器,每一台机器都回复ACK确认后再给客户端确认,这就是强一致性。如果集群任何一台机器故障了,都回滚数据,对客户端返回失败,因此影响了可用性。如果只满足高可用性,任何一台机器写入成功都返回成功,那么有可能中途因为网络抖动或者其他原因造成了数据不同步,部分客户端读到的仍然是旧数据,因此,无法满足强一致性。

1.2 异地多活的挑战

  • 延迟 异地多活面临的主要挑战是网络延迟,以北京到上海 1468 公里,即使是光速传输,一个来回也需要接近10ms,在实际测试的过程中,发现上海到北京的网络延迟,一般是 30 ms。
  • 一致性 用户在任何一个机房写入的数据,是否能在任何一个机房读取的时候返回的值是一致性的。

1.3 误区

  • 所有业务都要异地多活 以用户中心为例,注册是没必要做异地多活的,假如用户在A机房注册了,在数据没有向外同步的时候,A机房网络中断,这个时候如果让用户切换到B机房注册,就有可能发生数据不一致,出现两个基本相同的账号,这是不可容忍的。但是相对应的来说,用户登录这种是关键核心业务,就有必要做到异地多活了,用户在A机房登录不了,那就让用户在B机房登录。
  • 必须做到实时一致性 受限于物理条件,跨地域的网速一定会存在延迟,一般是几十毫秒,如果遇上网络抖动,延迟超过几秒甚至几十秒都有可能。解决方法只能是减少需要同步的数据和只保证数据的最终一致性,有时候用户在A机房修改了一条数据,业务上实际上是能容忍数据的短时间不一致的,即使其他用户在B机房读到的是旧数据,实际上对业务也没有任何影响。
  • 只使用存储系统的同步功能 大部分场景下,MySQL Redis自带的同步功能已经足以满足需求了,但是在某些极端情况下,可能就不合适了,MySQL的单线程复制可能会产生较大的延迟,Redis可能会有全量复制,所以系统要灵活使用各种解决方案。

解决方案:

  • 用消息队列把数据广播到各个数据中心
  • 回源读取,当A机房发现没有这条数据的时候,根据路由规则去B机房去读取该数据
  • 重新生成数据,A机房登录后生成session数据,这时候A机房挂了,可以把用户切换到B机房,重新生成session数据。
  • 实现100%的高可用 100%的高可用是无法保证的,硬件的损坏,软件的BUG,光纤传输等太多不可控的因素,而且也要在成本上做一个权衡,尤其是对于强一致性业务,C和A只能取一个平衡,容忍短时间的不可用来保证数据的完全一致性

2. 阿里“异地双活”

  • 阿里巴巴毕玄:异地多活数据中心
  • 解密阿里巴巴“异地多活”技术

阿里在部署异地多活的时候同样是碰到延时问题,解决方案是访问一次页面的操作都在本机房完成,不做跨机房调用。阿里把业务划分成各种单元,如交易单元,这个单元是完成交易业务,称之为单元化。

2.1 背景知识介绍

最主流的灾备技术是两地三中心,数据中心A和数据中心B在同城作为生产级的机房,当用户访问的时候随机访问到数据中心A或B。之所以随便访问,因为A和B会同步做数据复制,所以两边的数据是完全一样的。但是因为是同步复制的,所以只能在同城去做两个数据中心,否则太远的话同步复制的延时会太长。在两地三中心的概念里,一定会要求这两个生产级的数据中心是必须在同一个城市,或者在距离很近的另外一个城市也可以,但是距离是有要求的。

异地多活set化架构 异地多活中心_异地多活set化架构

2.2 阿里异地多活

异地备份数据中心通过异步复制去走,但是两地三中心很明显的是异地备份的数据中心是不起用的,正常情况下不对外服务,所以用户不会访问到异地的点。原因是因为数据从生产级数据中心到异地的节点是异步去复制,所以整个有延时。这是整个业界目前用的比较多的业界。

上面架构问题:

  • 这个模式不一定Work。当一地的数据中心出问题的时候,是不敢流量切往异地的备份数据中心,原因是异地的备份数据中心是冷的,平时是没有用户流量进去的。
  • 异地备份中心因为不对外提供服务,所以整个资源会处于浪费状态,成本比较高。
  • 在两地三中心中,数据一定是单点去写。其实数据只在一个地方去写,这个时候如果整个压力比较高。

异地多活set化架构 异地多活中心_系统架构_02

阿里整个高可用上也经历过了一段时间,主要是做了三个步骤,经历了三代体系的演进。

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

在完成同城双活的改造之后开始尝试异地,最早尝试的是只读业务和冷备,把阿里的某些业务部署到另外一个城市去,开始只是冷备用,决定把只读业务在异地起用,比如说像搜索等等算只读。但是发现对于阿里业务来讲,只读业务很难抽象,因为只能服务只读业务,如果有写就不能做。如果写的话,就意味着写到另外一个城市,这个延时接受不了,后来只读也觉得没有太大意义。当阿里完成同城双活以及异地只读、冷备尝试以后,阿里的阶段也是两地三中心,跟两地三中心是一样的。可以认为是两地三中心稍微的升级版本,因为只读业务有部分的开放,有一部分的进步,但不是最理想的状态。

我们要去做到异地多活,要的目标是:

异地多活set化架构 异地多活中心_缓存_03

1、需要多个跨地域的数据中心。异地多活是跨地域的,而且距离一定要做到1000公里以上的范围,其实在中国范围内全国城市都可以去布署了。2、每个数据中心都要承担用户的读写流量。如果只是备或只读业务来讲,作用不是很大。3、多点写。因为每个数据中心去承担用户读写流量的话,如果读或写集中到全国一个点的话,整个延迟是没有办法承受的。4、任意一个数据中心出问题的时候,其他中心都可以分钟级去接管用户的流量。 这个是阿里在做异地多活项目的时候,希望在这四点上都能够做到,然后也只有这样的情况下才认为是一个异地多活的业务。

2.3 异地多活的挑战

  • 1、距离。看起来距离没有什么,比如说1000公里以上也就是30毫秒的网络延迟,来回一次是30毫秒左右。购物页面的背后大概有100多次以上的后端交互,响应时间将增加3秒。直接带来用户体验的不可用。 成本,当系统响应时间增高的时候,意味着每年“双十一”增加的QPS将付出更大的成本,因为吞吐量在下降,这个时候的成本也是很难接受的。距离带来的延时问题是最大的问题。
  • 2、既然要解决掉距离的问题,多点写是解决距离的问题,如果没有延时问题可以不多点写。如果一旦出现多点写带来的数据正确性问题,这对我们来讲是最致命的。多点写,比如说出现这一次访问在A数据中心写的数据,然后再访问的时候到B数据中心又写了一条数据,两条数据如果合不到一起的话。对于大家最直观的感受是有可能买了一个东西付了钱,然后看到可能是没付钱。或者干脆买了一个东西,压根就没有看到购买。对于阿里来讲,这是最大的一个问题。对于我们来讲,在多点写的情况下最大的挑战是怎么保证用户写入的数据一定是在正确的地方,另外看到的一定是一致的,这是整个异地多活中最大的挑战

2.4 问题解决

 针对这两个问题,对于延时的问题来讲,其实延长时的问题意味着最好的解决方案是什么呢?如果这一次访问页面的整个操作全部在当前机房内完成的,自然就不存在延时问题,因为没有跨出去。针对第二个问题,异地。在全国部署的时候,意味着是不是要把整个业务全部全国部署,因为这有成本因素。为了解决延时问题,能在一个机房内完成就不存在延时问题。另外一个核心思想是单元封闭,需要让单元内的应用访问和数据的读写操作全部处于封闭状态,这就是最完美的状况。

多点写数据一致性处理:多点写以后,怎么去保障整个数据写入的正确性以及一致性。阿里确实做了非常多的东西,因为一个用户访问阿里的时候,其实阿里的背后是庞大的分布式系统,你访问了一层可能只访问了一个系统,事实上背后牵涉进来几十个系统。若你在访问每一层的时候路由都是正确的,比如这个用户访问数据中心A,但是由于某个原因访问到数据中心B,怎么在保证后面访问不同系统的时候准确跳转到正确的地方去,因为每个数据中心的数据不太一样。为了保证一个用户真正写数据的时候不要写错,写入数据库之前都会做保护动作,确保用户写的数据没有写错一个地方。如果写错一个地方,可能就无法恢复了,所以在那个地方有最后的一层保护。同时有实时数据校验系统检查是否符合我们的期望。

流量切换的挑战,比如说AB两个数据中心,A开始是承担20%的流量,8承担80%的流量。当把流量从一个地方切到另外一个地方的时间,有可能出现切换过程中你还在A数据中心写,但其实写完之后到B了,有可能看到出现的数据是不一致的。怎么保证在整个流量切换过程中数据是绝对一致的,我们也做了很多的东西。

在2013年首先采用的是在同城启用了两个单元双活,真正意义的双活,因为那两个单元都是写自己的数据库的,两个单元都是双写。MySQL自己的主备是没有办法满足要求,在异地做到延时是没有办法满足的,我们决定做了自研的数据同步产品。

2015年单元化可以宣告能力基本成熟的阶段,所以在今年开始起用了距离在1000公里以上的另外一个数据中心,然后今年数据中心是多点部署。

异地多活set化架构 异地多活中心_缓存_04

2.5 阿里多活总结

阿里在部署异地多活的时候同样是碰到延时问题,解决方案是访问一次页面的操作都在本机房完成,不做跨机房调用。阿里把业务划分成各种单元,如交易单元,这个单元是完成交易业务,称之为单元化。

服务延时:让操作全部在同一中心内完成,单元化 比如用户进入以后,比如说在淘宝上看商品,浏览商品,搜索、下单、放进购物车等等操作,还包括写数据库,就都是在所进入的那个数据中心中完成的,而不需要跨数据中心

部署:异地部署的是流量会爆发式增长的,流量很大的那部分。流量小的,用的不多的,不用异地部署。其他一些功能就会缺失,所以我们在异地部署的并非全站,而是一组业务,这组业务就成为单元。比如:在异地只部署跟买家交易相关的核心业务,确保一个买家在淘宝上浏览商品,一直到买完东西的全过程都可以完成

路由一致性:买家相关的数据在写的时候,一定是要写在那个单元里。要保障这个用户从进来一直到访问服务,到访问数据库,全链路的路由规则都是完全一致的。如果说某个用户本来应该进A城市的数据中心,但是却因为路由错误,进入了B城市,那看到的数据就是错的了。造成的结果,可能是用户看到的购买列表是空的,这是不能接受的。

延时:异地部署,我们需要同步卖家的数据、商品的数据。能接受的延时必须要做到一秒内,即在全国的范围内,都必须做到一秒内把数据同步完中心之间骨干网。

数据一致性:把用户操作封闭在一个单元内完成,最关键的是数据。在某个点,必须确保单行的数据在一个地方写,绝对不能在多个地方写。为了做到这一点,必须确定数据的维度。淘宝除了用户本身的信息以外,还会看到所有商品的数据、所有卖家的数据,面对的是买家、卖家和商品三个维度。因为异地的是买家的核心链路,所以选择买家这个维度。按买家维度来切分数据。但因为有三个维度的数据,当操作卖家、商品数据时,就无法封闭。

在所有的异地多活项目中,最重要的是保障某个点写进去的数据一定是正确的。这是最大的挑战,也是我们在设计整个方案中的第一原则。业务这一层出故障我们都可以接受,但是不能接受数据故障。

多个单元之间一定会有数据同步。一方面,每个单元都需要卖家的数据、商品的数据;另一方面,我们的单元不是全量业务,那一定会有业务需要这个单元,比如说买家在这个单元下了一笔定单,而其他业务有可能也是需要这笔数据,否则可能操作不了,所以需要同步该数据。所以怎样确保每个单元之间的商品、卖家的数据是一致的,然后买家数据中心和单元是一致的,这是非常关键的。

3. 新浪微博异地多活方案

微博“异地多活”部署经验谈

异地多活的好处阿里同学已经充分阐述,微博的初始出发点包括异地灾备、提升南方电信用户访问速度、提升海外用户访问速度、降低部署成本(北京机房机架费太贵了)等。通过实践发现优势包括异地容灾、动态加速、流量均衡、在线压测等,而挑战包括增加研发复杂度、存储成本增加等。

3.1 多活历程

微博的主要机房都集中在北京,只有很小一部分业务在广州部署,2010年10月因微博高速发展准备扩大广州机房服务器规模,并对微博做异地双活部署。第一版跨机房消息同步方案采取的是基于自研的MytriggerQ(借助MySQL从库的触发器将INSERT、UPDATE、DELETE等事件转为消息)的方案,这个方案的好处是跨机房的消息同步通过MySQL的主从完成的,方案的成熟度高。而缺点则是,微博同一个业务会有好几张表,而每张表的信息又不全,这样每发一条微博会有多条消息先后到达,这样导致有较多时序问题,缓存容易花。第一套方案未能成功,但也让我们认识了跨机房消息同步的核心问题,并促使我们全面下线MytriggerQ的消息同步方案,而改用基于业务写消息到MCQ(MemcacheQ,新浪自研的一套消息队列,类MC协议。

异地多活set化架构 异地多活中心_缓存_05

2011年底在微博平台化完成后,开始启用基于MCQ(微博自研的消息队列)的跨机房消息同步方案,并开发出跨机房消息同步组件WMB(Weibo Message Broker)。经过与微博PC端等部门同学的共同努力,终于在2012年5月完成Weibo.com在广州机房的上线,实现了“异地双活”。

由于广州机房总体的机器规模较小,为了提升微博核心系统容灾能力,2013年年中我们又将北京的机房进行拆分,至此微博平台实现了异地三节点的部署模式。依托于此模式,微博具备了在线容量评估、分级上线、快速流量均衡等能力,应对极端峰值能力和应对故障能力大大提升,之后历次元旦、春晚峰值均顺利应对,日常上线导致的故障也大大减少。上线后,根据微博运营情况及成本的需要,也曾数次调整各个机房的服务器规模,但是整套技术上已经基本成熟。

3.2 异地多活面临的挑战

根据微博的实践,一般做异地多活都会遇到如下的问题:

  • 机房之间的延时: 微博北京的两个核心机房间延时在1ms左右,但北京机房到广州机房则有近40ms的延时。对比一下,微博核心Feed接口的总平均耗时也就在120ms左右。微博Feed会依赖几十个服务上百个资源,如果都跨机房请求,性能将会惨不忍睹;
  • 专线稳定性问题: 为了做广州机房外部,微博租了两条北京到广州的专线,成本巨大。同时单条专线的稳定性也很难保障,基本上每个月都会有或大或小的问题;
  • 数据同步问题: MySQL如何做数据同步?HBase如何做数据同步?还有各种自研的组件,这些统统要做多机房数据同步。几十毫秒的延时,加上路途遥远导致的较弱网络质量(我们的专线每个月都会有或大或小的问题),数据同步是非常大的挑战;
  • 依赖服务部署问题: 如同阿里目前只做了交易单元的“异地双活”,微博部署时也面临核心服务过多依赖小服务的问题。将小服务全部部署改造成本、维护成本过大,不部署则会遇到之前提到的机房之间延时导致整体性能无法接受的问题;
  • 配套体系问题: 只是服务部署没有流量引入就不能称为“双活”,而要引入流量就要求配套的服务和流程都能支持异地部署,包括预览、发布、测试、监控、降级等都要进行相应改造;

3.3 微博异地多活解决方案

由于几十毫秒的延时,跨机房服务调用性能很差,异地多活部署的主体服务必须要做数据的冗余存储,并辅以缓存等构成一套独立而相对完整的服务。数据同步有很多层面,包括消息层面、缓存层面、数据库层面,每一个层面都可以做数据同步。由于基于MytriggerQ的方案的失败,微博后来采取的是基于MCQ的WMB消息同步方案,并通过消息对缓存更新,加上微博缓存高可用架构,可以做到即便数据库同步有问题从用户体验看服务还是正常的。

这套方案中,每个机房的缓存是完全独立的,由每个机房的Processor(专门负责消息处理的程序,类Storm)根据收到的消息进行缓存更新。由于消息不会重复分发,而且信息完备,所以MytriggerQ方案存在的缓存更新脏数据问题就解决了。而当缓存不存在时,会穿透到MySQL从库,然后进行回种。可能出现的问题是,缓存穿透,但是MySQL从库如果此时出现延迟,这样就会把脏数据种到缓存中。我们的解决方案是做一个延时10分钟的消息队列,然后由一个处理程序来根据这个消息做数据的重新载入。一般从库延时时间不超过10分钟,而10分钟内的脏数据在微博的业务场景下也是可以接受的。

微博的异地多活方案如下图(三个节点类似,消息同步都是通过WMB):

异地多活set化架构 异地多活中心_系统架构_06

跟阿里同学遇到的问题类似,我们也遇到数据库同步的问题。由于微博对数据库不是强依赖,加上数据库双写的维护成本过大,我们选择的方案是数据库通过主从同步的方式进行。这套方案可能的缺点是如果主从同步慢,并且缓存穿透,这时可能会出现脏数据。这种同步方式已运行了三年,整体上非常稳定,没有发生因为数据同步而导致的服务故障。从2013年开始,微博启用HBase做在线业务的存储解决方案,由于HBase本身不支持多机房部署,加上早期HBase的业务比较小,且有单独接口可以回调北京机房,所以没有做异地部署。到今年由于HBase支撑的对象库服务已经成为微博非常核心的基础服务,我们也在规划HBase的异地部署方案,主要的思路跟MySQL的方案类似,同步也在考虑基于MCQ同步的双机房HBase独立部署方案。

数据同步问题解决之后,紧接着就要解决依赖服务部署的问题。由于微博平台对外提供的都是Restful风格的API接口,所以独立业务的接口可以直接通过专线引流回北京机房。但是对于微博Feed接口的依赖服务,直接引流回北京机房会将平均处理时间从百毫秒的量级直接升至几秒的量级,这对服务是无法接受的。所以,在2012年我们对微博Feed依赖的主要服务也做了异地多活部署,整体的处理时间终于降了下来。

而配套体系的问题,技术上不是很复杂,但是操作的时候缺很容易出问题。比如,微博刚开始做异地多活部署时,测试同学没有在上线时对广州机房做预览测试,曾经导致过一些线上问题。配套体系需要覆盖整个业务研发周期,包括方案设计阶段的是否要做多机房部署、部署阶段的数据同步、发布预览、发布工具支持、监控覆盖支持、降级工具支持、流量迁移工具支持等方方面面,并需开发、测试、运维都参与进来,将关键点纳入到流程当中。

关于为应对故障而进行数据冗余问题,阿里的同学也做了充分的阐述,在此也补充一下我们的一些经验。微博核心池容量冗余分两个层面来做,前端Web层冗余同用户规模成正比,并预留日常峰值50%左右的冗余度,而后端缓存等资源由于相对成本较低,每个机房均按照整体两倍的规模进行冗余。这样如果某一个机房不可用,首先我们后端的资源是足够的。接着我们首先会只将核心接口进行迁移,这个操作分钟级即可完成,同时由于冗余是按照整体的50%,所以即使所有的核心接口流量全部迁移过来也能支撑住。接下来,我们将会把其他服务池的前端机也改为部署核心池前端机,这样在一小时内即可实现整体流量的承接。同时,如果故障机房是负责数据落地的机房,DBA会将从库升为主库,运维调整队列机开关配置,承接数据落地功能。而在整个过程中,由于我们核心缓存可以脱离数据库支撑一个小时左右,所以服务整体会保持平稳。

3.4 异地多活最好的姿势

就像没有完美的通用架构一样,异地多活的最佳方案也要因业务情形而定。如果业务请求量比较小,则根本没有必要做异地多活,数据库冷备足够了。不管哪种方案,异地多活的资源成本、开发成本相比与单机房部署模式都会大大增加。

下是方案选型时需要考虑的一些维度:

  • 能否整业务迁移:如果机器资源不足,建议优先将一些体系独立的服务整体迁移,这样可以为核心服务节省出大量的机架资源。如果这样之后,机架资源仍然不足,再做异地多活部署;
  • 服务关联是否复杂:如果服务关联比较简单,则单元化、基于跨机房消息同步的解决方案都可以采用。不管哪种方式,关联的服务也都要做异地多活部署,以确保各个机房对关联业务的请求都落在本机房内;
  • 是否方便对用户分区:比如很多游戏类、邮箱类服务由于用户可以很方便分区就非常适合单元化,而SNS类的产品因为关系公用等问题不太适合单元化;
  • 谨慎挑选第二机房:尽量挑选离主机房较近(网络延时在10ms以内)且专线质量好的机房做第二中心。这样大多数的小服务依赖问题都可以简化掉,可以集中精力处理核心业务的异地双活问题。同时,专线的成本占比也比较小。以北京为例,做异地多活建议选择天津、内蒙古、山西等地的机房;


  • 控制部署规模:在数据层自身支持跨机房服务之前,不建议部署超过两个的机房。因为异地两个机房,异地容灾的目的已经达成,且服务器规模足够大各种配套的设施也会比较健全,运维成本也相对可控。当扩展到三个点之后,新机房基础设施磨合、运维决策的成本等都会大幅增加;
  • 消息同步服务化:建议扩展各自的消息服务,从中间件或者服务层面直接支持跨机房消息同步,将消息体大小控制在10k以下,跨机房消息同步的性能和成本都比较可控。机房间的数据一致性只通过消息同步服务解决,机房内部解决缓存等与消息的一致性问题。跨机房消息同步的核心点在于消息不能丢,微博由于使用的是MCQ,通过本地写远程读的方式,可以很方便的实现高效稳定的跨机房消息同步;

4. 饿了么异地多活方案

饿了么技术团队花了1年多的时间,实现了业务的整体异地多活,能够灵活的在多个异地机房之间调度用户,实现了自由扩容和多机房容灾的目标。

4.1 背景

为什么要做异地多活?

受业务发展的驱动,经过几年的高速发展,我们的业务已经扩大到单个数据中心撑不住了,主要机房已经不能再加机器,业务却不断的要求加扩容,所以我们需要一个方案能够把服务器部署到多个机房。另外一个更重要的原因是,整个机房级别的故障时有发生,每次都会带来严重的后果,我们需要在发生故障时,能够把一个机房的业务全部迁移到别的机房,保证服务可用。

归纳起来,我们要达到两个目标:

  • 服务可以扩展到多个机房
  • 能够应对整个机房级别的故障 解决这两个问题的常见办法是做异地多活,把服务分散到多个机房,自然扩展和高可用的问题就迎刃而解了。对于其他大部分公司来说,其实要不要做多活,主要就是看下图这样一个曲线。 

对于一个业务快速增长的企业,每次故障带来的损失也相应是加速增长的,而技术的投入总体上是线性的,初期故障损失小于技术投入,在某个时间点,故障的损失会超过技术投入,这时就要用一些高可用方案,来避免故障,多活就是其中最重要的一种。

异地多活面临的主要挑战是网络延迟,以北京到上海 1468 公里,即使是光速传输,一个来回也需要接近10ms,我们在实际测试的过程中,发现上海到北京的网络延迟,一般是 30 ms。

北京上海两地的网络延迟时间,大致是内网网络访问速度的 60 倍(30ms/0.5ms),如果不做任何改造,一方直接访问另外一方的服务,那么我们的APP的反应会比原来慢 60 倍,其实考虑上多次往返,可能会慢600倍。

如果机房都在上海,那么网络延迟只有内网速度的2倍,可以当成一个机房使用。所有有些公司的多活方案,会选择同城机房,把同城的几个机房当成一个机房部署,可以在不影响服务架构的情况下扩展出多个机房,不失为一个快速见效的方法。我们在做多活的初期也讨论过同城方案,比如在北京周边建设一个新机房,迁移部分服务到新机房,两个机房专线连接,服务间做跨机房调用。虽然这个方案比较容易,也解决了机房的扩展问题,但是对高可用却没有好处,相反还带来了更高的风险。与同城多活的方案不同,异地多活的方案会限制机房间的相互调用,需要定义清晰的服务边界,减少相互依赖,让每个机房都成为独立的单元,不依赖于其他机房。

异地多活set化架构 异地多活中心_异地多活set化架构_07

4.2 方案设计

异地多活的实现思路和方法我们的异地多活方案的,有几条基本原则,整个多活方案都是这些原则的自然推导。但在介绍一下这些原则之前,先要说明一下饿了么的服务流程,才能让大家更好的理解这些原则的来由

下面这张简图是我们的主流程:

异地多活set化架构 异地多活中心_数据_08

整个下单到配送完成,有严格的时间要求,必须在短短的几十分钟内完成,我们的服务和地理位置强相关,并且实时性要求高,服务的地域性和实时性是我们的核心特性,多活设计最重要的是满足这两个特性。

进过反复讨论,我们的多活架构通过遵循以下几条基本原则,来满足这两个核心特性:

异地多活set化架构 异地多活中心_数据中心_09

  • 业务内聚: 单个订单的旅单过程,要在一个机房中完成,不允许跨机房调用。这个原则是为了保证实时性,旅单过程中不依赖另外一个机房的服务,才能保证没有延迟。我们称每个机房为一个 ezone,一个 ezone 包含了饿了么需要的各种服务。一笔业务能够内聚在一个 ezone 中,那么一个定单涉及的用户,商家,骑手,都会在相同的机房,这样订单在各个角色之间流转速度最快,不会因为各种异常情况导致延时。恰好我们的业务是地域化的,通过合理的地域划分,也能够实现业务内聚。
  • 可用性优先: 当发生故障切换机房时,优先保证系统可用,首先让用户可以下单吃饭,容忍有限时间段内的数据不一致,在事后修复。每个 ezone 都会有全量的业务数据,当一个 ezone 失效后,其他的 ezone 可以接管用户。用户在一个ezone的下单数据,会实时的复制到其他ezone。
  • 保证数据正确: 在确保可用的情况下,需要对数据做保护以避免错误,在切换和故障时,如果发现某些订单的状态在两个机房不一致,会锁定该笔订单,阻止对它进行更改,保证数据的正确。
  • 业务可感: 因为基础设施还没有强大到可以抹去跨机房的差异,需要让业务感知多活逻辑,业务代码要做一些改造,包括:需要业务代码能够识别出业务数据的归属,只处理本 ezone 的数据,过滤掉无关的数据。完善业务状态机,能够在数据出现不一致的时候,通过状态机发现和纠正。

这几条基本原则,贯穿了饿了么多活的整个设计。基于这几条原则,我们从服务划分,流量路由,业务改造等方面设计了多活方案,下面简要介绍一主要逻辑。

服务划分(Sharding):为了实现业务内聚,我们首先要选择一个划分方法(Sharding Key),对服务进行分区,让用户,商户,骑手能够正确的内聚到同一个 ezone 中。分区方案是整个多活的基础,它决定了之后的所有逻辑。最终选择的方案如下图,自定义地理划分围栏,用围栏把全国分为多个 shard,围栏的边界尽量按照行政省界,必要的时候做一些调整,避免围栏穿过市区。一个ezone可以包含多个 shard,某个 ezone 的 shard ,可以随时切换到另外一个 ezone ,灵活的调度资源和failover。

异地多活set化架构 异地多活中心_数据_10

这样的划分方案,基本解决了垮城市下单的问题,线上没有观察到有跨 ezone 下单的情况。

疑问:1.如果两个城市是接壤的,会出现商家和用户处于不同 ezone 的情况,岂不是破坏了内聚性原则?A: 尽量避免,我们在划分shard的时候没有简单的用城市名称,而是用了复杂的地理围栏实现,地理围栏主体按照省界划分,再加上局部微调,我们最大限度的避免了跨ezone下单的情况。

2.用户是会动的,如果用户从北京到了上海,那么划分规则应该怎么应对?A: 用户在北京下单,数据落在北京shard,到上海下单,数据则落在上海的 shard,借助于底层的数据同步工具,用户无论在什么地方,都能看到自己的数据,但是有1s左右的延时

3.为什么不简单点,按照用户的ID来切分?阿里是按照用户ID的取模来划分单元的,比较简洁。我们如果也用ID做切分,同一地方的用户,商户,骑手可能被划分到不同 ezone,就会出现比较多的跨机房调用,这样就更可能出现延迟,难以保证实时性。所以,我们本地配送的业务模式,决定了需要用地理位置来划分服务。

4.3 技术实现

4.3.1 流量路由:

基于地理位置划分规则,我们开发了统一的流量路由层(API Router),这一层负责对客户端过来的 API 调用进行路由,把流量导向到正确的 ezone。API Router 部署在多个公有云机房中,用户就近接入到公有云的API Router,还可以提升接入质量。

异地多活set化架构 异地多活中心_数据中心_11

前端 APP 做了改造,为每个请求都带上了分流标签,API Router 会检查流量上自带的分流标签,把分流标签转换为对应的 Shard ID,再查询 Shard ID 对应的 eZone,最终决定把流量路由到哪个 ezone。

异地多活set化架构 异地多活中心_缓存_12

4.3.2 数据复制:

为了实现可用优先原则,所有机房都会有全量数据,这样用户可以随时切换到其他机房,全量数据就需要对数据进行实时复制,我们开发了相应的中间件,对 mysql,zookeeper ,消息队列和 redis 的数据进行复制。

异地多活set化架构 异地多活中心_数据_13

Mysql 数据复制工具 DRC:Mysql 的数据量最大,每个机房产生的数据,都通过 DRC 复制到其他 ezone,每个ezone的主键取值空间是ezoneid + 固定步长,所以产生的 id 各不相同,数据复制到一起后不会发生主键冲突。按照分区规则,正常情况下,每个 ezone 只会写入自己的数据,但万一出现异常,2个 ezone 同时更新了同一笔数据,就会产生冲突。DRC 支持基于时间戳的冲突解决方案,当一笔数据在两个机房同时被修改时,最后修改的数据会被保留,老的数据会被覆盖。

ZK,消息队列和Redis复制:有些全局的配置信息,需要在所有机房都完全一致,我们开发了 zookeeper 复制工具. MQ,Redis 的复制与 ZK 复制类似,也开发了 相应的复制工具。

Global Zone强一致保证:对于个别一致性要求很高的应用,我们提供了一种强一致的方案(Global Zone),Globa Zone是一种跨机房的读写分离机制,所有的写操作被定向到一个 Master 机房进行,以保证一致性,读操作可以在每个机房的 Slave库执行,也可以 bind 到 Master 机房进行,这一切都基于我们的数据库访问层(DAL)完成,业务基本无感知。

异地多活set化架构 异地多活中心_异地多活set化架构_14

切换过程和各种异常保护:

  • 在网络中断时,如果不是必要,不做切换,因为任意单个机房能够提供完整服务。
  • 如果需要切换,对锁定切换过程中的订单,直到切换完成,数据复制正常,才开放锁定。这个过程也通过 DAL 来实现
  • 对于标记为其他机房的写入数据,DAL 会进行保护,拒绝写入。
  • DRC 会检查并报告错误的写入操作,方便检查隐藏问题。通过以上4条的保护,我们保证了数据的正确性,频繁的切换也不会出现异常的业务数据。

多个机房的Cache刷新:数据的变更信息,通过 DRC 广播到多个机房,实现缓存的刷新,保证各个机房的缓存一致性。

4.3.3 整体结构

饿了么多活的整体结构如下图:

异地多活set化架构 异地多活中心_数据_15

4.3.4 多活的基础中间件

下面简要介绍一下支持以上功能的中间件,我们归纳为多活 5 大基础组件,之后会有系列文章,介绍每个基础组件的具体实现。

APIRouter :路由分发服务

API Router是一个HTTP反向代理和负载均衡器,部署在公有云中作为HTTP API流量的入口,它能识别出流量的归属 shard ,并根据 shard 将流量转发到对应的 ezone。API Router 支持多种路由键,可以是地理位置,也可以是商户ID,订单ID等等,最终由 API Router 映射为统一的 Sharding ID。

Global Zone Service:全局状态协调器

GZS 维护着整个多活的路由表,其他所有的服务都从 GZS 订阅路由信息。切换机房的操作也在 GZS 控制台中完成。路由表包括:地理围栏信息,shard 到 ezone 的归属信息,商铺ID/订单ID 等路由逻辑层到 shard id 的映射关系等。GZS 通过在 SDK 端建立 Cache,来保证shard 逻辑能够最快速度执行,基本不需要和 GZS 交互,同时也有实时推送机制,确保在数据变更后能够快速通知到其他的服务。

SOA Proxy:内部网关

SOA Proxy 实现了对 SOA 调用的路由,执行和 API Router 相似的逻辑,但只用在机房之间进行通信的场景。业务使用 SOA Proxy 需要对代码做一些修改,把路由信息加入到调用的上下文中。

Data Replication Center:数据复制

DRC 负责 Mysql 数据的实时双向复制,保证跨机房延时在 1s 以内。提供了基于时间的冲突解决方案,确保各个机房的数据一致。DRC 除了复制数据,还对外提供了数据变更的通知,让业务能够感知到其他机房的数据变化,做相应的处理,例如清除Cache等。除了DRC,我们还有 ZK复制工具,RMQ 复制工具,Redis复制工具,基本每个数据层次,都有对应的复制方案。

Data Access Layer:数据访问

数据访问层支撑了 Globa Zone 的逻辑,还提供了最后一道保护,拒绝路由错误的数据写入,是多活最底层的支撑。

4.4 未来:下一步多活的计划

目前饿了么的服务已经部署到2个异地机房,下一步我们会扩展到3-4个机房,并且在公有云上建立一个新的ezone,充分利用公有云的强大的扩展能力

参考:

  • 阿里巴巴毕玄:异地多活数据中心
  • 解密阿里巴巴“异地多活”技术
  • 微博“异地多活”部署经验谈
  • 饿了么异地多活技术实现