“异地多活”看起来就是这样一个万能的大杀器,很多人理想中认为只要实现了“异地多活”,不管是新奥尔良水灾,美加大停电,蓝翔挖掘机。。。。。。等等都不再是问题。

不过,事实上,作为一个解决方案,异地多活是否最终有效

  • 既取决于实施方如何去使用方案如何去设计(千万不要以为捡到倚天剑就是武林盟主)
  • 也取决于工具方案本身还有哪些不如意之处固有的约束和限制
  • 要知道,即便是淘宝这样能够做到“异地多活”的公司而言,实际上也仍然有不够完美的地方。如果不意识到这些impossible mission,那么对于异地多活的理解就是错误的。

Impossible Mission1 - “实时异地多活”

异地多活本质上是通过异地的数据冗余,来保证在极端异常的情况下业务也能够正常提供给用户,因此数据同步是异地多活设计方案的核心。

通过网络将数据从A地同步到B地,会受限于一个普遍的物理规则,即:光速真空传播是每秒30万公里,在光纤中传输的速度大约是每秒20万公里,再加上传输中的各种网络设备的处理,实际还远远达不到光速的速度。

除了距离上的限制外,中间传输各种不可控的因素也非常多,例如挖掘机把光纤挖断,中美海底电缆被拖船扯断、骨干网故障等,这些故障是第三方维护,业务方根本无能为力也无法预知。

例如广州机房到北京机房,正常情况下RTT大约是50ms左右,遇到网络波动之类的情况,RTT可能飙升到500ms甚至1s,更不用说经常发生的线路丢包问题,那延迟可能就是几秒几十秒了。

因此“实时异地多活”是不可能实现的,因为不管是购买优质的运营商网络,还是采用云方案,甚至是自己拉光纤,都不可能突破物理上的限制,也不可能预防所有的意外情况。在设计异地多活方案的时候,业务上必须考虑这种时延或者中断的情况,不能认为底层传输是绝对可靠的。

Impossible Mission2 - 所有用户异地多活

不能实现“实时异地多活”就意味着某些极端情况下,肯定会有部分用户的数据无法及时同步到异地机房,这部分用户的业务在切换到异地机房后短时间内会异常。比如说小明在A机房成功发布了一条#某某明星出轨#相关的微博,此时A机房故障,数据还没有同步到B机房,我们将业务切换到B机房,此时小明登录到B机房就看不到自己刚才发的微博了!

这种情况下,运气好一些的话(比如说只是网络设备坏了)需要等到A机房业务恢复,数据重新同步到B机房;运气不好的话(比如说机器坏了,硬盘没坏)需要人工来修复数据,可能耗时几十分钟,也可能耗时几个小时;运气最差的情况下(比如说机房起火,硬盘被烧掉了)可能这些未同步的数据就永久丢失了。

站在用户的角度这样肯定很不爽(甚至直接打电话投诉开骂之类的),但这种现象也是无法完全避免的,异地多活不能保证每一个用户的业务无论在什么场景下都不受损,只要能保证绝大部分用户的业务在异常的场景下继续可用,就是很好的异地多活方案了。

虽然我们无法做到100%可用性,但并不意味着我们什么都不能做,除了抓紧时间恢复业务恢复数据外,为了让用户心里更好受一些,我们可以采取一些措施进行安抚或者补偿,例如:安抚、挂公告(“技术哥哥正在紧急处理”)、事后补偿(代金券、小礼包)等。

Impossible Mission3 - “所有业务异地多活”

异地多活效果看起来很诱人,但如果不假思索贪大求全的要求所有业务都实现异地多活的话,就会把自己带到坑里去。

第一个原因是异地多活是有成本的,包括开发成本和维护成本。需要实现异地多活的业务越多,方案越复杂,投入的设计开发时间越多;同时维护成本也会越高,需要更多的机器,需要更多的带宽。

第二个原因是有的业务理论上就无法实现异地多活。典型的有“余额”和“库存”这两个业务。

以余额为例,假设我们实现了余额的异地多活业务,用户小明有10000块钱,在A机房给女友转账了5000块,还剩余5000块;如果此时A机房异常且数据还没同步到B机房,小明登录到B机房发现自己又有10000块了,小明感觉中彩票了,赶紧又转了10000块给女友,最后出现了小明只有10000块却转账了15000块的问题,对于和资金相关的业务,这样的问题是绝对无法容忍的,哪怕一个用户有问题都不行。

