一 背景
优酷内容分发业务涵盖了优酷主客的首页、频道页、二级页等不同场景下的内容分发,服务端之前采用传统的Java应用结合阿里集团中间件的开发模式,一直是产品评审、API设计、前后端联调、前后端发版等节奏。然而,随着端上内容的多样化,产品需求迭代的加速,传统的服务端架构开发模式已显得力不从心,我们虽然沉淀出一套通用框架,但受限于开发模式的本质并没有变化,业务开发的灵活性与开发成本依然很高。总结起来,面临的挑战主要是:API依赖数据源多,业务需求变化快,前后端联调成本大等。
随着Serverless技术的发展,FaaS的相关实践探索都在阿里内部逐渐多起来,我们思考了FaaS的特点和面临的挑战,希望通过FaaS技术的引入,把一系列基础能力沉淀下来,在此之上,通过FaaS来承接上层业务逻辑,阿里巴巴文娱优酷FaaS平台应运而生。
二 平台设计与技术难点
1 设计目标
希望实现一个通用的函数计算平台,在这个平台上,开发者直接通过编写、运行和管理一个或多个函数对外提供服务,允许通过微服务、HTTP接口、事件源触发等多种方式调用函数。同时,函数的开发及发布应该是秒级生效,且无需重启宿主应用的,这样就可以克服传统Java应用发布部署的时间成本,极大的减轻开发者在代码开发之外的时间成本,同时可以快速回滚。
FaaS平台应该提供函数式应用的运行环境,应该支持轻量级脚本语言编写函数。我们首选Groovy语言,主要是考虑了Groovy的代码简洁,同时可以访问Java的原生的类和对象。
FaaS可以根据实际的访问情况进行函数实例的动态加载和资源分配。
总结起来,在FaaS平台上运行的函数应该是一个短小、离散、可复用的代码块,我们希望它有以下几个特点:
- 生命周期短,支持快速发布部署
- 非守护进程(不需要长时间运行,按需加载)
- 不提供长连接服务
- 无状态
- 可重用现有服务或第三方资源(重点,FaaS应该建立在完善的基础服务上)
- 毫秒级执行时间
2 平台整体设计
FaaS平台的整体核心架构主要由网关、运行时容器、一站式运维发布平台、基础服务等组成:
网关层主要负责接受函数调用请求,通过函数的唯一标识及函数的集群信息分发函数调用到对应集群的机器环境中执行。
函数容器层是整个系统的核心,主要通过函数执行引擎进行实例的调用执行,同时负责函数实例的生命周期管理,包括按需加载、代码预热、实例卸载回收等工作。
一站式发布运维平台(FaaS Platform)是面向开发者的主要操作平台,开发者在平台上进行函数编写、版本提交发布、回滚、监控运维等一系列工作。整个监控体系打通了集团的基础服务监控体系,,可以提供实时大盘,集群性能等基本监控指标的查询功能。
整个FaaS平台建立在集团中间件以及优酷内容分发依赖的各基础服务之上,通过良好的封装向开发者提供简洁的服务调用方式,同时函数本身的执行都是运行在互相隔离的环境中,通过统一的函数实例管理,进行函数的调度、执行监控、动态管理等。
整体技术栈服务端容器层主要是采用Java实现,结合集团中间件完成整个容器层的主要功能。
前端主要基于React框架和Dva状态管理框架实现。当然,在实际开发过程中我们选择了蚂蚁金服的Bigfish框架和Odin脚手架。React提供了组件化的概念,这意味着我们开发的组件可以像HTML基本DOM元素一样不断被复用。为了实现组件的复用化和研发效率的提升,Bigfish在Web页面上进行了分层设计,细粒度从大到小依次为:页面模板 -> 区块 -> 业务组件 -> 组件。Odin脚手架是优酷推出一款面向中后台业务系统的前端开发脚手架,集成了Bigfish的框架,支持以配置化的方式构建网站路由,使得开发者不需要关注过多底层细节,可以快速上手实现业务逻辑和页面构建。
类似于服务端侧的MVC分层模式,前端在实现业务逻辑和数据通信时也有对应的封层设计模式,来实现组件的状态管理。经历了从Flux -> Redux -> Dva的衍变,状态管理机制对复杂业务带来的益处正在不变突出。Dva的完整数据流图如下:
State是负责保存整个应用状态,View是React组件构成的视图层,Action是描述事件的对象。connect方法是绑定 State 到 View的函数,使得View层的组件可以动态监听State中的属性,同时可以通过dispatch方法负责将Action发送至State触发状态改变。触发状态改变有两种类型的函数:effect函数和reducer函数。前者会与服务端进行数据通信,可以处理异步动作;后者处理同步动作,并直接更新State。
FaaS Platform前端主要分为函数创建、函数管理、函数发布、函数模板和应用统计五个模块。在FaaS Platform系统中,函数是对外可被调度的最小单元,而应用是划分机器资源的最小单位,所以我们设定应用与函数存在一对多的映射关系。
函数创建模块
函数创建模块主要提供添加函数的功能。一个完整函数必须包括函数名称、函数标识、函数类型、函数所属应用及应用下所属分类等基本信息;同时类似于mtop网关,我们提供对于函数入参、响应业务结果、响应业务错误码的配置页面,用于自动生成函数调用入参表单和函数接口文档。函数的英文标识唯一确定一个函数,不可重复。
函数管理模块
函数管理模块主要提供函数的CRUD操作和函数的在线编写功能。在本页面我们可以快速进行复杂条件的函数查询和函数基本信息和状态的编辑。同时我们提供函数编写的在线Web IDE,支持文件增删、代码编写、自动保存、函数提交、函数调试、日志打印等功能。
函数发布模块
函数发布模块主要提供函数提交历史的查询和执行函数发布的功能。我们像传统Java应用支持引入二三方依赖,但不同于传统的Java应用发布,FaaS Platform系统中的函数发布可以实现秒级发布。目前函数发布已经支持函数回滚发布和函数分批次发布,从部署环节实现对复杂多变业务需求的快速响应。
函数模板模块
函数模板模块主要提供函数模板的CRUD操作和函数的在线编写功能。结合实际的业务场景,我们首先提供一些基础的内置模板,方便函数的快速初始化。同时对于某一个业务问题的完整解决方案,我们允许该函数保存为自定义的函数模板。函数模板的Web IDE同样支持函数模板的在线编写、调试、自动保存等功能。
应用统计模块
由于函数隶属于应用从而具备机器资源,我们计划提供应用统计模块以应用为拆分进行函数上线状态、发布版本的数据统计;同时我们也基于函数日志提供函数调用情况(调用量、成功率、响应时间)的统计分析和监控。关于具备的细节,我们正在逐步实现和完善。
3 主要特性
优酷FaaS平台的主要特性是开发接入低成本、函数运行时环境隔离以及运维监控操作的透明化。
开发接入低成本
FaaS平台通过一站式的云端开发平台,使用户可以直接面向业务逻辑的开发,而无需关注基础服务及中间件的依赖,平台本身提供完善的基础能力封装,包括:快捷开发能力,中间件快速接入能力,数据存储快速接入能力,基础能力封装直接调用等。
业务逻辑开发模式轻量化、无应用化,发布回滚秒级生效,极大的减轻了传统服务端开发过程的繁琐流程,将开发者的精力更多的集中于核心业务逻辑的开发。
同时提供如下的简洁易于操作的开发部署流程设计,减轻开发者开发部署的时间成本。
FaaS平台上的函数除了开发成本低,调用者接入的方式也比较简单。我们同时提供了中心化和去中心化两种使用方式,不管去中心化还是中心化使用方式,函数代码的编写、调试、发布均在一站式运维发布平台上完成。在中心化接入方式下,我们通过统一的函数服务集群提供对外服务,允许调用者通过统一的函数调用接口以HSF服务或者HTTP接口调用函数,而函数代码的执行完全在我们的函数服务集群上,开发者无需自己申请应用。
对于去中心化接入方式,开发者如果想调用函数平台上的FaaS函数,可以引入我们提供的SDK,此时,函数的执行完全在调用者应用的本地进程里,FaaS平台只提供函数的开发发布功能。
运行时环境的隔离
运行时环境的隔离分为两个层次,一个层次是函数容器内部函数实例之间的隔离;另外一个层次是不同函数本身就运行在不同的虚拟应用集群上,集群与集群之间的隔离性。
函数容器内部函数实例的隔离指的是在FaaS平台上编写的Groovy函数运行在统一的JVM进程中,每个函数在开发的过程中都会生成多个版本,而不同函数之间、同一函数的不同版本之间在运行时的环境都是相互隔离,互不干扰的。
函数运行集群的隔离性主要是根据函数的访问量、函数的服务特点(长尾服务还是通用服务)等特性,在函数创建之初就将函数绑定在不同的虚拟应用上,而不同的应用会运行在不同的机器集群上,函数在被调用时,网关层可以根据函数的应用将函数的调用分发到不同的集群上执行,保证函数之间物理隔离。
运维监控的透明化
FaaS平台的函数都能在平台上直接进行监控运维操作,我们通过在函数执行流程上收集函数的执行日志,并将日志实时上报到集团监控服务,可以在平台上实时监控函数运行。
4 技术难点
函数执行引擎设计
函数执行引擎是整个FaaS的核心部分,负责函数实例的加载、预热、调度执行、卸载等生命周期管理。FaaS的函数目前支持Groovy语言,选择Groovy主要是由于JVM提供的运行时环境天然支持Groovy语言的运行。FaaS平台上每个函数都具有一个自己独立的代码版本库,每次提交都将生成递增的版本,执行引擎加载函数实例时会从版本库中加载当前最新版本的代码,通过初始化、预编译等操作生成函数的实例放到实例池中,由于每个函数都有唯一标识,因此,当调用某个具体的函数时,执行引擎会从实例池中取出对应实例加载执行。整个流程如下图所示:
由于函数实例都存在于同一个JVM进程中,并且不同于服务,函数的粒度更小,因此函数的生命周期需要严格控制,不然大量函数加载到内存中,有可能出现内存占用过大的问题。同时兼顾SDK调用方式,防止多个函数常驻内存将宿主应用的内存耗尽。所以目前采用了懒加载机制,按需加载函数实例到内存中,过期自动回收,有助于释放内存提高内存利用率。
每个Groovy函数对应一个Groovy的解释器环境GroovyEngine,不同的函数之间相互独立,每个函数在加载到内存的过程中都分别独立的进行预编译,初始化等流程,防止不同函数之间相互干扰,同时为二三方JAR包加载提供隔离的环境,防止出现不同函数之间的类加载器相互影响的情况。
二三方JAR包加载能力
FaaS平台提供二三方JAR包的加载能力,允许在不重启整个底层容器的情况下,加载函数自己的二三方依赖,我们通过实现Groovy二三方JAR包加载能力的Classloader,实现了函数与函数之间、函数不同版本之间的二三方依赖加载能力。FaaS平台的Classloader体系:
三 FaaS平台的落地探索
结合目前阿里文娱业务的特点,即大多以内容分发为主,以首页、二级页等业务来看,内容分发具有运营坑位多、需求变化快、数据源多等特点,传统的Java服务端开发方式,前后端联调以及后端开发部署都逐渐成了影响迭代效率的重要瓶颈,以往都是服务端开发在客户端发版前发布线上,发布耗时长,回滚成本高,因此通过引入FaaS,希望提高服务端开发的灵活性,让开发者更多的面向业务逻辑而不是花较大量的时间在服务的部署维护上面。
优酷内部的内容分发目前主要在统一的内容搭建投放框架之上开发,这套框架是一套流程编排的框架,通过流程编排,从不同数据源获取内容,通过业务逻辑处理,最终通过模版字段映射输出API内容。目前FaaS主要应用在数据源及模版字段映射阶段。数据源即原始数据接口的封装,通过数据源获取实际业务需要的原始数据,比如媒资节目视频、节目专题数据、用户关注等业务数据;模版字段映射主要通过编写Java的函数根据实际业务逻辑生成字段内容。以往的开发模式下,如果业务逻辑有变化,需要变更然后发布Java应用才能生效,采用FaaS开发之后,只需要发布对应的FaaS函数即可,由于FaaS函数的发布是秒级,因此极大的提高了迭代效率。
1 统一的数据源封装
我们使用FaaS实现数据源接口的封装,当有新的数据接口需要接入时,直接在FaaS平台上通过编写函数实现,可以做到在本地Java应用不发布的情况下,直接上线新数据源。对于新业务接口的快速接入具有重要意义。同时这些数据源可以被重用,因此在多人协作的模式下,通过复用函数实现的数据源极大的减少了重复开发量。
2 FaaS函数处理API协议模版字段映射
我们扩展了搭投框架,通过Faas的SDK,服务端接口的模版解析阶段除了能解析普通的Java函数,也可以支持解析FaaS函数,这类函数的代码不是通过原生Java代码编写,而是在Faas平台上用Groovy代码编写而成,这类函数的特点是编写、更新、发布均不需要重新部署哥伦布业务应用,只需要在Faas平台上操作函数即可。字段逻辑的修改可以完全不用重启Java应用,快速应对迭代变更。每个函数都有独立的生命周期和发布流程,不同函数的发布变更之间相互隔离。当有字段逻辑的变化时,可以完全不重启本地Java应用,直接通过函数的秒级发布来完成,极大提高了迭代效率。
四 总结与展望
目前优酷内容分发相关业务已经陆续引入FaaS能力,在FaaS的助力下,迭代效率提升。但是平台整体上还处于刚刚起步阶段,也是我们Serverless实践的初步尝试。后续我们希望在以下几个方面继续探索FaaS平台的技术与落地:
- 支持更多编程语言的运行时环境,以及更友好的云端IDE开发体验。
- 优化函数运行集群的资源调度策略,合理分配函数执行需要的资源,支持动态扩缩容。
- 结合内容分发业务的特点,寻找更多业务的切入点,通过FaaS进一步提升现有技术架构的灵活性和迭代效率。