什么是单元化

  1. 单元化技术是阿里异地多活方案的内部代号,同城多活是它的第一步,主要解决双11购物的大流量问题,涉及到数据库垂直、水平分库、数据库复制、数据库一致性、应用层多活、接入层流量控制等一系列技术。是目前国内异地多活实施稳定、学习资料容易查阅的一个方案。核心设计思想是从C端用户角度考虑,将用户核心业务的闭环链(比如购物闭环),内聚到一个单元,用户在C端操作的一个完整过程,相关数据都能在一个单元内完成,避免跨单元获取数据。这里的一个单元可以是一个机房,也可以是一个机房内的某一组子系统群。

2. 单元化系统里面是一个能完成所有业务操作的自包含集合,在这个集合中包含了所有业务所需的全部服务,以及分配给这个单元的数据、资源。单元化架构的外在形态是把一个单元作为系统部署的基本单位,在全站所有机房中部署数个单元,每个机房里的单元数目不定,任意一个单元都部署了系统所需的所有的应用,数据则是全量数据按照某种维度划分后的一部分。如下是单元化的部署形态:

3. 在传统的服务化架构下(如下图),服务是分层的,每一层使用不同的分区算法,每一层都有不同数量的节点,上层节点随机选择下层节点。当然这个随机是比较而言的。


图 2 :传统的服务化架构,为伸缩性设计,上层节点随机选择下层节点
与其不同的是,在单元化架构下,服务虽然分层划分,但每个单元自成一体。按照层次来讲的话,所有层使用相同的分区算法,每一层都有相同数量的节点,上层节点也会访问指定的下层节点。因为他们已经在一起。


图 3 :单元化架构,为性能和隔离性而设计,上层节点访问指定下层节点

为什么要单元化

单元化,最初是用来解决双11数据库连接不够的问题提出的一种解决方案。早在 2011 年,支付宝系统就开始对核心数据库做水平拆分,而更早之前,对多个关键业务数据库的垂直拆分就已完成。到了 2013 年时,几乎所有支付宝核心数据库,都完成了水平拆分,拆分维度为用户,拆分为 100 个数据分区。此时系统的部署模式是这样的:


同一个应用的所有节点,都会连接这个业务的所有数据分库,每个分库上部署了若干数据分区。任意一个应用节点都可能接收到来自任意用户的业务请求,然后再根据数据分区规则,访问对应分库的数据。这个架构帮助支付宝系统撑过了 2012 年双 11,却无论如何过不了 2013 年大促了,原因在于数据库连接不够用了。主流的商业数据库,连接都不是共享的,就是说一个事务必须独占一个连接。而连接却又是数据库非常宝贵的资源,不能无限增加。当时的支付宝,面临的问题是不能再对应用集群扩容,因为每加一台机器,就需要在每个数据分库上新增若干连接,而此时几个核心数据库的连接数已经到达上限。应用不能扩容,意味着支付宝系统的容量定格了,不能再有任何业务量增长,别说大促,很可能再过一段时间连日常业务也支撑不了了。 如果 OceanBase 在当时已经成熟,可以很好的解决 Sharding 带来的连接数瓶颈问题,因为 OceanBase 是分布式数据库,连接可以共享。然而那时并没有一个合适的经过验证的共享连接数据库产品,因此单元化成为了最好的也是唯一的解决办法。


