美团是如何解决落地Serverless的五大难题的?

嘉宾 | 殷琦

编辑 | 李慧文

近年来,在容器、Kubernetes、云原生等技术推动下,Serverless 技术也迎来了迅速发展,国内各大厂都在积极建设 Serverless 相关产品,美团也于 2019 年初开始了 Serverless 平台的建设。Serverless 平台的建设挑战较多,例如技术选型、冷启动优化、高可用保障、容器稳定性提升、研发体系建设等。

在 QCon 全球软件开发大会(2021)北京站上,美团基础技术部技术专家殷琦分享了美团面对 Serverless 平台落地的探索与实践。我们整理了他的演讲,以期解决您在 Serverless 落地时可能遭遇的某些类似问题。(下文以殷琦老师第一人称叙述)

美团之所以选择建设 Serverless,是因为现阶段业务遇到资源利用率低、研发成本高、运维成本高三类问题,具体来说:

第一,公司大部分业务流量存在明显波峰、波谷现象,另外还存在大量低频业务。为了保证业务 SLA 和容灾要求需要进行冗余部署,这就导致服务的资源利用率较低。

第二,在研发方面:a. 运维流程繁琐(如申请机器、构建或发布配置、申请域名等);b. 组件平台不统一,建设一个服务需熟悉并使用多个组件平台(如日志中心、网关等);c. 各个中间件开发模型不统一(如 RPC 和 MQ 组件开发模型差异大),学习使用成本高 ;d. 发布、回滚慢,影响迭代速度。

第三,公司在节点异常、机房级别容灾、安全风险组件升级时均需要人工处理,存在大量运维和运营成本。这点对 Node.js 开发同学来说痛点尤为突出,因为 Node.js 不太熟悉后端的运维和运营动作。

Serverless 在产品形态上分为 3 种:FaaS(Function as a Service)、BaaS(Backend as a Service)和面向应用的 Serverless 服务。FaaS 即函数服务,如 AWS 的 Lambda,BaaS 即云上 PaaS 组件,如 DB、消息队列等;近几年新提出来的面向应用的 Serverless 服务,即阿里云的 SAE(Serverless 应用引擎)、谷歌开源的 Knative 等。

Serverless 运行架构比较简单:触发源触发 FaaS 平台,FaaS 平台内部会去执行包含业务逻辑的函数,函数内部逻辑可以调用各种 BaaS 组件。

Serverless 具有以下特点:弹性伸缩、按需付费、事件驱动、无需运维。其中,弹性伸缩和按需付费可解决资源利用率低,成本高的问题;事件驱动可统一编程模型,降低研发成本;无需运维可以降低运维成本。

公司内部 Serverless 产品为 Nest,这个项目是 19 年初开始立项,主要经过了三个阶段演进:第一,快速落地,上线试点;第二,优化技术,提升稳定性;第三,完善生态,提升效能。具体里程碑如下图所示:

选型与实现原理

技术选型

技术选型需要关注三点:演进路线、基础设施、开发语言。

演进路线上,我们主要权衡当前的三种 Serverless 形态(FaaS、BaaS 和面向应用的 Serverless 服务)。首先考虑到私有云上的 BaaS 实为中间件及其他 PaaS 产品,这方面美团已相对比较成熟,可建设的空间不大。其次考虑到面向应用的 Serverless 服务还不太成熟,尤其是冷启动问题暂时还没有较好的解决办法。因此,我们决定先做 FaaS。

公司内部基础设施为自研 Hulk。Hulk 本身基于 Kubernetes,但考虑到落地推进的阻碍,主力集群未完全应用 Kubernetes 的能力。我们考虑业界注重 Kubernetes 原生能力的未来趋势,Nest 选用了原生 Kubernetes(由 Hulk 团队提供的原生的 Kubernetes 形态的产品 MKE)。

开发语言和基础设施相关。Kubernetes 主流开发语言为 Go,但公司内部 Java 研发生态更加完善且应用广泛,因此考虑再三我们选择了 Java 语言。Java 语言的 Kubernetes 客户端,虽然当时不是很完备,但随着这两年的发展,目前应对基础开发已绰绰有余。

架构

Nest 为面向 FaaS 的架构设计,如下图所示:

左侧为事件源,中间为 Serverless 平台,包括管理平台、事件网关,函数实例、弹性伸缩及控制器,右侧为 BaaS 平台。

