(声明:本文部分内容摘自网络优秀文章,版权归原作者所有)
一、缓存方式
1 进程内缓存
直接在jvm虚拟机中缓存(堆、直接内存、磁盘3级缓存),速度快,效率高;但是缓存共享麻烦。
2 独立缓存服务
redis、memcache均为缓存服务。
3 分布式缓存
指在每个程序中都有一个相同的缓存,同步更新有延时的。如ehcache集群,缓存共享复杂,维护不方便,所以不常用。
二、缓存的访问模式
使用缓存时有几种常见的访问模式:
1 预留缓存(Cache-Aside)
应用程序 在访问数据库之前必须要先访问缓存,如果缓存中包含了该数据则直接返回,不用再经过数据库,否则应用程序必须要从先数据库中取回数据,存储在缓存中并且将数据返回,当有数据要写入的时候,缓存内容必须要和数据库内容保持一致。
这种方式是将数据库与缓存通过客户端应用程序主动管理来进行同步,这不是很好的使用方式。
2 Read-Through模式
相比上面的由客户端应用程序来管理数据库和缓存同步的方式,这种模式缓存会配有一个缓存中间件,该中间件来负责数据库数据和缓存之间的同步问题。当我们应用要获取缓存数据时,这个缓存中间件要确认缓存中是否有该数据,如果没有,从数据库加载,然后放入缓存,下次以后再访问就可以直接从缓存中获得。(缓存和数据库 通常是一对一的关系,中间件需要维护这种业务关系)
3 Write-Through模式
这种模式就是缓存能够感知数据的变化。也就是说在更新数据库的同时也要更新缓存,该模式其实也是弱一致性,当数据库更新数据失败的时候,缓存不能继续更新数据,要保持数据库和缓存的最终一致性。
4 Write-behind模式
该模式是以缓存为优先,将缓存更新的数据存放队列中,然后数据库定时批量从队列中取出数据更新数据库。(Cassandra是否是该中模式,待确认)
三、缓存问题及解决方案
1 缓存穿透
1.1、 场景
应用程序 通常先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库,然后再缓存查询结果返回。这个时候如果查询的某一个数据在缓存中一直不存在(1 数据库中也不存在,如利用不存在的key频繁攻击;2 未加载至缓存或者缓存失效),就会造成每一次请求都查询DB,这样缓存就失去了意义,在流量大时,可能DB就挂掉了。
1.2、解决方案
可以在封装的缓存SET和GET部分增加个步骤,如果查询一个KEY不存在,就以这个KEY为前缀设定一个标识KEY;以后再查询该KEY的时候,先查询标识KEY,如果标识KEY存在,就返回一个协定好的非FALSH或者NULL值,然后应用程序 做相应的处理,这样缓存层就不会被穿透。当然这个KEY的失效时间不能太长。
2 缓存并发
2.1、 场景
有时候如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的问题。
2.2、 解决方案
对缓存查询加锁,如果KEY不存在,就加锁(如果是分布式,需要加分布式锁),然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询。
3 缓存失效
3.1、 场景
引起这个问题的主要原因还是高并发的时候,平时我们设定一个缓存的过期时间时,可能有一些会设置5分钟啊,10分钟这些;并发很高时可能会出在某一个时间同时生成了很多的缓存,并且过期时间都一样,这个时候就可能引发一当过期时间到后,这些缓存同时失效,请求全部转发到DB,DB可能会压力过重。
3.2、 解决方案
将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
四、二级缓存
1 使用二级缓存好处
1.1 进程内缓存,无需IO和序列化,速度更快。适合小数量的热点数据。
1.2 减少缓存数据的网络传输开销。
1.3 当集中式缓存出现故障的时候;Ehcache等本地缓存依然能够支撑应用程序正常使用,增加了程序的健壮性。
1.4 使用二级缓存策略可以在一定程度上阻止缓存穿透问题。
2 使用二级缓存,注意点
2.1 堆内缓存,涉及GC。只能缓存少量热点数据。
2.2 分布式环境中,各个应用中本地缓存的同步问题。
3 二级缓存方案
3.1 结构图如下:
1、 redis做共享缓存,ehcache做本地缓存。
2、 本地缓存的 热点数据key 集合,定期放入 redis 共享缓存中。
3、 程序重启时,从共享缓存中获取数据本地缓存。
3.2 实现集群环境中的本地缓存变更同步
(一)MQ广播模式,实现分布式系统中的本地缓存同步
需消息持久化,以保证缓存程序宕机后,能接收到消息。
(二)基于zookeeper的watcher机制,
1)建立持久化节点/localCacheChange,本地缓存程序watch /localCacheChange节点的数据变更。
2)当缓存发生变化时,设置/localCacheChange节点数据为变更数据。
3)本地缓存程序,watch到/localCacheChange节点数据变更,将本地缓存更新。
(问题:以上方案,如果缓存程序宕机,无法watch到节点数据的变化。)