根据单元化的特性,每个单元中的应用服务器仅连接本单元的数据分库,如果有 n 个单元,这就相当于所有数据分库的连接数立刻降为原来的 1/n。从支付宝并没有倒在 2013 年双 11 可以知道,这次架构改造是成功的,解决了当初的燃眉之急,并且在落地单元化架构的过程中,蚂蚁技术团队也充分拓展了单元化架构的应用场景和架构的能力,从中获得了更多的架构红利,主要表现在以下几个方面:

  1. 无限可伸缩微服务架构通过中间件和 PaaS 平台的配合,能够通过快速搭建一个业务完整的逻辑部署单元对系统进行整体扩容,对新机房扩容等操作带来了非常大的便利。突破接入层、应用层和数据层的瓶颈,可支持无限扩展。异地多活部署除了具备异地容灾能力以外,还能做到异地多城市多活,可随时在多个城市间调配流量比例,在提升容灾能力的同时,降低了成本。
  2. 全站蓝绿发布和线上灰度仿真通过多单元之间灵活的流量调配机制,可以实现大规模集群的蓝绿发布,极大的提升了发布效率。同时,通过单元内自包含的服务发现/路由和数据层的单元化分片,保证故障被切割的更小且具备独立性,不会传播到其他机房,从而实现发布时的故障自包含,蚂蚁基于这个机制实现了线上全链路压测和灰度仿真环境,为业务提供了更真实的验证环境,这对充分验证业务正确性,降低技术故障起到了关键的作用,尤其对金融类业务。
  3. 异构机房上的弹性混合云架构通过单元化伸缩的机制和容器化技术对底层虚拟化平台的屏蔽,实现多个异构机房的资源充分利用,无论基于什么架构,无论在哪个城市,都可以快速建站部署单元,并在业务高峰期过后快速回收,完成数据的回迁。
    虽然结果很好,整个单元化演进的过程也是极其艰辛的。在关键业务数据库都已经水平拆分完毕的情况下,支付宝还是用了将近 1 年的时间,改造了几乎全站业务系统以支持单元化。除此之外,还花费了相当大的力量改造系统使用的各类中间件,包括微服务框架、消息中间件、调度系统等,还建设了专门的单元化运维监控平台。无论在架构方案上还是技术产品上,蚂蚁技术团队都积累了大量的经验。这个过程带来的启示是,单元化确实能带来不少激动人心的特性,但同时也会让系统架构变的复杂。这不仅会对系统的运维平台提出更高要求,也会给业务研发带来一些理解成本,这个思想非常像业界流行的 Cloud Native 的思想,要充分利用云架构的好处,还需要让应用层架构设计遵照一定的范式,而蚂蚁逐步将这些范式和能力沉淀到了 PaaS 平台、中间件和数据库中,成为一种原生 (Native) 的能力,为业务开发提供单元化能力支撑。所以说,单元化不能被神话,它有利有弊,只是看着它的好处就贸然决定开始的做法不可取。应当充分评估当前的系统现状,识别其中存在的和潜在的问题,判断单元化是否能解决以及解决所带来的成本,选择最合适的性价比最高的架构来支撑业务发展。 当然,如果最后的判断是需要引入单元化,那么一定义无反顾,毕竟它产生的价值及其诱人。

是否可以单元化

由上面什么是单元化的问题可以看出,单元化架构要求系统必须具备的一项能力:数据分区,实际上正是数据分区决定了各个单元可承担的业务流量比例。数据分区(shard),即是把全局数据按照某一个维度水平划分开来,每个分区的数据内容互不重叠,这也就是数据库水平拆分所做的事情。 仅把数据分区了还不够,单元化的另外一个必要条件是,全站所有业务数据分区所用的拆分维度和拆分规则都必须一样。若是以用户分区数据,那交易、收单、微贷、支付、账务等,全链路业务都应该基于用户维度拆分数据,并且采用一样的规则拆分出同样的分区数。比如,以用户 id 末 2 位作为标识,将每个业务的全量数据都划分为 100 个分区(00-99)。
有了以上两个基础,单元化才可能成为现实。把一个或几个数据分区,部署在某个单元里,这些数据分区占总量数据的比例,就是这个单元能够承担的业务流量比例。 选择数据分区维度,是个很重要的问题。一个好的维度,应该: 粒度合适。粒度过大,会让流量调配的灵活性和精细度收到制约;粒度过小,会给数据的支撑资源、访问逻辑带来负担。 足够平均。按这个维度划分后,每个分区的数据量应该几乎一致。 以用户为服务主体的系统(To C),比如支付宝,通常可以按照用户维度对数据分区,这是一个最佳实践。

如何实施单元化

实际中,单元化很难有实施到完美的模型,因为数据这里,就存在很多分类的数据,不能满足完美的单元化。而数据分类问题,也是影响单元化最后质量好坏的关键。

  1. 数据归类
    多数据中心系统数据需要解决3类数据的问题:业务层可分区数据、高频全局数据、低频全局数据。” ,数据分类是单元化的设计前提,因为各种类型的数据,在数据复制和垂直、水平设计时,同步方案不一样。

可分区数据

可以按照选择好的维度进行分区的数据,真正能被单元化的数据。这类数据通常在系统业务链路中处于核心位置,单元化建设最重要的目标实际上就是把这些数据处理好。比如订单数据、支付流水数据、账户数据等,都属于这一类型。 这类数据在系统中的占比越高,整体单元化的程度就越高,如果系统中全部都是这样的数据,那我们就能打造一个完美单元化的架构。不过现实中这种情况存在的可能性几乎为零,因为下面提到的两类数据,或多或少都会存在于系统当中。

