SuperCache:一个Rails缓存小组件

前言

SuperCache最早由我在负责糗事百科开发工作时,所使用的缓存手段发展而来。当时糗事百科使用了页面整个静态缓存,而页面上诸如登录信息、每个文章的分数评论等,由Ajax载入,这样可以最大化缓存的效率。
但在扩展到两台应用服务器之后,caches_pagecaches_action就显得比较局限了。于是我将页面缓存放入了Memcached,由Nginx直接根据页面地址为key在memcached中查找,如果没有对应缓存才访问Rails层。经过一系列优化,最后变成了现在的SuperCache。现在“暴走漫画”也用的是这套方式。

为什么不用caches_action

caches_action可以指定路径,通过:store_options参数也可以指定rawtrue(默认情况下会序列化,nginx无法读取)以及失效时间,然而caches_action使用了fragment的存储方式,会始终在key前面加上views/,这就使得nginx在处理上变得麻烦。导致caches_action必须走Rails层,导致效率变低。

为什么不用caches_page

caches_page是将页面内容存到磁盘上作为静态文件,这样可以直接通过nginx或者Apache等服务器进行存取,不通过Rails进程,可以极大提高速度。但是caches_page并不支持将页面存入诸如memcache,这样就把caches_pages的应用局限于一台服务器上,要求nginx和rails都在一台服务器上。而且缓存很难共享或者是进行清理。当然也许可以通过nfs或者fuse挂载memcache等方式实现,但这些的增加了部署的复杂度,同时也不知性能如何。

为什么不用rack-cache

rack-cache是符合HTTP标准的透明反向代理,其失效和过期都要使用HTTP头来操作,无法使用Sweeper来清理缓存。和rack-cache作用类似的有Squid和Varnish,但是本人并不了解他们的配置,所以

所以SuperCache的目的就是解决这些问题:可以在多服务器的环境下运作,可以直接由前端的反向代理直接读取缓存而不用经过rails进程。并且可以像Rails原有操作缓存一样进行清理。

使用方法

安装

Gemfile中加入以下

然后命令行下更新:

使用

caches_actioncaches_page一样在Controller中指定所需缓存的页面:

super_caches_page可以有以下参数:

  • cache_path 指定缓存的key,默认是请求的路径
  • expires_in 控制了失效的时间

nginx配置

如果要发挥SuperCache的能力,那必须还得通过nginx的配置:

这样,如果nginx后端有多台Rails服务器,也可以使用静态页面缓存了。

其他好东西

除了上述的简单功能之外,我还正在筹划以下功能:

Dog-Pile Effect

所谓Dog-Pile Effect是指当某个缓存失效的时候,同时有大量请求发现没有缓存,进而请求后端的应用服务要求建立缓存,从而导致服务器卡顿甚至系统宕机的现象。

事实上很多场景下,当缓存失效的时候,只需要其中一个请求去建立新缓存,而其他请求可以使用过期的缓存继续服务。在SuperCache中,我使用Memcache来建立一个锁机制:当缓存失效的时候,只有获得锁的请求可以向后端发送请求并建立新缓存,其他未能获得锁的请求,直接缓存过期的版本。
这样就可以使缓存失效时,系统负载不会突然提升而能平稳过渡。

当然还有一些别的方式来处理这种现象,比如可以在请求之后直接更新缓存(类似Write through机制),而不是等缓存失效之后再处理,但我认为这种方式比较复杂。

目前暴走漫画使用了SuperCache的这种机制之后大大提高了响应并发的能力。

Cache Meta Info

手工处理缓存的清理非常复杂,在SuperCache将来的版本中,我会加入对缓存依赖对象的跟踪,也就是记录和ActiveRecord相关的cache key,当记录发生变更的时候,只需遍历删除对象所影响的Cache key,就可以一次搞定所有问题。

暴走漫画网站已经应用了这套逻辑,相关代码已经提交在了cache_meta_info分支中。


将来我会不断完善SuperCache这个小组件,希望朋友么多多支持,不吝赐教。

“SuperCache:一个Rails缓存小组件”的4个回复

  1. NFS 的话,我测试 rps 6K,就是要挂载一下,搞几台的时候比较麻烦,但是这样省内存

发表评论

电子邮件地址不会被公开。 必填项已用*标注