SOFAStack Mesh 的大规模落地实践与展望


零、引言

云原生的理念正如火如荼,然而真正大规模落地的公司依然屈指可数,蚂蚁作为国内比较早一批吃螃蟹的公司,经过了 2 年多的探索,沉淀出了一套切实可行的方案并最终通过了双十一的考验。本文主要分享我们在 Service Mesh 大规模落地过程中的一些经验以及对未来的思考,希望能给大家带来一些启发。

一、为什么需要 Service Mesh?

1.1 微服务治理与业务逻辑解耦

在 Service Mesh 之前,传统微服务体系的玩法都是由中间件团队提供一个 SDK 给业务应用使用,在 SDK 中会实现各种服务治理的能力,如:服务发现、负载均衡、熔断限流、服务路由等。

在运行时,SDK 和业务应用的代码其实是混合在一个进程中运行的,耦合度非常高,这就带来了一系列的问题:

  1. 升级成本高
    • 每次升级都需要业务应用修改 SDK 版本号,重新发布。
    • 以蚂蚁为例,以往每年我们在中间件版本升级上就需要耗费数千人日
  2. 版本碎片化严重
    • 由于升级成本高,但中间件还是会持续向前发展,久而久之,就会导致线上 SDK 版本各不统一、能力参差不齐,造成很难统一治理
  3. 中间件演进困难
    • 由于版本碎片化严重,所以中间件向前演进过程中就需要在代码中去兼容各种各样的老版本逻辑,就像是戴着『枷锁』前行,无法实现快速迭代

有了 Service Mesh 之后,我们就可以把 SDK 中的大部分能力从应用中剥离出来,拆解为独立进程,以 Sidecar 的模式运行。通过将服务治理能力下沉到基础设施,可以让业务更加专注于业务逻辑,而中间件团队则更加专注于各种通用能力建设,真正实现独立演进,透明升级,提升整体效率。

decouple.png

1.2 异构系统统一治理

随着新技术的发展,在同一家公司中往往也会出现使用各种不同语言、不同框架的应用和服务。以蚂蚁为例,内部业务也是百花齐放,有前端、搜索推荐、人工智能、安全等各种业务,它们使用到的技术栈也非常多样化,除了Java 以外,还有 NodeJS、Golang、Python、C++ 等,为了能够统一管控这些服务,我们要为每种语言、每种框架都重新开发一套完整的 SDK,维护成本非常高,而且对我们的人员结构也带来了很大的挑战。

multi-language.png

有了 Service Mesh 之后,通过将主体的服务治理能力下沉到基础设施,多语言的支持就轻松很多了,只需要提供一个非常轻量的 SDK、甚至很多情况都不需要一个单独的 SDK,就可以方便地实现多语言、多协议的统一流量控制、监控等治理需求。

multi-language-sidecar.png 图片来源

1.3 金融级网络安全

当前很多公司的微服务体系建设都建立在『内网可信』的假设之上,然而这个假设在当前大规模上云的背景下可能不再合适,尤其是涉及到一些金融场景的时候。

通过 Service Mesh,我们可以更方便地实现应用的身份标识和访问控制,辅之以数据加密,就能实现全链路可信,从而使得服务可以运行于零信任网络中,提升整体安全水位。

security.png

二、SOFAStack Mesh 的大规模落地实践

2.1 灵魂拷问

正因为 Service Mesh 带来了上述种种的好处,所以我们从 2018 年初就开始了技术探索并进行了小规模的落地试点,然而初期在和业务团队推广时,却面临了灵魂拷问:

  1. 需要业务改代码吗?
    • 业务团队日常面临着非常繁重的业务压力,没有太多精力做技术改造
  2. 升级过程不要影响我业务
    • 对公司业务而言,稳定肯定是第一位的,新的架构不能对业务产生影响
  3. 其它你随便
    • 言下之意就是只要我们能确保改造成本够低,稳定性够好,那业务团队还是愿意配合我们一起去落地 Service Mesh 的

promotion.png 图片来源

