异地多活


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



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



异地多活的挑战
延迟 异地多活面临的主要挑战是网络延迟,以北京到上海 1468 公里,即使是光速传输,一个来回也需要接近10ms,在实际测试的过程中,发现上海到北京的网络延迟,一般是 30 ms。
一致性 用户在任何一个机房写入的数据,是否能在任何一个机房读取的时候返回的值是一致性的。
误区
所有业务都要异地多活
以用户中心为例,注册是没必要做异地多活的,假如用户在A机房注册了,在数据没有向外同步的时候,A机房网络中断,这个时候如果让用户切换到B机房注册,就有可能发生数据不一致,出现两个基本相同的账号,这是不可容忍的。但是相对应的来说,用户登录这种是关键核心业务,就有必要做到异地多活了,用户在A机房登录不了,那就让用户在B机房登录。虽然有极端的情况,用户在A机房修改了密码,但是出现网络中断,B机房的用户仍然保存的是旧密码,但是相对于不可登录来说,这种情况是可容忍的。同时有些业务仍然是无法实现异地多活的,比如涉及到金钱的业务,加入有一个用户有100块,消费了50块,A机房发生异常,数据没有同步出去,这时候用户在B机房登录后发现自己还有100块,可以继续消费,就会对业务造成严重的影响。



必须做到实时一致性
受限于物理条件,跨地域的网速一定会存在延迟,一般是几十毫秒,如果遇上网络抖动,延迟超过几秒甚至几十秒都有可能。解决方法只能是减少需要同步的数据和只保证数据的最终一致性,有时候用户在A机房修改了一条数据,业务上实际上是能容忍数据的短时间不一致的,即使其他用户在B机房读到的是旧数据,实际上对业务也没有任何影响。



只使用存储系统的同步功能
大部分场景下,MySQL Redis自带的同步功能已经足以满足需求了,但是在某些极端情况下,可能就不合适了,MySQL的单线程复制可能会产生较大的延迟,Redis可能会有全量复制,所以系统要灵活使用各种解决方案。
用消息队列把数据广播到各个数据中心
回源读取,当A机房发现没有这条数据的时候,根据路由规则去B机房去读取该数据
重新生成数据,A机房登录后生成session数据,这时候A机房挂了,可以把用户切换到B机房,重新生成session数据。
实现100%的高可用
100%的高可用是无法保证的,硬件的损坏,软件的BUG,光纤传输等太多不可控的因素,而且也要在成本上做一个权衡,尤其是对于强一致性业务,C和A只能取一个平衡,容忍短时间的不可用来保证数据的完全一致性。
饿了么异地多活方案
特点
业务内聚,单个订单的所有流程保证在一个机房内完成调用,不允许进行跨机房调用。每一个机房称为一个ezone,对服务进行分区,让用户,商户,骑手按照规则聚合到一个ezone内。根据业务特点,饿了么选择了把地理位置作为划分业务的单元,以行政省界用围栏
把全国分为多个shard。在某个机房出现问题的时候,也可以按照地理位置把用户,商户,骑手打包迁移到别的机房即可。



可用性优先,当机房发生故障的时候,优先保证可用,用户可以先下单吃饭,有限时间窗口内的数据不一致可以事后再修复。每个 ezone 都会有全量的业务数据,当一个 ezone 失效后,其他的 ezone 可以接管用户。用户在一个ezone的下单数据,会实时的复制到其他ezone。



保证数据的正确性,在切换和故障时,检测到某些订单在两个机房不一致,会锁定改订单,避免错误进一步扩散。



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



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



新浪微博异地多活方案
微博使用了基于 MCQ(微博自研的消息队列)的跨机房消息同步方案,并开发出跨机房消息同步组件 WMB(Weibo Message Broker)。
每个机房的缓存是完全独立的,由每个机房的 Processor(专门负责消息处理的程序,类 Storm)根据收到的消息进行缓存更新。由于消息不会重复分发,而且信息完备,所以 MytriggerQ 方案存在的缓存更新脏数据问题就解决了。而当缓存不存在时,会穿透到 MySQL 从库,然后进行回种。可能出现的问题是,缓存穿透,但是 MySQL 从库如果此时出现延迟,这样就会把脏数据种到缓存中。解决方案是做一个延时 10 分钟的消息队列,然后由一个处理程序来根据这个消息做数据的重新载入。一般从库延时时间不超过 10 分钟,而 10 分钟内的脏数据在微博的业务场景下也是可以接受的。
由于微博对数据库不是强依赖,加上数据库双写的维护成本过大,选择的方案是数据库通过主从同步的方式进行。这套方案可能的缺点是如果主从同步慢,并且缓存穿透,这时可能会出现脏数据。这种同步方式已运行了三年,整体上非常稳定,没有发生因为数据同步而导致的服务故障。



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



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



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



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



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



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



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



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


Category algorithm