所以,异地多活也不能保证所有业务都异地多活,在设计异地多活方案的时候,需要从业务和用户的角度出发,识别出核心和关键业务,明确哪些业务是必须实现异地多活,哪些是可以不实现异地多活,哪些是不能实现异地多活的。比如“登录”必须实现异地多活、“注册”和“修改用户信息”不一定要实现异地多活。

Impossible Mission4 - “通用的异地多活”

异地多活方案设计的关键是数据同步,提到数据同步我们自然而然的就想到了底层存储的同步功能,例如Oracle的数据复制、MySQL的数据同步、Redis的数据集群、云数据库跨机房复制等。如果使用这些底层存储方案的同步功能就能实现异地多活,那异地多活方案实现就很轻松了。

但现实显然没有这么美好,原因就在于这些底层存储的同步方案是通用的解决方案,无法基于业务的特点来有针对性的处理,而且这些底层同步方案一样会有bug,一样会延迟。以MySQL为例,MySQL5.1版本的复制是单线程的复制,在网络抖动或者大量数据同步的时候,经常发生延迟较长的问题,短则延迟十几秒,长则可能达到十几分钟。而且即使我们通过监控的手段知道了MySQL同步时延较长,也难以采取什么措施,只能干等。

所以只通过底层存储的同步方案来实现异地多活是不可能的,我们不能把鸡蛋都放到一个篮子里面,需要结合多种手段来实现业务数据同步或者读取,例如:重新生成数据、消息队列异步同步数据、多个机房间通过API直接访问数据等。

一句话谈异地多活

综合前面的分析,异地多活设计的理念可以总结为一句话:“采用多种手段,保证绝大部分用户的核心业务异地多活!” 不要被那些号称用一个工具或者方案解决所有问题的说法给骗了,工程领域不存在“银弹”。

单元化

为什么要有架构实践? 很多人喜欢的是细节,因为有句名言叫魔鬼在细节里,于是都去细节里寻找魔鬼。但是打败了魔鬼就能看到天使么?未必。细节其实是最容易掌握的部分,细节之外还有很多。就像有了水泥和沙子,你能够做出混凝土,但是离建成高楼大厦还有很长的路要走一样,你要学着去设计架构。

但是事情并没有完,就像没有唯一的真理一样,架构也并不是只有一种。你不可能一朝学会,从此天下无敌。如果要赈灾,你需要的是帐篷,如果要重建,你需要的是瓦房。不同的住所需要的是不同的架构。

不同的服务也需要不同的架构设计,这也就是我们需要架构实践的重要原因。在这之后的原因,是我们做任何服务,都要考虑服务的性能和成本。

但优化有很多方式,为什么是架构呢?诚然,从硬件到操作系统,从共享库到应用软件,从算法到架构,每一层都可以优化,但每一层所做的工作量和收益也都是不同的。架构可能是需要投入最多精力的,但在很多时候却也是很少的可以提供超过数量级的提升方式。

所以,思维方式的转变才是你最应该在意的部分,单元化只是一个例子,而粉丝服务平台只是这个例子的例子,而已。

言归正传,接下来本文将从三个问题来介绍这次实践,单元化是什么,为什么要用以及我们如何做到的。

1. 单元化是什么

单元化架构是从并行计算领域发展而来。在分布式服务设计领域,一个单元(Cell)就是满足某个分区所有业务操作的自包含的安装。而一个分区(Shard),则是整体数据集的一个子集,如果你用尾号来划分用户,那同样尾号的那部分用户就可以认为是一个分区。单元化就是将一个服务设计改造让其符合单元特征的过程。

图 1 :洋葱细胞的显微镜截图,单元化要达到的目的就是让每个单元像细胞一样独立工作

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

图 2 :传统的服务化架构,为伸缩性设计,上层节点随机选择下层节点

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

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

2. 为什么要用单元化

在性能追求和成本限制的情况下,我们需要找到一种合适的方法来满足服务需求。在传统的分布式服务设计,我们考虑的更多是每个服务的可伸缩性,当各个服务独立设计时你就要在每一层进行伸缩性的考虑。这是服务化设计(SOA)流行的原因,我们需要每个服务能够单独水平扩展。

