字节跳动亿级视频处理系统高可用架构实践
火山引擎视频中台支撑了火山引擎视频相关的 toB 业务,同时也支持了字节跳动抖音、西瓜视频等产品的视频生产、下发、播放等全部视频周期。本文主要向大家全面介绍我们的视频处理系统以及在高可用性方面做的一些工作。
作者|张清源,火山引擎点播多媒体平台技术负责人
视频处理系统整体介绍
视频整体的生命周期大致可以分为四个阶段:
端侧生产:视频的创作者用手机或者其他设备拍摄一个视频,可以对视频做一些增强和编辑,通过上传 SDK,即可把这个视频上传到云端。
云端生产:在云端有两个比较核心的流程:视频处理和审核,这两个流程是在并行执行的。
云端下发:当以上两个流程都执行完以后,一个视频也就可以给大家看了,接下来就进入云端下发的阶段。在这个阶段,点播服务负责下发视频的播放地址(包括相关的 meta 信息),然后视频的内容是通过 CDN 下发。
视频播放:这个阶段由播放 SDK 进行端上视频的处理以及渲染。
在视频生命周期里,视频处理系统是云端生产的核心环节,这也是本文主要介绍的内容。我们先来看一下字节跳动在视频处理这方面所面临的一些挑战。
大规模:目前,字节跳动每天处理的视频量级在亿级,因为每一个视频都会产生不同档位、不同格式的视频,实际生产出的是接近十亿量级的视频。这对计算和存储都是非常大的消耗,这么大体量的业务对系统整体的稳定性和性能也有非常高的要求。
多业务:字节跳动的视频业务非常多样,包括短视频、中视频、长视频,以及点播、直播、RTC 相关的一些业务,涉及教育、游戏等不同的垂直的行业。
资源复杂:除了常规的 CPU 资源,还有很多弹性资源,以及 CPU/GPU/FPGA 等多种类型的资源,还有一些其他的硬件转码设备等。
业务高速增长,以及大型活动的峰值:到目前为止,每年处理的视频量级至少都是在翻倍地增长。每年又有很多大型的活动,给系统带来了非常巨大的考验。
视频处理系统的目标
面临以上这些挑战,视频处理系统要实现哪些目标呢?
大家可以看上图,这张图是一个更偏逻辑的关系。视频处理系统最终的目标总结起来只有三点:
满足业务需求。
提升用户体验:比如画质、流畅性等方面的体验。
降低成本。字节跳动的体量带来的计算、存储以及 CDN 成本都非常巨大,所以降低成本也是一个很重要的目标。
为了实现这些目标,就需要对视频做不同类型的处理,包括转码、编辑、分析,也包括一些图片处理,每一项都是一种视频的应用。
每一个视频的应用再往下拆解会对应非常多的处理能力,比如对于转码应用来说,会有一些新的编码器、自适应转码来降低码率;通过一些增强的方式提高画质等等。
所有这些能力在最底层都由一个基础的处理系统做支撑。这个系统需要满足高可用、高可扩展以及高效的运维开发效率的需求。
总结起来就是,整个视频处理系统以底层的系统支撑为基础,构建各种各样的视频处理的能力,形成多种视频应用,从而满足业务场景的需要,提升体验,降低成本。
视频处理系统架构
为了实现这些目标,视频处理系统的架构如上图所示,外层被划分为三个平面:
用户平面:顾名思义,就是从用户的角度,如何去调用系统。
控制平面:它面向的是开发人员、运维人员、支持人员,如何去控制这个系统,以及当系统出问题的时候,怎么样对系统做一些管理和应急处理的动作。
数据平面:系统每天会产生海量的数据。这些数据一方面可以进行分析,来指导系统的优化。另一方面也用于计量、计费、监控等。
中间的这四层分别是:
服务层:主要是处理鉴权、任务队列的管理、上层的模板管理、策略控制等等。
工作流系统:主要是为了串联异步、分布式的媒体处理流程。
Lambda:高可用的函数计算平台,它最大的作用是管理底层海量的资源,并且对资源进行高效的调度,以及任务的执行。
BMF:它是一个动态多媒体处理框架,目标是把所有多媒体处理的原子能力进行插件化管理,然后提高系统的可扩展性以及开发和运维的效率。
下面将为大家详细介绍几个核心层。
服务层和工作流系统
系统服务层介绍
服务层有几个重要的组件。
服务网关:它可以进行跨机房的流量调度以及一些接口的鉴权,包括接口层的限流。
管理服务:它有两个作用,首先是对整个视频处理系统的所有元数据进行管理,包括任务队列、模版和工作流信息等;另外是会触发底层工作流的执行,同时会去管理整个工作流的生命周期状态。
弹性队列:可以隔离业务侧的资源。它实现的功能包括:队列的资源配置(任务的 QPS,最大并发任务的数量 MRT)、队列管理以及弹性资源的管理。
媒体工作流介绍
服务层下方就是媒体工作流的引擎,工作流是以 dag 的形式组织一系列视频处理的流程。比如说在西瓜视频上传一个视频后,需要去抽取它的封面,并对视频进行无水印转码,还需要进行各种档位的转码。这些都是处理视频的流程,每一个流程都是一个细粒度的任务。把这些单个的流程组织起来就形成了一个工作流。
工作流能解决什么问题呢?
第一是它解决了复杂业务的调用流程。如果没有工作流,处理一个视频就要进行多次的调用。
第二是能够比较好管理视频处理流程的依赖关系。在实际的处理过程中,前后的流程之间是会有依赖的,比如画质增强的流程,需要先对原片作增强,再进行普通转码,或者通过分片转码的功能对视频预先切片,再对每一个切片再做转码,最后再把它们拼接在一起。这些都可以通过工作流的方式实现。
第三是提供任务超时、错误重试等高可用的能力,降低了业务使用成本。
下面再简单看一下工作流内部是怎样的结构。
工作流内部主要包含这么几个模块:
Gate:处理流量调度,包括鉴权的功能。
Engine:管理所有工作流的状态。
Scheduler:一个工作流包含很多节点,Scheduler 可以对每一个节点进行细粒度的任务调度。
VWorker:它是上层和下层的粘合层,会把上层一些偏业务属性的模板转换成一个底层,实际可以执行的函数任务的参数。
上图中间绿色的部分就是整个工作流的引擎。上层就是服务层,下层是等会要介绍的函数计算平台。下面简单介绍一下在工作流层面所做的一些高可用方面的工作。
高可用性:任务执行
视频处理系统是一个离线处理系统,每一个任务都会执行几十秒、几分钟甚至更长的时间。这时最重要是保证过来的每一个任务都能够最终被执行,而且最终保持一致。所以对系统来讲,需要有 at least once 的保证。
其次是任务幂等的要求。任务幂等有两个意思:首先是这个任务无论什么时候执行多少次,最终的结果是一样的,而且对业务方来讲是透明的。第二点是在一定的时间内,如果同一个视频做同一个处理提交了非常多次,这时就需要有去重的机制,只执行一次。对调用者来讲,这个过程也要是透明的,这在某些场景下能够比较好的提升系统的效率。
为了保证任务幂等,我们在视频 meta 信息关联以及视频的存储方面都做了比较多的工作,同时要保证 at least once,在工作流和节点层面都做了比较多的超时检测和重试的机制。
高可用性:快速响应和恢复
视频处理系统的下游涉及到计算资源和存储资源。一旦计算资源和存储资源出问题,很难有一个完美的方案对上层业务做到无感知,要做的就是尽量避免损失,尽量减少对于业务的影响。这里面有两个比较重要的措施:
多级限流:限流是常用的手段,但是视频处理的不同点是会有一个任务筛选的过程,需要保证在有限的资源里,所有重要的任务要被优先执行。举个例子,假设底层计算资源现在突然变为正常的一半,如何减少对业务的影响?首先,在工作流层面,需要把一些对于任务延时不敏感的工作流任务进行 delay,这就需要一些策略的预设置;另外,同一个工作流里面,需要对不同的节点进行优先级的配置,比如视频要转出五个档位,可能其中有两个档位是大家消费概率最高的,就需要把这两个档位优先转出来,其他的档位进行延迟处理。这整体就涉及到了多级限流以及限流策略配置的一种能力。
批量重转:它是什么意思呢?举个例子,假设昨天底层的同学上线了一个有问题的功能,但是今天才发现。这时要做的是把昨天这个功能上线这个功能以后所影响的视频全部筛选出来,快速进行重转,而且不能够影响目前正在运行的业务。这里面涉及到两个问题。第一点是:如何准确的从某任意一个时间点到另外一个时间点把这一批视频全部都挑出来。第二点是快速重传,而且不能影响线上的业务。所以这里需要有一个单独的子系统来负责整体的批量任务的查找和重转。
高可用性:系统维度
从系统维度我们也做了一些工作,包括中间件的冗余备份、对下游异常的检测等。当发现某些实例有问题,会对这些实例进行熔断和摘除。其次,系统有比较完善的流量切换方案,因为系统经过多次大型活动的考验,也具有完备的压测及预案,这些对于系统的高可用也非常关键。
函数计算平台
上面介绍了工作流的系统。下面介绍一下它的下层函数计算平台。首先介绍一下函数的概念。函数对应于媒体工作流里的一个节点,同时,它也对应于一个细粒度的视频处理的任务。更通俗一点,它对应一段可以执行的程序。
这个函数计算平台需要提供哪些能力呢?
首先,也是最重要的,是为视频处理程序提供大规模水平扩展的能力,使一个视频处理程序能够很容易大规模稳定地服务于线上业务。
其次,这个平台需要管理比较庞大的资源,这些资源是多种类型的,并且能够提供高效的资源调度能力。
最后是能够处理各种异常情况及容灾等的高可用能力。
上图是这个函数计算平台的基本架构。图中左侧部分是一个控制平面,开发者可以开发一个函数,通过管理 UI 注册到函数计算平台上。图中右边是整个函数调用的流程。这个流程首先会经过该函数计算平台的一个 gateway,到集群层面的调度,然后会到一个单集群里,单集群内部是我们自研的一个中心调度系统 Order。Order 有一个中心调度器,会把这个任务调度到一个具体的节点上去执行。这个节点就会拉取整个函数的可执行的包,然后执行这个函数。
高可用性:多集群
在多集群层面,首先,我们做了多集群的容灾,流量的一键切换;其次,我们也会根据一些预设配置进行流量的自动调节。
上图是简单的多机房示意图。图中左右两边都是一个机房,每一个机房里有多个集群。每个机房里会有一个集群级别的调度器模块,多机房之间又会有一个模块,这个模块负责同步各个机房的资源情况,包括资源的总量和使用情况等。
高可用性:单集群
我们的单集群是一个中心调度系统,有一个中心调度器,称为 Server;会有一个执行单元,称为 Client。Server 是多实例、无状态的,能够进行平滑、动态地升级。
在 Server、 Client 之间会有状态检测,以及问题节点的熔断和任务重试等机制。一般情况下,Server 会通过心跳判断一个节点是否活着。除了这一点,Server 也会观测节点整体的状态。比如一个任务是否超时比较多,或者任务的失败率比较高等,当发生这种情况的时候,也会对节点执行熔断策略。
控制面——服务治理
前面介绍过函数计算平台分了几层,从上层的网关层,到集群的调度,到机器内部的调度。每一层都是一个多实例的服务。所以,每个上游都会对下游有一些异常检测+摘除,也就是所有的组件都会有单点的异常处理。此外还有一些中间件熔断的策略。
动态多媒体框架 BMF
BMF 全称是 ByteDance Media Framework。它是字节跳动自研的多媒体处理框架。之所以会去自研一个视频处理框架,是因为我们发现传统的一些视频处理框架会有一些限制。
首先,传统框架一般使用 C/C++ 开发和扩展,扩展门槛比较高,并且扩展以后需要重新编译,在一个大型系统里面这是一件很麻烦的事情。如果这个框架有很多人在开发和维护,那么它的依赖越来越多,最后会大大降低开发和运维的效率。
第二点,传统的视频处理,比如视频转码,它的流程比较固定,一般处理框架都可以支持。但是对于一些更加复杂的场景,比如视频编辑,或者 AI 分析,传统框架本身在灵活性上会有一些限制。
第三个就是传统的框架本身性能上也会有一些瓶颈。以 ffmpeg 为例,filter graph 是单线程执行的。如果在 filter graph 里面放一个 GPU 的 filter,它的执行效率就会下降得很厉害,而且 GPU 的占用率也不会很高。
为了解决上面这些问题,我们自研了 BMF 多媒体处理框架。它的目标包括:
减少视频应用开发的成本,使应用开发标准化。
通过一套框架支持各种复杂的应用场景,从框架本身来讲,它具有比较高的灵活性。
通过这个框架把所有视频处理的原子能力模块化,并且做到动态管理和复用,以此解决大规模协同开发的问题;同时,也能使这些能力比较好地复用在不同的场景和业务上。
屏蔽底层的硬件差异。业务现在会越来越多地用到不同的异构硬件,比如说 GPU,我们希望这个框架能够 原生支持这些硬件。
上图是 BMF 框架的整体架构。
最上层是应用层。应用层上的每一块是一个视频应用,比如前面提到的的视频转码、视频编辑、图片处理等等。下层是模块层,每一个模块是视频处理的一个细粒度的原子能力。例如对视频进行编解码,或者是进行 ROI 检测等等。
应用层和模块层是通过中间的框架层串联起来的。这个框架层的中心是一个核心的引擎。这个引擎对上提供一套比较通用、简洁的流式接口,使开发者能够比较容易地搭建出视频处理的应用。该接口支持多语言,包括 C++、Python、Go。对下,它会提供一套比较完善的模块开发的 SDK,也支持 C++、Python、Go 这几种语言。围绕着核心引擎,我们又做了一些相关的服务和工具集,这个服务主要用来管理模块的版本、依赖等。
这样的架构有一个最大的好处,就是把开发者做了一个比较好的划分。不同模块的开发者可以只专注于自己模块的开发,选用自己熟悉的语言。模块开发完以后,整个模块就可以注册到系统里去。上层的应用开发是支持业务的,业务可以不用了解底层的模块是怎么实现的,也不用知道这个模块是什么语言实现的,只要借助这个框架提供的接口,就可以对这个模块进行无缝的串联和使用。
上图进一步介绍了 BMF 的动态开发模式。举个例子,在实际情况当中,算法开发人员研发了视频处理的算法。首先这个算法会送到算法优化同学那里做优化。优化完以后,整个算法就会形成一个模型。接下来算法优化的同学会把这个模型注册到系统上,模块的开发同学会把这个模型封装成具体的模块,也是注册到系统里面去,这个模块就是一个具体的原子的能力。再接下去,函数开发者,也就是业务开发同学,就可以把模块串联成一个具体的视频处理应用,做成一个函数注册到函数管理平台,然后灰度上线。
大家可以看到整个流程里面的各个团队分工都非常明确,独立地开发协作效率很高。而且这个流程里面所有模块的原子能力都是可以复用的。流程也不会牵扯到任何编译的依赖,全部都是动态进行的。
总结和展望
上图是视频转码的完整流程示例。当用户上传一个视频以后,这个视频首先会进入服务端的存储,这时会触发一个转码的流程,也就是提交一个工作流任务,这个任务首先会经过转码的服务,然后被放到弹性队列里去;下一步,任务从弹性队列出队,进入到工作流引擎里面去执行;工作流引擎会把工作流拆解为一个个细粒度的任务,然后送到函数计算平台去执行,每一个函数,会采用前面介绍的 BMF 动态开发的方式去构建。最终,当所有细粒度节点的任务都执行完,整个工作流也完成以后,转码或者视频处理的流程就完成,再一步步往上返回。
最后,再回顾一下本文的一些要点:
首先,视频处理系统,最重要的几点要求包括:高可用,也就是系统的稳定性;高可扩展性,当支持非常多的业务场景的时候,系统的可扩展性对整体高可用也会有很大的影响;开发以及运维的效率。
整体的架构,总结起来就是媒体工作流加上函数计算平台,加上动态的多媒体框架 BMF 这三个核心的部分。高可用性方面,在服务层会有任务幂等、多级限流以及批量重传。在平台层会有多机房、多集群的切流策略,单集群内部的冗余、上下游的异常检测等。最后,底层的动态多媒体框架,它虽然没有直接提升系统的高可用性,但是它提升了系统的可扩展性,以及开发和运维的效率,所以它对于系统也起到了非常重要的作用。
未来,系统会往更智能化的方向去发展。我们希望能构建一种分布式调度的执行平台,用户只需要关注处理流程,流程的拆分、资源调度,如何执行都由平台来决定。
评论