全局数据,不被关键链路业务频繁访问

不能被分区的数据,全局只能有一份。比较典型的是一些配置类数据,它们可能会被关键链路业务访问,但并不频繁,因此即使访问速度不够快,也不会对业务性能造成太大的影响。 因为不能分区,这类数据不能被部署在经典的单元中,必须创造一种非典型单元用以承载它们。

全局数据,需要被关键链路业务频繁访问

乍看与上面一类相似,但两者有一个显著的区别,即是否会被关键链路业务频繁访问。如果系统不追求异地部署,那么这个区别不会产生什么影响;但如果希望通过单元化获得多地多活的能力,这仅有的一点儿不同,会让对这两类数据的处理方式截然不同,后者所要消耗的成本和带来的复杂度都大幅增加。

究其原因是异地部署所产生的网络时延问题。根据实际测试,在网络施工精细的前提下,相距约 2000 公里的 2 个机房,单向通信延时大约 20ms 左右,据此推算在国内任意两地部署的机房,之间延时在 30ms 上下。假如一笔业务需要 1 次异地机房的同步调用,就需要至少 60ms 的延时(请求去,响应回)。如果某个不能单元化的数据需要被关键业务频繁访问,而业务的大部分服务都部署在异地单元中,网络耗时 60ms 的调用在一笔业务中可能有个几十次,这就是说有可能用户点击一个按钮后,要等待数秒甚至数十秒,系统的服务性能被大幅拉低。这类数据的典型代表是会员数据,对于支付宝这类 To C 的系统来说,几乎所有的业务都需要使用到会员信息,而会员数据却又是公共的。因为业务必然是双边的,会员数据是不能以用户维度分区的。支付宝的单元化架构中,把单元称之为 “zone”,并且为上面所说的3类数据分别设计了三种不同类型的 zone:
RZone(Region Zone):最符合理论上单元定义的 zone,每个 RZone 都是自包含的,拥有自己的数据,能完成所有业务。
GZone(Global Zone):部署了不可拆分的数据和服务,这些数据或服务可能会被RZone依赖。GZone 在全局只有一组,数据仅有一份。
CZone(City Zone):同样部署了不可拆分的数据和服务,也会被 RZone 依赖。跟 GZone 不同的是,CZone 中的数据或服务会被 RZone 频繁访问,每一笔业务至少会访问一次;而 GZone 被 RZone 访问的频率则低的多。
RZone 是成组部署的,组内 A/B 集群互为备份,可随时调整 A/B 之间的流量比例。可以把一组 RZone 部署的任意机房中,包括异地机房,数据随着 zone 一起走。GZone 也是成组部署的,A/B 互备,同样可以调整流量。GZone 只有一组,必须部署在同一个城市中。CZone 是一种很特殊的 zone,它是为了解决最让人头疼的异地延时问题而诞生的,可以说是支付宝单元化架构的一个创新。 CZone 解决这个问题的核心思想是:把数据搬到本地,并基于一个假设:大部分数据被创建(写入)和被使用(读取)之间是有时间差的。把数据搬到本地:在某个机房创建或更新的公共数据,以增量的方式同步给异地所有机房,并且同步是双向的,也就是说在大多数时间,所有机房里的公共数据库,内容都是一样的。这就使得部署在任何城市的 RZone,都可以在本地访问公共数据,消除了跨地访问的影响。整个过程中唯一受到异地延时影响的,就只有数据同步,而这影响,也会被下面所说的时间差抹掉。时间差假设:举例说明,2 个用户分属两个不同的 RZone,分别部署在两地,用户 A 要给用户 B 做一笔转账,系统处理时必须同时拿到 A 和 B 的会员信息;而 B 是一个刚刚新建的用户,它创建后,其会员信息会进入它所在机房的公共数据库,然后再同步给 A 所在的机房。如果 A 发起转账的时候,B 的信息还没有同步给 A 的机房,这笔业务就会失败。时间差假设就是,对于 80% 以上的公共数据,这种情况不会发生,也就是说 B 的会员信息创建后,过了足够长的时间后,A 才会发起对 B 的转账。通过对支付宝 RZone 业务的分析发现,时间差假设是成立的,实际上超过 90% 的业务,都对数据被创建和被使用之间的时间间隔要求很低。余下的那些不能忍受时间差的业务(即要求数据被创建后就必须马上可用,要不就干脆不能访问),则必须进行业务改造,忍受异地访问延时。
2. 数据复制

