1. 缓存穿透
key对应的数据在数据库并不存在,每次针对次key的请求从缓存获取不到,请求都会压到数据库,从而可能压垮数据库。
比如用一个不存在的用户id来获取用户信息,缓存和数据库中都没有该条数据,黑客可能会利用此漏洞来攻击压垮数据库
目的是让服务器崩溃
1.1 出现的现象
- 应用服务器压力变大了
- redis命中率降低
- 一直查询数据库
1.2 出现的原因
- redis中没有数据,数据库中也没有
- 出现很多非正常url访问
1.3 解决方案
一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查询不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
- 对空值缓存:如果一个查询返回的数据为空(不论数据是否不存在),我们仍然把这个空结果进行缓存,设置空结果的过期时间会很短,最长不超过5分钟
- 设置可访问的名单(白名单):使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap中的id进行比较,如果访问id不在bitmaps中,进行拦截,不允许访问。
- 采用布隆过滤器:布隆过滤器实际上是一个很长的二进制向量(位图)和一系列随机映射函数(hash函数)。布隆过滤器可以检索一个元素是否在一个集合中。它的优点时空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难
- 进行实时监控:当发现redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
2. 缓存击穿
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求都会从数据库中读取并回设到缓存,这时大量的并发可能会把数据库压垮。
2.1 出现的现象
- 数据库访问压力瞬时增大
- redis中没有出现大量key过期
- redis正常运行
2.2 出现的原因
redis中某个key过期了,同时这个key被大量访问
2.3 解决方案
key可能会在某些时间点被超高并发的访问,是一种非常热门的数据。这个时候就需要考虑一个问题:缓存被击穿的问题。
- 预设热门数据:在redis访问高峰之前,把一些热门数据提前存入到redis中,加大这些热门key的时长
- 适时调整:现场监控那些数据热门,实时调整过期时间
- 使用锁:
- 在缓存失效的时候(拿出来的值为空),不是立即去load db
- 先使用缓存工具某些带成功操作返回值的操作(比如setnx)
2.4 热点key重建
- 当前key是一个热点key(例如一个热门的娱乐新闻)失效,并发量非常大,有大量的线程来重建缓存,造成后端负载太大,甚至奔溃。
- 因为重建缓存不能在短时间内完成,可能是一个复杂计算,会有复杂的SQL,多次IO等;
- 这种情况下,要避免大量线程同时重建缓存
- 通过互斥锁,只允许一个线程重建缓存,其他线程等待线程执行完,重新获取缓存数据。
3. 缓存雪崩
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求都会从数据库中读取并回设到缓存,这时大量的并发可能会把数据库压垮。
缓存雪崩和缓存击穿的区别在于缓存雪崩针对很多key缓存。
3.1 出现的现象
数据库压力变大服务器崩溃
3.2 出现的原因
在极少的时间段内,大量key的集中过期情况
3.3 解决办法
- 构建多级缓存架构:nginx缓存+redis缓存+其他缓存
- 使用锁或队列:用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
4. 缓存与数据库双写不一致
在大并发下,同时操作数据库与缓存会存在数据不一致性问题。
4.1 情况1:
线程1先写数据库、再准备写入缓存期间,线程也写了数据库、又更新了缓存;这就造成redis写覆盖;
4.2 情况2:
线程1写完数据后,删除缓存数据、然后线程3查询这条缓存数据,由于被删除了,查找不到,因此会从数据库中获取,获取到后,在准备更新缓存期间,线程2写了同一条记录,又删除了缓存,最后线程3Web层查出来的数据其实和数据库的不一致,如果把Web层查出来的数据stock更新缓存,那么得到的是脏数据。
4.3 解决方案:
- 并发很高,如果业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单等),缓存加上过期时间依然可以解决大部分业务对于缓存的要求。
- 如果不能容忍缓存数据不一致、可以通过读写锁保证并发读写或写写的时候是串行化的,读读操作相当于无锁;
- 用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入了新的中间件,增加了系统的复杂度。
我们针对读多写少的情况加入缓存可以提高性能,如果写多读多又不能容忍数据不一致,那就没必要加缓存了,直接从Web层查询MySQL,不经过Redis,可以保证一致性;
放入缓存的数据应该是对实时性、一致性要求不高的数据。