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

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

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

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

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

相关文章
|
6天前
|
弹性计算 Kubernetes Cloud Native
云原生架构下的微服务设计原则与实践####
本文深入探讨了在云原生环境中,微服务架构的设计原则、关键技术及实践案例。通过剖析传统单体架构面临的挑战,引出微服务作为解决方案的优势,并详细阐述了微服务设计的几大核心原则:单一职责、独立部署、弹性伸缩和服务自治。文章还介绍了容器化技术、Kubernetes等云原生工具如何助力微服务的高效实施,并通过一个实际项目案例,展示了从服务拆分到持续集成/持续部署(CI/CD)流程的完整实现路径,为读者提供了宝贵的实践经验和启发。 ####
|
30天前
|
缓存 监控 API
探索微服务架构中的API网关模式
【10月更文挑战第5天】随着微服务架构的兴起,企业纷纷采用这一模式构建复杂应用。在这种架构下,应用被拆分成若干小型、独立的服务,每个服务围绕特定业务功能构建并通过HTTP协议协作。随着服务数量增加,统一管理这些服务间的交互变得至关重要。API网关作为微服务架构的关键组件,承担起路由请求、聚合数据、处理认证与授权等功能。本文通过一个在线零售平台的具体案例,探讨API网关的优势及其实现细节,展示其在简化客户端集成、提升安全性和性能方面的关键作用。
70 2
|
4天前
|
监控 安全 应用服务中间件
微服务架构下的API网关设计策略与实践####
本文深入探讨了在微服务架构下,API网关作为系统统一入口点的设计策略、实现细节及其在实际应用中的最佳实践。不同于传统的摘要概述,本部分将直接以一段精简的代码示例作为引子,展示一个基于NGINX的简单API网关配置片段,随后引出文章的核心内容,旨在通过具体实例激发读者兴趣,快速理解API网关在微服务架构中的关键作用及实现方式。 ```nginx server { listen 80; server_name api.example.com; location / { proxy_pass http://backend_service:5000;
|
5天前
|
缓存 监控 API
探索微服务架构中的API网关模式
随着微服务架构的兴起,API网关成为管理和服务间交互的关键组件。本文通过在线零售公司的案例,探讨了API网关在路由管理、认证授权、限流缓存、日志监控和协议转换等方面的优势,并详细介绍了使用Kong实现API网关的具体步骤。
22 3
|
6天前
|
运维 NoSQL Java
后端架构演进:微服务架构的优缺点与实战案例分析
【10月更文挑战第28天】本文探讨了微服务架构与单体架构的优缺点,并通过实战案例分析了微服务架构在实际应用中的表现。微服务架构具有高内聚、低耦合、独立部署等优势,但也面临分布式系统的复杂性和较高的运维成本。通过某电商平台的实际案例,展示了微服务架构在提升系统性能和团队协作效率方面的显著效果,同时也指出了其带来的挑战。
40 4
|
5天前
|
存储 缓存 监控
探索微服务架构中的API网关模式
探索微服务架构中的API网关模式
20 2
|
5天前
|
JavaScript 持续交付 Docker
解锁新技能:Docker容器化部署在微服务架构中的应用
【10月更文挑战第29天】在数字化转型中,微服务架构因灵活性和可扩展性成为企业首选。Docker容器化技术为微服务的部署和管理带来革命性变化。本文探讨Docker在微服务架构中的应用,包括隔离性、可移植性、扩展性、版本控制等方面,并提供代码示例。
30 1
|
13天前
|
监控 Cloud Native Java
云原生架构下微服务治理策略与实践####
【10月更文挑战第20天】 本文深入探讨了云原生环境下微服务架构的治理策略,通过分析当前技术趋势与挑战,提出了一系列高效、可扩展的微服务治理最佳实践方案。不同于传统摘要概述内容要点,本部分直接聚焦于治理核心——如何在动态多变的分布式系统中实现服务的自动发现、配置管理、流量控制及故障恢复,旨在为开发者提供一套系统性的方法论,助力企业在云端构建更加健壮、灵活的应用程序。 ####
59 10
|
8天前
|
Kubernetes Cloud Native API
云原生架构下微服务治理的深度探索与实践####
本文旨在深入剖析云原生环境下微服务治理的核心要素与最佳实践,通过实际案例分析,揭示高效、稳定的微服务架构设计原则及实施策略。在快速迭代的云计算领域,微服务架构以其高度解耦、灵活扩展的特性成为众多企业的首选。然而,伴随而来的服务间通信、故障隔离、配置管理等挑战亦不容忽视。本研究聚焦于云原生技术栈如何赋能微服务治理,涵盖容器编排(如Kubernetes)、服务网格(如Istio/Envoy)、API网关、分布式追踪系统等关键技术组件的应用与优化,为读者提供一套系统性的解决方案框架,助力企业在云端构建更加健壮、可维护的服务生态。 ####
|
13天前
|
运维 Cloud Native 持续交付
云原生架构下的微服务设计原则与实践####
【10月更文挑战第20天】 本文深入探讨了云原生环境中微服务设计的几大核心原则,包括服务的细粒度划分、无状态性、独立部署、自动化管理及容错机制。通过分析这些原则背后的技术逻辑与业务价值,结合具体案例,展示了如何在现代云平台上实现高效、灵活且可扩展的微服务架构,以应对快速变化的市场需求和技术挑战。 ####
42 7