数据复制指不同机房间数据库复制, 数据复制基本会有如下几种方案:

基于mysql的原生复制,做主主架构,两边都开启写入。

基于PXC galera类似的数据库集群方案

基于otter+canal、Apache Pulsar的开源数据同步方案

自研数据复制方案

前两种合适在机房内的数据库复制,不合适地域级跨机房复制,而阿里开源的otter+canal方案是创业企业较常用的跨机房复制方案。

如果将单元化一系列难点作排名,数据库复制是排前3的,因为多活解决的最大难点就是数据复制带来的延迟。目前数据库复制的中间件很多,比如阿里otter+canal方案、Apache Pulsar等。一般的公司如果强调业务的快速迭代,支持用开源的中间件方案实施,当然,熟练使用中间件的成本和代价也很高。如果有研发能力,可以考虑自研实现。数据同步这里,总体上需要基于mysql等存储的私有协议,基于协议读取bin_log数据,然后再使用队列传输到另一个数据中心恢复数据。

需要的支持

前面提到,支付宝系统早已实现了数据水平拆分,并且全站拆分维度一致,在具备了这个重要基础能力的前提下,仍然举全站之力用了近一年时间才完成单元化改造,除了因为要改造业务系统以妥善处理 GZone/CZone 的数据以外,还有一项工作也耗费了大量人力和时间,那就是建设起了一个单元化技术支持平台。 单元化架构增加了构复杂性和运维复杂度,如果没有这样一个平台支撑,很难把如此复杂的一个系统健康的维护起来。 所谓单元化技术支持平台,至少应该包含三方面功能。

一套支持单元化的中间件

支付宝的业务开发因单元化架构而变得复杂、不易理解,而实际上现在看到的复杂性比起它的原本的程度来说已经降低了太多。这其中最大程度屏蔽了单元化细节,让单元化架构尽量对业务层透明,起到至关重要作用的,就是一整套专门针对单元化而研制的中间件,也即原生 (Native) 单元化能力。
比较关键的几个包括,数据访问层中间件,把数据水平分库分表的细节隐藏在水面之下,上层业务使用起来跟单库单表没什么区别。更进一步的,数据访问层能实时动态感知单元化分流规则,根据规则变化决策是否连接某个数据分库,以及一笔业务是否允许访问某个数据分库。可以说单元化的本质就是对数据的逻辑隔离,有这样一个数据访问中间件对单元化建设来说是必不可少的,它解耦了业务层对数据层结构的依赖,大大降低了理解成本。
另外更为重要的是几个负责信息传递的中间件,正是它们让单元可以成为单元。这其中有微服务框架、消息中间件等,它们能够根据单元化规则把系统应用间的服务调用或消息流转约束在一个逻辑区域内,再配合上数据访问中间件对数据访问的约束,就让这一个个逻辑区域形成了一个个单元。如果有些调用必须跨单元发生,也由它们根据规则执行转发,不需要业务层关心。
单元化涉及到接入路由、数据队列、缓存、api路由、数据库复制、数据库事务等一些系列关键技术。从阿里资料上显示,单元化改造让本身的业务开发变得更复杂。而实际上现在看到的复杂性比起它的原本的程度来说降低了太多。这其中是由于中间件技术最大程度屏蔽了单元化的很多细节,让单元化架构尽量对业务层透明。阿里巴巴大牛些提供了一整套专门针对单元化而研制的中间件,即阿里原生 (Native) 单元化生态,如数据库中间件MyCAT,将数据库垂直和水平分库的细节隐藏在业务之下,上层业务使用起来跟单库单表没什么区别,同时数据访问层能实时动态感知单元化分流规则,根据规则变化决策是否连接某个数据分库,以及一笔业务是否允许访问某个数据分库。阿里单元化的本质是对数据的逻辑隔离,有这样一个数据访问中间件对单元化建设来说是必不可少,它解耦了业务层对数据层结构的依赖,大大降低了理解成本和业务开发成本。另外重要的有负责信息传递的中间件,正是它们让业务可以成为单元。这其中有dubbo微服务框架、消息中间件等,它们能够根据单元化规则把系统应用间的服务调用或消息流转约束在一个逻辑区域内,再配合上数据访问中间件对数据访问的约束,就让这一个个逻辑区域形成了一个个单元。如果有些调用必须跨单元发生,也由它们根据规则执行转发,不需要业务层关心。

总之,中间件的抽象降低了单元化的连接成本,同时也相对降低了实施成本,这是单元实施最关键的设计