其中,管理平台负责服务和函数的管理监控、构建与发布函数、函数配置。事件网关负责承接事件源流量,然后路由到具体函数实例。函数实例内部则主要是执行函数,它包含 Runtime 和函数两部分。弹性伸缩负责根据流量进行函数实例的扩缩容。控制器负责 Kubernetes CRD(Custom Resource Definition)处理。

弹性伸缩

弹性伸缩的核心问题是:何时伸缩、伸缩量、伸缩速度。

Nest 通过实时统计经过事件网关的流量,实时计算函数期望实例数,决策何时调整该函数实例的个数。伸缩量通过以下公式计算:指标 / 单实例阈值 = 期望实例数。其中指标为统计所得,单实例阈值需业务配置,获得期望实例数后即可调整实例个数。

伸缩速度取决于冷启动耗时。若启动快,则扩容快,需要预留实例少,资源利用率高,稳定性也相应提升。

函数如何发布

函数在编写后需经过构建、打版本、发布、弹性伸缩这四步,构建即将代码及配置构建为镜像或可执行文件;打版本即通过镜像或可执行文件,根据发布配置,生成可发布的固定版本;发布后函数实例会接受流量,驱动弹性伸缩。

传统发布面向机器,更新机器上的代码包,但 Serverless 屏蔽机器,此时该如何发布呢?

Nest 抽象出了一个逻辑概念:分组。分组由三个信息组成:地区、Set、泳道。地区即为实际地理分区,如北京、上海等。Set 和泳道是内部为实现路由策略的信息,这两个信息是和机器实例绑定的。平台会根据弹性及分组配置创建机器实例。也就是说通过分组来屏蔽机器实例。

核心技术优化

弹性伸缩

Serverless 对弹性要求极高,生产试点业务后,主要面临三个问题:第一,伸缩频繁,服务不稳定;第二,扩容来不及,服务不稳定;第三,不同场景,需求不一。

针对伸缩频繁,Nest 一方面采用了滑动窗口策略,即设定窗口值,计算均值,调整、计算实例个数,避免了基于统计数据独立采点导致的伸缩波动;另一方面,采用延时缩,实时扩的策略,降低伸缩频率;此外,Nest 还增添了 QPS 指标(相对于并发指标更加稳定)。

针对扩容速度的问题,Nest 采取了提前扩容策略,如达到阈值的 0.7 倍时即开始扩容。

针对场景需求差异问题,Nest 针对部分可预测高峰的业务实现定时伸缩;针对关注 CPU 和 Memory 等的业务支持混合指标伸缩(CPU、Memory、并发度、QPS);针对时延敏感业务,支持预留实例。

下图为 Nest 弹性伸缩真实的案例:

上方曲线为请求指标,下方曲线为扩缩决策指标。两曲线相互对应,即实现了配合业务流量扩缩容。

冷启动

冷启动即函数调用链路中包含了资源调度、镜像 / 代码下载、启动容器、运行时初始化、用户代码初始化等环节。

冷启动的优化并没有统一的指导规范,Nest 也是进行了多阶段持续优化。

第一阶段,优化镜像启动。镜像启动相关原因包括容器 IO 限速、⼀些特殊 Agent 进程启动耗时、启动盘与数据盘数据拷贝等,排除后耗时从 42s 降至 12s(不包含业务函数自身启动时间)。

第二阶段,Nest 采取空间换时间的策略优化了资源池,在扩容实例时,不启动新实例,从资源池中直接获取实例,节省了下载镜像及启动容器的时间,实现了耗时从 12s 降至 3s(不包含业务函数自身启动时间)。

第三阶段,针对下载代码实现了核心路径优化,将原解压算法换成高性能的压缩解压算法(LZ4 与 Zstd),同时采用并行下载和解压策略,实现了耗时降至 1s 以下(不包含业务函数自身启动时间)。

第四阶段,业务自身代码启动耗时长,Nest 通过基础逻辑下沉到资源池方案来减小包大小,并预启动资源池中的基础逻辑。减小包大小后可提高下载速度,加载函数只需加载函数业务逻辑,耗时从 21s 降至 2s(包含业务自身启动时间)。

容器稳定性

刚扩容的实例不稳定,负载高,导致请求超时,该问题的根因是镜像中 Agent 版本升级,升级过程非常消耗 CPU,但一般函数 CPU 配置低,函数实例资源小,因此容器内资源竞争剧烈。

