高并发的业务场景下,实际应用 Redis 缓存时,我们经常会遇到一些异常问题。例如缓存穿透、缓存穿击、缓存雪崩、缓存污染以及缓存和数据库一致性。下文将列举一下这些常见问题的解决方案。
缓存穿透
场景
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
如果有人利用不存在的key频繁攻击我们的应用,这会导致数据库压力过大,甚至挂掉。
方案
- 缓存空值。将从缓存和数据库中都取不到的数据,设置缓存key:null,同时expireTime设置短点(设置太长会导致正常情况也没法使用)。
- BloomFilter布隆过滤器。利用布隆过滤器快速判断一个key是否存在于某容器,不存在就直接返回。
- 请求层加参数校验,把恶意请求(参数不合理、参数非法)直接过滤掉。
对于空数据的key有限的,重复率比较高的,我们则可以采用缓存空值的方式处理。对于key异常多、请求重复率比较低的数据,可以使用布隆过滤器进行过滤。
缓存击穿
场景
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
方案
- 热点数据预加载。即在缓存过期之前,提前异步地加载数据,确保缓存一直有数据。
- 加互斥锁。在第一个请求去查询数据库的时候对其加一个互斥锁,其它的查询请求都会被阻塞住,直到锁被释放。但系统吞吐量会下降,需要结合实际的业务去考虑是否要这么做。
- 接口限流与熔断降级。重要的接口要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些服务不可用时候,进行熔断,失败快速返回机制。
缓存雪崩
场景
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。例如大促时,缓存的数据可能在后续的同一时间失效。还有一种情况是部分缓存节点不可用,导致整个缓存体系甚至甚至服务系统不可用的情况。
方案
- 设置不同过期时间(基础时间+/-随机时间)。防止同一时间大量数据过期现象发生。
- 对缓存增加多个副本,缓存异常时再读取其他缓存副本,而且多个缓存副本尽量部署在不同机组,确保在任何情况下,缓存系统都会正常对外提供服务。
- 接口限流与熔断降级。重要的接口要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些服务不可用时候,进行熔断,失败快速返回机制。
缓存污染
场景
在一些场景下,有些数据被访问的次数非常少,甚至只会被访问一次。当这些数据服务完访问请求后,如果还继续留存在缓存中的话,就只会白白占用缓存空间。
如果数据占满了缓存空间,我们再往缓存中写入新数据时,就需要先把这些数据逐步淘汰出缓存,这就会引入额外的操作时间开销,进而会影响应用的性能。
方案
Redis可以采用LFU策略,使用衰减因子配置项 lfu_decay_time来控制访问次数的衰减。如果业务应用中有短时高频访问的数据的话,建议把 lfu_decay_time 值设置为 1,这样一来,LFU策略在它们不再被访问后,会较快地衰减它们的访问次数,尽早把它们从缓存中淘汰出去,避免缓存污染。
缓存和数据库一致性
参考本人另一篇博文 缓存和数据库一致性