很早就听说过Redis的大名,但是一直也没有去研究。因为前一阵有朋友需要用到,来问过我时大致看了看,正好最近自己也碰到需求,就研究了一下。
我这里的情况其实很简单:
在一个VPS上跑了一个应用,提供了短链接功能,原来的实现是直接在web server上 做了一个反向代理,通过urlrewrite映射到后端服务器上。这么用当然是没有问题,功能是完全可以实现的,只是后来发现,当把短链接分享到社交网络 的时候,会有短时的大量访问,这些访问全部压到后端去查询数据库,就会有一点压力了——因为我的VPS配置很低,所以在突发高访问时还是有一定的概率发生 50x错误。
于是考虑把反向代理用redis改写成一个缓存代理。其实这种需求用memcached也可以,因为持久化并非必须,大不了重启以后从后端再取一次就是了。不过因为我对redis更感兴趣,所以还是用redis来做了。
redis的安装很简单,我用的是debian,直接apt-get install redis-server即可。
配置上也没什么可说的,大部分都是用默认配置,只是对内存使用作了一点限制——VPS资源紧张,要节约。反正短链接的数据量也不大。
安装完成以后可以用 service redis-server start 启动。用 redis-cli 可以通过命令操作数据库。
虽然我对Python比较熟,但是这种简单应用就懒得折腾环境了,直接用现成的PHP做吧。
首先是需要下载安装一个PHP的redis库, 官方最推荐的两个PHP库 是 predis 和 phpredis ,虽然目测用C写的phpredis应该性能好些,但为了图方便,我还是用纯PHP的predis。
下载最新稳定版的Predis后,运行bin/create-single-file(需要系统中安装了php-cli)即可生成一个单独的Predis.php,把这个文件放到你的项目路径中就可以使用了,相当简单。
这个缓存功能很简单:
先取得短链接的ID,然后用这个ID去redis里查询。如果查到URL就直接返回302重定向到这个URL。如果查不到就向后端查询,取得302响应返回的redirect_url,然后把这个URL保存到redis,并作302重定向。
需要注意的是,对于向后端查询失败的ID也要保存起来,并返回404错误,以避免错误的ID不断向后端查询,但是风险在于如果这个错误的ID以后用到了,也会查询不到,所以还需要给错误的ID设置超时,过期后删除以便可以重新查询。
主体部分代码就这么点:
function raise_404() { header('HTTP/1.1 404 Not Found'); header("status: 404 Not Found"); die(); } function redirect($url) { header("Location: $url"); } require 'Predis.php'; $redisdb = new Predis/Client([ 'database' => 1, ]); $query = (array) explode('/', $_SERVER['REQUEST_URI']); if (!isset($query[1]) || $query[1]=="") raise_404(); $id = $query[1]; $url = $redisdb->get($id); if (!isset($url)) { $url = get_redirect($id); $redisdb->set($id, $url); if ($url == "") { $redisdb->expire($id, 3600); } } if ($url != "") { redirect($url); } else { raise_404(); }
其中get_redirect是通过curl向后端查询实际URL的函数,这里就不列出具体实现了。
实际跑了一段时间以后的效果还是很明显的。从LOG统计上看,在这个缓存代理处理了500个请求的时候,后端实际上只处理了不到100个,而且随着时间推移,缓存的ID越来越多,对后端的请求会越来越少。
这个试验成功以后,我把RSS功能也缓存起来,不过这个就复杂一些,但原理还是差不多的,只不过因为字段多了,改为使用redis的HashMap值方式(支持多种类型的value是redis的一大强顶),另外还加上了Last-Modified和Etag支持,期望爬虫们能聪明一点,会用这两个东西来进一步减少不必要的访问量。