从“挖光缆”到“剪网线”|蚂蚁金服异地多活单元化架构下的微服务体系

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: “异地多活”是互联网系统的一种高可用部署架构,而“单元化”正是实现异地多活的一个解题思路。 说起这个话题,不得不提两个事件:一件是三年多前的往事,另一件就发生今年的杭州云栖大会上。

“异地多活”是互联网系统的一种高可用部署架构,而“单元化”正是实现异地多活的一个解题思路。说起这个话题,不得不提两个事件:一件是三年多前的往事,另一件就发生今年的杭州云栖大会上。

从“挖光缆”到“剪网线”

2015年5月27日,因市政施工,支付宝杭州某数据中心的光缆被挖断,造成对部分用户服务不可用,时间长达数小时。其实支付宝的单元化架构容灾很早就开始启动了,2015年也基本上成型了。当时由于事发突然,还是碰到很多实际问题,花费了数小时的时间,才在确保用户数据完全正确的前提下,完成切换、恢复服务。虽然数据没有出错,但对于这样体量的公司来说,服务不可用的社会舆论影响也是非常大的。

527这个数字,成为蚂蚁金服全体技术人心中悬着那颗苦胆。我们甚至把技术部门所在办公楼的一个会议室命名为527,把每年的5月27日定为技术日,来时刻警醒自己敬畏技术,不断打磨技术。
image1.png | center | 1920x1080
经过几年的卧薪尝胆,时间来到2018年9月。云栖大会上,蚂蚁金服发布了“三地五中心金融级高可用方案”。现场部署了一个模拟转账系统,在场观众通过小程序互相不断转账。服务端分布在三个城市的五个数据中心,为了感受更直观,把杭州其中一个数据中心机柜设置在了会场。工作人员当场把杭州两个数据中心的网线剪断,来模拟杭州的城市级灾难。
image2.png | center | 1920x1080
网线剪断之后,部分用户服务不可用。经过26秒,容灾切换完成,所有受影响的用户全部恢复正常。这个Demo当然只是实际生产系统的一个简化模型,但是其背后的技术是一致的。这几年来,其实每隔几周我们就会在生产环境做一次真实的数据中心断网演习,来不断打磨系统容灾能力。

从大屏幕上可以看到,容灾切换包含了“数据库切换”“缓存容灾切换”“多活规则切换”“中间件切换”“负载均衡切换”“域名解析切换”等多个环节。异地多活架构是一个复杂的系统工程,其包含的技术内涵非常丰富,单场分享实难面面俱到。本场是微服务话题专场,我们也将以应用层的微服务体系作为切入点,一窥异地多活单元化架构的真面目。

去单点之路

任何一个互联网系统发展到一定规模时,都会不可避免地触及到单点瓶颈。“单点”在系统的不同发展阶段有不同的表现形式。提高系统伸缩能力和高可用能力的过程,就是不断与各种层面的单点斗争的过程。
image3.png | center | 1920x1080
我们不妨以一个生活中最熟悉的场景作为贯穿始终的例子,来推演系统架构从简单到复杂,所遇到的问题。
image4.png | center | 1920x1080
上图展示的是用支付宝买早餐的情景,当然角色是虚构的。
最早支付宝只是从淘宝剥离的一个小工具系统,处于单体应用时代。这个时候移动支付当然还没出现,我们的例子仅用于帮助分析问题,请忽略这个穿帮漏洞。
image5.png | center | 1920x1080
假设图中的场景发生在北京,而支付宝系统是部署在杭州的机房。在小王按下“支付”按钮的一瞬间,会发生什么事情呢?
支付请求要从客户端发送到服务端,服务端最终再把结果返回客户端,必然会有一次异地网络往返,耗时大约在数十毫秒的数量级,我们用红色线表示。应用进程内部会发生很多次业务逻辑运算,用绿色圈表示,不涉及网络开销,耗时忽略不计。应用会访问多次数据库,由于都在部署在同一个机房内,每次耗时按一毫秒以下,一笔支付请求按10次数据库访问算(对于支付系统来说并不算多,一笔业务可能涉及到各种数据校验、数据修改)。耗时大头在无可避免的用户到机房物理距离上,系统内部处理耗时很小。
image6.png | center | 1920x1080
到了服务化时代,一个好的RPC框架追求的是让远程服务调用像调本地方法一样简单。随着服务的拆分、业务的发展,原本进程内部的调用变成了网络调用。由于应用都部署在同一个机房内,业务整体网络耗时仍然在可接受范围内。开发人员一般也不会特别在意这个问题,RPC服务被当成几乎无开销成本地使用,应用的数量也在逐渐膨胀。