这就让我想起了著名的产品价值公式:

product-value.png

由此公式可知:

  • 『新体验 - 旧体验』就是前述提到的 Service Mesh 所带来的种种好处,这部分价值需要被最大化
  • 『迁移成本』就是业务在迁移至 Service Mesh 新架构过程中的种种成本,这部分成本需要被最小化,主要包括
    • 接入成本:已有的系统如何接入 Service Mesh?是否要做业务改造?
    • 是否平滑迁移:生产环境已经运行着众多的业务系统,是否能平滑地迁移到 Service Mesh 架构下?
    • 稳定性:Service Mesh 是一套全新的架构体系,业务迁移上去之后如何确保稳定性?

接下来我们就来看一下蚂蚁是怎么做的。

2.2 接入成本

由于蚂蚁的服务统一使用了 SOFA 框架,所以为了最小化业务的接入成本,我们的方案是修改 SOFA SDK 的逻辑,从而能够自动识别运行模式,当发现运行环境启用了 Service Mesh,就会自动和 Sidecar 对接,如果没有启用 Service Mesh,则继续以非 Service Mesh 的方式运行。对业务方而言,只需要升级一次 SDK,就完成了接入,不需要改业务代码。

sdk-adaptor.png

我们再来看一下 SDK 是怎么和 Sidecar 对接的,首先来看服务发现的过程:

  1. 假设服务端运行在1.2.3.4这台机器上,监听20880端口,首先服务端会向自己的 Sidecar 发起服务注册请求,告知 Sidecar 需要注册的服务以及 IP + 端口(1.2.3.4:20880)
  2. 服务端的 Sidecar 会向注册中心发起服务注册请求,告知需要注册的服务以及 IP + 端口,不过这里需要注意的是注册上去的并不是业务应用的端口(20880),而是Sidecar自己监听的一个端口(例如:20881)
  3. 调用端向自己的 Sidecar 发起服务订阅请求,告知需要订阅的服务信息
  4. 调用端的 Sidecar 向调用端推送服务地址,这里需要注意的是推送的IP是本机,端口是调用端的 Sidecar 监听的端口(例如:20882)
  5. 调用端的 Sidecar 会向注册中心发起服务订阅请求,告知需要订阅的服务信息
  6. 注册中心向调用端的 Sidecar 推送服务地址(1.2.3.4:20881)

sidecar-service-discovery.png

再来看一下服务通信过程:

  1. 调用端拿到的『服务端』地址是127.0.0.1:20882,所以就会向这个地址发起服务调用
  2. 调用端的 Sidecar 接收到请求后,通过解析请求头,可以得知具体要调用的服务信息,然后获取之前从服务注册中心返回的地址后就可以发起真实的调用(1.2.3.4:20881)
  3. 服务端的 Sidecar 接收到请求后,经过一系列处理,最终会把请求发送给服务端(127.0.0.1:20880)

sidecar-service-invocation.png

通过上面的过程,就完成了 SDK 和 Sidecar 的对接。可能会有人问,为啥不采用 iptables 的方案呢?主要的原因是一方面 iptables 在规则配置较多时,性能下滑严重,另一个更为重要的方面是它的管控性和可观测性不好,出了问题比较难排查。

2.3 平滑迁移

蚂蚁的生产环境运行着非常多的业务系统,有着复杂的上下游依赖关系,有些是非常核心的应用,稍有抖动就会产生故障,所以对于像 Service Mesh 这样一个大的架构改造,平滑迁移是一个必选项,同时还需要支持可灰度和可回滚。

得益于我们在架构体系中保留的注册中心,平滑迁移方案也是比较简单直白的:

1.初始状态

以下图为例,初始有一个服务提供者,有一个服务调用者。

migration-initial.png

2.透明迁移调用方

