缓存穿透
查询一个一定不存在的数据,就会一直去数据库查
解决:null结果缓存,并加入短暂过期时间,实际在配置中让spring.cache.redis.cache-null-value=true
缓存雪崩
大量key在某一时刻都失效,就会将全部请求查询放给数据库
解决:一定要加上过期时间,spring.cache.redis.time-to-live=3600000 即1小时,也可以加上随机过期时间
缓存击穿
一个热点key正好过期,但是大量请求进来,都查数据库
解决:分布式锁
2.9 分布式锁Redisson
读写锁:
改数据加写锁,写锁没释放都要等待
读数据加读锁,读锁没释放写要等待
保证一定能读到最新数据,修改期间写是排它锁(互斥锁,独享锁),读锁是共享锁
读+读:相当于无锁,并发读,只会在redis中记录好,所有当前的读锁,他们都会同时加锁成功
写+读:等待写锁释放
写+写:阻塞方式
读+写:有读锁,也要等待
but:在浏览器访问多个read,会有被阻塞的现象,在等待读,jmeter没有
闭锁:RCountDownLatch
信号量:RSemaphore
参考文档
2.10 SpringCache
基本原理(P172)
CacheManager(RedisCacheManager)>Cache(RedisCache)>Cache负责缓存的读写
可以解决:缓存穿透和缓存雪崩
不能解决:缓存击穿(默认cache配置),可以让sync=true,即加本地锁,就会进入synchronized方法
也就是,springboot对读模式有处理方法,对于写模式没有(主要加读写锁,或者写多的去数据库别缓存)
// spring中对于缓存是有个分区的概念,就是value,便于删除所有"category"分区的数据
// 代表当前方法的结果需要缓存,如果缓存中有,方法不调用,如果缓存中没有,调用方法,最后将方法的结果放入缓存
@Cacheable(key = "#root.methodName",value = {"category"},sync = true)
@CacheEvict(value = "category",key = "'getLevelFirstCategorys'")
@CacheEvict(value = "category",allEntries = true) //所有都删除
缓存数据一致性
双写模式
比如:要更新三级分类菜单,那么更新完后,就把缓存手动改掉
失效模式
比如:要更新三级分类菜单,那么更新完后,就把缓存直接删掉
上面两种其实都有漏洞,比如
双写模式下:两个请求该一个数据,第一个先到,但是第一个超慢,就会把第二个要改的数据在缓存中覆盖。可以加锁避免这种情况,只有一个写请求执行完,才放另一个进来,也可以给过期时间,就能最终一致
失效模式:三个请求,第一个写1号数据然后删,第二个是写2号数据然后删,但是时间久,第三个进来读发现没有就读数据库,读到之后准备写缓存碰巧很久,久到2号数据修改完成都删了,此时第三个请求只包含1号的修改数据,然后就把读到的缓存起来,正好漏过2号数据的修改。可以加锁避免这种情况,因为同时存在读写
一句话,经常修改的数据注解去读数据库,非要缓存的加过期时间,加读写锁,写都排队就行。在商城系统中,对于缓存的数据,不应该是实时性一致性太高的,所以只需加上过期时间,保证一天都能拿到最新数据即可,不用过度设计。