聊一聊分片存储的话题

2015年12月8日,nginx发布1.9.8版本,如changelog所说:

Changes with nginx 1.9.8 08 Dec 2015 
… 
*) Feature: the ngx_http_slice_module. 

里面赫然出现了一个特性,ngx_http_slice_module。这个模块的出现,正如阿里卫越所说,”这俨然是一个杀手级的特性“。它解决了一个问题呢?处理过http range请求的同学可能了解,在当前的cache proxy里,有个很棘手的问题,就是range的缓存和回源问题。

背景

对大多数的cache proxy来说,基本都是处理基于完整文件的存储,也就是说hit和miss的对象是针对一个完整的文件。而range请求是为了获取文件的部分片而非全部内容。这样的局面下,cache本地若没有相应的完整文件,range请求就会产生回源。在CDN领域有个经典的场景,比如有一部热播的剧集更新,一上线,会有大量的用户去点播观看。现代的浏览器(或者播放器)一般都通过分片取的方式来拉取文件,再加上用户在播放过程中的各种随机拖动,对cache proxy来说,就会带来range请求大爆发。由于对于缓存来说,是被动获取文件的,有人请求完整文件才会回源取。但是在大量range片的请求下,完整文件没有机会缓存下来,从而引起了大量的回源流量,源站很快就game over了。

CDN为了应对这种局面一般都会提供预加载技术,也叫预缓存。主要的做法就是在原站文件在线上之前,先对CDN开放,让CDN提前将文件缓存到边缘节点,或者是客户先把文件上传到CDN指定的服务器上。但是这并不能解决全部问题,有很多客户没有那么多时间来上传,然后让CDN在内部先完成一定程度的分发。

处理这个问题,最多的方案就是文件首次range请求到来的时候,cache proxy用一个子请求去取完整文件,原始的请求该怎么处理还是怎么处理(透传)。取源的子请求会在后台默默的拉取整个文件,当拉取完成以后,后续的range请求就可以本地hit了。这个方案最大的问题是放大了回源量,在range流量爆发的同时,增加了额外的拉取完整文件的访问压力。这种方案是优化自己,但却让客户买单的行为。不是长久之计,而且很多客户根本不接受这种方案。

nginx在1.9.8版本上给出的解决方案不是完美的,但是解决了很大一块的问题。它怎么做的呢?


当在编译时添加了--with-http_slice_module,那么slice模块的相关配置就会启用。我们假设现在要取一个5M的文件,名为字test.data。

下面的讨论,我们都假设目标是5M的test.data,slice的配置是2M。

完整文件的请求

当我们发送一个GET请求,不携带Range头,我们在后端的服务器上会看到3个Range请求,每个range请求的请求范围都是2M,即第一个是0-2M,第二个是2-4M,第三个是4-6M。这里回答几个问题:

  1. 为什么每次都是去后端取2MB范围的片?

    因为slice 2m;这个配置产生的作用。

  2. 为什么向后端发起了3个range请求?

    nginx一开始并不知道要发几个range子请求,它会根据配置的slice 2m;,先发起一个2m的range请求,这个请求返回的Content-Range头会给出文件总长度,这样nginx就知道一共需要发几个range请求来取完所有内容。

  3. 为什么发到后端的请求变成了Range请求?

    因为回源的请求头添加了Range:proxy_set_header Range $slice_range;

由于nginx天然支持子请求,所有这三个range请求收到的数据,会按顺序无缝的发给客户端。响应码给的是200 OK(客户端是非range请求)。这三个子请求是一个接一个来发出的,发不发下一个子请求,要看当前子请求的处理情况,整个过程就是子请求触发子请求的过程,触发条件如下:



重点来了!

每个子请求收到的数据都会形成一个独立文件,这个文件就是通过proxy_cache_key $uri$is_args$args$slice_range;来定位的,也就是说文件跟key有绑定关系。在slice模块的运作下,一个非range的请求,本来应该缓存成一个大文件,最后却被切成了大小为2MB的文件片(注意最后一个片可能不够2MB)。由于每个片都是一个独立的文件,而且片与片之间没有什么必要的联系,这样完整文件具有的特性(可以hit,可以过期等等),每个片文件也同样具备。


我们假设三个子请求是这样子的: 
A: range: bytes=0-2MB 
B:range: bytes=2-4MB 
C: range: bytes-4-6MB


原始的完整请求到来,nginx先产生一个range请求(A请求),然后拿proxy_cache_key $uri$is_args$args$slice_range;判断对应片(这里是第一片)是否已经在系统中缓存了,如果没有,就会miss取源。