Nest 将富容器演进成了轻量级容器,将 Agent 进程隔离到 Sidecar 容器中,业务进程隔离到 App 容器中,双方互不干扰,降低了资源竞争程度。

合并部署

考虑到业务对时延的要求,大部分低频业务都不能缩容到 0,必须预留实例,但低频业务预留的实例资源利用率不是很高。另外,容器自身系统开销大,因此导致资源利用率就更低了。

针对这个问题,Nest 采取了合并部署策略,从一个 Pod 中部署一个函数演进到一个 Pod 中部署多个函数,整体架构如下所示:

该架构参考了 Kubernetes 自定义 CRD,将 Pod 类比成 Kubernetes Node, Sandbox 类比成 Kubernetes Pod,Nest Sidecar 类比成 kubelet,实现了合并部署。合并部署后,美团面对不同的语言采取了不同的函数隔离方案,Java 采取 ClassLoader 隔离,Node.js 采取进程隔离。这是因为 Java 本身内存占比高,如采用进程隔离,内存占将更高,合并部署收益会得不偿失。

研发生态

传统的研发流程包括需求、开发、构建、测试、部署、运维等环节,每个环节均有体系配套工具支撑。加入 Serverless 后,在发布环节会产生分支,导致研发流程割裂。

Nest 采取了集成与被集成策略,打通了公司内部研发工具链。另外,还提供了一些 FaaS 开发工具,如 CLI、WebIDE、IDE 插件等。

高可用

传统业务 PaaS 组件只需做到平台可用,但对于 Serverless 来说,业务托管在 Serverless 平台上,需同时保证平台和业务的高可用。

平台高可用方面:在架构层实现了地域隔离和业务线隔离。地域隔离实现了 Kubernetes 多地部署,业务线隔离实现了不同的服务在事件网关层面独立承接流量;在服务层上事件网关采用了异步化和限流的策略;在监控运营上实现了监控告警、核心链路 治理、故障演练、梳理 SOP 等策略;在业务层增加了 7×24 小时不间断实时巡检。

业务高可用方面:在服务层针对业务服务支持降级、限流策略,保护业务的服务;在平台层面,支持了实例保活、机房打散,多层级容灾、监控告警。

实例保活即若某实例出现问题便自动隔离,立即拉新实例。机房打散即自动帮业务打散所需机器。下图为 Nest 线上业务实时监控。

落地场景与收益

前端 Node.js 技术栈在美团已经规模化落地,几乎涵盖了所有业务线,如外卖、优选、酒旅等,后端 Java 技术栈目前只在个别业务线做了落地。

Serverless 主要适合以下场景:

  • BFF(Backend for Frontend) 场景;
  • SSR(Server Side Render) 场景;
  • 后台管理端场景;
  • 定时任务场景;
  • 数据处理场景;
  • 集成平台场景。

美团 Serverless 落地后,在资源利用率方面:高频业务通过弹性伸缩,资源利用率提升到 40% 以上,低频业务通过合并部署资源利用率成倍提升。

同时,研发效率提升了 30%-40%,降低了申请机器的成本,提供了 CLI、WebIDE 工具,可一键生成代码模版,统一了开发模型,降低了学习和研发成本;另外,实现了秒级别发布、回滚;可一键接入日志中心,并自带监控。

运维方面实现了全托管,无需运维。可自动替换异常实例,高低峰集群自动扩缩容,各机房自动打散实例,机房故障自动容灾。

未来规划

Nest 未来会集中在以下四个方面进行演进:

  • 场景化改造:Serverless 接入场景多,Nest 决定迎合场景,实行产品化改造。
  • 支持 Serverless 工作流:通过编排使使业务复用函数的能力。
  • 完善研发生态:持续完善 CLI、WebIDE 工具、丰富研发流水线等。
  • 传统服务 Serverless 化:未来考虑结合 ServiceMesh,使公司内部大量 Java 微服务享受到 Serverless 红利。

外部链接

PPT:https://static001.geekbang.org/con/83/pdf/2668862090/file/%E6%AE%B7%E7%90%A6.pdf

嘉宾介绍:

殷琦,美团基础架构部技术专家。一直致力于高性能、高并发、高可用中间件研发实践。先后负责消息系统、微服务框架、任务调度、API 网关、Serverless 等中间件产品。