谈谈服务端缓存的几种用法

标签: 缓存 php | 发表时间:2015-04-22 03:44 | 作者:iammutex
出处:http://segmentfault.com/blogs

缓存是一个常谈常新的话题,作为一名服务端的技术,如果你入行一年都还没用过memcached类产品,那只能说你的公司实在太小了,或者你干的活实在太边缘了。

说起缓存,可能大家最直接想到的就是:“在数据库前面挡一层”。这是缓存最原始的意义,同时也引申出了缓存最普遍的用法。

原始模式

代码示例1(原始模式):

  //从缓存中获取数据[较快的方式]
data = getfromcache(id)
if data == null then
    //从数据库中获取数据[较慢的方式]
    data = getfromdb(id)
    //缓存1天
    setintocache(id, data, 86400)
    return data
end

return data

缓存加锁

上面这种情况下,当同时有N个请求到达,都同时执行getfromcache,那么都会发现data在缓存中不存在,然后都会去调用getfromdb,以及setintocache。这是不必要的,那么我们有没有办法减少这些并发呢。

最直接的想法是加锁,当进入if条件中时,加一把锁,让其他进程不再执行下面的逻辑,而是等第一个进程的setintocache执行完成后,再重新执行一次getfromcache。

那这个锁如何加呢?这里推荐一种省时省力的方法。通过直接在缓存value中设置过期时间来实现。

比如缓存的value值为data,那我们修改一下,把它放到一个json中,改成

  {data:data,atime:1429618765}

我们增加了一个atime来记录缓存生成的时间。而逻辑就变成下面这样。

代码示例2(缓存加锁):

  //从缓存中获取数据[较快的方式]
data = getfromcache(id)
data = json.decode(data)
//如果通过检查缓存生成时间,发现缓存已经过于陈旧,那么就将缓存过期时间设置为现在开始的5分钟以后(这样其他并发进程就会以为此缓存还未过期,还会继续使用5分钟,只让当前这一个请求去重建缓存)
if data != null && data.atime+86400 < now then
    data.atime = now+300-86400
    data = json.encode(data)
    //对真正的cache来说,缓存10天或者更长时间
    setintocache(id, data, 864000)
    //这里把data设置成null是为了走到下面的if中去重建缓存
    data = null
end

if data == null then
    //从数据库中获取数据[较慢的方式]
    data = getfromdb(id)
    data = {data:data, atime:now}
    data = json.encode(data)
    //对真正的cache来说,缓存10天或者更长时间
    setintocache(id, data, 864000)
    return data
end

return data

你可以会发现,这里也会存在并发啊,和上面例1一样,第一个getfromcache到setintocache之间,如果同时有N个请求到来,不还是都会执行这段操作,都会去查库吗。

没错,是这样的。但是我们仔细看一下,例1中,从getfromcache到setintocache之间,经历了一次漫长的getfromdb操作,这个时间耗费可能是上百毫秒的。而我们例2中,并没有进行什么操作,这个时间耗费只在毫秒甚至微秒级的。

所以例1中getfromcache到setintocache之间的并发是远大于例2中的。例2中通过减小时间窗口,有效的模拟了锁机制。同时还没有增强额外的存储复杂度。所以是推荐的一种方式。

可以说,我们所有的缓存都应该是例2的方式,他在各方面都优于例1(多保存的一个atime字段耗费的内存基本可以忽略不计。且atime很多时候对于调试程序还很有用)。

主动更新缓存

那这样就够了吗?对于被动过期型的缓存,这样基本就可以了。但是现实中还有一种缓存,是主动更新的。试想有一种缓存,我们要求必须和数据库中的数据一致,不能出现陈旧数据。那么上面的缓存方式就不合适了。

我们必然会添加一个流程:即当数据库有更新时,同时更新缓存,因为缓存会自己重建,也可以修改为当数据库有更新时,同时删除缓存。

这里提到删除或者更新缓存,就有点意思了。我们上面讲到的都是非常简单的缓存,即一个id对应一个key。那么试想,如果我们有一个分页缓存,缓存了某一个文章最新的前10页数据。分别的key是page_1,page_2...page10。

那么当我们有一条新数据产生,这10页就都失效了,需要更新或者删除10次。这显然是不太科学的做法。

那么我们应该怎么做呢。我们可以借用上面例2中的方法,例2中,我们在缓存中增加了一个atime字段,标识为缓存的生成时间。我们既然知道缓存什么时候生成的,那问题就好解决了。我们在每次有新数据产生时,都去更新一个updatetime字段。然后获取分页缓存的时候,看一下这个updatetime字段是不是在atime之后,如果是,那么说明这份缓存太旧了,需要走更新流程。

代码示例3(避免批量更新):

  //从缓存中获取数据[较快的方式][这里的两次get普通的缓存系统都支持一个请求完成]
data = getfromcache(id)
updatetime = getupdatetime(id)
data = json.decode(data)
//如果通过检查缓存生成时间,发现缓存已经过于陈旧,那么就将缓存过期时间设置为现在开始的5分钟以后(这样其他并发进程就会以为此缓存还未过期,还会继续使用5分钟,只让当前这一个请求去重建缓存)
if data != null && (data.atime+86400 < now || date.atime < updatetime) then
    data.atime = now+300-86400
    data = json.encode(data)
    //对真正的cache来说,缓存10天或者更长时间
    setintocache(id, data, 864000)
    //这里把data设置成null是为了走到下面的if中去重建缓存
    data = null
end