服务化解决了应用层的瓶颈,紧接着数据库就成为制约系统扩展的瓶颈。虽然我们本次重点讨论的是服务层,但要讲单元化,数据存储是无论如何绕不开的话题。这里先插播一下分库分表的介绍,作为一个铺垫。
image7.png | center | 1920x1080
通过引入数据访问中间件,可以实现对应用透明的分库分表。一个比较好的实践是:逻辑拆分先一步到位,物理拆分慢慢进行。以账户表为例,将用户ID的末两位作为分片维度,可以在逻辑上将数据分成100份,一次性拆到100个分表中。这100个分表可以先位于同一个物理库中,随着系统的发展,逐步拆成2个、5个、10个,乃至100个物理库。数据访问中间件会屏蔽表与库的映射关系,应用层不必感知。

解决了应用层和数据库层单点后,物理机房又成为制约系统伸缩能力和高可用能力的最大单点。
image8.png | center | 1920x1080
要突破单机房的容量限制,最直观的解决办法就是再建新的机房,机房之间通过专线连成同一个内部网络。应用可以部署一部分节点到第二个机房,数据库也可以将主备库交叉部署到不同的机房。
这一阶段,只是解决了机房容量不足的问题,两个机房逻辑上仍是一个整体。日常会存在两部分跨机房调用:

  1. 服务层逻辑上是无差别的应用节点,每一次RPC调用都有一半的概率跨机房
  2. 每个特定的数据库主库只能位于一个机房,所以宏观上也一定有一半的数据库访问是跨机房的
    同城跨机房专线访问的耗时在数毫秒级,图中用黄色线表示。随着微服务化演进如火如荼,这部分耗时积少成多也很可观。

image9.png | center | 1920x1080
改进后的同城多机房架构,依靠不同服务注册中心,将应用层逻辑隔离开。只要一笔请求进入一个机房,应用层就一定会在一个机房内处理完。当然,由于数据库主库只在其中一边,所以这个架构仍然不解决一半数据访问跨机房的问题。
这个架构下,只要在入口处调节进入两个机房的请求比例,就可以精确控制两个机房的负载比例。基于这个能力,可以实现全站蓝绿发布。
image10.png | center | 1920x1080
“两地三中心”是一种在金融系统中广泛应用的跨数据中心扩展与跨地区容灾部署模式,但也存在一些问题。异地灾备机房距离数据库主节点距离过远、访问耗时过长,异地备节点数据又不是强一致的,所以无法直接提供在线服务。
在扩展能力上,由于跨地区的备份中心不承载核心业务,不能解决核心业务跨地区扩展的问题;在成本上,灾备系统仅在容灾时使用,资源利用率低,成本较高;在容灾能力上,由于灾备系统冷备等待,容灾时可用性低,切换风险较大。

小结一下前述几种架构的特点。直到这时,微服务体系本身的变化并不大,无非是部署几套、如何隔离的问题,每套微服务内部仍然是简单的架构。

架构类型 优势 问题
单体应用 网络开销小 扩展性差,维护困难
单机房服务化 解耦,可扩展 容量受限,机房级单点
同城多机房阶段一 突破单机房容量瓶颈 非必要的跨机房网络开销大
同城多机房阶段二 非必要的跨机房网络开销小;机房级容灾能力 城市级单点
两地三中心 异地容灾能力 网络耗时与数据一致性难两全

蚂蚁金服单元化实践

蚂蚁金服发展单元化架构的原始驱动力,可以概括为两句话:

  • 异地多活容灾需求带来的数据访问耗时问题,量变引起质变
  • 数据库连接数瓶颈制约了整体水平扩展能力,危急存亡之秋

第一条容易理解,正是前面讨论的问题,传统的两地三中心架构在解决地区级单点问题上效果并不理想,需要有其他思路。但这毕竟也不是很急的事情,真正把单元化之路提到生死攸关的重要性的,是第二条。

