2018 年 9 月 8 日,由 IT 趣学社主办的 IITechSummit 全球行业互联网技术峰会在北京举行,本次会议设立 2 个主论坛、4 个分论坛。
又拍云 CDN 性能优化负责人丁雪峰在主论坛上做了《又拍云自研缓存组件 BearCache》的精彩分享。
本次分享介绍了又拍云 CDN 在缓存组件方面做的努力与成果—— BearCache,缓存组件是 CDN 网络中非常重要的组件。在使用 Squid 和 ATS 时遇到了不少问题,又拍云选择了自研缓存组件,推出了 BearCache。BearCache 极大提高了又拍云 CDN 的稳定性、可靠性以及性能。
以下是分享实录:
CDN 行业有两个核心的组件:Web 服务器和缓存组件。CDN 就是内容分发,必然有内容的缓存,所以缓存组件是整个 CDN 行业的核心组件之一。
又拍云的 Web 服务器是 Marco,是基于 OpenResty 项目进行上层实现的。
缓存组件作为 CDN 行业的痛点已经有很久了,业界一直没有一个非常优秀的、类似于 Nginx 的完美产品,我们也遇到了很多问题。
又拍云缓存组件历程
缓存组件也由开源项目,大家比较认可的是 Squid 和 ATS。 又拍云在 2013 年之前使用的是 Squid,遇到了很多的问题;2013 年之后公司转用了 Traffic Sever,也就是 ATS,运行了四年左右,同样遇到了很多问题。
CDN 行业已经是一个相对成熟的行业,但是没有一个非常成熟的缓存产品,而这个产品是整个 CDN 行业的两大重要功能之一,这非常不可思议。基于此,又拍云就自己开发了一个缓存组件:BearCache。
之前使用 Squid 和 ATS 遇到的问题:
1、稳定性问题:
内存泄露:这个问题在 ATS 社区已经悬而未决很多年了,举个例子 120G 的内存,运行 20 天左右,ATS 可能占用到 80-100G。但实际上刚运行起来的时候给它分配的内存可能只有 40G-50G,我们能够让它跑得更久一点,所以开始的时候配得比较少,但是必须要隔 20、30 天重启一次。
连接数增长:并不是压力大的情况下增长,而是压力大之后或者压力不大的情况下连接数会产生增长,比如说大量的 close-wait 的现象,这些连接消耗了大量的系统资源。
2、功能性问题:412 卡住等一些功能性问题的出现。
3、性能问题:用户的一些请求到又拍云有时候会被卡住,甚至会影响到业务部的一些服务。
4、运维:不支持热升级,要定时重启,所以运维人员会经常抱怨。
基于这些问题,我们决定自研缓存组件。
自研之路:BearCache
正所谓名正而言顺。当初项目立项的时候,我们就想着起一个有一定内涵的名字。大家对 Nginx 的印象是比较轻盈,速度比较快;而 缓存组件拖着磁盘和缓存,给我们的感觉就是很重,它必须要强大而富有力量,所以我们就选择了熊,项目名称就叫 BearCache。
BearCache 基础功能
基于我们应用 Squid 和 ATS的经验,又拍云根据CDN网络的实际需求,大大地拓展了BearCache的基础功能,其中一些是 Squid 和 ATS所部具备的,也有一些是这两者缺失的。
支持 HTTP/1.1 协议, HTTP PROXY。
完整的 HTTP Cache,之前选择 Squid 和 ATS,就是因为它有这个功能。
完全支持内容协商,相比之下Squid 不支持内容协商, ATS 支持一部分。
缓存 HEAD 响应,HEAD 请求其实很多都不是缓存,但在线上实际的发行中,很多用户会有大量的 HEAD 请求,所以又拍云把 HEAD 请求这个也作为一个基础功能。
BearCache 的特性功能
因为是自研,所以我们在研发过程中,能够根据又拍云 CDN 环境,为 BearCache 策划了很多特性的功能,这些特性功能基本上也反应了我们CDN网络的运维、调度需求。我简单介绍几个:三层缓存 【内存/SSD/SATA】 热点资源调度。
HRC,也就是 HTTP Request Config:请求进来的时候通过 HTTP 的头来控制一些更加细致的行为控制,这在又拍云线上应用非常广泛。
通过请求头指定上游地址:又拍云CDN网络有很多中转,在中转选择的时候可以由 Marco(又拍去 Web 服务器)带给 BearCache,指定这个请求具体去哪一个中转。
Range skip, 异步 GET。
支持 read while write,就是边写边读,一边写入的情况下一边往外面读出去。
BearCache 的运维和配置优势
前面提到 Squid 和 ATS不支持热升级,要定时重启等运维方面的问题,我们在自研过程中根据运维需求,做了一些改进,增加了一些特性,让 BearCache 能够满足运维的需求。
支持热更新。
丰富的状态信息,这些状态信息对于运维以及后期的一些诊断是非常重要的。
自动感知磁盘异常。以前磁盘需要一些外部的监控,但的外部监控不准,毕竟对磁盘的操作者是缓存组件,现在 BearCache 能够直接感知到磁盘,有异常的时候会把磁盘踢掉、发报警,可以尽快地做替换。
灵活的语法配置,包括 if、变量。
这是 BearCache 的线程结构,跟 Nginx 比较像的就是 master 进程和 worker 进程。
worker 进程中有一个 master 线程,它主要是跟 master 进程通信的。而其中有一个 socket pair 是对 master、worker 进行通信的。其他就是 worker 线程,worker 线程中有一个 proxy 和 proxy 会跳线程,它会从一个线程下来之后跳到另外一个线程。因为资源进来的时候,并不知道它是在哪块磁盘上的,会根据 proxy 跳。如果跳对了,进程进来后所有的磁盘操作都无锁的,如果在同一个线程上对多个线程同时访问的话可能就要加锁,我们这样设计的目的就是无锁。
BearCache Cache Struct
当请求进来之后,在这一线程中:
上层是 HTTP proxy,如果这个资源没有这种情况会向后面 BearCache 去中转, BearCache 会取用户的源件,在 Cache 层。每一个线程都独立有一个 Cache 层,Cache 层下面是多个设备,设备包括它的索引和内存缓存,内存缓存和 Cache 是常驻在内存里面的。再下一层是异步 IO,对于磁盘的操作全部是异步 IO 来实现的。这三块内容组合起来就是每一个磁盘所私有的。
如果资源在内存缓存中没有找到,配了 SSD ,请求会跳到另外一个线程,这个线程会在 SSD 里面持续。如果没有配 SSD,会在 SATA 里面持续。所以我们的缓存是内存、SSD、SATA 三层缓存。如果资源足够热就会在内存,这时候唯一的瓶颈就是网卡,经常就是网卡被卡爆,但对内存和服务来说没影响。
热重启解决思路:SysV shared memory
热升级指的是更新二进制执行文件。如果要修改 ATS,只能重启程序,但万一重启服务不支持热重启,有一段时间的服务是间断的,间断就会影响到客户,会需要一系列的步骤保证对用户的影响降到最低。
为什么缓存组件没有热重启功能?因为缓存组件面临着两个问题。
1、内存占用过大,缓存组件会执行内存缓存,并且需要划一些内存给它。假设你有一台 100G 的内存的机器,起码你得给缓存组件 60G,那么热重启的时候必然同时存在着两个 worker,一个新的、一个旧的,每个 worker 60G 第二个就会无法运行,因为已经超过物理内存大小了。
2、缓存组件是有状态的,它要维持磁盘数据的正确性、不能冲突。
基于这两个原因,缓存组件是不太容易实现热升级的,但是热升级对于运维以及版本迭代意义非常重大。
我们的解决思路就是 SysV 的内存共享,不同于 mmap 共享的内存都是与进程绑定,SysV 可以跟进程解绑。SysV 创建了内存之后,是独立于进程存在的,进程重启的时候,内存数据还在, 避免了比如说 60G 的数据从磁盘里面再加载一遍。
这样之后, 上层的代码修改和功能迭代都是非常快的,基本上跟 Nginx 的发布速度一致。
为了方便数据访问,我们把共享内存映射到相同的虚拟地址上,这样在访问数据的时候可以直接使用指针快速访问。
而有一个小问题, io/index 层有一些私有数据,,比如 aio, config 之类的。 这些数据的指针必然是在共享内存上,所以不同的进程访问这些数据的时候是一样的指针,所指向的数据并不在共享内存里,而在两个进程所独立的虚拟内存上。BearCache 在指定的位置上保存了这些进程私有数据。
为了解决磁盘数据的同步, 旧的 worker 不执行磁盘写操作, 旧 worker 写数据到内存缓存中, 新的 worker 同步内存的数据到磁盘,也就是说只会有一个进程执行写操作。
磁盘压力控制
读写磁盘是缓存组件的重要工作内容,但磁盘是一个慢设备,而 CDN 对于客户的请求要快速响应,在这个情况下磁盘的压力就显得非常重要。
BearCache 主要在几个方面控制磁盘的压力:
基于磁盘的队列 skip r/w
请求的磁盘操作增加超时控制。每一个请求从上层创建的时候就会分配一个定时器,当这个定时器超了之后,马上就会采取一些措施来保证用户的请求不卡。
BearCache 完成解决了内存缓存的连接数、连接可控,所以它上线以来就非常稳定。
自研 BearCache 目的:为客户创造价值
又拍云自研 BearCache 的目的很明确,就是希望通过缓存组件的革新,提升 CDN 服务的速度、可用性,给客户带来更多的价值,事实证明,BearCache 也的确给客户提供了更优质的 CDN 服务。
1、快速响应
使用的 ATS 的时候,CDN 网络容易受到磁盘的影响, 用户在下载的时候, 如果磁盘压力很大, 用户的下载可能会卡住或者很慢;并且情况会越来越差, 因为磁盘压力在不断增加。
BearCache 把用户的下载速度放第一位, 当感知到磁盘性能下降, 会快速结束对磁盘的请求, 向 CDN 中转转发请求, 从而保证了用户的下载体验。
2、减小回源流量
BearCache 为客户提供更加稳定可靠的服务,主要是减少用户的回源量,用户访问我们的 CDN,我们会对资源请求进行减少回源量的操作。一般是以 Range Skip 的方式,通常 range 请求是不缓存的, ATS 多数采用异步 GET 的方式来另外缓存文件, 这个功能 BearCache 也有。很多浏览器、用户的一些播放器在做 GET 的时候,对于 Range: bytes=1- 这样的请求, 异步 GET 其实也是回源了两次,一次回源站,把整个文件从 1 的位置请求一次,然后缓存组件发起一个 GET,从 0 文件再请求一次,整个过程中请求量是蛮大的。
BearCache 是对于给定阀值内的 range start 采用直接请求完整文件,吐出用户所需数据的方式。阈值比较小的情况,请求从1或2的地方开始请求这个文件和请求整个文件,对于用户的源站来说,后者其实更小一点。
阈值比较大的情况,ATS 实现类似的过程的话,它就会卡住。比如说一个 500MB 的文件,用户请求 100MB,要等用户把从 0 到 100 这一块数据吐完,视频可能就会有一段时间是黑屏,BearCache 就会解决这个问题。
3、强制合并加源
缓存组件一直都面临着一个问题: 只有收到响应之后才可以判断一个资源能不能缓存从而合并请求。存在一个时间窗口,窗口期内如果有大量并发请求, 会对源站造成很大的压力。
ATS 是不支持这个强制合并加源的,如果试图把资源卡住或者锁住,提前把它合并,没有时间窗口的话这是违反协议的。
上文提到的 BearCache(HRC) ,用户可以配置一个域名下的资源是不是可以缓存的, 如果是, 那么从收到第一个请求开始, BearCache 就可以合并请求,不会给源站造成压力。
4、提升性能
上图是 BearCache 上线之后,我们的性能数据,前半段是使用 ATS,后半段是更新之后,那么响应速度大概提升了 38%。
磁盘压力也是对应这块的数据,使用 BearCache 之后磁盘的压力会有一个很明显的下降。