目录
几种缓存
缓存使用中存在的问题
缓存不足
缓存击穿问题(热点数据单个key)也叫缓存失效
缓存雪崩
缓存穿透
热点缓存
缓存与数据库一致性问题
缓存与数据库双写不一致
几种缓存
缓存使用中存在的问题
缓存不足
缓存击穿问题(热点数据单个key)也叫缓存失效
缓存击穿是指数据库原本有得数据,但是缓存中没有,一般是缓存突然失效了,
这时候如果有大量用户请求该数据,缓存没有则会去数据库请求,会引发数据库压力增大,可能会瞬间打垮
解决方案
1.加锁 ,在未命中缓存时,通过加锁避免大量请求访问数据库 2.不允许过期 。物理不过期,也就是不设置过期时间。而是逻辑上定时在后台异步的更新数据。 3.采用二级缓存 。L1缓存失效时间短,L2缓存失效时间长。请求优先从L1缓存获取数据,如果 未命中,则加锁,保证只有一个线程去数据库中读取数据然后再更新到L1和L2中。然后其他线 程依然在L2缓存获取数据。
缓存雪崩
缓存雪崩是指缓存中有大量的数据,在同一个时间点,或者较短的时间段内,全部过期了,这个时候请求过来,缓存没有数据,都会请求数据库,则数据库的压力就会突增,扛不住就会宕机。
解决方案
1、 缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列 的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。 这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增 加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发 集体失效的事件。 2、事前 :这种方案就是在发生雪崩前对缓存集群实现高可用,如果是使用 Redis,可以使用 主 从+哨兵 ,Redis Cluster 来避免 Redis 全盘崩溃的情况。 3、事中 :使用 Hystrix进行限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能 有一秒 2000个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑。然后去调用 我们自己开发的降级组件(降级),比如设置的一些默认值呀之类的。以此来保护最后的 MySQL 不会被大量的请求给打死。 4、事后 :开启Redis持久化机制,尽快恢复缓存集群
缓存穿透
缓存穿透是指查询一个根本不存在的数据, 缓存层和存储层都不会命中, 通常出于容错的考虑, 如果从存储层查不到数据则不写入缓存层。缓存穿透将导致不存在的数据每次请求都要到存储层去查询, 失去了缓存保护后端存储的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频 繁攻击我们的应用,这就是漏洞。
造成缓存穿透的基本原因有两个:
- 自身业务代码或者数据出现问题。
- 一些恶意攻击、 爬虫等造成大量空命中。
注意:穿透的意思是,都没有,直接一路打到数据库。
解决方案
1:接口增加业务层级的Filter,进行合法校验,这可以有效拦截大部分不合法的请求。作为第一点的补充,最常见的是使用布隆过滤器,针对一个或者多个维度,把可能存在的数据值hash到bitmap中,bitmap证明该数据不存在则该数据一定不存在,但是bitmap证明该数据存在也只能是可能存在,因为不同的数值hash到的bit位很有可能是一样的,hash冲突会导致误判,多个hash方法也只能是降低冲突的概率,无法做到避免。
2、另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回 的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的 过期时间会很短,最长不超过五分钟。
热点缓存
开发人员使用“缓存+过期时间”的策略既可以加速数据读写, 又保证数据的定期更新, 这种模式基本能够满足绝大部分需求。 但是有两个问题如果同时出现, 可能就会对应用造成致命的危害:
- 当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大。
- 重建缓存不能在短时间完成, 可能是一个复杂计算, 例如复杂的SQL、 多次IO、 多个依赖等。
在缓存失效的瞬间, 有大量线程来重建缓存, 造成后端负载加大, 甚至可能会让应用崩溃。
解决方案
我们可以利用互斥锁来解决,此方法只允许一个线程重建缓存, 其他线程等待重建缓存的线程执行完, 重新从缓存获取数据即可。
- 双重检测锁机制:
- 用分布式锁控制访问的线程
- 使用redis的setnx互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数据库。
缓存与数据库一致性问题
对于分布式高并发场景下使用缓存,优化策略
缓存与数据库双写不一致
在大并发下,同时操作数据库与缓存会存在数据不一致性问题
1、双写不一致情况
2、读写并发不一致
解决方案
1、对于并发几率很小的数据(如个人维度的订单数据、用户数据等),这种几乎不用考虑这个问题,很少会发生缓存不一致,可以给缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。
2、就算并发很高,如果业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单等),缓存加上过期时间依然可以解决大部分业务对于缓存的要求。
3、如果不能容忍缓存数据不一致,可以通过加读写锁保证并发读写或写写的时候按顺序排好队,读读的时候相当于无锁。
4、也可以用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入了新的中间件,增加了系统的复杂度。
总结:
以上我们针对的都是读多写少的情况加入缓存提高性能,如果写多读多的情况又不能容忍缓存数据不一致,那就没必要加缓存了,可以直接操作数据库。放入缓存的数据应该是对实时性、一致性要求不是很高的数据。切记不要为了用缓存,同时又要保证绝对的一致性做大量的过度设计和控制,增加系统复杂性!
文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览91268 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!