if data == null then
    //从数据库中获取数据[较慢的方式]
    data = getfromdb(id)
    data = {data:data, atime:now}
    data = json.encode(data)
    //对真正的cache来说,缓存10天或者更长时间
    setintocache(id, data, 864000)
    return data
end

return data

这仅仅是在代码示例2的基础上增加了下面这一个条件判断而已

  date.atime < updatetime

这样,无论是缓存保存时间过期了,还是缓存本身有更新,都会触发带锁机制的缓存更新。

好了,先说到这里,回头有想起来的再做更新。 原文地址


顺便插播一则招聘广告。(码字不易,求别删招聘广告,谢!)

题主现在供职于乐视网,负责用户互动类的产品研发,诚招PHP服务端开发(发展机会待遇绝对牛X)。

有兴趣的同学请私信或简历发邮箱:ligang1#letv.com

相关 [服务 缓存] 推荐:

Google  的 favicon 缓存服务

- 小猫 - 我爱水煮鱼
在添加友情链接或者其他操作的时后需要应用其它网站的图标(favicon),如 iPad 网址导航,如果一个一个去找会非常麻烦,Google 提供了一个比较快速得到相应网站 favicon 的服务,使用下面的 URL 调用 GOOGLE 的 favicon 缓存,将后面的域名改成相应网址即可,没有favicon的网站会显示一个小地球.

用 Acrylic 自建DNS缓存服务器

- CHris - Page to Page
Acrylic是win下小巧的DNS缓存服务器,使用它可以加快本机的域名解析 ,提高你上网的速度. 今发布一方案,照猫画虎做之,今后可一劳永逸也. 各种hosts修改器及修改脚本,可扔之. 1.下载Acrylic(目前版本0.9.18)http://nchc.dl.sourceforge.net/project/acrylic/Acrylic/0.9.18/Acrylic.exe.

线上服务增加varnish缓存

- - CSDN博客互联网推荐文章
(1)是基于内存缓存,重启后数据将消失. (2)利用虚拟内存方式,io性能好. (3)支持设置0~60秒内的精确缓存时间. (4)VCL配置管理比较灵活. (5)32位机器上缓存文件大小为最大2G. (6)具有强大的管理功能,例如top,stat,admin,list等. (7)状态机设计巧妙,结构清晰.

让网站飞起来02--服务器缓存技术

- Bloger - 博客园-首页原创精华区
第一个介绍的是《让网站飞起来01---浏览器缓存技术》. 介绍服务器,肯定要先支持服务器在网站架构中的位置和作用,然后在介绍几种常见的服务器缓存配置. 对服务器在网站中位置作用有个大概了解:lamp架构图. 上图主要介绍了三种服务器,也是比较常用的服务器,下面就介绍这三种服务器的缓存配置. apache是作为正向代理服务器缓存,nginx和squid主要作为反向代理服务器缓存..

高性能缓存服务器Varnish解析

- - 技术改变世界 创新驱动中国 - 《程序员》官网
Varnish是一款高性能、开源的反向代理服务器和缓存服务器,其开发者Poul-Henning Kamp是FreeBSD核心的开发人员之一. Varnish采用全新的软件体系结构,和现在的硬件体系配合比较紧密. 当前计算机系统的内存除了主存外,还包括CPU的L1级缓存、L2级缓存,甚至还包括L3级缓存.

linux中squid3(高命中率)缓存服务器配置

- - 操作系统 - ITeye博客
今天对我的varnish进行了下小小的压力测试,40s里的8000并发,没有失败一个,估计还可以承受更大的并发,先不说varnish了,我最近找到个命中率很高的squid的配置文件,当然是squid3.0的配置文件,有需要的可以copy回去自己改.   系统:centos 5.x.   需要的软件:squid-3.0.STABLE25.tar.gz.

谈谈服务端缓存的几种用法

- - SegmentFault 最新的文章
缓存是一个常谈常新的话题,作为一名服务端的技术,如果你入行一年都还没用过memcached类产品,那只能说你的公司实在太小了,或者你干的活实在太边缘了. 说起缓存,可能大家最直接想到的就是:“在数据库前面挡一层”. 这是缓存最原始的意义,同时也引申出了缓存最普遍的用法. 代码示例1(原始模式):. //从缓存中获取数据[较快的方式] data = getfromcache(id) if data == null then.

redis作为mysql的缓存服务器(读写分离)

- - 数据库 - ITeye博客
Redis是一个key-value存储系统. 和Memcached类似,为了保证效率,数据都是缓存在内存中. 区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步. 在部分场合可以对关系数据库起到很好的补充作用. 它提供了Java,C/C++(hiredis),C#,PHP,JavaScript,Perl,Object-C,Python,Ruby等客户端,使用很方便.

应用多级缓存模式支撑海量读服务

- - ImportNew
缓存技术是一个老生常谈的问题,但是它也是解决性能问题的利器,一把瑞士军刀;而且在各种面试过程中或多或少会被问及一些缓存相关的问题,如缓存算法、热点数据与更新缓存、更新缓存与原子性、缓存崩溃与快速恢复等各种与缓存相关的问题. 而这些问题中有些问题又是与场景相关,因此如何合理应用缓存来解决问题也是一个选择题.

Nginx的Web缓存服务与新浪网的开源NCACHE模块(1)

- - CSDN博客推荐文章
Nginx的Web缓存服务与新浪网的开源NCACHE模块. Web缓存位于内容源web服务器和客户端之间,当用户访问一个 URL时,web缓存服务器回去后端web源服务器取回要输出的内容,然后,当下一个请求到来时,如果访问的是相同的URL,web缓存服务器直接输出内容给客户端,而不是像源服务器再次发送请求.