本次LiveVideoStackCon 2021 音视频技术大会 北京站邀请到了新浪微博视频平台架构师——黄阳全,他将为我们介绍微博视频处理系统的架构演进与云原生之路上的探索,为什么选择自建,以及如何实现基于原有基础服务的FAAS平台。为尝试云原生架构模式的开发者提供参考。
文 | 黄阳全
整理 | LiveVideoStack
大家好,我是来自微博视频平台的黄阳全,今天分享的主题是微博视频处理系统云原生之路。
我在2017年加入微博研发中心,负责微博视频基础组件的开发与维护,多次参与了微博视频架构升级,主导了微博视频中台的建设。目前正在建设基于云原生架构的微博视频处理系统。
本次分享主要分为4部分:
1.背景介绍:主要介绍微博视频,微博视频处理系统,微博视频处理系统所包含子模块,以及它的特点;
2.原视频处理系统架构:针对微博视频处理系统所具有的特点,我们设计出基本满足需求的原架构,但是随着业务的发展,逐渐发现了原架构的短板;
3.FaaS平台的探索与实践:针对短板,我们在云原生领域开始了如火如荼的FaaS平台的探索,在原基础服务之上开发出了解决业务痛点的FaaS平台;
4.总结与未来展望。
一、背景介绍
随着4G/5G技术和设备的不断普及,越来越多的用户选择通过视频或者直播来记录生活,传递知识,表达观点。
微博作为一个即时分享平台,视频所占的比例也越来越大,目前微博视频的日发布量已达到百万级,日播放量也早已突破十亿级。
图示为一个微博视频从生产到消费经历的整条链路:
首先视频创作者从手机、PC等设备上传视频或是开启直播,视频处理服务接收请求后进行视频的处理。处理完成且审核通过后,视频发布,最后用户能通过播放服务看到视频。
下面放大视频处理服务部分(黄色部分)。
视频处理系统包含四个子模块:视频转码,视频内容理解,直播录制和转码实验室
1、视频转码:对于用户上传的视频,我们需要将视频转码成不同的清晰度(1080p、720p等),以满足不同场景下用户的播放需求。对于用户来说,上传后视频越快发出,体验越好,所以视频转码有速度要求。另外视频微博的上行有热点特性,需要有较好的峰值应对能力。
2、视频内容理解:负责提取视频中的内容信息,转换成结构化数据,助力于业务发展。内容理解具有资源多样,算法多样,服务多样的特性。
3、直播录制:从源站拉取直播流,转码后的视频存储到文件服务,以供用户点播观看。
4、转码实验室:顾名思义,是各种转码算法、图像算法、内容理解算法的试验田。转码实验室是离线处理任务,对实时性要求不高,可能白天需要验证的算法,晚上跑,第二天能得出结果就可以。
综上所述,微博视频处理系统具有大流量、实时性、核心服务、峰值明显、在线/离线同时存在、资源多样的特点。
二、原视频处理系统
对于视频转码来说,保证转码的效率是至关重要的。
这是一个整文件转码和分片转码的对比示意图,整文件转码分为下载、转码,上传。分片转码将下载好的视频分片后并行处理,从而提高转码速度,可以看到分片转码速度远优于整文件转码速度,特别是转码大视频文件时,这也是业内通用的解决方案。
但是分片转码也带来了新的挑战:
1、流程编排复杂:如果采用传统的编码方式进行分布式分片并行转码是比较困难的,另外还要考虑失败超时重试等情况,整个流程会非常复杂。
2、高并发,低延迟:在微博的场景下,结合前面讲到的微博视频的特点,我们还有高并发低延迟的挑战。
有的同学会疑惑,视频上传这种上行接口大概是几百qps,怎么会达到高并发?这里解释一下,假如微博的上传qps是100,一个视频分成100个分片,需要转出10个label的清晰度,那么系统内部需要承载的qps将被放大到10w。
为了解决这两个问题,我们开发出了流程编排引擎和高性能的调度器。
首先是DAG有向无环图的框架和Olympiadane。我们采用与Java亲和度比较高的Grovvy定义DAG流程。
这是DAG编排后的分片转码流程图,每一个task(绿色节点)是一个单独的实现类,以此解耦流程和任务,DAG还提供重试重做,可视化的功能。
有了流程的编排,还需要将任务下发到机器上执行,于是我们开发了TaskScheduler任务调度器。
任务调度器采用任务优先级队列和机器优先级队列相结合的方式,将调度性能优化到一次redis命令执行,支撑转码10w级并发,毫秒级调度的需求。
此外,TaskScheduler还具有双发调度,任务组发、水平伸缩、宕机自动摘除,忙时堆积等特性。
这是任务调度执行的全景图,有了DAG和TaskScheduler,视频处理任务由DAG描述依赖关系,通过调度器调度到Worker上执行。
以上是原视频处理系统的一些关键设计。
那么,随着业务的发展,我们遇到了哪些挑战,系统又随之显露了哪些短板呢?
问题一:资源整体利用率不高
算力紧张,但是资源整体利用率不高:业务高速发展同时伴随着越来越多新算法的不断落地,对资源的需求也越来越多,不断舔砖加瓦的同时,我们也在审视当前的服务资源使用情况。
右图是某一时刻服务资源利用率的横截面图,由于不同的服务模块具有资源差异(计算密集型业务/内存密集型系统),和运行时段的差异(业务流量高峰期/凌晨流量低谷),虽然我们尽量做到局部最优,但还是很难达到全局最优。
这是线上某台转码机器的CPU利用率的截图,可以看到,机器CPU利用率存在明显的波峰波谷。高峰期,机器利用率可高达95%,但是低谷期,利用率还不到10%
在评估服务资源的时候,为了保证系统的可靠性,往往需要根据服务的峰值进行评估,另外还要预留一定的冗余度。
虽然我们已经做了基于公有云的晚高峰弹性扩缩容,比如每天晚上8点开始扩容,到凌晨流量下降时再缩容。但整体看来,服务还存在着巨大的冗余与浪费
图中的金币部分就是浪费的的资源。
问题二:开发运维效率不高**
需求紧急,但是开发运维效率不高。
这是一个真实的案例,产品组大半夜发来消息:“算法Demo已经跑通了!似乎可以上线了!明天能灰度了吗?”。
但后端开发人员遇到的问题远不止于此:
1.算法代码如何转成工程代码,有没有坑?
2.代码写在哪个工程里?会不会有冲突?后期怎么维护?
3.灰度功能在哪儿加?
4.如何保证系统的可用性?
5.流量热点怎么应对?
6.扩缩容怎么做?
这些问题都需要考虑。
在当前服务模式下,从算法到上线,再到有数据,需要经历哪些步骤呢?
首先,需要将算法翻译成工程代码,因为负责算法的同学关注算法本身的正确性多于工程方面,所以他们提供的算法可能并不能直接应用于工程。然后进行适配开发,加入到现有的工程中,假设转码后有几万行代码,这就需要考虑AB测试,兼容性,后期维护,高可用等问题。通过测试后,即可打包为线上镜像并上线,上线完成后,还需要考虑服务保障、扩缩容等,最后才能得到效果数据
总结可得在开发效率方面遇到的三个问题:
1、开发纵深太深,新手难以上手;
2、开发流程复杂;
3、运维难度大。
如果无法妥善处理这三个问题,结果就会如左图所示只有工程开发在默默挖坑。
三、FaaS平台的探索与实践
现有的服务模式似乎难以解决资源利用率低及开发运维效率低的问题,于是我们开启了对FaaS平台的探索与实践。
FaaS的全称是Function as a service,是为用户提供开发、运行和管理的函数服务平台
以承载”函数”的方式来启动基础应用框架,并对应用实现资源编排、自动伸缩、全生命周期覆盖等保障,从而实现云服务的Serverless。
由IaaS发展到现在的FaaS(左图),开发者需要关注的内容越来越少,可扩容单元越来越小。
介绍了FaaS后,我们将遇到的痛点和FaaS平台可以解决的问题进行以下对比。
痛点一:开发纵深太长;FAAS平台可以消除技术壁垒,理论上只需要了解函数的输入输出就可以,开发人员没有其他心智负担;
痛点二:开发流程复杂;FAAS可以提供部署、发布、监控“一条龙”的服务,并提供稳定性保障;
痛点三:运维难度大,体现在服务保障,宕机处理等方面;FAAS平台免运维,全托管;
痛点四:资源整体利用率不高;通过平台化的思想,整合资源,共享冗余,实现按需调度,从而提高整体资源利用率。
对于FaaS平台的选型,我们有三个选择:云厂商、开源FaaS框架和自建。
1、云厂商:由于只能覆盖公有云的场景,微博场景下暂时不考虑;
2、开源FaaS框架:根据对常用FaaS框架进行调研的结构显示,在场景、语言以及适配上有一定的局限性,现有系统迁移起来难度比较大,而且有些框架还不够成熟。
于是我们选择了自建FaaS平台,通过复用现在的基础设施和框架,打造适配微博处理系统的FaaS平台。
以下是基于原框架基础构建FaaS平台的步骤:
第一步,将DagTask抽象为function,使其能够独立部署运维,上文提到DagTask是单独的实现类,与原项目代码有耦合。假设内容理解工程中有几十个task,会使工程代码量膨胀,增加开发人员的心智负担。而在Function的设计中,开发人员只需关注输入输出,开发更简单高效;
第二步,原DagTask会依赖整个服务的部署,转码时整个服务中会部署几十个task且task的升级和扩容都不能独立进行。Function的设计中完全解决了这一问题,它提供了函数级别的部署运维,可独立伸缩。
另外,原DagTask仅支持Java,Function不限制语言,目前已支持了Java、go、python,为算法人员提供便利。
第二点升级是将原DAG流程服务化。原DAG流程采用grovvy描述、SDK依赖的方式。那么在升级DAG流程时,需要上线两次,首先全量上线class文件,否则当task被调度到没有此class的机器上时,会出现“class Not Found”。
WeiboFlow采用yaml文件描述DAG,通过服务化管理DAG版本,对版本迭代更友好。
此外,由于原DAG流程采用了grovvy描述、SDK依赖的方式,与项目代码耦合。服务化之后可与项目代码彻底解耦。
完成以上两个重要改造之后,基本形成了FaaS平台雏形。
将FaaS平台分为两层,第一层是服务化的WeiboFlow函数编排层,负责函数的编排流程管理,第二层是WeiboFunction提供的云函数层,负责具体的函数调度执行。
这是FaaS平台的任务调度链路图,主要分为三部分:
1、WeiboFlow,上文提到是DAG服务化的版本,主要功能是对DAG进行版本管理及DAG的执行。
2、WeiboFunction,主要负责函数管理及部署管理,其中的重要组件Task Scheduler是复用了原有的Task Scheduler从而进行函数的调度。
3、具体执行的node节点,其中的重要组件FunctionLet负责接收任务,与WeiboFunction保持心跳,上报函数信息和机器槽数。
蓝色虚线表示任务的调用链路,应用可直接调用WeiboFlow的submit接口提交完整的转码流程或内容理解流程,也可以直接调用WeiboFunction执行某一个函数。
WeiboFlow收到调用请求后,开始执行DAG上的节点,每一个节点可对应一个Function,调用到WeiboFunction执行,Function收到请求后,使用Task Scheduler下发调度任务,将优先级最高的任务和最优的机器进行匹配后调度到具体的机器上执行,具体的node,FunctionLet收到请求后进行函数执行及回调。以上就是整个调用链路。
介绍完FaaS平台后,再回到最初的问题,它是如何解决以上两个问题的呢(资源利用率低、开发运维效率低)?
3.1 提高资源利用率
3.1.1 整合管理所有资源
FaaS平台将不同机器型号,资源类型统一抽象为槽数,比如8核16G抽象为2000个槽,16核32G抽象为4000个槽,同时将GPU和FPGA设备也抽象为相应的槽数。还可对此转换公式进行配置,统一了资源的度量衡。
通过这一层抽象,FaaS平台能够统一管理调度多类型的资源。
FaaS平台还实现了动态资源标签化,以此实现机器资源的隔离,结合上文提到的多队列调度器,即可充分利用资源。
3.1.2 动态扩缩容
FAAS平台实现了基于函数维度的动态扩缩容,左图显示了原晚高峰的扩缩容,右图显示了基于函数的动态扩缩容。
得益于函数启动的速度更快,动态扩缩容更贴合服务权限。
我们将动态扩缩容功能分为四层:决策、聚合、Function扩缩容和Node扩缩容。
决策层:在做服务冗余度决策时,可根据服务冗余量或当前队列堆积情况,动态计算服务冗余度,还可根据时间策略,通过某一时刻的流量情况进行决策,人工干预也算作一种决策。
聚合层:聚合决策层下发的不同策略后做出最终的决策——服务最终的扩缩容策略。
Function扩缩容:在服务容量足够的情况下进行动态的函数扩缩容。
Node扩缩容:当服务容量满载,有些服务没有空余槽时,会触发Node层的扩缩容。Node层由微博的DCP平台打造而来,目前DCP平台可以达到5min,2000台机器,基本满足我们的要求。
动态扩缩容能够提升高峰期的资源利用率,那么如何解决常备资源量以下的浪费呢?
方式一:分时复用,在A服务的冗余度达到某一水平时,对A服务进行自动缩容,再部署B服务,以此达到分时复用的目的。
此方法可应用于转码服务和转码实验室服务(无论在线或离线)。白天转码实验室的任务由少量机器处理,先进行堆积,到凌晨时,转码服务缩容,部署转码实验室,消耗队列中待处理的任务。
方式二:挖矿。空闲资源代表“矿”,执行完即销毁的Function代表“挖矿工具”。
执行完即销毁的Function:云函数的服务模型分为两种,常驻进程型和用完即毁型。常驻进程型的函数不会被销毁,而用完即毁型的函数在一次调用完毕后就会被销毁。我们将Gif转码任务设计为用完即毁型,因其运行周期较短,Gif任务也常作为“挖矿工具”。
如何“挖矿”呢?
这是1~5,五台服务器,Node1有4000个slots,Node2有3500个slots。假设5台服务器都部署了A服务,此时A服务只占3000个slots,那么会出现1000个slots的冗余。如何减少冗余呢?
假设要对Node1~4进行挖矿,只需要给1~4打上B的tag,此时调用器通过心跳感知这些机器,会将函数B调度到机器1~4上执行。
如果Node1空闲1000个slots,而函数B的一个请求占100个slots,那么Node1上可以并行跑10个函数B,Node2可以并行跑5个函数B,通过调用器实现“指哪挖哪,哪里有矿挖哪里,有多少挖多少”。
进一步探索“挖矿”——如何更高效率地“挖矿”呢?
执行完即销毁的function采用冷启动的方式,(如图)先start up,再run stack,如此反复,每次都需要重新start up,累积下来对机器的损耗较大。
于是我们进行了以下优化,采用热启动的方式,一次函数启动后,如果在最大空闲时间内有请求进来,就继续执行任务,不被销毁;如果超过最大空闲时间内无请求进入,那么函数运行环境会被销毁,下次任务到来时需要重新启动。通过以上步骤达到连续挖矿的目的。
优化后的效果如图,使整个服务的资源曲线更加贴合服务的流量,提高了资源利用率。
3.2 提高开发运维效率
1、FaaS平台提供了开箱即用的模板工程实现统一工程化及标准化。
一个云函数对应一个gitlab的项目,函数开发与发布都围绕单个项目进行CI/CD,高效且安全。从而使开发人员更关注业务逻辑,达到提高开发效率的目的。
2、FaaS平台提供了云函数的高可用保障。
这是任务从WeiboFunction调度到具体机器上执行的细致流程图:
在FunctionLet和Function之间利用QueQiao进行通信,FunctionLet在接收到任务的时候,写入TaskQueue,Function读取TaskQueue进行函数逻辑处理,处理完毕后将结果写入ResultQueue,FunctionLet读取ResultQueue中的结果,并返回。
这样的设计如何提供高可用保障呢?
1)假设WeiboFunction宕机或者脱网导致回调失败,此时无需重新运行所有任务,已执行完的任务会将结果写入ResultQueue中,FunctionLet回调失败后也会将回调失败的任务重新写入ResultQueue中,等待WeiboFunction恢复后,重新将执行完成的ResultQueue进行回调,已执行完毕的任务不会丢失或被再次执行。
2)假设FunctionLet上线重启或者服务异常,已执行完毕的任务也会被写入队列,等待FunctionLet恢复后重新回调。
3)假设Function挂了,FunctionLet将停止接收新的任务,FunctionLet对Function有具体函数的保护,重新启动函数,执行Queue中任务进行回调。
那么假设Node节点整个都挂了,且处于不可恢复的状态时,执行完毕的任务会丢失吗?
答案是否定的,我们在Task Scheduler层设计了派发重试及故障转移的功能。除了node上各个组件的高可用,还保证了node级别的高可用。
当任务被派发到执行机器上时出现了超时的情况,任务会从Task Scheduler的执行任务队列中取出并选择一台当前活跃而且空闲度最高的机器重新派发,以实现派发重试的功能。
当Node无法恢复时,会被移出执行器队列,后续的任务不再被派发到这台机器,以实现故障的转移。
WeiboFunction还提供了Function级别的调谐保活机制,根据申明的服务节点个数重新找到合适的节点,启动服务,以保证服务达到申明的最终状态。
3、下沉通用功能。
我们将常用的比如限流降级,AB测试等下沉到平台来实现,这样用户不用自己实现,只需要在FAAS后台通过配置的方式即可操作。
1)AB测试:可通过WeiboFlow的DAG配置来实现,如右图所示,将80%流量打到图1,20%流量打到图2,开发人员通过配置即可实现流量的分发。
2)任务动态优先级:每个任务都有对应优先级,在转码服务中,假设用户发博的任务是480P,那么此480P任务的优先级就相对较高,可以通过动态调整任务节点的优先级,并配合调度,先执行优先级高的任务。
3)任务动态权重:一个h265的任务通常需要占用更多的slots,此时可以通过任务动态权重功能达到资源的平衡利用。
4)限流降级:为每个节点配置限流降级的阈值。
5)错误处理:节点错误后可以配置重试频率及重试次数。
6)大小图、父子图:针对复杂的流程图,提供了大小图和父子图的方式,每一个节点都有可能是另外一个完整的图,可以理解为对节点进行foreach的操作,分片完毕后,如有100分片,就对分片进行foreach循环,每一个循环都有一套处理逻辑,而处理逻辑是可以复用的,这就可以通过大小图的方式解决。
7)多种语义控制:目前提供了pass/foreach/return/suspense/choice等语义,可支持复杂的流程编排。
4、Faas平台提供了多维度的可观测性。
右图1是执行完毕的转码DAG图,截自Weibo Funciton后台,展示了整个执行链路,节点出现问题时会变红,便于追踪链路,排除故障。此外。每个节点还有统计信息,执行时间、横向或纵向的Metrics。点击每个节点,会出现右图2所示的详细信息,其中包含整个执行关键日志,便于用户调看。
5、FaaS平台重建了开发流程。
开发人员在代码编写完毕后,通过本地测试构建镜像,注册或升级函数,再进行远端测试,最后发布函数,函数即可弹性执行。
通过用户维护WeiboFlow流程或者其他事件可触发执行函数。
四、总结与未来展望
微博视频处理系统在具有实时性、大流量、核心服务、峰值明显、资源多样、在线/离线等特点的背景下,为了解决高并发,低延迟及流程编排复杂的问题,我们开发出了DAG编排引擎及任务调度器。
但随着业务的发展,陆续出现了资源利用率低,研发运维效率低的问题,于是我们开启了云原生架构中的探索,开发了高弹性,全托管的FaaS平台,通过资源整合、按需调度、减小开发粒度,下沉通用能力以解决利用率和运维效率的问题。
最终达到的效果,主要分为两方面:
1、降低成本:集群负载标准差优化30%,视频转码弹性成本了降低44%,通过分时复用、挖矿的方式将算法迭代周期从40天优化至6天。
2、提高效率:支持算法的上线周期由天级或者周级优化至小时级。举一个真实的案例,在奥运会期间,我们收到了识别某logo的需求, 从中午接到需求到下午上线完成得到数据,仅用了几个小时。
未来,我们希望FaaS平台能够支持更多的场景,比如在WeiboFunction增加负载均衡层,从而支持传统微服务场景。
其次是冷启动优化,虽然目前已经支持常驻内存的方式减少冷启动带来的消耗,但在需要频繁冷启动的场景下,还会造成较大的损耗,未来我们会在冷启动方面投入更多时间精力。
最后是智能化运维,在FaaS的运维场景下加入服务画像,根据服务画像动态调整服务容量,达到更高的利用效率。
以上是本次的分享,谢谢!