但是在摩尔定律下,随着硬件的不断升级,计算机硬件能力已经越来越强,CPU越来越快,内存越来越大,网络越来越宽。这让我们看到了在单台机器上垂直扩展的机会。尤其是当你遇到一个性能要求和容量增长可以预期的业务,单元化给我们提供另外的机会,让我们可以有效降低资源的使用,提供更高性能的服务。

总体而言,更高性能更低成本是我们的主要目标,而经过单元化改造,我们得以用更少(约二分之一)的机器,获得了比原来更高(接近百倍)的性能。性能的提升很大部分原因在于服务的本地化,而服务的集成部署又进一步降低了资源的使用。

当然除了性能收益,如果你做到了,你会发现还有很多收益,比如更好的隔离性,包括请求隔离和资源隔离,比如更友好的升级,产品可以灰度发布等。单元化改造后对高峰的应对以及扩容方式等问题,各位可以参考#微博春节技术保障系列#中的单元化架构文章,也不在此一一赘述。

3. 我们如何做到

此次单元化改造基于微博现有的业务,因此这里也先行介绍一下。粉丝服务平台是微博的内容推送系统(代号Castalia),可为V用户提供向其粉丝推送高质量内容的高速通道(单元化之后已到达百万条每秒)。整个服务涉及用户筛选、发送计费、屏蔽检查、限流控制和消息群发等多个子服务。由于改造思想相通,这里以用户筛选和消息群发两个服务为例,下面两图分别为商业群发在服务化思想和单元化思想下不同的架构。

图 4: 服务化思想下的商业群发架构设计(旧版)

图 5 :商业群发在单元化思想下的架构设计(新版)

对于筛选服务,在服务化架构里,需要去粉丝服务获取粉丝关系,然后去特征服务进行用户特征筛选,最后将筛选结果传输到群发服务器上;而在单元化架构里,粉丝关系直接就在本地文件中,用户特征服务也在本地,最后的筛选结果再不需要传输。服务本地化(粉丝关系和用户特征存储)减去了网络开销,降低了服务延时,还同时提高了访问速度和稳定性,而筛选结果本地存储又进一步节省了带宽并降低了延迟。以百万粉丝为例,每次网络操作的减少节省带宽8M左右,延时也从400ms降为0。

群发服务同样如此。由于在服务化架构里,我们使用MySQL和Memcache的方案,由于关系数据库的写入性能问题,中间还有队列以及相应的队列处理机,所有四个模块都有单独的机器提供服务,而在单元化架构里,四合一之后,只需要一套机器。当然机器的配置可能会有所提升,但真正计算之后你就会发现其实影响微乎其微。原因除了前面介绍的硬件增长空间外,上架机器的基本配置变高也是一个原因。而且,在单元化方案里,当我们把缓存部署在本地之后,其性能还有了额外的20%提升。

一些业务特有问题 不过群发这个场景,我们也遇到了一些特定的问题,一是分区问题,一是作业管理。这里也与各位分享下我们的解决方法。

分区问题

分区问题其实是每个服务都会遇到的,但单元化后的挑战在于让所有服务都适配同一分区算法,在我们的场景下,我们按照接收者进行了分区,即从底层往上,每一层都来适配此分区算法。

这里有特例的是用户特征和屏蔽服务,由于总体容量都很小,我们就没有对数据进行分区,所有单元内都是同一套全量数据,都是一个外部全量库的从库。不过由于本单元内的上层服务的关系,只有属于本分区的用户数据被访问到。所以,适配同一分区算法在某种程度上讲,可以兼容即可。

作业管理

按照前面的分区方式,将群发服务的整体架构变成了一个类似Scatter-Gather+CQRS的方案,因为Gather不是一个请求处理的必须要素。也就是说,一个群发请求会被扩散到所有单元中,每个单元都要针对自己分区内的用户处理这个群发请求。

广播方式的引入,使得我们首先需要在前端机进行分单元作业的处理监控,我们在此增加了持久化队列来解决。同时,由于单元内每个服务也都是单独维护的,作业可能在任何时间中断,因此每个作业在单元内的状态也都是有记录的,以此来达到作业的可重入和幂等性,也就可以保证每个作业都可以在任何时间重做,但不会重复执行。

除此之外,我们还对服务器进行了更为精细的控制,使用CPU绑定提高多服务集成部署时的整体效率,使用多硬盘设计保证每个服务的IO性能,通过主从单元的读写分离来提高整体服务等等。

