摘要:缓存是一个常谈常新的话题,作为一名服务端的技术,如果你入行一年都还没用过类产品,那只能说你的公司实在太小了,或者你干的活实在太边缘了。这是缓存最原始的意义,同时也引申出了缓存最普遍的用法。但是现实中还有一种缓存,是主动更新的。
缓存是一个常谈常新的话题,作为一名服务端的技术,如果你入行一年都还没用过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
这样,无论是缓存保存时间过期了,还是缓存本身有更新,都会触发带锁机制的缓存更新。
好了,先说到这里,回头有想起来的再做更新。原文地址
顺便插播一则招聘广告。(码字不易,求别删招聘广告,谢!)
易手机坐标深圳,做一款易用安全的老年智能手机,做老年手机第一品牌。现在灰常需要服务端同学入伙。有兴趣的同学请私信或简历发邮箱:ligang#pingyijinren.com
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/61769.html
摘要:缓存是一个常谈常新的话题,作为一名服务端的技术,如果你入行一年都还没用过类产品,那只能说你的公司实在太小了,或者你干的活实在太边缘了。这是缓存最原始的意义,同时也引申出了缓存最普遍的用法。但是现实中还有一种缓存,是主动更新的。 缓存是一个常谈常新的话题,作为一名服务端的技术,如果你入行一年都还没用过memcached类产品,那只能说你的公司实在太小了,或者你干的活实在太边缘了。 说起...
摘要:以下为大家整理了阿里巴巴史上最全的面试题,涉及大量面试知识点和相关试题。的内存结构,和比例。多线程多线程的几种实现方式,什么是线程安全。点击这里有一套答案版的多线程试题。线上系统突然变得异常缓慢,你如何查找问题。 以下为大家整理了阿里巴巴史上最全的 Java 面试题,涉及大量 Java 面试知识点和相关试题。 JAVA基础 JAVA中的几种基本数据类型是什么,各自占用多少字节。 S...
摘要:并总结经典面试题集各种算法和插件前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快速搭建项目。 本文是关注微信小程序的开发和面试问题,由基础到困难循序渐进,适合面试和开发小程序。并总结vue React html css js 经典面试题 集各种算法和插件、前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快...
阅读 881·2021-11-15 11:38
阅读 2512·2021-09-08 09:45
阅读 2811·2021-09-04 16:48
阅读 2562·2019-08-30 15:54
阅读 928·2019-08-30 13:57
阅读 1616·2019-08-29 15:39
阅读 494·2019-08-29 12:46
阅读 3518·2019-08-26 13:39