缓存使用处理得当绝对能很大程度上提高程序性能,目前缓存技术早已应用在各大平台系统中。但使用不当不仅可能降低程序性能,还可能挖出让你痛不欲生的神坑,多么痛的领悟....
下面我先来列一列使用缓存所要面临的一系列挑战,然后逐个分析,限于个人水平有限可能会分析漏了或不到位的地方,请见谅!!
1. 缓存更新
数据库的数据不是一成不变,当数据发生了改变,如何同步到缓存中?在数据同步前或同步中,如何防止用户读到脏数据。
2. 缓存穿透
查询一个必然不存在的数据。比如文章表,查询一个不存在的id,每次都会访问DB,如果有人恶意破坏,很可能直接对DB造成影响。
3. 缓存并发
网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的问题。
4. 缓存失效
引起这个问题的主要原因还是高并发的时候,平时我们设定一个缓存的过期时间时,可能有一些会设置1分钟啊,5分钟这些,并发很高时可能会出在某一个时间同时生成了很多的缓存,并且过期时间都一样,这个时候就可能引发一当过期时间到后,这些缓存同时失效,请求全部转发到DB,DB可能会压力过重
5. 数据序列化
把数据保存到缓存中,需要对数据进行序列化和反序列化,这是一个时间消耗较大的操作,序列化的消耗也是衡量缓存性能的指标之一
就以上几个问题,给出部分解决方案,但没有最好的方案只有针对业务特征的最合适方案
缓存更新
1. 如果允许短时间内的数据不同步,可采用"淘汰缓存"和"更新缓存"两种方案,下面来对比一下这两种方案
方案一:"淘汰缓存"就是先清空缓存再更新数据库,那么请求已清空的缓存数据时将重新加载数据库对应数据
优点:简单
缺点:一次cache miss
这种方案也不能100%保证不会有脏数据,原因如下:
在写请求完成前,有读请求出现,这时又读入脏数据到缓存
"更新缓存"就是先更新数据再更新缓存
优点:cache命中率高
缺点:如果更新缓存失败则读取到脏数据,同时在写请求完成前读请求进来也是会读到脏数据
针对方案二:如果更新缓存失败,可采用定时重试机制进行缓存更新
2. 假设采用方案一,如果存在读写分离的情况,既缓存数据来源于读库,则可能会产生下面问题
写请求成功了,但主库还没同步到从库,这时读请求产生,读库的脏数据就会被读入缓存
解决方案:上述情况是由于主从同步延时造成的,可以增加采用异步更新缓存线程,该线程监听从库日志,发现记录变化后再次更新缓存。监听日志再异步更新还能一定程度上解决方案一与方案二中的脏数据,异步更新成功读请求就再不会读到脏数据,只会有小概率读到脏数据
缓存穿透
当访问某个key的value为null向数据库请求数据也null时,先给该的value设置一个标志如&&,表明该key暂没有value,当有写请求写到数据库再更新缓存,之后读请求就能读到数据了
缓存并发
如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询。
这种情况和刚才说的预先设定值问题有些类似,只不过利用锁的方式,会造成部分请求等待。
缓存失效
缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1~5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。(针对多个缓存来说)
数据序列化
将数据保存到内存中和从内存中取出使用是需要经过序列化与反序列化,这个过程不仅有时间消耗还会有空间消耗,序列化后的数据比序列化前更大,因为插入很多标记。所以只缓存有价值的数据也可以降低序列化带来的性能消耗。但关键还是选择合适的序列化协议,通用性强的有json、xml(json速度更快,序列化后大数据更小),如果要求性能的有Protobuf,Thrift,Avro
欢迎Q群交流:432550774