短视频加速利器-LocalCDN SDK

1. 背景

当前短视频已经成为最广泛、最普及的内容载体,短视频的观看体验无疑是至关重要的。当前普遍都采用边下边播的模式,使得用户不用等完全下载完视频,就可以立刻享受流畅的播放体验,但是对于某些特定场景,缓存的策略还是有其作用的。 一个场景是使用移动网络时,在反复观看同一个视频内容时,已经下载过的内容如果没有缓存,就会重新从服务器请求一遍,造成一定的网络流量资费开销。 如果把看过的内容(哪怕只是部分数据)在本地缓存下来,就可以节省这部分的流量;同时,从本地缓存里获取数据,也能让播放快速且流畅,当请求的数据缓存里没有的时候,再继续从服务端请求。 另外一个场景,一些移动 APP 采用划屏的切换视频模式,其实是可以准确的预测到下一个要播放的视频内容的。 在当前播放的视频缓冲足够的时候,可以预先下载一部分下一个要播放的视频内容,这样可以在切换到下个播放视频的时候立即就有数据可用,直接秒开,这种功能叫做预缓存。360 视频云提供一个与播放器一起使用的组件LocalCDN SDK,可以提供缓存和预缓存功能,既能节省较大的流量,也能提升打开新视频的首屏速度、优化播放流畅度。

2. 关键技术

1) 如何接入

为了对应用程序有最小的代码侵入性,我们采用一种本地Http服务的方式。在播放器看来,还是访问的一个Http的链接,播放器并不感知这个链接是本地的还是真实远程的服务端。LocalCDN接收到请求后,如果本地缓存没有命中,那么就从远程的CDN节点下载数据,一方面数据吐给播放器,另一方面也会同时把数据存储到本地的Cache里,供后续的访问。

除了被动式的缓存外,LocalCDN还要做一些预缓存的任务(应用层主动添加的),应用层如果预先判断了接下来要播放的视频,那么就可以添加一个预缓存任务,提前加载文件头部一定量的数据(比如800KB、1MB)。从严格意义上来说,LocalCDN不是一个代理服务的逻辑,更像一个具有业务逻辑的网关服务。

LocalGateway

2)如何管理本地的缓存

LocalCDN缓存的数据是要落到本地存储的。但是需要缓存的数据,并不是完整的文件,用户观看一个视频可能只观看了一部分就退出了,或者观看过程中用户进行了seek操作,都会导致没有完整下载完整个文件。即使最终各个部分数据都下载回来了,但也不一定是按顺序下载回来的。用户连续观看的一段对应的是文件字节偏移量以及数据长度,那么最终下载时请求的就是一个一个的Range范围。HTTP1.1是支持Range请求的,这也是早些年多线程下载软件的一个基础。

LocalCDN
分段文件存储

对于音视频文件,通常是一个一个独立的文件,但由于用户有seek操作,会导致访问数据的跳跃。当前都是采用“边下边播”的模式,跳跃的播放会导致媒体文件在存储上存在一些“空洞”。对于Windows的文件系统提供了一种类似稀疏文件的机制,可以直接留空洞的写入后面的数据。对于Linux和移动端的设备,跳跃超越文件大小的范围写文件时,底层还是进行了数据填充,从而会消耗一定的时间,如果跳跃的幅度较大,就需要一路“填充”过去。这个势必会影响一些用户体验。LocalCDN的做法是用分片文件来管理,每个分片文件内是连续的,如果发生了seek等操作导致写数据不连续,就重新生成一个新的片段文件。当然,如果频繁的拖动,就会导致一个文件被分成了很多很多的分片文件。在实际情况下,用户seek操作毕竟是低频操作,在短视频场景seek就更少了,所以我们采用这种分段存储的方式是可行的。

文件系统辅助存储元信息

既然每个文件最终都是一些连续的Range片段组成,那么我们必须要记录这些信息,通常包括片段对应在原始文件里的偏移量和当前片段的数据长度。一个很自然的想法就是单独记录到本地数据库(如sqlite),但这种做法会遇到一个问题:不能保证文件数据和元信息的一致性。如果先写数据,再把Range信息更新到数据库,由于写文件有缓存,在掉电的情况下,并不能保证数据会落盘,就可能出现错数据。如果每次都flush一下写入的数据,性能也会比较差。如果先写记录元信息到数据库,再写数据,那么问题就更大了,出现应用程序的crash后,数据可能根本就没写入。这里既有一致性的问题,又有刷盘开销的问题。

还有另外一个思路,可以借助文件系统的meta信息存储来记录偏移量和长度信息。起始的偏移量在创建片段时就已经决定了,可以直接记录到文件名里。而实际数据的长度信息,则可以通过文件系统里记录的片段文件长度来记录。这里有一个前提,那就是需要保证对片段文件的写入永远都是顺序追加写入,当发生crash或掉电的情况,文件系统记录的长度范围内的数据肯定是落盘了的。最差的结果就是丢了一些数据,但是永远不会导致数据的错误,没有错数据也是缓存的最基本要求。

缓存淘汰策略

只要是缓存,就会有淘汰更新的问题。因为缓存的存储空间是有限的,必须有一个清理缓存的机制。LocalCDN采用了最简单的最久未使用的方式来淘汰缓存的分片文件。LocalCDN模块在每次启动时,就会加载所有的缓存文件信息,包括文件的最后访问时间。运行的过程中也会一直保持对访问信息的追踪,当缓存的空间不够时,就需要触发淘汰清理策略。

3)数据加载策略

缓冲数据量管理

