多活
多活系统
业务分级
按照一定的标准将业务进行分级,挑选出核心的业务,只为核心业务核心场景设计异地多活,降低方案整体复杂度和实现成本。例如:1、访问量;2、核心场景;3、收入;避免进入所有业务都要全部多活,分阶段分场景推进。
数据分类
挑选出核心业务后,需要对核心业务相关的数据进一步分析,目的在于识别所有的数据及数据特征,这些数据特征会影响后面的方案设计。常见的数据特征分析维度有:1、数据量;2、唯一性;3、实时性;4、可丢失性;5、可恢复性;
数据同步
确定数据的特点后,我们可以根据不同的数据设计不同的同步方案。常见的数据同步方案有:1、存储系统同步;2、消息队列同步;3、重复生成;
异常处理
无论数据同步方案如何设计,一旦出现极端异常的情况,总是会有部分数据出现异常的。例如,同步延迟、数据丢失、数据不一致等。异常处理就是假设在出现这些问题时,系统将采取什么措施来应对。常见的异常处理措施:1、多通道同步;2、同步和异步访问;3、日志记录;4、补偿;
多活不是整个体系业务的多活,而是分成不同维度,不同重要性的多活,比如我们业务观看体验为主(淘宝以交易单元,买家为维度),那么第一大前提就是浏览、观看上的多活。我们将资源分为三类:
Global
资源:多个Zone
(机房)共享访问的资源,每个Zone
访问本Zone
的资源,但是Global
层面来说是单写Core Zone
(核心机房),即:单写 + 多读、利用数据复制(写Zone
单向)实现最终一致性方案实现(例如账号数据);Multi Zone
资源:多个Zone
分片部署,每个Zone
拥有部分的Shard
数据,比如我们按照用户维度拆分,用户A
可能在ZoneA
,用户B
可能在ZoneB
,即:多写+多读、利用数据复制(写Zone
双向复制)方案实现;Single Zone
资源:单机房部署业务;
核心主要围绕:PC/APP
首页可观看、视频详情页可打开、账号可登录、鉴权来开展,我们认为最合适我们观看类业务最合适的场景就是采用 Global
资源策略,对于社区类(评论、弹幕)可能会采用 Multi Zone
的策略。
蚂蚁金服单元化架构设计:
单元化,指的是业务尽量在一个单元内完成的架构。
整体架构包含RZone
、GZone
和CZone
。
GZone
,全局唯一:其中GZone
部署的是无法拆分的数据和业务,GZone
的数据和业务被RZone
依赖,GZone
全Paxos
局只部署一份,而RZone
部署的是可拆分的业务和对应的数据。
RZone
,区域:每个RZone
内的数据分片如图所示有五副本,实现三地五中心部署,每个分片内只有一个可写入的主副本,其余副本按照协议做数据强一致。每个RZone
内实现业务单元封闭,独立完成自己的所有业务。
CZone
,城市,只读副本:而CZone
的出现是因为GZone
全局只有一份,不同城市的RZone
可能依赖GZone
服务和数据的时候需要远距离调用,延迟比较大,所以在每个城市部署一个CZone
作为GZone
的只读副本,为本城市的RZone
提供服务。
核心指标:
RPO
(Recovery Point Object
):表示机房级别故障时,未被同步的数据时长。考虑到MySQL
在特殊情况下复制延迟较大,RPO
设置为分钟级别,正常情况下RPO
为秒级RTO
(Recovery Target Object
):表示机房故障情况下,关键流程或系统切换恢复时间,一般为分钟级别WRT
(Work Recovery Time
):表示故障时,由于RPO
导致的未同步异常数据修复完成时长,一般为小时级别。
行业常见分布式架构分析:
行业常见的分布式架构主要包含,单活架构、双活架构和冷备架构。从容灾能力角度来看,双活架构和冷备架构均能做到应用级跨机房容灾,但是数据库因为使用了异步复制的技术,无法做到机房级RPO=0
的容灾(需要启动之后进行预热和备份数据同步)。
饿了么多活
业务模型
业务过程中包含3个最重要的角色,分别是用户、商家和骑手,一个订单包含3个步骤:
- 用户打开饿了么APP,系统会推荐出用户位置附近的各种美食,推荐顺序中结合了用户习惯,推荐排序,商户的推广等。用户找到中意的食物,下单并支付,订单会流转到商家。
- 商家接单并开始制作食物,制作完成后,系统调度骑手赶到店面,取走食物。
- 骑手按照配送地址,把食物送到客户手中。
基本原则
业务内聚
单个订单的履单过程,要在一个机房中完成,不允许跨机房调用。这个原则是为了保证实时性,履单过程中不依赖另外一个机房的服务,才能保证没有延迟。
我们称每个机房为一个
ezone
,一个ezone
包含了饿了么需要的各种服务。一笔业务能够内聚在一个ezone
中,那么一个订单涉及的用户,商家,骑手都会在相同的机房(或者一个单元),这样订单在各个角色之间流转速度最快,不会因为各种异常情况导致延时。恰好饿了么的业务是地域化的,通过合理的地域划分,也能够实现业务内聚。
可用性优先
当发生故障切换机房时,优先保证系统可用,首先让用户可以下单吃饭,容忍有限时间段内的数据不一致,在事后修复。每个
ezone
都会有全量的业务数据,当一个ezone
失效后,其他的ezone
可以接管用户。用户在一个ezone
的下单数据,会实时的复制到其他ezone
。保证数据正确
在确保可用的情况下,需要对数据做保护以避免错误,在切换和故障是,如果发现某些订单的状态在两个机房不一致,会锁定该笔订单,阻止对它进行更改,保证数据的正确。
业务可感
因为基础设施还没有强大到可以抹去跨机房的差异,需要让业务感知多活逻辑,业务代码要做一些改造,包括:需要业务代码能够识别出业务数据的归属,只处理本
ezone
的数据,过滤掉无关的数据。改善业务状态机,能够在数据出现不一致的时候,通过状态机发现和纠正。
业务特征
为了实现业务内聚,我们首先要选择一个划分方法(Sharding Key
),对服务进行分区,让用户,商户,骑手能够正确的内聚到同一个 ezone
中。分区方案是整个多活的基础,它决定了之后的所有逻辑。
根据饿了么的业务特性,可以自然的选择地理位置(地理围栏,地理围栏主体按照省界划分,再加上局部微调)作为划分业务的单元,把地理位置上接近的用户,商户,骑手划分到同一个 ezone
,这样一个订单的履单流程就会在一个机房完成,能够保证最小的延时,在某个机房出现问题的时候,也可以按照地理位置把用户,商户,骑手打包迁移到别的机房即可。
位置划分
基于地理位置划分规则,开发了统一的流量路由层(API Router
),这一层负责对客户端过来的 API
调用进行路由,把流量导向到正确的 ezone
,API Router
部署在多个公有云机房中,用户就近接入到公有云的 API Router
,还可以提升接入质量。
最基础的分流标签是地理位置,有了地理位置,API Router
就能计算出正确的 shard
归属。但业务是很复杂的,并不是所有的调用都能直接关联到某个地理位置上,我们使用了一种分层的路由方案,核心的路由逻辑是地理位置,但是也支持其他的一些 High Level Sharding Key
,这些 Sharding Key
由 API Router
转换为核心的 Sharding Key
,具体如上图。
这样即减少了业务的改造工作量,也可以扩展出更多的分区方法。出了入口处的路由,我们还开发了 SOA Proxy
,用于路由 SOA
调用的,和 API Router
基于相同的路由规则。
阿里多活
基本原则:
- 按买家维度来进行数据切分
- 只取与买家链路相关的业务(单元)做多活
- 单元内最大限度的封闭
- 无法接受数据最终一致的跨单元单点写
业务架构
下单需要立即可见,就需要有多分数据存储在不同的单元,同时同步到中心机房。并且需要扩散到其他所有单元。
又需要其他单元查询到卖家数据,因此卖家数据是通过中心全量复制到其他单元。
技术架构
通过 CDN
实现路由功能,CDN
通过业务逻辑判断用户分流到具体的 IDC
。
除了多个 IDC
之外,还有一个中心机房,好处是复制过程简化,IDC
数据先同步到中心机房,再由中心机房将数据同步到其他的 IDC
。
容灾能力
同城容灾
RZone1
出现故障先看同城容灾能力,我们目标将RZone1
切换至同城同在RZone2
。先做数据库分片切换,RZone1
对应的分片为分片1,把分片1在RZone2
的副本提升为主副本,数据库副本提升完毕后,将RZone1
的流量切换至RZone2
,实现同城容灾RPO=0
、RTO<1min
异地容灾
同样以
RZone1
故障为例。目标切换至RZone3
,先做数据库切换,分片1在RZone3
的副本切换成主副本,完成后将RZone1
的流量切换至RZone3
,实现异地容灾,该过程RPO=0
、RTO<1min
这种情况,使用 Raft
或者 Paxos
这种需要满足写入半数的强一致性,会牺牲写入延迟。
流量路由
流量路由模块核心是将用户的 uid
信息和对应的 Zone
信息植入到 cookie
中,供路由模块做精准路由。
服务路由分为本机房服务路由和跨机房服务路由调用。
本机房服务路由
服务调用端向本机房服务注册中心订阅服务,发现服务地址后做本机房服务路由调用。
跨机房服务路由调用
服务调用端向其他
IDC
的注册中心订阅服务地址,发现服务地址后做跨机房服务调用。
数据高可靠
蚂蚁使用自研的分布式关系数据库 OceanBase
,每个分片的数据库做5副本部署,部署地域实现三地五中心部署,5副本中有3副本实现强一致,如图所示可以实现同城、IDC
容灾和异地容灾。(同城两副本,最近地域1副本,保证写入3副本时的延迟。)
苏宁多活
Cell
业务可封闭收敛最小执行分片:业务对请求空间按一定维度(比如会员、门店等)划分分片
LDC
逻辑数据中心,是由多个业务可封闭
cell
组成的集合单元,拥有独立的基础中间件系统(包括RPC
,MQ
,DNS
等),以及出口网络等。PDC
物理数据中心,指物理上独立的一栋建筑,一般每栋有好几层,存放一系列机柜和上千和上万服务器,构成一个
PDC
AZ
(Avaliable Zone
)可用区,具有独立的故障隔离空间,拥有独立网络设施或电力设备,由相邻的单个或多个
PDC
组成。Region
地理区域,有多个可用区所组成的集合,区域之间故障域完全隔离
服务架构
分片服务
对应数据仅在某个
Cell
存在,其他Cell
不与交叉或共享,比如会员服务、订单服务等共享服务
所有
Cell
拥有相同的数据,相互共享,比如价格服务、商品服务等索引服务
用于索引数据提供服务,类似共享服务
竞争(控制)服务
各个
Cell
相互操作同一个数据,为了保证数据一致性,需要在同一个数据中心进行控制,比如库存的扣减、用户注册等竞争
Proxy
服务用于竞争服务前置服务,比如库存前置调拨服务(例如一次性获取100个库存)
容灾能力
为了确保数据高可用以及任何一个机房故障都可被接管,所有数据中心都包含全量数据,当主数据中心的变更将会实时同步到各个从数据中心。
数据中心之间的延迟相对数据中心内部延迟较大,数据中心之间的同步一般采用异步复制方式。在机房故障等极端情况,将出现少量数据未同步到其他数据中心,针对此类故障场景,在机房恢复后,需要对未同步的数据进行人工修复。
Facebook Memcache 一致性
- 写入缓存时带一个 marker
- 将数据写入到主库
- 删除缓存
- 另外有一个请求过来,如果标记存在,从主库读取;如果标记不存在,则从从库读取
- 从主库到从库同步的时候,将
cache
中删除marker
微信朋友圈异地多活
因果关系对事件施加了一种顺序:
因在果之前,消息发送在消息收取之前。而且就像现实生活中一样,一件事会顺序地导致另一件事发生:某个节点读取了一些数据然后写入一些结果,另一个节点读取其他写入的内容,并依次写入一些其他内容等等。这些因果依赖的操作链定义了系统中的因果顺序,即什么在什么之前发生。从而我们也引出了分布式系统的因果一致性,如果一个系统服从因果关系所规定的顺序,我们说它是因果一致性的。
微信朋友圈某条状态的评论以及对评论的答复(也是评论)所构成的因果关系。
微信分布在全球四地的数据中心,可知用户小王有两个朋友:Mary、Kate,分别在不同的区域下(数据中心),所以他们要看到彼此朋友圈的内容时,必须等到相关的数据在不同数据中心间的副本同步到用户所在的IDC完成之后才能看到。
需要保证不同数据中心的因果一致性来保证一个用户在刷朋友圈的时候不会出现看到评论所对应的答复,却看不到答复对应的评论。
由于网络在不同副本间复制数据时的延迟、中断等分布式系统中常见的场景,导致两条消息在同步到用户 Kate
(加拿大)所在数据中心上的副本时已经乱序了。
即原先顺序是这样的:
“Mary: 这是哪里” ->
“小王:Mary,这是梅里雪山”
然而 Kate
去数据库中查到的消息却是这样的顺序:
“小王:Mary,这是梅里雪山” ->
“Mary: 这是哪里”
或者中间的某个时刻只能查询到 “小王:Mary,这是梅里雪山” 这一条消息,你说 Kate
会不会懵逼。
我们可以将 Mary
对小王所发布的朋友圈状态的评论 “Mary: 这是哪里” 当成因,而把小王对 Mary
评论的答复 “小王:Mary,这是梅里雪山” 当成果。
按照这样的约定,当这两条数据同步到 Kate
所在的数据中心副本时,即使发生乱序,Kate
根据在刷朋友圈时,根据因果关系也可以将这个评论、答复的顺序调整到正确的、可阅读的方式。
因果一致性算法
每条评论都有一个唯一的且递增的数字
ID
那么背后肯定是一个
ID
生成器,各个数据中心都有一个这样的入口来获取本ID
内唯一、递增的ID
每条新评论的
ID
都必须比本地已经见过的全局最大的ID
大,确保因果关系在香港的数据中心,当发表完2的评论,并且已经同步上海数据中心过来的1,4,7等
ID
的评论之后,如果再有香港地域下的用户发表新评论时,那么一定要大于当前香港数据中心能看到的全局最大ID
,此时是7,所以香港地域此时用户最新发表的评论的ID
必须大于7.广播本地看到的所有评论和新评论到其他
IDC
;相同ID
的评论合并排重
本地域下的用户,针对同一条朋友圈状态有评论时,该地域就负责申请一个全局 ID
,然后将这个评论的事件广播给其他的数据中心。
注意这个过程需要合并所有看到的序列,例如香港数据中心就合并1,2,4,7,8等针对同一条朋友圈状态的一系列评论事件IDs
,然后再整体广播出去,这样才能保证针对同一条状态的所有当前最新的事件整体被广播出去,否则此时香港IDC
只广播8的话,如果前面的事件序列在广播的中途丢失了,那么其他节点比如加拿大IDC
就会漏掉部分评论时间,这也是数据多重补位的措施。
当然这个方法有一个前提就是:因为同一个朋友圈的发布状态,一般的评论不会很多,所以造成的数据冗余交互不会很大,否则是不执行的。之余相同ID
的评论合并排重,加拿大IDC
会收到来自香港IDC
同步过来的1,4,7,8事件系列,这两个广播的事件系列有重复,所以需要去重。
账号多活
A 中心注册了用户,数据还未同步到B中心,此时A中心宕机,为了支持注册业务多活,那我们可以挑选B中心让用户去重新注册。
看起来很容易就支持多活了,但仔细思考一下会发现这样会有问题:一个手机号只能注册一个账号,A中心的数据没有同步过来,B中心无法判断这个手机号是否重复,如果B中心让用户注册,后来A中心恢复了,发现数据有冲突,怎么解决?
实际上是无法解决的,因为注册账号不能说挑选最后一个生效;而如果B中心不支持本来属于A中心的业务进行注册,注册业务的双活又成了空谈。
但很多朋友在考虑这个业务的时候,会不自觉的陷入一个思维误区:我要保证所有业务的异地多活。因此账号系统一般不会做多活,一般主写从读,多个备库支持读。
减少数据同步
用户登录所产生的
token
或者session
信息,数据量很大,但其实并不需要同步到其他业务中心,因为这些数据丢失后重新登录就可以了。- 某些情况下,可能出现消息队列同步也延迟了,用户在 A 中心注册,然后访问 B 中心的业务,此时 B 中心本地拿不到用户的账号数据。为了解决这个问题,B中心在读取本地数据失败的时候,可以根据路由规则,再去 A 中心访问一次(这就是所谓的二次读取,第一次读取本地,本地失败后第二次读取对端)。
- 对于登录的
session
数据,由于数据量很大,我们可以不同步数据;但当用户在 A 中心登录后,然后又在 B 中心登录,B 中心拿到用户上传的session id
后,根据路由判断session
属于 A 中心,直接去 A 中心请求session
数据即可,反之亦然,A 中心也可以到 B 中心去拿取session
数据。
最终一致性
A机房注册了一个用户,业务上不能要求在
50ms
内就同步到所有机房,正常情况下要求5分钟
同步到所有机房即可,异常情况下甚至可以允许 1小时 或者 1天 后能够一致。最终一致性在具体实现的时候,还需要根据不同的数据特征,进行差异化的处理,以满足业务需要。例如对账号信息来说,如果在 A机房新注册的用户 5分钟内正好跑到B机房了,此时B机房还没有这个用户的信息,为了保证业务的正确,B 机房就需要根据路由规则到A机房请求数据。
而对用户信息来说,5分钟后同步也没有问题,也不需要采取其他措施来弥补,但还是会影响用户体验,即用户看到了旧的用户信息,这个问题怎么解决呢?
数据流向
- 将用户账号密码等用户信息同步到所有机房节点,账号登录和认证都在本机房实现
- 当本机房数据没有同步过来,则降级到中心机房
稿件多活
稿件体系就是一个典型的单写,同步到其他机房。通过 MySQL
的同步来同步其他机房,同时订阅从库的 binlog
来做缓存刷新和预热。
References
饿了么异地多活技术实现(一)总体介绍
饿了么异地多活技术实现(二)API-Router的设计与实现
饿了么异地多活技术实现(三)GZS&DAL
饿了么异地多活技术实现(四)- 实时双向复制工具(DRC)
分布式系统 - 关于异地多活的一点笔记 - overview
OPPO互联网业务多活架构演进和实践
OPPO异地多活实践——缓存篇
Scaling Memcache in Facebook 笔记(一)
Scaling Memcache in Facebook 笔记(二)
Scaling Memcache in Facebook 笔记(三)
阿里异地多活与同城双活的架构演进
多中心容灾实践:如何实现真正的异地多活?
异地多活设计辣么难?其实是你想多了!
历时三年,苏宁如何建设多数据中心多活的实践项目?
异地多活数据流基础设施DRC 双11支持571亿交易额背后的武器
阿里异地多活与同城双活的架构演进
多中心容灾实践:如何实现真正的异地多活?
一线大厂都在用的异地多活的 5 种解决方案!
异地多活技术方案的原则,技巧,步骤
全球异地多活架构设计(一): Why and How
全球异地多活架构设计(二): 数据层的支持
从微信朋友圈的评论可见性,谈因果一致性在分布式系统中的应用
异地多活的单元化设计
谈谈异地多活架构
30 | 异地多活设计4步走
互联网异地多活方案发展历史
荔枝FM架构师刘耀华:异地多活IDC机房架构