美团Serverless产品落地与演进
今天继续大厂中间件系列,本期介绍的是美团的Serverless产品。
美团的Serverless产品叫做Nest,诞生于2019年。其产品发展经历了三个阶段:
第一阶段:快速验证Serverless产品基本能力,涵盖构建、发布、弹性伸缩、对接触发源、函数执行等,通过一些业务试点接入验证。
第二阶段:保障产品稳定性问题,包括优化弹性伸缩稳定性、冷启动速度、系统与业务可用性、容器稳定性等工作。
第三阶段:完善生态,如研发工具、上下游产品打通、平台开放能力补齐等,促进产品业务收益。
Serverle服务主要包含两部分:FaaS、BaaS。
FaaS:运行在一个无状态的计算容器中d 函数服务,通常由事件触发、生命周期很短,由三方托管。
BaaS:建立在云服务生态之上的后端服务,美团内部的中间件属于这一范畴。
由于弹性伸缩是Serverless平台的必备能力,因此很多Serverless产品都是建立在K8S之上实现的,所以Nest也是将K8S作为基础设施。
在云原生领域Go是开发主流,美团主流语言是Java,所以Nest还是基于Java的。
针对于函数之间的隔离性,NodeJs语言函数使用的是不同进程的隔离方式,Java采用的是类加载隔离,主要是Java进程占用内存空间较NodeJs进程会大很多。
Nest上函数执行的过程:流量由EventTrigger触发到Nest平台(FaaS平台),Nest平台根据流量特征路由到具体函数实例,触发函数执行,函数内部执行可以调用公司各个BaaS服务,最终函数执行完成,返回结果。
EventTrigger(事件触发源)包括:Nginx、应用网关、定时任务、消息队列、RPC调用等。
Nest架构包含以下几个部分:
事件网关:负责对接外部事件源流量,路由到函数实例上,同时负责统计各个函数进出流量信息,为弹性伸缩提供决策数据支撑。
弹性伸缩:负责函数实例的弹性伸缩,根据运行的流量数据及实例阈值配置计算所需函数实例数,借助K8S资源控制能力,调整实例数。
控制器:负责K8S CRD控制逻辑实现。
函数实例:函数运行实例,执行函数代码逻辑。
治理平台:面向用户使用的平台,负责函数构建、版本、发布、函数元数据管理等。
函数在Nest平台执行的流程包括:构建、版本、部署、伸缩。
构建:开发人员编写完代码和配置依赖后,生成镜像或可执行文件。
版本:构建生成的镜像或可执行文件发布成一个不可变的版本。
部署:将版本发布,完成部署。
伸缩:根据函数实例流量及负载信息,进行实例弹性伸缩。
区别于传统部署过程,Serverless屏蔽了机器的概念,抽象了机器分组的概念。
一个机器分组由:SET(单元化架构标识,机器带有)+ 泳道(测试环境隔离标识,机器带有)+ 区域(上海、北京等)组成。
用户部署时,只需在相应分组上操作,不涉及对具体机器的操作。
函数是由事件触发的,函数触发流程如下:
流量进入:先向事件源注册事件网关信息,将流量引入到网关,比如MQ事件源,通过注册MQ消费组,引入MQ的流量到事件网关。
流量适配:事件网关对事件源进入的流量适配对接。
函数发现:依据函数元数据(函数实例信息、配置信息等)获取,类似于服务发现过程,事件网关将接收到的流量发送到具体函数实例,本质是获取K8S中内置资源或CRD资源存储的信息。
函数路由:事件流量路由的过程,将流量路由到特定的函数实例上。为支持传统路由(SET、泳道、区域路由)和版本路由能力,采用多层路由,第一层路由到分组(SET、泳道、区域路由),第二层路由到具体版本。基于版本路由可以轻易支持金丝雀、蓝绿发布。
FaaS服务执行的是代码片段,自身不能单独执行。函数的执行首先需要的是函数环境。
Nest基于K8S实现,函数需要运行在K8S的Pod内,Pod内是容器,容器里是运行时,运行时是函数流量接收的入口。
不同流量下需要不同的实例数,解决弹性伸缩需要考虑:什么时候伸缩、伸缩多少、伸缩速度要快。
伸缩时机:根据流量Metrics实时计算函数期望实例数,进行扩缩。流量的Metrics数据来源于事件网关,主要统计函数并发度指标,弹性伸缩组件每秒钟主动从事件网关获取一次Metrics数据。
伸缩算法:并发度 / 单机实例阈值 = 期望实例数,收集Metrics数据及业务配置的阈值,通过算法计算出期望的实例数,然后通过K8S接口设置具体实例数。
伸缩速度:主要取决于冷启动时间。
函数实例还支持0实例,当函数无实例时,事件网关会将函数请求流量缓存下来,同时立即通过流量Metrics驱动弹性伸缩组件进行扩容,等实例启动完成后,将缓存的请求重试到扩容的实例上触发函数执行。
当前技术做不到毫秒级别的伸缩,因此在线上实际场景中,弹性伸缩不及预期时,会导致服务稳定性问题。
实例伸缩频繁问题:在伸缩组件中增加了统计数据滑动窗口,通过计算均值来平滑指标,同时延时缩容,同时增加了QPS指标伸缩策略,QPS指标和并发度更稳定一些。
扩容来不及问题:系统支持了提前扩容方式,当达到实例阈值的70%就扩容,可以比较好的缓解这个问题,同时结合多个指标(并发度、QPS、CPU、内存)伸缩。
函数实例扩容之后有一段冷启动时间,包括了资源调度、镜像/代码下载、启动容器、运行时初始化、代码初始化等工作,这些工作完成之后才可以执行函数,冷启动时间决定了弹性伸缩的速度。
平台还需要具备高可用能力,包括平台本身高可用及宿主在平台之上的函数的高可用。
平台高可用从架构层、服务层、监控运营层、业务视角层保障。
架构层对于有状态的服务(弹性伸缩模块),采用了主从架构,当主节点异常时,从节点立即替换。
同时架构上横向地域隔离,K8S两地两集群强隔离,服务(事件网关、弹性伸缩)集群内两地弱隔离(上海弹性伸缩只负责上海K8S集群内的业务弹性伸缩,事件网关调度两地K8S集群)。
同时纵向上采用业务线强隔离,不同业务线使用不同集群,K8S层资源采用namespace实现业务线弱隔离。
服务层的网关服务,由于所有函数流量都经过事件网关,因此事件网关可用性非常重要,这层支持了异步和限流,保障服务的稳定性。
运营监控层是完善监控告警、梳理核心链路推动相关依赖方进行治理,定期梳理SOP通过故障演练发现系统隐患。
业务视角层需要在线不间断巡检,模拟用户函数请求流量,实时监测系统核心链路是否正常。
函数高可用包括:业务降级、限流能力,当后端函数故障时,可通过配置降级,返回降级结果。针对异常的函数流量,平台限制其流量,防止后端函数实例被异常流量打挂。
当实例异常时,平台自动隔离该实例并扩容新实例,平台支持业务多区部署,将函数实例打散到不同机房。当宿主机、机房、地区故障时,会立即在可用宿主机、可用机房、可用区重建实例。平台同时提供函数运行时的时延、成功率、实例伸缩、请求数等多种指标监控,当指标不符合预期时,自动告警给负责人。
为提高新扩容实例的稳定性,将运维相关的Agent放到Sidecar容器中,业务进程单独在容器中,通过这种容器隔离机制,解决了异常CPU、资源抢占问题,保障了业务稳定性。
Serverless产品少不了开发工具,一般是WebIDE,支持在线一站式代码修改、构建、发布、测试。
同时平台还完善了函数的编排,通过OpenAPI开放了资源池,让业务自定义资源池。
Serverless收益还是主要围绕于弹性伸缩的降低成本,提高资源利用率。比如高频业务可以收益40%~50%,低频业务通过合并部署,极大降低成本。
提升研发效率上,让研发同学聚焦于业务本身,快速研发、测试,依托于完备的在线日志与监控能力,提高了需求上线速度。
未来工作围绕于将传统的Java微服务Serverless化,考虑结合服务治理体系,如ServiceMesh和Serverless相结合,Mesh为Serverless提供伸缩指标,在伸缩过程中基于Mesh实现精准的流量调配。