在处理A请求时,slice_range的值就是字符串”bytes=0-2MB”,由于slice_range这个变量在nginx内部是no_cacheable类型的,所以每次使用这个变量都要去重新获取(计算)。当处理B请求时,该值变成了”bytes=2-4MB”,依次类推。每次处理一个请求,都会对slice_range背后的值进行重新计算。这样保证了每次算出的key都不一样,也能每次取不一样的range片。


这种将片缓存为独立文件的方式,还获得了一个额外的优势。就是如果某个片取源连接断掉,那么前面已经缓存的片依然有效。试想一个大文件(在未开启分片的功能下)在最后还有几个字节就收完的时候,连接突然断了,那么前面的内容都会作废,努力全都付诸东流了。这是基于完整文件的缓存模式下,一个很讨厌的问题。

普通的http range请求


普通的range请求在slice模块作用下,同样的通过proxy_cache_key $uri$is_args$args$slice_range;来定位应该访问哪个”slice片文件”。这不过比较棘手的问题,就是请求跨多个slice片文件的情况。下图形象的描述了一种情况: 



原始请求的访问是0.8M-3.3M,即图中的A-B线段的范围,这个range请求会在nginx内部被转变成r1(0-2M)和r2(2-4M)两个子请求,当这两个子请求对应的片不存在时就会取源。然后缓存成两个文件,但是发送给客户端时,会选择正确的部分拼在一起响应。从实现上讲,也很简单,只需要将A的位置向下对齐,B的位置向上对齐就可以很容易算出相应的子请求range的范围。


再延伸一下讲,如果r1对应的片已经存在了,那么只需要r2取源就可以了。取源得到的数据跟r1已经存在的内容结合在一起,给客户端响应所需要的内容。

多range请求

很遗憾,slice模块没有处理multiple range类型的请求。例如:

 
 
  1. Range: bytes=2048-4095,4096-6143,6144-8191

在开启slice模块时,nginx会直接忽略range头,当成完整文件来处理。很明显这里是做了妥协(处理起来略复杂一些)。好在multiple range在实际中用的并不多。

取源合并

当多个range请求同时取同一个片的时候,会有并发的取源问题。所以进一步的优化就是在取源的时候,做请求合并。nginx本身有个比较鸡肋的取源合并,理论上可以稍微改动一下,应用在slice模块中。因为现有的取源合并功能是处理完整文件的,而slice片本身其实就是一个”完整文件”。

业界其他的探索

关于range片的存储,在BAT,各大视频网站,音乐网站和cdn厂商,都有各种自己的处理方案。当然不同的系统里去实现这个功能难度不一样。我相信很多公司都在ngixn里开发类似的分片功能。蓝讯在squid做过,而且还做了在片基础上的请求合并。之前阿里的traffficserver团队,在ts上也实现了该功能。不过在ts上实现这个功能,需要对存储模块逻辑比较熟悉,难度还是比较大的。不过在ts上实现这个功能有个得天独厚的架构优势,ts的存储结构本身是基于片的。


也有个别公司是在一个大文件(基于fs)的内部分片,也就是说仍旧只根据url计算的key来定位文件,在内部通过树,hash表或者位图等来进一步确定其中分片的状态(存在或者不存在)。这样的模型,处理起来很棘手:

  • 如果使用位图的话,往往需要先确定文件的大小。可能需要先发送HEAD请求,获取文件总长度,然后通过分片的大小,计算出需要占用的位图空间,那么没有文件长度怎么办?chunked编码怎么办?

  • 取文件片的时候,过期了。那么前面的片要作废。放在一个文件中的片要是一致的,这个问题需要考虑

  • 如果现有的存储逻辑都是基于完整文件,那么在文件内部再分片,架构会受到严重的冲击,带来稳定性的挑战

阿里在2012年左右,曾经开源了一个类似的模块,名字也叫slice模块。做的事情差不多,不过略简单。有兴趣的可以去看一下:

https://github.com/alibaba/nginx-http-slice

总结

总之,这是一个非常不错的功能,适合的情景我认为主要是音乐类,视频类的网站等,有不少大尺寸的文件。不过建议使用的时候,要注意完整文件或者range在bytes=0-的时候,会有大量的取源连接,这样对源站压力有一定冲击,同时有耗尽proxy本地端口的风险。

尾巴

这个模块的作者是Roman Arutyunyan,此人就是赫赫有名的nginx rtmp模块的作者。rtmp模块从去年5月份左右就不再更新。我同事开玩笑的说,这哥们原来在琢磨这玩意啊。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值