在我们的方案中,对于先迁移调用方还是先迁移服务方没有任何要求,这里假设调用方希望先迁移到 Service Mesh 上,那么只要在调用方开启 Sidecar 的注入即可,SDK 会自动识别到当前启用了 Service Mesh,就会和Sidecar 做服务的订阅和通信,然后 Sidecar 会订阅服务并和真正的服务方通信,而服务方则完全不感知调用方是否迁移了。所以调用方可以采用灰度的方式一台一台开启 Sidecar,如果有问题直接回滚即可。

migration-consumer.png

3.透明迁移服务方

假设服务方希望先迁移到 Service Mesh 上,那么只要在服务方开启 Sidecar 的注入即可,SDK 会自动识别到当前启用了 Service Mesh,就会和 Sidecar 做服务的注册和通信,然后 Sidecar 会把自己作为服务提供方注册到注册中心,调用方依然从注册中心订阅服务,完全不感知服务方是否迁移了。所以服务方可以采用灰度的方式一台一台开启 Sidecar,如果有问题直接回滚即可。

migration-provider.png

4.终态

最后就达到了终态,调用方和服务方都平滑地迁移到了 Service Mesh 上,如下图所示。

migration-final.png

2.4 稳定性

通过 Service Mesh 架构的引入,我们初步实现了应用和基础设施的解耦,大大加快了基础设施的迭代速度,不过这对稳定性意味着什么?

在 SDK 的模式下,中间件同学在发布 SDK 后,业务应用会逐步升级,并且会按照开发、测试、预发、灰度、生产等环境逐步推进并进行完整的功能验证,从一定程度上来说,其实是有大量的业务同学在帮中间件的产品做测试,并且是分环境小规模逐步升级,所以风险非常小。 然而,在 Service Mesh 的架构下,业务应用和基础设施解耦了,这个使迭代速度大大加快,但是也意味着我们无法再用以前的这套模式来确保稳定性,我们不仅需要在研发阶段保证产品质量,更要在线上变更时控制风险。

考虑到蚂蚁的集群规模,线上变更往往涉及到几十万个容器,如何保证这么大规模下升级的稳定性呢?我们给出的方案是:无人值守变更

在了解无人值守变更之前,我们先来看下无人驾驶,下面这副图定义了无人驾驶的成熟度级别,从L0 - L5。L0 就对应着我们现在大部分的驾驶模式,汽车本身没有任何自动化能力,需要由驾驶员来完全控制,而 L5 呢就是最高级别,能够实现真正的无人驾驶。像我们熟知的 Tesla,它的自动驾驶就处于L2 – L3 之间,具备了在一定场景下的自动驾驶能力。

我们也参照了这套体系定义了无人值守变更的级别,如下图所示:

auto-drive-level.png 图片来源

  • L0:纯人肉变更,黑屏操作,没有任何工具辅助
  • L1:有了一些工具,不过并没有体系化串联起来,需要人编排不同的工具来完成一个变更,确保人工灰度
  • L2:具备了初步的自动化能力,系统能够自己编排将整个变更流程串起来,具备强制灰度的能力,所以相比于 L1 级别,人的手解放了,一次变更只需要提交一个工单即可
  • L3:系统具备了观测能力,在变更过程中,如果发现有异常会通知用户并且阻断变更,所以相比于 L2 级别,人的眼睛也解放了,我们不需要时刻盯着变更过程,不过电话还得开着,一旦有问题需要及时上线处理
  • L4:就更进一步了,系统具备了决策能力,当发现变更有问题的时候,系统可以自动处理实现自愈,所以相比于 L3 级别,人的大脑也解放了,变更可以放在半夜进行,有问题系统会按照预定义的方案自动处理,实在解决不了才需要电话通知人上线处理
  • L5:就是终极状态了,提交完变更后,人就可以离开了,系统会自动执行并且确保没有问题

目前我们自评已经做到了 L3 级别,主要体现在:

  1. 系统自动编排分批策略,实现强制灰度
  2. 引入了变更防御,增加了前置、后置校验,当问题发生时,能够及时阻断变更