传统的播放器的缓冲逻辑是当缓冲数据到达一定的阈值后,就停止读取数据,用户退出出观看了也不会浪费太多的下载数据。但加了一级LocalCDN模块后,播放器并不知到LocalCDN模块缓冲了多少数据。而且LocalCDN模块自身也需要控制预先加载数据量的大小,防止不再观看造成的数据浪费。LocalCDN也采用和播放器缓冲类似的策略,如下图所示,我们可以实时检查正在播放视频对应下载任务已缓冲数据量,当这部分数据量超过一定的门限时,我们就停止下载数据。当播放器消耗一定的数据量后,待播放的缓冲数据量就会减少,那么就可以继续从远处的CDN服务去加载数据。这里需要有一个策略来避免频繁的开启和停止远程数据加载,这个策略就是双门限触发策略。简单来说就是,缓冲数据量小于门限1的时候,开始加载新数据,而当缓冲数据量大于门限2的时候(门限2 > 门限1),停止加载数据,这样就避免了只有一个门限时频繁的切换状态。

PlayBuffer

当然,多了一级LocalCDN模块后,在本地预先加载的缓存数据量就由两个部分组成,播放器的缓冲与LocalCDN的缓冲,相对原来的播放器单独播放还是多了一些缓存数据量。如果播放器的接口开放了缓冲数据量的设置,那么可以把播放器的缓冲数据量门限值调小来做一定的反向补偿。

当前播放任务优先

对于常规的CDN来说,肯定是可以同时缓存多个文件的,而且对具体某个文件是怎么使用的并不感知,且服务端的带宽通常来说不是瓶颈。不过在LocalCDN这个场景下,用户的接入网络的带宽通常是瓶颈,如果多个文件同时下载,肯定会有相互抢带宽的情形。对于下载类的场合,这个关系并不大,如果针对的是音视频播放的场景,那么对于一路视频来说,至少要保证视频码率的下载带宽才可以流畅的播放。考虑到通常人们同一时刻只会播放一个视频,我们可以做一个简单的策略。如果正在播放的任务没有缓存足够的数据量(没有触发门限2),那么其他的预缓存任务就先都暂停。如果缓存了足够的数据量,那么其他的预缓存任务就可以正常的运行。

mp4尾部索引的问题

我们常见的短视频大多是mp4的文件格式,如果没有做过索引位置的优化,那么通常索引数据会是在文件尾部,这样就会导致多次连接会话(如下图所示需要3次连接)。我们做了一个自动探测的机制,当发现是mp4文件并且索引在文件尾部的情况下,会自动分析索引数据的位置,预先去获取尾部索引数据,这样等到播放器来取索引数据时就已经缓存住了。

Mp4Index

4)复杂场景下的LocalCDN

PCDN

当前市面上有越来越多的PCDN服务提供厂商,PCDN是通过P2P/P2SP技术,充分利用Peer/云边缘/用户侧边缘节点的上行带宽来节省成本的一种技术和服务。同样出于降低对业务代码侵入性的考虑,PCDN也是通过一个本地Http服务的方式来和播放器配合使用。如果PCDN与LocalCDN混合使用,那么就会出现串联的现象。这里需要保证一个串联的顺序,就是LocalCDN永远在最靠近播放器的位置上。

M3U8、DASH的支持

对于单文件方式的媒体文件,LocalCDN可以工作得很好。但还有一些是复合类型的,比如HLS与DASH,先会有一个文件列表,然后才是具体的片段文件。像HLS这种,里面的TS段可以是相对地址,也可以是绝对地址,LocalCDN要进行相应的变换处理,DASH也是类似的。目前LocalCDN并没有提供对M3U8和DASH的缓存支持(不过可以直接透传,并不缓存)。

QUIC

越来越多的应用开始使用QUIC,在使用LocalCDN的情况下,LocalCDN对播放器一侧由于是本地的非TLS的链接,所以是否走QUIC协议影响不大,可以保持非TLS的HTTP,而LocalCDN从服务器获取数据时,是可以走QUIC协议的,从而保留了使用QUIC获得的收益。

5)其他异常场景

上网先需要授权

对于一些受限制的网络,在没有登录授权时,你访问一个网络资源会接受到一个登录认证的网络,这种情况下LocalCDN是不应该把把数据缓存并吐给播放器的。所以LocalCDN对资源的Content-Type做了一个判断,对于类似text/html这样的非音视频的内容不做缓存,只做透明转发。

请求Range超出文件范围

某些ffmpeg版本在处理视频文件时,会读到超出文件范围的位置,导致触发416(Requested Range Not Satisfiable)的返回码。LocalCDN检测到播放器请求到范围超出文件大小,就直接给出错误的响应即可。

文件更新后,信息不匹配

一般情况下,服务端存储的文件尽量不要同名覆盖,因为同名覆盖对CDN并不友好,文件大小和数据内容发生变化,如果CDN处理得不够细致健全就会导致下载到旧的或错误的数据(一部分是旧文件的数据,一部分是新文件的数据)。LocalCDN可以通过文件大小以及Http头里的ETag信息判断文件是否有变化。当检测到文件已经发生变化后,就立即清理掉本地的缓存和meta信息,重新下载新的数据。

多实例与端口占用问题

由于LocalCDN是一个通用的SDK,可能有多个应用都集成了LocalCDN SDK,那么LocalCDN本地监听的端口就可能发生冲突。解决的办法是每次应用层来换链接时,都会使用自己真正监听的端口,并不是固定的写死特定的端口。另外,每一个应用实例都需要传递自己的缓存目录,这样保证不同应用之间的隔离性。

3. 总结

在当今的时代,视频已经成为最重要的信息载体,视频技术平台则成为了底层的基础设施。本文介绍了360视频云针对短视频场景的一款加速模块LocalCDN模块,分析了其中的关键技术与策略,也对一些异常场景进行了讨论。期望360视频云的LocalCDN SDK这一款短视频加速利器能帮助更多的客户改善视频体验,发挥更大的价值。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