2018 年 9 月 8 日,由 IT 趣学社主办的 IITechSummit 全球行业互联网技术峰会在北京举行,本次会议设立 2 个主论坛、4 个分论坛。

又拍云 CDN 性能优化负责人丁雪峰在主论坛上做了《又拍云自研缓存组件 BearCache》的精彩分享。

本次分享介绍了又拍云 CDN 在缓存组件方面做的努力与成果—— BearCache,缓存组件是 CDN 网络中非常重要的组件。在使用 Squid 和 ATS 时遇到了不少问题,又拍云选择了自研缓存组件,推出了 BearCache。BearCache 极大提高了又拍云 CDN 的稳定性、可靠性以及性能。

WX20180913-144133.jpg

以下是分享实录:


CDN 行业有两个核心的组件:Web 服务器和缓存组件。CDN 就是内容分发,必然有内容的缓存,所以缓存组件是整个 CDN 行业的核心组件之一。

又拍云的 Web 服务器是 Marco,是基于 OpenResty 项目进行上层实现的。

缓存组件作为 CDN 行业的痛点已经有很久了,业界一直没有一个非常优秀的、类似于 Nginx 的完美产品,我们也遇到了很多问题。

又拍云缓存组件历程

缓存组件也由开源项目,大家比较认可的是 Squid 和 ATS。 又拍云在 2013 年之前使用的是 Squid,遇到了很多的问题;2013 年之后公司转用了 Traffic Sever,也就是 ATS,运行了四年左右,同样遇到了很多问题。

CDN 行业已经是一个相对成熟的行业,但是没有一个非常成熟的缓存产品,而这个产品是整个 CDN 行业的两大重要功能之一,这非常不可思议。基于此,又拍云就自己开发了一个缓存组件:BearCache。

11111.jpg

之前使用 Squid 和 ATS 遇到的问题:

1、稳定性问题:

内存泄露:这个问题在 ATS 社区已经悬而未决很多年了,举个例子 120G 的内存,运行 20 天左右,ATS 可能占用到 80-100G。但实际上刚运行起来的时候给它分配的内存可能只有 40G-50G,我们能够让它跑得更久一点,所以开始的时候配得比较少,但是必须要隔 20、30 天重启一次。

连接数增长:并不是压力大的情况下增长,而是压力大之后或者压力不大的情况下连接数会产生增长,比如说大量的 close-wait 的现象,这些连接消耗了大量的系统资源。

2、功能性问题:412 卡住等一些功能性问题的出现。

3、性能问题:用户的一些请求到又拍云有时候会被卡住,甚至会影响到业务部的一些服务。

4、运维:不支持热升级,要定时重启,所以运维人员会经常抱怨。

基于这些问题,我们决定自研缓存组件。

自研之路:BearCache

123.jpg

正所谓名正而言顺。当初项目立项的时候,我们就想着起一个有一定内涵的名字。大家对 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、变量。


2222222.jpg

这是 BearCache 的线程结构,跟 Nginx 比较像的就是 master 进程和  worker 进程。

worker 进程中有一个 master 线程,它主要是跟 master 进程通信的。而其中有一个 socket pair 是对 master、worker 进行通信的。其他就是 worker 线程,worker 线程中有一个 proxy 和 proxy 会跳线程,它会从一个线程下来之后跳到另外一个线程。因为资源进来的时候,并不知道它是在哪块磁盘上的,会根据 proxy 跳。如果跳对了,进程进来后所有的磁盘操作都无锁的,如果在同一个线程上对多个线程同时访问的话可能就要加锁,我们这样设计的目的就是无锁。

BearCache Cache Struct

3333.jpg

当请求进来之后,在这一线程中:

上层是 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 的发布速度一致。

444.jpg

为了方便数据访问,我们把共享内存映射到相同的虚拟地址上,这样在访问数据的时候可以直接使用指针快速访问。

而有一个小问题, 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、提升性能

6666.jpg

上图是 BearCache 上线之后,我们的性能数据,前半段是使用 ATS,后半段是更新之后,那么响应速度大概提升了 38%。

88888.jpg

磁盘压力也是对应这块的数据,使用 BearCache 之后磁盘的压力会有一个很明显的下降。