变更防御流程如下图所示:

  • 提交变更工单以后,系统会对变更进行分批,按照机房、应用、单元等维度开启分批变更
  • 在每个批次变更开始前首先会进行前置校验,比如检查当前时间是否是业务高峰期,是否是故障期间,检查系统容量等
  • 前置校验如果不通过,则变更终止并通知变更的同学,如果通过,则会开始 Mosn 的升级或接入流程
  • 变更完成后会进行后置校验,比如会检查业务监控,像交易、支付成功率是否下跌等,还会检查服务健康度,比如RT、错误率是否有异常,同时也会检查上下游的系统,还会和告警关联,检查变更期间是否有故障产生等
  • 后置校验如果不通过,则变更终止并通知变更的同学,如果通过,则会开始下一批次的变更流程

change-defense-process.png

这是我们实际的一个变更工单,可以看到前置校验有 12 条规则验证通过,后置校验有 3 条规则。

change-defense-ticket.png

目前的灰度策略如下,可以看到,我们按照环境、机房、应用级别等制定了一个 17 天的升级计划,所以目前我们可以做到最快 17 天完成全站升级,通过无人值守变更的引入,对Service Mesh 架构下的稳定性有很大的帮助。

gray-strategy.png

2.5 整体架构

我们再来看一下蚂蚁 SOFAStack Mesh 的整体架构,这里的『双模微服务』是指传统的基于SDK 的微服务和 Service Mesh 微服务双剑合璧,从而可以实现:

  • 互联互通:两个体系中的应用可以相互访问
  • 平滑迁移:应用可以在两个体系中平滑迁移,对于上下游依赖可以实现透明无感知
  • 灵活演进:在互联互通和平滑迁移实现之后,我们就可以根据实际情况进行灵活的应用改造和架构演进

在控制面上,我们引入了 Pilot 实现配置的下发(如服务路由规则),在服务发现上仍然保留了独立的注册中心来实现平滑迁移和规模化落地。

在数据面上,我们使用了自研的 Mosn,不仅支持 SOFA 应用,同时也支持 Dubbo 和 Spring Cloud 应用。

在部署模式上,我们不仅支持容器/k8s,同时也支持虚拟机场景。

dual-mode-service-platform.png

2.6 落地规模和业务价值

目前 Service Mesh 覆盖了蚂蚁数千个应用,实现了核心链路全覆盖,生产运行的 Pod 数量有几十万,双十一当天处理的 QPS 达到了几千万,平均处理响应时间 <0.2 ms,取得了不错的技术成果。

technical-value.png

在业务价值上,通过 Service Mesh 架构,我们初步实现了基础设施和业务应用的解耦,基础设施的升级能力从 1 ~ 2 次/年提升为 1 ~ 2 次/月,不仅大大加快了迭代速度,同时节省了全站每年数千人日的升级成本;借助于 Mosn 的流量调拨实现了分时调度的场景,仅用了 3m40s 就完成了 2w+ 容器的切换,节省下 3.6w+ 物理核,实现了双大促不加机器;在安全可信方面,实现了身份认证、服务鉴权和通信加密,从而使得服务可以运行于零信任网络中,提升了整体安全水位;在服务治理方面,快速上线了自适应限流、全局限流、单机压测、业务单元隔离等能力,大大提升了精细化服务治理水平,给业务也带来了很大的价值。

business-value.png

2.6.1 分时调度

前面提到的分时调度大家可能比较陌生,所以这里简单展开一下,我们知道不同的业务往往有不同时间的波峰和波谷,比如像双十一大促中,蚂蚁会有两波流量高峰:一波是 0 点的支付高峰,另一波是早上的会员高峰,这两波高峰涉及到的应用链路是不一样的,时间上也是错开的。

如果要同时满足这两波高峰的资源诉求,就需要准备很多机器,成本很高。但如果能复用机器,那就可以节省可观的硬件成本。

如图所示,如果在 0 点(图中的 t1 时刻),我们能把机器多分一些给支付链路(图中的资源域 A)支撑大促,到早上(图中的 t2 时刻)再回收分配给会员链路(图中的资源域 B),就能实现这样的效果。

