【架构】一、服务单元化
转载:https://mp.weixin.qq.com/s/jfbHvEMSZtgXis3AtSOZyw
一、为什么要做单元化
决策一个系统的整体架构方向,将对这个系统的未来产生深远影响,并且会有实际的技术改造方面的人力投入。这样的的决策必须是谨慎的,有依据的。所以,对于要不要单元化这个问题,这里最想告诉大家的是一个忠告:切勿神话单元化。
回顾支付宝的整个单元化历程,最初促成这个决策的原因是一件看似无关的事情。早在 2011 年,支付宝系统就开始对核心数据库做水平拆分,而更早之前,对多个关键业务数据库的垂直拆分就已完成。到了 2013 年时,几乎所有支付宝核心数据库,都完成了水平拆分,拆分维度为用户,拆分为 100 个数据分区。此时系统的部署模式是这样的
同一个应用的所有节点,都会连接这个业务的所有数据分库,每个分库上部署了若干数据分区。任意一个应用节点都可能接收到来自任意用户的业务请求,然后再根据数据分区规则,访问对应分库的数据。
这个架构帮助支付宝系统撑过了 2012 年双 11,却无论如何过不了 2013 年大促了,原因在于数据库连接不够用了。主流的商业数据库,连接都不是共享的,就是说一个事务必须独占一个连接。而连接却又是数据库非常宝贵的资源,不能无限增加。当时的支付宝,面临的问题是不能再对应用集群扩容,因为每加一台机器,就需要在每个数据分库上新增若干连接,而此时几个核心数据库的连接数已经到达上限。应用不能扩容,意味着支付宝系统的容量定格了,不能再有任何业务量增长,别说大促,很可能再过一段时间连日常业务也支撑不了了。 如果 OceanBase 在当时已经成熟,可以很好的解决 Sharding 带来的连接数瓶颈问题,因为 OceanBase 是分布式数据库,连接可以共享。然而那时并没有一个合适的经过验证的共享连接数据库产品,因此单元化成为了最好的也是唯一的解决办法。
二、能不能单元化
如果您是新建一套系统,那么恭喜你,你只要考虑这个系统的重要性,即将来是否需要在可扩展性、容灾能力、业务连续性、成本控制等方面有很高的要求,如果答案是肯定的,那可以一开始就按照单元化的方式去设计,这套架构完全能支持业务从单机房到同城多机房,再到异地多活机房的平滑演进。但如果是一套老系统,就需要评估一下它是否具备单元化的基础了。
所谓单元,是指一个能完成所有业务操作的自包含集合,在这个集合中包含了所有业务所需的所有服务,以及分配给这个单元的数据。单元化架构就是把单元作为系统部署的基本单位,在全站所有机房中部署数个单元,每个机房里的单元数目不定,任意一个单元都部署了系统所需的所有的应用,数据则是全量数据按照某种维度划分后的一部分。 传统意义上的 SOA 化(服务化)架构,服务是分层的,每层的节点数量不尽相同,上层调用下层时,随机选择节点。
单元化架构下,服务仍然是分层的,不同的是每一层中的任意一个节点都属于且仅属于某一个单元,上层调用下层时,仅会选择本单元内的节点。
一个单元,是一个五脏俱全的缩小版整站,它是全能的,因为部署了所有应用;但它不是全量的,因为只能操作一部分数据。能够单元化的系统,很容易在多机房中部署,因为可以轻易的把几个单元部署在一个机房,而把另外几个部署在其他机房。借由在业务入口处设置一个流量调配器,可以调整业务流量在单元之间的比例。
从这个对单元的定义和特性描述中,可以推导出单元化架构要求系统必须具备的一项能力:数据分区,实际上正是数据分区决定了各个单元可承担的业务流量比例。数据分区(shard),即是把全局数据按照某一个维度水平划分开来,每个分区的数据内容互不重叠,这也就是数据库水平拆分所做的事情。 仅把数据分区了还不够,单元化的另外一个必要条件是,全站所有业务数据分区所用的拆分维度和拆分规则都必须一样。若是以用户分区数据,那交易、收单、微贷、支付、账务等,全链路业务都应该基于用户维度拆分数据,并且采用一样的规则拆分出同样的分区数。比如,以用户 id 末 2 位作为标识,将每个业务的全量数据都划分为 100 个分区(00-99)。
有了以上两个基础,单元化才可能成为现实。把一个或几个数据分区,部署在某个单元里,这些数据分区占总量数据的比例,就是这个单元能够承担的业务流量比例。 选择数据分区维度,是个很重要的问题。一个好的维度,应该: 粒度合适。粒度过大,会让流量调配的灵活性和精细度收到制约;粒度过小,会给数据的支撑资源、访问逻辑带来负担。 足够平均。按这个维度划分后,每个分区的数据量应该几乎一致。 以用户为服务主体的系统(To C),比如支付宝,通常可以按照用户维度对数据分区,这是一个最佳实践
三、怎么做单元化
在真正动手之前,有一个必须要知晓并始终记之在心的事实,完美的单元化是不存在的。 从单元化的角度,在一个系统当中实际上存在 3 类数据:
1、可区分数据
可以按照选择好的维度进行分区的数据,真正能被单元化的数据。这类数据通常在系统业务链路中处于核心位置,单元化建设最重要的目标实际上就是把这些数据处理好。比如订单数据、支付流水数据、账户数据等,都属于这一类型。 这类数据在系统中的占比越高,整体单元化的程度就越高,如果系统中全部都是这样的数据,那我们就能打造一个完美单元化的架构。不过现实中这种情况存在的可能性几乎为零,因为下面提到的两类数据,或多或少都会存在于系统当中。
2、全局数据,不被关键链路业务频繁访问
不能被分区的数据,全局只能有一份。比较典型的是一些配置类数据,它们可能会被关键链路业务访问,但并不频繁,因此即使访问速度不够快,也不会对业务性能造成太大的影响。 因为不能分区,这类数据不能被部署在经典的单元中,必须创造一种非典型单元用以承载它们。
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% 的业务,都对数据被创建和被使用之间的时间间隔要求很低。余下的那些不能忍受时间差的业务(即要求数据被创建后就必须马上可用,要不就干脆不能访问),则必须进行业务改造,忍受异地访问延时。
三、需要哪些支持
前面提到,支付宝系统早已实现了数据水平拆分,并且全站拆分维度一致,在具备了这个重要基础能力的前提下,仍然举全站之力用了近一年时间才完成单元化改造,除了因为要改造业务系统以妥善处理 GZone/CZone 的数据以外,还有一项工作也耗费了大量人力和时间,那就是建设起了一个单元化技术支持平台。 单元化架构增加了构复杂性和运维复杂度,如果没有这样一个平台支撑,很难把如此复杂的一个系统健康的维护起来。 所谓单元化技术支持平台,至少应该包含三方面功能。
1、一套支持单元化的中间件
支付宝的业务开发因单元化架构而变得复杂、不易理解,而实际上现在看到的复杂性比起它的原本的程度来说已经降低了太多。这其中最大程度屏蔽了单元化细节,让单元化架构尽量对业务层透明,起到至关重要作用的,就是一整套专门针对单元化而研制的中间件,也即上文提到的原生 (Native) 单元化能力。
比较关键的几个包括,数据访问层中间件,把数据水平分库分表的细节隐藏在水面之下,上层业务使用起来跟单库单表没什么区别。更进一步的,数据访问层能实时动态感知单元化分流规则,根据规则变化决策是否连接某个数据分库,以及一笔业务是否允许访问某个数据分库。可以说单元化的本质就是对数据的逻辑隔离,有这样一个数据访问中间件对单元化建设来说是必不可少的,它解耦了业务层对数据层结构的依赖,大大降低了理解成本。
另外更为重要的是几个负责信息传递的中间件,正是它们让单元可以成为单元。这其中有微服务框架、消息中间件等,它们能够根据单元化规则把系统应用间的服务调用或消息流转约束在一个逻辑区域内,再配合上数据访问中间件对数据访问的约束,就让这一个个逻辑区域形成了一个个单元。如果有些调用必须跨单元发生,也由它们根据规则执行转发,不需要业务层关心。
2、一个规则管理和流量调拨系统
前面多次提到了“规则”这个词,在单元化架构中,规则举足轻重,业务流量在单元间怎么分配,哪个单元能访问哪个数据分库,某次服务调用能否跨出单元,这些都由规则掌管。可想而知,必须有一个统一的位置来全局唯一的管理这套规则,并在变更时能够及时准确的的推送给各个执行点,否则不同模块识别到的规则不同,将会造成全系统信息混乱。
规则管理系统的目的就是成为这样一个单元化规则权威持有者,运维人员对规则的所有变更,都在这个系统上操作。它通过某种协议连接着系统中所有需要感知规则的模块,一旦发生规则变化就实时把新规则内容推送给各个模块,并识别推送结果,对推送失败的模块执行重试,确保规则下发到所有位置。
3、一个面向单元的发布部署和监控平台
单元化后,一个系统被划分成了数量众多的逻辑单元,这让系统发布换版的粒度更细,试错成本更低,但也让发布这件事情更难做,所以一个能面向单元进行发布的部署平台就变的非常必要。 这个部署平台能够以单元为维度执行发布,单元间可以互不影响的独立部署。
同时,平台需要有能力统一编排和控制不同单元的部署进程,从而能够实现基于单元的全站蓝绿发布等高阶功能。
此外,监控系统要求能够细化到单元粒度展示监控信息和发送报警,以协助判断每个单元中的系统健康程度,为快速执行流量切换以隔离故障等操作提供依据。
以上几项是一个单元化技术支持平台必须要具备的能力,在支付宝的单元化进程中,已经沉淀出了完整的这样一套平台,并在逐步产品化为具备完整单元化能力的 SOFA 平台和 OceanBase 数据库,搭载在蚂蚁金融云上对外输出。在几年中先后支持了蚂蚁保险、网商银行的单元化改造,在未来也将为更多的金融生态合作伙伴提供服务。