到2013年,支付宝核心数据库都已经完成了水平拆分,容量绰绰有余,应用层无状态,也可以随意水平扩展。但是按照当年双十一的业务指标做技术规划的时候,却碰到了一个棘手的问题:Oracle数据库的连接不够用了。
image11.png | center | 1920x1080
虽然数据库是按用户维度水平拆分的,但是应用层流量是完全随机的。以图中的简化业务链路为例,任意一个核心应用节点C可能访问任意一个数据库节点D,都需要占用数据库连接。连接是数据库非常宝贵的资源,是有上限的。当时的支付宝,面临的问题是不能再对应用集群扩容,因为每增加一台机器,就需要在每个数据分库上新增若干连接,而此时几个核心数据库的连接数已经到达上限。应用不能扩容,意味着支付宝系统的容量定格了,不能再有任何业务量增长。别说大促,可能再过一段时间连日常业务也支撑不了了。
image12.png | center | 1920x1080
单元化架构基于这样一种设想:如果应用层也能按照数据层相同的拆片维度,把整个请求链路收敛在一组服务器中,从应用层到数据层就可以组成一个封闭的单元。数据库只需要承载本单元的应用节点的请求,大大节省了连接数。“单元”可以作为一个相对独立整体来挪动,甚至可以把部分单元部署到异地去。

单元化有几个重要的设计原则:

  • 核心业务必须是可分片的
  • 必须保证核心业务的分片是均衡的,比如支付宝用用户ID作分片维度
  • 核心业务要尽量自包含,调用要尽量封闭
  • 整个系统都要面向逻辑分区设计,而不是物理部署

在实践上,我们推荐先从逻辑上切分若干均等的单元,再根据实际物理条件,把单元分布到物理数据中心。单元均等的好处是更容易做容量规划,可以根据一个单元的压测结果方便换算成整站容量。

我们把单元叫做Regional Zone。例如,数据按100份分片,逻辑上分为5个Regional Zone,每个承载20份数据分片的业务。初期可能是部署成两地三中心(允许多个单元位于同一个数据中心)。随着架构的发展,再整单元搬迁,演化成三地五中心,应用层无需感知物理层面的变化。
image13.png | center | 1920x1080
image14.png | center | 1920x1080
回到前面买早餐的例子,小王的ID是12345666,分片号是66,应该属于Regional Zone 04;而张大妈ID是54321233,分片号33,应该属于Regional Zone 02。
image15.png | center | 1920x1080
应用层会自动识别业务参数上的分片位,将请求发到正确的单元。业务设计上,我们会保证流水号的分片位跟付款用户的分片位保持一致,所以绝大部分微服务调用都会收敛在Regional Zone 04内部。
但是转账操作一定会涉及到两个账户,很可能位于不同的单元。张大妈的账号就刚好位于另一个城市的Regional Zone 02。当支付系统调用账务系统给张大妈的账号加钱的时候,就必须跨单元调用Regional Zone 02的账务服务。图中用红线表示耗时很长(几十毫秒级)的异地访问。
image16.png | center | 1920x1080
从宏观耗时示意图上就可以比较容易地理解单元化的思想了:单元内高内聚,单元间低耦合,跨单元调用无法避免,但应该尽量限定在少数的服务层调用,把整体耗时控制在可接受的范围内(包括对直接用户体验和对整体吞吐量的影响)。

前面讲的是正常情况下如何“多活”,机房故障情况下就要发挥单元之间的容灾互备作用了。
image17.png | center | 1920x1080
一个城市整体故障的情况下,应用层流量通过规则的切换,由事先规划好的其他单元接管。

数据层则是依靠自研的基于Paxos协议的分布式数据库OceanBase,自动把对应容灾单元的从节点选举为主节点,实现应用分片和数据分片继续收敛在同一单元的效果。我们之所以规划为“两地三中心”“三地五中心”这样的物理架构,实际上也是跟OceanBase的副本分布策略息息相关的。数据层异地多活,又是另一个宏大的课题了,以后可以专题分享,这里只简略提过。

这样,借助单元化异地多活架构,才能实现开头展示的“26秒完成城市级容灾切换”能力。

关键技术组件