time-sharing-scheduling-initial.png

我们先来看一下常规的做法,在这个方案中, 我们需要先对资源域 A 做缩容,然后再对资源域 B 做扩容,这个方案的主要问题就是耗时非常长,对我们的场景之前每次切换都要以小时计,另一个问题就是风险,在大促期间做大规模的发布部署,往往会带来意料之外的影响。

time-sharing-scheduling-normal-way.png

有了 Service Mesh 之后,我们有了新的解法。如下图所示,我们首先通过超卖把资源域 A 和资源域 B 的应用部署到同一批机器上,由于两者对资源的占用是错峰的,所以我们就可以用运行态和保活态来区分,对运行态的应用我们会照常分配 CPU 和内存资源,对于保活态的应用,CPU 和内存会被控制在非常低的水位,在 t1 时刻,资源域 A 的应用会被标识为运行态,资源域 B 的应用会被标识为保活态,到 t2 时刻,再做一次切换即可,整个切换过程只要几分钟,风险也非常小。

time-sharing-scheduling-service-mesh-way.png

为了确保保活态应用在极低资源占用时仍然能正常工作,就需要用上 Mosn 流量转发的能力了,对于保活态的实例,Mosn 会把 99% 的流量转发给其它正常的实例,只保留 1% 的流量用于保活。那为何不是把 100% 的流量都转走呢?这里的主要考虑是希望应用能继续维持工作状态,如各种长连接、缓存等,从而在切换成运行态时无需预热即可快速恢复。

time-sharing-scheduling-flow-splitting.png

三、展望未来

目前已经能非常清楚地看到整个行业正在经历从云托管(Cloud Hosted)到云就绪(Cloud Ready)直至云原生(Cloud Native)的过程。

不过这里希望强调的一点是,我们并不是为了技术而技术,技术的发展本质上还是为了业务发展。云原生也是一样,其根本还是在于提升效率,降低成本,所以云原生本身不是目的,而是手段。

cloud-native-path.png

我们通过 Service Mesh 的大规模落地,也是向着云原生走出了坚实的一步,验证了可行性,同时也确实看到了基础设施下沉后无论是对业务还是对基础设施团队都带来了研发和运维效率的提升。 目前 Mosn 主要提供了 RPC 和 MQ 的能力,然而还有大量的基础设施逻辑作为 SDK 嵌在业务系统中,离真正解耦基础设施与业务还有很大距离,所以未来我们会将更多的能力下沉到 Mosn 中(如事务、缓存、配置、任务调度等),实现 Service Mesh 到 Mesh 的演进。对业务应用而言,以后都会通过标准化的接口来和 Mosn 交互,不需要再引入各种很重的 SDK,从而使 Mosn 从单纯的流量代理演化成为下一代的中间件运行时。

mosn-runtime.png

通过这种方式能进一步降低业务应用和基础设施的耦合,使得业务应用更轻量化。如图所示,从最早的单体应用演化到微服务,实现了业务团队之间的解耦,但是并没有解开业务团队和基础设施团队之间的耦合,未来的方向就如第三幅图所示,我们希望业务应用朝着纯业务逻辑(Micrologic)这个方向前进,把非业务逻辑都下沉到 Sidecar 中,从而可以真正实现业务和基础设施的独立演进,提升整体效率。

multi-runtime.png 图片来源

另一个趋势是 Serverless,目前受限于应用体积、启动速度等因素,Serverless 主要的应用场景还是在 Function 上。

不过我们始终认为 Serverless 不会只局限在 Function 场景,它的弹性、免运维、按需使用等特性对普通的业务应用而言,价值显然是更大的。

所以当业务应用演进到 Micrologic + Sidecar 后,一方面业务应用自身的体积会变小、启动速度会加快,另一方面基础设施也能做更多的优化(比如提前和数据库建连、提前准备好缓存数据等),从而使得普通业务应用也能融入到 Serverless 体系中,真正享受到 Serverless 带来的效率、成本等方面的红利。

serverless.png 图片来源