移动互联网、大数据与云计算作为新的基础设施,催生了新的互联网经济,正在推动各行各业的升级。伴随蚂蚁金服在新金融领域的探索,蚂蚁金服技术团队也在金融技术与架构领域不断开拓。从2005 年每秒处理1笔交易到2016 年“双十一”每秒处理12 万笔交易,从单一的支付到覆盖微贷、理财、保险、信用、银行等,通过十多年的探索与实践,我们形成一套包含金融级分布式交易、分布式大数据分析与决策等在内的完整架构与技术体系。

金融级系统的关键目标

如果将建造系统比作盖楼的话,建一个常规的系统要先立稳四根柱子:高可用、安全、性能、成本。但要建一个移动互联网时代的金融级大厦,除了上述四根柱子需要更加牢固,还需要加上两根柱子:资金安全与数据质量。这六根柱子,是我们在架构蚂蚁金服的每一个系统时的首要目标。

具体来说,我们对金融级系统有以下关键目标。

高可用:具备99.99% 以上的高可用性。系统能够容忍各种软硬件设施的故障,可以在服务不中断的情况下进行升级,在严苛的应用场景下保证承诺的服务质量,容忍各种人为失误。对于关键系统,还需要具备异地容灾能力。

安全:具备多层次检测、感知与防御各类安全攻击的能力。系统有能力实时、精细地分析系统行为与数据流发现异常,必要时可以快速调集资源阻断大规模、有组织的攻击。

性能:对于实时交易业务,要求极快的响应时间与极高并发能力。对于批量业务,要求极大的吞吐量。尤其重要的是,系统必须具备很强的可伸缩性与弹性,在需要时可以快速调集资源应对突发的业务量。

成本:在满足高可用、安全与性能的前提下,成本是一个重要约束。我们将单笔交易的平均处理成本(月交易总笔数/ 月成本)、以及峰值交易的处理成本(每提升1000 交易TPS 需要追加的成本)作为两个关键指标去持续优化。除了必须在基础软硬件与系统关键链路上做极致的优化外,灵活的资源调度与按需伸缩能力是优化成本的关键。

资金安全:这是金融级系统与常规系统的一个关键差异。要做到资金处理绝对不出差错,需要交易与数据具备强一致性,需要在任何故障场景数据不丢不错,需要具备准实时的交易资金核对能力,需要在异常场景下有精细化熔断与快速恢复能力。

数据质量:数据质量是金融服务质量的基础。数据从采集、生成、流转、存储、计算、使用需要经历很多环节,要确保经过这么多环节后,数据依然是准确、完整和及时的,需要系统具备全链路的数据质量管控与治理能力。

金融交易系统是否可以走分布式路线?如何基于分布式的思想与技术达到以上6 个关键目标?接下来,我们就以蚂蚁金服的实践为基础,分享对这个问题的观点。

强一致的微服务

  • 微交易架构。微服务是一种广泛应用的分布式架构。通过将系统分解为单一职责、高内聚、松耦合、独立部署、自主运行的“微“服务,可以极大提升系统的灵活性与扩展能力。但由于每一个微服务是自包含的数据与计算单元,当一个有严格一致性要求的交易,被分布在很多节点上执行时,如何保证数据与服务处理达到金融级的强一致性,成为一个难题。尽管可以用支持分布式事务的数据库或数据中间件来保证数据分布时的一致性,但解决不了当服务分布时的一致性问题。由于分布式事务对资源加锁的时间长、粒度大,也制约了系统的可伸缩性与高可用性。

  • 为了解决这个难题,我们提出一种使微服务具备强一致性的微交易架构。在这种架构中,涉及交易操作的微服务具备事务属性。

    • 一个微交易提供三种操作TCC(Try-Confirm-Cancel), 其中Try 操作负责业务检查与资源预留,Confirm 操作负责实际操作,Cancel 操作负责释放预留的资源。一次完整的交易由一系列微交易的Try 操作组成,如果所有的Try 操作都成功,最终由微交易框架来统一Confirm,否则统一Cancel,从而实现了类似经典两阶段提交协议(2PC)的强一致性。但不同于2PC,微交易架构力求高效与可伸缩。
    • TCC 三个操作都是基于本地事务的短事务,Try 操作只预留必须的业务资源,比如一笔交易涉及10 元钱,仅预留账户中的10 元钱,而不是锁定整个账户,TCC 协议在提交时,也没有单独的Prepare 阶段,将提交协议的成本降到最低。

