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

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

提及缓存,可能你们最直接想到的就是:“在数据库前面挡一层”。这是缓存最原始的意义,同时也引伸出了缓存最广泛的用法。数据库

原始模式

代码示例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。这是没必要要的,那么咱们有没有办法减小这些并发呢。json

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

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

好比缓存的value值为data,那咱们修改一下,把它放到一个json中,改为并发

{data:data,atime:1429618765}

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

代码示例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中,并无进行什么操做,这个时间耗费只在毫秒甚至微秒级的。code

因此例1中getfromcache到setintocache之间的并发是远大于例2中的。例2中经过减少时间窗口,有效的模拟了锁机制。同时尚未加强额外的存储复杂度。因此是推荐的一种方式。htm

能够说,咱们全部的缓存都应该是例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

这样,不管是缓存保存时间过时了,仍是缓存自己有更新,都会触发带锁机制的缓存更新。

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


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

易手机坐标深圳,作一款易用安全的老年智能手机,作老年手机第一品牌。如今灰常须要服务端同窗入伙。有兴趣的同窗请私信或简历发邮箱:ligang#pingyijinren.com

相关文章
相关标签/搜索