2019 年 3 月 23 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙·北京站,京东云技术专家罗玉杰在活动上作了《 OpenResty 在直播场景中的应用 》的分享。html
OpenResty x Open Talk 全国巡回沙龙是由 OpenResty 社区、又拍云发起,邀请业内资深的 OpenResty 技术专家,分享 OpenResty 实战经验,增进 OpenResty 使用者的交流与学习,推进 OpenResty 开源项目的发展。活动已经在深圳、北京两地举办,将来将陆续在武汉、上海、杭州、成都等城市巡回举办。算法
罗玉杰,京东云技术专家,10 余年 CDN、流媒体行业从业经验,热衷于开源软件的开发与研究,对 OpenResty、Nginx 模块开发有较深刻的研究,熟悉 CDN 架构和主流流媒体协议。后端
如下是分享全文:缓存
你们下午好,我是来自京东云的罗玉杰,今天给你们分享的主题是 《OpenResty 在直播场景中的应用》。服务器
项目需求网络
京东云前期的服务是基于 Nginx 二次开发的,以后由于要对接上云的需求,因而新作了两个服务,一个是对接云存储的上传服务,另外一个是偏业务层的直播时移回看服务。项目的需求是作视频数据上云,主要是视频的相关数据对接云存储,需求的开发周期很紧,基本上是以周为单位。架构
咱们以前的服务用 C 、C++ 开发,但 C 和 C++ 的开发周期很长。咱们发现这个项目基于 OpenResty 开发是很是适合的,能够极大地缩短开发周期,同时提升运行效率,而且 OpenResty 对运维很是友好,能提供不少的配置项,让运维根据线上动态修改一些配置,甚至运维均可以看懂代码的主流程。负载均衡
项目体系结构运维
上图是一个直播服务的主流体系结构,先是主播基于 RTMP 协议推到 CDN 边缘,接着到视频源站接入层,而后把 RTMP 流推送到切片上传服务器,上面有两个服务:一个是切片服务,把流式的视频流进行切片存储到本地,生成 TS 视频文件和 M3U8 文本文件,每造成一个小切片都会通知上传服务,后者将这些 TS 文件和 M3U8 文件基于 AWS S3 协议上传到云存储服务,咱们的云存储兼容 AWS S3 协议。在此基础上,咱们用 OpenResty 作了一个直播时移回看服务,用户基于 HLS 协议看视频,请求参数里带上时间段信息,好比几天以前或者几个小时以前的信息,此服务从云存储上下载 M3U8 信息进行裁剪,再返回给用户,用户就能够看到视频了。HLS 协议的应用面、支持面很广,各大厂商、终端支持得都很是好,并且对 HTTP 和 CDN 原有的技术栈、体系很是友好,能够充分地利用原来的一些积累。有的播放是基于 RTMP,HDL(HTTP + FLV)协议的,须要播放器的支持。异步
项目功能
一、基于 s3 PUT 协议将 TS 文件上传至云存储。
二、S3 multi 分片上传大文件,支持断点续传。这个服务重度依赖于 Redis,用 Redis 实现任务队列、存储任务元数据、点播 M3U8。
三、基于 Redis 实现任务队列的同时作了 Nginx Worker 的负载调度。在此基础上作了对于后端服务的保护,链接和请求量控制,防止被短期内特别大的突发流量把后端的云服务直接打垮。实现任务队列以后,对后端的连接数是固定的,并且请求处理看的是后端服务的能力,简单地说,它处理得多快就请求得多快。
四、为了保证云和服务的高可靠性,咱们作了失败重试和异常处理、下降策略。其中,任务失败是不可避免的,如今也遇到了大量的任务失败,包括连接失败、后端服务异常等,须要把失败的任务进行重试,降级。把它在失败队列里面,进行一些指数退避。还有一些降级策略,我这个服务依赖于后面的 Redis 服务,和后端的云存储服务,若是它们失败以后,咱们须要作一些功能的降级,保证咱们的服务高可用。在后端 Redis 服务恢复的时候再把数据同步过去,保证数据不会丢失。
五、还有就是生成直播、点播 M38,为后续的服务提供一些基础数据。如直播时移回看服务。
AWS S3 协议
AWS S3 比较复杂的就是鉴权,主要用它的两个协议,一个是 PUT,一个是 MULTI PART。
AWS S3 的鉴权和 Nginx 中的 Secure Link 模块比较类似,将请求相关信息用私钥作一个散列,这个散列的内容会放到 HTTP 头 authorization 里面,服务端收到请求后,会有一样的方式和一样的私钥来计算这个内容,计算出的内容是相同的就会经过,不相同的话会认为是一个非法请求。
它主要分三步骤,第一步是建立任务,建立任务以后会返回一个 ID 当作任务的 Session ID,用 POST 和 REST 规范实现的协议。初始化任务以后,能够传各类分片了,而后仍是用 PUT 传小片,加上 Session ID,每一片都是这样。
上传任务成功以后,会发一个 Complete 消息,而后文件就认为是成功了,成功以后就会合并成一个新的文件,对外生成一个可用的大文件。
HLS 协议
HLS 协议,全称是 HTTP LIVE STREAMING 协议,是由苹果推出的,可读性很强。里面的每个片都是一个 HTTP 请求,整个文本协议就是一个索引。
上图是每个视频段的时长,这个是 8 秒是视频的最大长度。直播的应用中会有一个 Sequence 从零开始递增的,若是有一个新片,就会把旧片去掉,把新的加上去,并增长 Sequence。
任务队列、均衡、流控
下面再介绍一下具体的功能实现,任务收到请求以后不是直接处理,而是异步处理的。先把请求分发到各个 Worker 的私有队列,分发算法是用的 crc32,由于 crc32 足够快、足够轻量,基于一个 key 视频流会有域名、App、stream,再加上 TS 的文件名称。这样分发能够很好地作一次负载均衡。基于这个任务队列,能够处理大量的突发请求,若是忽然有了数倍的请求,能够把这些消息发到 Redis 里,由 Redis 存储这些请求。每一个 Worker 会同步进行处理,把 TS 片上传,上传完以后再生成 M3U8 文件。咱们如今对后端固定了链接数,一个 woker 一个连接,由于存储集群的链接数量是有限的,如今采起一个简单策略,后端能处理请求多快,就发送多快,处理完以后能够立刻发送下一个。因任务队列是同步处理,是同步非阻塞的,不会发送超事后端的处理能力。
咱们将来准备进行优化的方向就是把任务队列分红多个优先级,高优先级的先处理,低优先级的降级处理。好比咱们线上遇到的一些视频流,它不太正常会大量的切小,好比正常视频 10 秒一片,而它 10 毫秒就一片,这样咱们会把它的优先级下降,防止异常任务致使正常任务不能合理地处理。之后就是要实现能够动态调解连接数、请求速率和流量。若是后端的处理能力很强,能够动态增加一些连接数和请求速率,一旦遇到瓶颈后能够动态收缩。
任务分发比较简单,主要就是上面的三行代码,每个 Worker 拿到一个任务后,把任务分发给相应的 Worker ,它的算法是拿到总 Worker 数而后基于 crc32 和 key ,获得正确的 Worker ID,把它加到任务队列里。这样的作法好处是每一个任务分发是非单点的,每个 Worker 都在作分发,把请求的任务发到任务队列里,请求的元信息放入 Redis 里面,还有一个就是任务拉取消费的协程,拉取任务并执行。
失败重试、降级、高可靠
若是数据量大会有不少失败的任务,失败任务须要放入失败队列,进行指数退避重试。重试成功后再进行后续处理,好比添加进点播 m3u八、分片 complete。分片 complete 是若是原来有 100 个任务会同时执行,可是如今有 3 个失败了,咱们能够判断一下它是否是最后一个,若是是最后一个的分片就要调一下 complete,而后完成这个分片,完成整个事务。
同时咱们作了一个 Redis 失败时的方案,Redis 失败后须要把 Redis 的数据降级存到本地,一部分存到 share dict,另外一部分用 LRU cache,TS 对应 m3u8 的索引信息会用 share dict 作缓存。LRU 主要是存一些 m3u8 的 key,存储哪些信息和流作了降级,Redis 恢复后会把这些信息同步到 Redis。由于存在于各个 Worker 里面数据量会比较大,有些任务会重复执行,咱们下一步工做就想基于 share dict,加一个按照指定值来排序的功能,这样就能够优先处理最近的任务,将历史任务推后处理。
咱们还有一些 M3U8 的列表数据存储在 Redis,由于线上的初版本是单实例的,存储空间比较有限,可是如今对接的流量愈来愈多,单实例内存空间不足,因而咱们作了支持 Redis 集群的工做,实现 Reids 高可用,突破内存限制。
还有一个比较兜底的策略:按期磁盘巡检,从新处理失败任务。事务多是在任何的时点失败的,可是只要咱们可以重作整个任务,业务流程就是完整的。
遇到的问题和优化方案
初版的时候是全局的单一任务队列,基于 resty lock 的锁取保护这个队列,每个 woker 争用锁,获取任务,锁冲突比较严重,CPU 消耗也高,由于那个锁是轮询锁,优化后咱们去掉了一个锁实现了无锁,每个 Worker一个任务队列, 每一个 Worker 基于 CRC_32 分发任务。
旧版一个 TS 更新一次 M3U8,一次生成一个哈希表,数量较多的状况下 CPU 开销比较大。咱们进行了优化,作了一些定时触发的机制,进行按期更新,由于点播 M3U8 对时间是不敏感的,能够按期地更新,减小开销。固然直播的 仍是实时生产的,由于要保证直播的实时性。
直播方面若是异常切片太多,用户也不能很好观看,会进行主动丢片,主要是基于 Redis 锁去实现;对于 Redis 内存消耗高的问题咱们搭建了 Redis 集群。
直播时移回看服务
咱们开发了一个直播时移回看服务,根据用户请求的时间去后台下载相应的 M3U8 的数据进行裁剪拼接返回给用户。这一块的 M3U8 信息不是很大,很是适合用 MLCACHE 保存,它是一个开源的两级缓存,Worker 一级的和共享内存一级,由于共享内存缓存有锁冲突,MLCACHE 会把一些热点数据缓存到 Worker 级别,这样是无锁的,使用后效果很是好,虽然文件不大,可是运行时间建连,网络IO耗时很大,通过缓存以后能够大大提升处理效率,节省时间。时移的时候每个用户会也一个 Session 记录上次返回的 M3U8 位置,由于直播流会有中断,不是 24 小时都有流的,用户遇到了一个断洞,能够跳过看后面的视频,时移不须要等待,而且用户网络短暂异常时不会跳片。
点击观看演讲视频和 PPT~