单元化是个复杂的系统工程,需要多个组件协同工作,从上到下涉及到DNS层、反向代理层、网关/WEB层、服务层、数据访问层。
总体指导思想是“多层防线,迷途知返”。每层只要能获取到足够的信息,就尽早将请求转到正确的单元去,如果实在拿不到足够的信息,就靠下一层。
image18.png | center | 1920x1080

  • DNS层照理说感知不到任何业务层的信息,但我们做了一个优化叫“多域名技术”。比如PC端收银台的域名是cashier.alipay.com,在系统已知一个用户数据属于哪个单元的情况下,就让其直接访问一个单独的域名,直接解析到对应的数据中心,避免了下层的跨机房转发。例如上图中的cashiergtj.alipay.com,gtj就是内部一个数据中心的编号。移动端也可以靠下发规则到客户端来实现类似的效果。
  • 反向代理层是基于Nginx二次开发的,后端系统在通过参数识别用户所属的单元之后,在Cookie中写入特定的标识。下次请求,反向代理层就可以识别,直接转发到对应的单元。
  • 网关/Web层是应用上的第一道防线,是真正可以有业务逻辑的地方。在通用的HTTP拦截器中识别Session中的用户ID字段,如果不是本单元的请求,就 forward到正确的单元。并在Cookie中写入标识,下次请求在反向代理层就可以正确转发。
  • 服务层RPC框架和注册中心内置了对单元化能力的支持,可以根据请求参数,透明地找到正确单元的服务提供方。
  • 数据访问层是最后的兜底保障,即使前面所有的防线都失败了,一笔请求进入了错误的单元,在访问数据库的时候也一定会去正确的库表,最多耗时变长,但绝对不会访问到错误的数据。

image19.png | center | 1920x1080
这么多的组件要协同工作,必须共享同一份规则配置信息。必须有一个全局的单元化规则管控中心来管理,并通过一个高效的配置中心下发到分布式环境中的所有节点。
规则的内容比较丰富,描述了城市、机房、逻辑单元的拓扑结构,更重要的是描述了分片ID与逻辑单元之间的映射关系。

image20.png | center | 1920x1080
服务注册中心内置了单元字段,所有的服务提供者节点都带有“逻辑单元”属性。不同机房的注册中心之间互相同步数据,最终所有服务消费者都知道每个逻辑单元的服务提供者有哪些。RPC框架就可以根据需要选择调用目标。
image21.png | center | 1920x1080
image22.png | center | 1920x1080
RPC框架本身是不理解业务逻辑的,要想知道应该调哪个单元的服务,信息只能从业务参数中来。如果是从头设计的框架,可能直接约定某个固定的参数代表分片ID,要求调用者必须传这个参数。但是单元化是在业务已经跑了好多年的情况下的架构改造,不可能让所有存量服务修改接口。要求调用者在调用远程服务之前把分片ID放到ThreadLocal中?这样也很不优雅,违背了RPC框架的透明原则。
于是我们的解决方案是框架定义一个接口,由服务提供方给出一个实现类,描述如何从业务参数中获取分片ID。服务提供方在接口上打注解,告诉框架实现类的路径。框架就可以在执行RPC调用的时候,根据注解的实现,从参数中截出分片ID。再结合全局路由规则中分片ID与逻辑单元之间的映射关系,就知道该选择哪个单元的服务提供方了。

后记

本文着重介绍了蚂蚁金服异地多活单元化架构的原理,以及微服务体系在此架构下的关键技术实现。要在工程层面真正落地单元化,涉及的技术问题远不止此。例如:数据层如何容灾?无法水平拆分的业务如何处理?

蚂蚁技术团队会坚持走技术开放路线,后续还会以不同的形式分享相关话题,也欢迎各位读者留言探讨。

image | left | 216x216

长按关注,获取分布式架构干货

欢迎大家共同打造 SOFAStack https://github.com/alipay