一个规则管理和流量调拨系统

前面多次提到了“规则”这个词,在单元化架构中,规则举足轻重,业务流量在单元间怎么分配,哪个单元能访问哪个数据分库,某次服务调用能否跨出单元,这些都由规则掌管。可想而知,必须有一个统一的位置来全局唯一的管理这套规则,并在变更时能够及时准确的的推送给各个执行点,否则不同模块识别到的规则不同,将会造成全系统信息混乱。
规则管理系统的目的就是成为这样一个单元化规则权威持有者,运维人员对规则的所有变更,都在这个系统上操作。它通过某种协议连接着系统中所有需要感知规则的模块,一旦发生规则变化就实时把新规则内容推送给各个模块,并识别推送结果,对推送失败的模块执行重试,确保规则下发到所有位置。
具有各个单元化系统后,就需要有一个支持全部单元的调度系统,调度所有单元系统。比如业务流量在单元之间怎样分配,单元之间是否能跨越访问业务,这些规则都需要一个全局性的调度中心实现并且能及时实时控制所有单元节点的运行。运营人员就在这个调度中心设置各种单元规则并实时性下发到各个子单元。

一个面向单元的发布部署和监控平台

单元化后,一个系统被划分成了数量众多的逻辑单元,这让系统发布换版的粒度更细,试错成本更低,但也让发布这件事情更难做,所以一个能面向单元进行发布的部署平台就变的非常必要。 这个部署平台能够以单元为维度执行发布,单元间可以互不影响的独立部署。
同时,平台需要有能力统一编排和控制不同单元的部署进程,从而能够实现基于单元的全站蓝绿发布等高阶功能。
此外,监控系统要求能够细化到单元粒度展示监控信息和发送报警,以协助判断每个单元中的系统健康程度,为快速执行流量切换以隔离故障等操作提供依据。

面临的挑战

在 专访阿里巴巴毕玄:异地多活数据中心项目的来龙去脉(https://www.kancloud.cn/infoq/toptech02/47787)
这篇文章中,提到了如下的主要挑战:

第一个是路由的一致性。因为我们是按买家维度来切分数据的,就是数据会封闭在不同的单元里。这时就要确保,这个买家相关的数据在写的时候,一定是要写在那个单元里,而不能在另外一个单元,否则就会出现同一行数据在两个地方写的现象。所以这时一定要保证,一个用户进到淘宝,要通过一个路由规则来决定这个用户去哪里。这个用户进来以后,到了前端的一组Web页面,而Web页面背后还要访问很多后端服务,服务又要访问数据库,所以最关键的是要保障这个用户从进来一直到访问服务,到访问数据库,全链路的路由规则都是完全一致的。如果说某个用户本来应该进A城市的数据中心,但是却因为路由错误,进入了B城市,那看到的数据就是错的了。造成的结果,可能是用户看到的购买列表是空的,这是不能接受的。所以如何保障路由的一致性,非常关键。

第二个是挑战是数据的延时问题。因为是异地部署,我们需要同步卖家的数据、商品的数据。如果同步的延时太长,就会影响用户体验。我们能接受的范围是有限的,如果是10秒、30秒,用户就会感知到。比如说卖家改了一个价格,改了一个库存,而用户隔了很久才看到,这对买家和卖家都是伤害。所以我们能接受的延时必须要做到一秒内,即在全国的范围内,都必须做到一秒内把数据同步完。当时所有的开源方案,或者公开的方案,包括MySQL自己的主备等,其实都不可能做到一秒内,所以数据延时是我们当时面临的第二个挑战。

第三个挑战,在所有的异地项目中,虽然冷备成本很高,多活可以降低成本,但是为什么大家更喜欢冷备,而不喜欢多活呢?因为数据的正确性很难保证。数据在多点同时写的时候,一定不能写错。因为数据故障跟业务故障还不一样,跟应用层故障不一样。如果应用出故障了,可能就是用户不能访问。但是如果数据写错了,对用户来说,就彻底乱了。而且这个故障是无法恢复的,因为无法确定到底那里写的才是对的。所以在所有的异地多活项目中,最重要的是保障某个点写进去的数据一定是正确的。这是最大的挑战,也是我们在设计整个方案中的第一原则。业务这一层出故障我们都可以接受,但是不能接受数据故障。

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

参考文章
https://yq.aliyun.com/articles/665155
https://kuaibao.qq.com/s/20180514G1XNXQ00?refer=spider
https://www.kancloud.cn/infoq/toptech02/47787