前言
其实redis作为缓存,就是为了阻止请求进入数据库;
当缓存击穿/穿透/雪崩时,就是没有成功阻止;
所以我们解决这仨问题,就是在向怎么更好的阻止请求进入数据库.
缓存击穿
redis作为缓存使用,每个key都有自己的expire时间;
当请求的key过期时,就需要去数据库查询一边,然后把数据再放入redis;
假设一个key刚好过期的时候,有几万个请求并发访问它,那么就会同时有几万个请求去访问数据库,这就是缓存击穿问题.
怎么解决?
当从redis中找不到对应数据,查询数据库之前,加一把锁 setnx,只有拿到锁的线程才去数据库查询;拿不到锁的线程,(可以睡一定时间后)自旋再去查询redis,然后setnx.
但是这样会带来问题,第一个获取到锁的线程挂了,其他线程就永远阻塞了;
这时想到,可以给锁设置一个过期时间
这样又带来新的问题,假如过期时间设置过大,其他线程就可能多等很久;假如过期时间设置过小,第一个拿到锁的线程还没操作完呢,锁过期了,第二个线程拿到锁后又执行相同的操作
这时,可以通过多线程,第一个线程拿到锁去数据库查询后,马上再开启一个线程每隔一段时间(0.5s)去观察下,如果第一个线程没挂并且对应的该key还没更新,就把锁的expire延长一些
这样虽然代码复杂了很多,但能基本解决问题啦;
这相当于通过redis实现了一个简单的分布式协调,后面通过zookeeper实现分布式锁会好一些.
缓存穿透
假设有坏人,高并发的查询系统中根本不存在的数据,大量的请求穿透到数据库,引起性能问题.
解决方案:
布隆过滤器;
客户端实现算法和bitmap数据结构,这样客户端压力比较大;
客户端实现算法,bitmap数据放到redis,这样客户端时无状态的;
redis集成BloomModule,算法和bitmap都在redis
布隆过滤器缺点是不能删除;
如果需要删除的话,可以使用布谷鸟过滤器,或者把删除的数据对应的key,在redis存一份空value
缓存雪崩
雪崩和击穿有点像,
击穿是好多请求高并发去请求一个过期的key,(感觉发生几率略小);
雪崩是大量的key同时失效,然后很多请求进来,大量的访问数据库,(有可能是定时任务每晚12点更新缓存)
解决方案:
- 日常的缓存,时点性无关,过期时间加一个随机值,不让他们同时失效
- 如果是定时任务,比如零点更新数据,并且需要在零点后读到新的值,这时候可以像缓存击穿解决方案那样,搞个锁;
或者在业务端控制,到零点时,延时一段时间再请求.
架构师一定要有全局感,解决高并发问题,不一定非要在最末的数据库/redis上下手
可以在任何一个环节,比如dns,lvs,nginx,JAVA服务端上下手
分布式锁
思路:
- setnx
- setnx expire 设置过期时间
- 守护线程延长过期时间
可以自己实现,也可以使用redission;
但最好还是通过zookeeper实现分布式锁.zookeeper没有redission快,但是它保证准确性,开发也简单.
因为已经觉得上锁了,那说名对准确度和一致性的需求 是优先于 效率的.