自建CDN实战经验合集之一种实现分片缓存方式的探讨

1. 写在前面

随着互联网的飞快发展,尤其最近几年运营商提速降费,用户的接入带宽大大提升,随之而来的就是互联网上的资源文件也越来越大,各种游戏安装包,从原先的几十M扩展到了几百M,甚至数G都很正常。各种高清的多媒体文件更大。

2. 分片缓存简介

缓存整个大型文件的话会给CDN带来巨大的IO压力和回源压力

举例来说,一般一个对外的CDN节点背后是一整个缓存集群,为了缓存容量和命中率的提升,一般是采用一致性hash,将用户请求的相同url导向到某台机器。那么在推广或者更新的时候,这台机器很可能扛不住这么大的访问量,而其他的集群节点io又很空闲。由于大文件的大小特性,也不可能全部提升到内存缓存中。这就导致了集群内的IO压力负载不均衡,累的累死,闲的闲慌

还有一种情况,用户可能只希望获取大型文件的中间一段数据(比如视频拖动),而传统CDN也很难处理,如果直接回源的话会造成巨大的回源压力。如果缓存起来的话,用户请求数据范围的多样性又导致直接缓存是做无用功。因此市面上不管是squid,nginx还是trafficserver,对于206的请求的处理方式一般都是直接回源(当然如果已经有缓存完整的文件,可以直接截取响应给用户)。

对于这种场景,比较合适的做法是开启分片缓存。所谓分片缓存,顾名思义就是将一个较大的文件,按照特定的规则切片成多个分片,然后分别的缓存起来。根据用户的请求的不同,读取一个或者多个切片,拼接成正确的文件内容,再响应给用户。其实根据协议和实现的不同,分片也有多种不同的概念。

这里探讨的是基于HTTP/HTTPS的静态CDN的分片缓存实现方案

3. 分片缓存的实现

分片缓存关键点在于:

1. 大文件切片:

能够按照一定的规则将用户的请求进行分片,并且后续用户的请求能够准确对应到特定的分片中。不能单纯的使用用户的206请求来进行分片,必须进行整形,否则会产生大量无用的缓存分片。一般是获取整个文件(Content-Length)的大小,按照设定的分片大小,均匀的将数据切分到多个分片中。这样不管用户请求的是文件中任何范围的数据,都可以按照规则读取出来。

2. 切片缓存:

切的分片要能缓存起来,这个没什么好说的了。

3.1 大文件切片

在nginx未推出切片模块(ngx_http_slice_module)之前,一般是自己实现切片逻辑,或者再nginx插件中自己实现,或者在trafficserver中实现切片逻辑。不过既然nginx分片插件推出来,我们可以直接拿来用。

ngx_http_slice_module插件有几个地方需要留意:

1. nginx的ngx_http_slice_module模块是用来支持Range回源。

2. ngx_http_slice_module从Nginx的1.9.8版本开始有的,所以要注意nginx版本。

3. 启用ngx_http_slice_module模块需要在编译Nginx时,额外加多一行参数--with-http_slice_module。

nginx分片参考配置如下:

如果我们发起一个200请求的话,那么分片逻辑大致如下:

1. nginx接收到请求,因为此时nginx不知道文件的总大小,也不知道应该要发起几个子请求到后端。所以nginx会根据配置的slice大小,先发起一个range请求,响应头中肯定会包含content-length。

2. 这样nginx知道了content-length,再接着发起多个range请求到后端。

3. 最终nginx将多个range请求的响应处理后,再返回给用户。虽然用户到nginx只请求了一次,但是nginx到后端则是请求了多次。

3.2 切片缓存

切片缓存,trafficserver官方有cache_range_requests插件可以实现。这里用的是github上网友二次修改的一个插件。(具体的代码和逻辑和查看github:https://github.com/oxwangfeng/ats_slice_range)

git clone下载后,修改代码(handle_server_read_response函数中下面代码)

原代码:

替换为:

之所以修改代码,主要是因为没有考虑状态码的情况,会导致304之类的无centent-length header的响应会被缓存。

安装插件:

diags.log中可以看到插件初始化ok了。

3.3 下载测试

我们模拟下载一个200m的mp4文件。

第一次发起完全的下载请求:

结果文件能够正常下载,md5正确,ats后端的确接受到了多个206的请求,第一次因为ats中没有缓存,所以多个206请求都是TCP_MISS状态。

第二次重新发下相同的完整的下载请求:

结果文件能够正常下载,文件md5值正确,ats后端的确接受到了多个206请求,并且是TCP_HIT的,ATS已经缓存了206,并且直接从缓存中响应给了nginx,nginx拼接多个响应给用户。

第三次发起range请求:

结果请求的数据能够正常下载,并且所请求的数据md5正确。同样可以命中缓存,不会产生回源。

4. 常见问题

4.1 分片缓存对源机的要求?

要求源机支持range,并且开启etag,如果有多个源机存在的话,一定要保证多个源机响应中etag的完全一致。否则nginx在接受到多个响应的时候,可能会出现多个子请求的响应etag不同,会报“etag mismatch”错误,导致无法继续拼接。即使强制拼接,拼接生成的文件也有可能是错误的。

4.2 分片缓存对预取的影响?

对预取无影响,预取某种意义上来讲是模拟用户提前请求资源使得资源能够在正式用户访问前提前缓存起来。正常用户的请求不受影响,预取理论上也不会有问题。

4.3 分片缓存对文件刷新的影响?

看采用何种方式进行文件刷新。由于cache_range_requests插件rewrite过cachekey,即http://www.baidu.com/haha.apk会变成http://www.baidu.com/haha.apk-bytes=41943040-62914559的方式。采用curl-X PURGE的方式肯定是会失效的。因为cachekey已经变了,如果真的要PURGE删除缓存(不是标记为过期)的话,可以手动根据slice大小和rewrite规则生成多个真实的推送url,进行PURGE删除刷新。

如果采用的是regex_revalidate插件进行目录和url刷新的话(标记为过期,而不是直接删除缓存),因为正则刷新插件的逻辑在rewrite之前并且使用的是pcre正则表达式规则,rewrite前的url实际上能够匹配到所有rewrite后的url,所以正则刷新插件依然有效

5. 总结

本文探讨了基于nginx ngx_http_slice_module模块和trafficserver cache_range_requests插件实现的分片缓存的一种方案。在线上业务中可以有效的降低大文件的回源压力和硬盘IO压力,显著提升下载体验。尤其是对于mp4之类,并且采用206进行拖放的多媒体文件,效果更加明显。

  • 发表于:
  • 原文链接:http://kuaibao.qq.com/s/20180309B164VD00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券