从2008 年初上线至今,微交易架构已经应用到蚂蚁金服的各种金融业务场景,经历过历次大促高峰考验,证明这套架构与技术的可行性。

异地多活与容灾

  • 单元化架构。

    • “两地三中心”是一种在金融系统中广泛应用的跨数据中心扩展与跨地区容灾部署模式,但也存在一些问题:在扩展能力上,由于跨地区的备份中心不承载核心业务,不能解决核心业务跨地区扩展的问题;在成本上,灾备系统仅在容灾时使用,资源利用率低,成本较高;在容灾能力上,由于灾备系统冷备等待,容灾时可用性低,切换风险较大。

    • 因此,蚂蚁金服没有选择“两地三中心”部署模式,而是实现了异地多活与容灾模式。异地多活与容灾架构的基础是对系统进行单元化。每个单元可以认为是一个缩小规模的、包含从接入网关、应用服务到数据存储的全功能系统。每个单元负责一定比例的数据与用户访问,有以下关键特性。

  • 自包含性:比如用户的一次账户充值交易,涉及的所有计算与数据都在一个单元内完成。

  • 松耦合性:跨单元之间只能进行服务调用,不能直接访问数据库或其他存储。对于一些必须跨单元的交易处理,比如分属于两个不同单元的用户之间的转账交易,跨单元的服务调用次数尽可能少,在业务与用户体验允许的情况下尽量异步处理。这样,即使两个单元之间相距上千公里,也可以容忍跨单元的访问时延。

  • 故障独立性:一个单元内的故障,不会传播到其他单元。

  • 容灾性:单元之间相互备份,确保每个单元在同城和异地都有可在故障期间进行接管的单元。数据在单元间的备份方式,我们以OceanBase 提供的多地多中心强一致方案为主。

  • 通过单元化架构,能够将一个大规模系统分拆成许多个相对独立的小规模系统,每一个单元系统可以部署到任何地区的数据中心,从而实现了灵活的异地多数据中心部署模式。系统的主要伸缩模式变成单元的增减,但一个单元内部的规模与复杂性不变,降低了系统的复杂性。单元之间的故障隔离,降低了软硬件故障的影响面。“活”的单元和跨单元的快速切换能力,使同城异地的容灾处理更为简单高效。

目前,蚂蚁金服的核心系统已经分布在上海、深圳、杭州等多个城市的多个数据中心,核心交易流量分布在各个数据中心,并且可以进行调度与切换。通过异地多活,系统可以在全国范围内任意扩展,服务器资源得到了充分利用,提升了系统应对地区级灾难的能力。

按需伸缩:弹性混合云架构。每年,支付宝系统都要应对“双十一”、新春红包等活动的极高交易量。尽管单元化架构让我们具备应对峰值的能力,但要降低高峰期的资源投入,系统还需具备按需伸缩的能力。

我们解决这个问题的方法是,活动前,在云计算平台上快速申请资源,构建新的单元,部署应用与数据库。然后将流量与数据“弹出”到新的单元,快速提升系统容量。当活动结束后,再将流量与数据“弹回”,释放云计算平台上的资源。通过这种方式,可以大大降低资源采购与运行成本。

弹性操作,需要在流量、数据与资源之间协调一致地操作,尤其是有状态的数据的弹性操作最困难,需要不中断业务,也需要保证数据的一致性。这些操作如果依靠运维人员人工执行会十分复杂低效,需要架构、中间件与管控系统的支持。

  • 弹性混合云架构与技术在实践中有以下一些关键点:
    • 通过统一资源调度,灵活地申请与分配计算、存储与网络资源,创建单元,快速部署数据库、中间件与应用;
    • 通过中间件,将应用与基础设施充分解耦,无论流量、数据与资源如何分布,应用系统不需要改变;
    • 通过分布式架构与数据规范,以及中间件支持,保证所有请求、服务、数据、消息等都有全局唯一的ID 和一致的ID 编码规则。根据ID,从接入网关、服务中间件、消息中间件、数据中间件等都能够正确地路由服务请求与数据访问;
    • 通过统一管控平台,将高层的弹性操作,翻译成各个组件的部署与配置指令,并且统一调度执行,使操作协调一致、精准高效。