目录
相关文章
|
5天前
|
敏捷开发 监控 数据管理
构建高效微服务架构的五大关键策略
【4月更文挑战第20天】在当今软件开发领域,微服务架构已经成为一种流行的设计模式,它允许开发团队以灵活、可扩展的方式构建应用程序。本文将探讨构建高效微服务架构的五大关键策略,包括服务划分、通信机制、数据管理、安全性考虑以及监控与日志。这些策略对于确保系统的可靠性、可维护性和性能至关重要。
|
5天前
|
消息中间件 监控 持续交付
构建高效微服务架构:后端开发的进阶之路
【4月更文挑战第20天】 随着现代软件开发的复杂性日益增加,传统的单体应用已难以满足快速迭代和灵活部署的需求。微服务架构作为一种新兴的分布式系统设计方式,以其独立部署、易于扩展和维护的特点,成为解决这一问题的关键。本文将深入探讨微服务的核心概念、设计原则以及在后端开发实践中如何构建一个高效的微服务架构。我们将从服务划分、通信机制、数据一致性、服务发现与注册等方面入手,提供一系列实用的策略和建议,帮助开发者优化后端系统的性能和可维护性。
|
1天前
|
持续交付 API 开发者
构建高效微服务架构:后端开发的新范式
【4月更文挑战第24天】 随着现代软件系统的复杂性日益增加,传统的单体应用已难以满足快速迭代与灵活扩展的需求。微服务架构作为一种新兴的软件开发模式,以其服务的细粒度、独立部署和弹性伸缩等优势,正在逐渐成为后端开发的重要趋势。本文将深入探讨微服务架构的设计原则、关键技术以及在实际业务中的应用实践,旨在为后端开发者提供构建和维护高效微服务架构的参考指南。
|
3天前
|
监控 API 持续交付
构建高效微服务架构:后端开发的新趋势
【4月更文挑战第23天】 随着现代软件开发实践的不断演进,微服务架构已经成为企业追求敏捷、可扩展和弹性解决方案的首选。本文深入探讨了如何构建一个高效的微服务架构,涵盖了关键的设计原则、技术选型以及实践建议。通过分析微服务的独立性、分布式特性和容错机制,我们将揭示如何利用容器化、服务网格和API网关等技术手段,来优化后端系统的可维护性和性能。文章旨在为后端开发人员提供一套全面的指南,以应对不断变化的业务需求和技术挑战。
|
7天前
|
机器学习/深度学习 运维 Prometheus
探索微服务架构下的系统监控策略
【4月更文挑战第18天】在当今快速迭代和持续部署盛行的软件工程实践中,微服务架构因其灵活性和可扩展性受到企业青睐。然而,随着服务的细粒度拆分和网络通信的增加,传统的监控手段已不再适用。本文将探讨在微服务环境中实施有效系统监控的策略,包括日志聚合、性能指标收集、分布式追踪以及异常检测等关键技术实践,旨在为读者提供构建稳定、可靠且易于维护的微服务系统的参考指南。
12 0
|
7天前
|
监控 持续交付 开发者
构建高效微服务架构:后端开发的新趋势
【4月更文挑战第18天】在数字化转型的浪潮中,微服务架构已成为企业提升系统灵活性、加速产品迭代的关键。此文深入探讨了构建高效微服务架构的实践方法,包括服务划分原则、容器化部署、持续集成/持续部署(CI/CD)流程以及监控与日志管理等关键技术点。通过分析具体案例,揭示了微服务在提高开发效率、降低维护成本及促进团队协作方面的显著优势。
|
8天前
|
负载均衡 Java 开发者
细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
【4月更文挑战第17天】Spring Cloud是Java微服务治理的首选框架,整合了Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Zuul(API网关)和Config Server(配置中心)。通过Eureka实现服务注册与发现,Ribbon提供负载均衡,Hystrix实现熔断保护,Zuul作为API网关,Config Server集中管理配置。理解并运用Spring Cloud进行微服务治理是现代Java开发者的关键技能。
|
10天前
|
监控 JavaScript 安全
构建微服务架构下的API网关
【4月更文挑战第15天】在微服务架构中,API网关扮演着至关重要的角色。它作为系统的唯一入口,不仅负责请求的路由、负载均衡和认证授权,还涉及到监控、日志记录和服务熔断等关键功能。本文将探讨如何构建一个高效且可靠的API网关,涵盖其设计原则、核心组件以及实现策略,旨在为后端开发人员提供一套实用的指导方案。
25 4
|
11天前
|
监控 负载均衡 API
构建高性能微服务架构:后端开发的最佳实践
【4月更文挑战第14天】 在当今快速发展的软件开发领域,微服务架构已成为构建可扩展、灵活且容错的系统的首选方法。本文深入探讨了后端开发人员在设计和维护高性能微服务时需要遵循的一系列最佳实践。我们将从服务划分原则、容器化部署、API网关使用、负载均衡、服务监控与故障恢复等方面展开讨论,并结合实际案例分析如何优化微服务性能及可靠性。通过本文的阅读,读者将获得实施高效微服务架构的实用知识与策略。
|
13天前
|
运维 监控 自动驾驶
构建可扩展的应用程序:Apollo与微服务架构的完美结合
构建可扩展的应用程序:Apollo与微服务架构的完美结合
32 10