在当今的互联网业内,很多大型互联网系统,比如淘宝、支付宝、网商银行等,都已经实现了单元化架构,并从中获益匪浅,更多企业正加入其中。为什么要做单元化,单元化架构能给系统带来什么样的能力。本文将从架构发展历史的角度作为切入点来了解一下单元化架构的发展历史以及一些落地方案。
单点架构
支付请求要从客户端发送到服务端,服务端最终再把结果返回客户端,必然会有一次异地网络往返。应用进程内部会发生很多次业务逻辑运算,耗时忽略不计。应用会访问多次数据库,一笔支付请求按10次数据库访问算(对于支付系统来说并不算多,一笔业务可能涉及到各种数据校验、数据修改)。耗时大头在无可避免的用户到机房物理距离上,系统内部处理耗时很小。
单机房SOA架构
到了服务化时代,一个好的RPC框架追求的是让远程服务调用像调本地方法一样简单。随着服务的拆分、业务的发展,原本进程内部的调用变成了网络调用。由于应用都部署在同一个机房内,业务整体网络耗时仍然在可接受范围内。开发人员一般也不会特别在意这个问题,RPC服务被当成几乎无开销成本地使用,应用的数量在逐渐膨胀。
同城多机房架构
服务化拆分,解决了应用层的可扩展性问题。随着业务的发展,物理机房逐渐成为系统容量的瓶颈。要突破单机房的容量限制,最直观的解决办法就是再建新的机房,机房之间通过专线连成同一个内部网络。
应用可以部署一部分节点到第二个机房,数据库也可以将主备库交叉部署到不同的机房。这一阶段,只是解决了机房容量不足的问题,两个机房逻辑上仍是一个整体。日常会存在两部分跨机房调用,积少成多也很可观:
- 服务层逻辑上是无差别的应用节点,每一次RPC调用都有一半的概率跨机房
- 每个特定数据库的主库只能位于一个机房,所以宏观上也一定有一半的数据库访问是跨机房的
两地三中架构
“距离”是一个矛盾体:距离越远,同时受同一灾害(电力故障、网络故障、自然灾害等)影响的可能性就越低,另一方面,网络访问延时就越高。同城级距离(几十公里)是一个实践临界值,前述的同城多机房架构,整体耗时在可接受范围内;而跨城访问耗时被复杂业务链路放大后,将明显影响业务。
对容灾要求不高的系统,做到同城多机房的程度就足够了,可以省去不少设计复杂度。但是金融行业有异地容灾的要求,就不得不面对距离带来的访问延迟问题。
“两地三中心”是一种在金融系统中广泛应用的跨数据中心扩展与跨地区容灾部署模式,但也存在一些问题。异地灾备机房距离数据库主节点距离过远、访问耗时过长,所以无法直接访问主库。异地备节点数据又不是强一致的,所以无法直接提供在线服务。
在扩展能力上,由于跨地区的备份中心不承载核心业务,不能解决核心业务跨地区扩展的问题;在成本上,灾备系统仅在容灾时使用,资源利用率低,成本较高;在容灾能力上,由于灾备系统冷备等待,容灾时可用性低,切换风险较大。
什么是单元化架构
回头来看历史的架构演进历史,我们不难发现从一开始的单点架构,会遇到扩展性差,维护困难
的问题;然后升级到到SOA架构,容易遇到容量受限,机房级单点
的问题;再升级到同城多机房架构以及两地三中心架构又会带来城市级单点
的问题。
多地多机房部署,是互联网系统的必然发展方向,一个系统要走到这一步,也就必然要解决上面提到的问题:流量调配、数据拆分、延时等。业界有很多技术方案可以用来解决这些问题,而承载这些方案的,是一个部署架构。尽管可采用的部署架构不止一个,但不论是纯理论研究,还是一些先行系统的架构实践,都把“单元化部署”推崇为最佳方案。
单元(即单元化应用服务产品层的部署单元),是指一个能完成所有业务操作的自包含集合,在这个集合中包含了所有业务所需的所有服务,以及分配给这个单元的数据。单元化架构就是将单元作为部署的基本单位,在全站所有机房中部署多个单元,每个机房内单元数目不固定,任一单元均部署系统所需的全部应用,数据则是全量数据按照某种维度划分后的一部分。
传统意义上的 SOA 化(服务化)架构,服务是分层的,每层的节点数量不尽相同,上层调用下层时,随机选择节点。
单元化架构下,服务仍然是分层的,不同的是每一层中的任意一个节点都属于且仅属于某一个单元,上层调用下层时,仅会选择本单元内的节点。
一个单元,是一个五脏俱全的缩小版整站,它是全能的,因为部署了所有应用;但它不是全量的,因为只能操作一部分数据。能够单元化的系统,很容易在多机房中部署,因为可以轻松将多个单元部署在一个机房内,而将另外几个单元部署在其他机房内。通过在业务入口处设置一个流量调配器,可以调整业务流量在单元之间的比例。
从上述对单元的定义和特性描述中,可以推导出单元化架构要求系统必须具备的一项能力:数据分区,实际上正是数据分区决定了各个单元可承担的业务流量比例。数据分区(shard),即是将全局数据按照某一个维度水平划分开来,每个分区的数据内容互不重叠,这也就是数据库水平拆分所做的事情。
仅把数据分区了还不够,单元化的另外一个必要条件是,全站所有业务数据分区所用的拆分维度和拆分规则都必须一样。若是以用户分区数据,那交易、收单、微贷、支付、账务等全链路业务都应该基于用户维度拆分数据,并且采用一样的规则拆分出同样的分区数。比如,以用户 id 末 2 位作为标识,将每个业务的全量数据都划分为 100 个分区(00-99)。
有了以上两个基础,单元化才可能成为现实。把一个或几个数据分区,部署在某个单元内,这些数据分区占总量数据的比例,就是这个单元能够承担的业务流量比例。 执行数据分区时一个很重要的问题是分区维度的选择,一个好的维度,应该:
- 粒度合适:粒度过大,会丧失流量调配的灵活性和精细度;粒度过小,会给数据的支撑资源,访问逻辑带来负担。
- 足够平均:按这个分区维度划分后,每个部署单元的数据量应该是几乎一致的。
以用户为服务主体的系统(很多面向用户的系统,比如支付宝)通常可以按用户维度对数据分区,这是一个最佳实践。
逻辑单元
LDC是logic data center(逻辑数据中心),是对IDC(internet data center,互联网数据中心,物理机房)的一种逻辑划分。
逻辑单元是单元化架构的基础,一个单元被称为一个 Zone。根据业务特点不同,我们可以将系统部署在不同类型的逻辑单元中。
- RZone(Region Zone):最符合理论上单元定义的zone,每个RZone都是自包含的,拥有自己的数据,能完成所有业务。
- GZone(Global Zone):部署了不可拆分的数据和服务,这些数据或服务可能会被RZone依赖。GZone在全局只有一组,数据仅有一份。
- CZone(City Zone):同样部署了不可拆分的数据和服务,也会被RZone依赖。跟GZone不同的是,CZone中的数据或服务会被RZone频繁访问,每一笔业务至少会访问一次;而GZone被RZone访问的频率则低的多。CZone是为了解决异地延迟问题而特别设计的。
RZone
每一个逻辑单元,可以独立的完成任务,甚至可以独立的迁移到别的机房。
CZone
有这样一类数据满足以下特点:读多写少,强依赖读弱依赖写,读操作只要求弱一致性。对于这类数据,我们可以把读副本搬到每个城市,每个城市的应用访问本地的数据副本,牺牲了一点数据的时效性,换取响应速度。写入点可以是一个集中点,也可以是多点,但是最终每个读副本都能同步到全量的数据。单元化架构中设计了一种逻辑单元叫 CZone,用来部署访问这种数据的应用。
GZone
无法被拆到 RZone 和 CZone 的全局数据,就成了理想单元化架构下的"钉子户"。LDC 设计了 GZone,给了这些不可能消除的“钉子户”一个合法的名分。
从下图可以看出,GZone 其实就是同城双机房架构,应用层分成两个互备的逻辑单元,分布在同城不同的机房,访问同一份全局数据。后来还建设了异地 GZone,仍然访问同一份全局数据,类似两地三中心。