如何解决Redis缓存击穿(失效)、缓存穿透、缓存雪崩?
本文来自码哥 Byte(ID:MageByte)经原公众号授权转载
原数据存储在DB(如MySQL等)中,但基于Read DB写入性能低。高延迟。
例如MySQL在4核8G上TPS=5000,QPS=10000左右,平均读写时间需要10~100ms。
使用Redis作为缓存系统只能弥补DB的缺点。 “码哥”在他的 MacBook Pro 2019 上进行了 Redis 性能测试,结果如下:
$ redis-benchmark -t set,get -n 100000 -q
SET: 107758.62 requests per second, p50=0.239 msec
GET: 108813.92 requests per second, p50=0.239 msec
TPS 和 QPS 达到了 10 万,所以我们引入了缓存架构。原始数据存储在数据库中,副本存储在缓存中。
当有请求进来时,首先从缓存中收集数据,如果有,则立即返回缓存中的数据。
如果缓存中没有数据,则从数据库中读取数据写入缓存中,然后返回结果。
完美吗?错误的缓存设计会导致严重的后果。本文将介绍缓存使用中三个常见问题及解决方案:
- 缓存击穿(失效);
- 缓存穿透;
- 缓存雪崩。
缓存击穿(失效)
高并发流量,访问的数据是热点数据,请求的数据存在于DB中,但Redis中存储的副本已过期,后端必须从DB中加载数据并写入雷迪斯。
关键词:热点数据单一、竞争激烈、数据故障
但由于竞争激烈,可能会导致DB不堪重负,导致服务不可用。如下图所示:
缓存故障
解决方案
过期时间+随机值
对于热点数据,我们不设置过期时间,这样所有的请求都可以在缓存中处理,足以受益于Redis的高吞吐量性能。
或者在过期时间上添加一个随机值。
设计缓存过期时间时,使用公式:过期时间=贝斯时间+随机时间。
即同一个业务数据写入缓存时,在基准过期时间上添加一个随机过期时间,让数据在未来慢慢过期,避免瞬间过期,造成过大的压力dDB
预热
将热门数据提前存储在Redis上,并将热门数据的过期时间设置为较大的值。
使用锁
如果发现缓存无效,则不会直接从数据库加载数据。
而是先获取分布式锁,成功获取锁后,再执行数据库查询并将数据写入缓存。如果获取锁失败,则说明当前有一个线程在执行数据库查询操作,当前线程会休眠一段时间再重试。
这个只允许一个请求去数据库读取数据。
伪代码如下:
public Object getData(String id) {
String desc = redis.get(id);
// 缓存为空,过期了
if (desc == null) {
// 互斥锁,只有一个请求可以成功
if (redis(lockName)) {
try
// 从数据库取出数据
desc = getFromDB(id);
// 写到 Redis
redis.set(id, desc, 60 * 60 * 24);
} catch (Exception ex) {
LogHelper.error(ex);
} finally {
// 确保最后删除,释放锁
redis.del(lockName);
return desc;
}
} else {
// 否则睡眠200ms,接着获取锁
Thread.sleep(200);
return getData(id);
}
}
}
缓存穿透
缓存穿透:表示有一个特殊的请求请求一个不存在的数据,即该数据在Redis中不存在而使其数据库中不存在。
这样一来,每次请求都会穿透数据库,缓存就成了摆设,会给数据库带来很大的压力,影响正常服务。
如图:
缓存穿透
解决方案
- 缓存空值:如果请求的数据在Redis或数据库中不存在,则设置一个默认值(例如:None)。当后续再次请求时,立即返回空值或默认值。
- Bloom Filter:当数据写入数据库时,该ID会与Bloom Filter同步。如果请求的ID在布隆过滤器中不存在,则说明查询请求的数据不在数据库中。保存它而不查询数据库。
BloomFilter 必须缓存所有的 key,这需要少量的 key,最好少于 100 亿条数据,因为 100 亿条数据大约需要 3.5 GB 内存。
“我们来说说布隆过滤器的原理
”
布隆过滤器的算法是首先分配一块内存空间作为位数组,并设置该位数组的所有位的初始值数组为0。
添加元素时,使用k个独立的哈希函数进行计算,然后将元素哈希图的所有K个位置设置为1。
检测key是否存在。还是用这k个哈希函数来计算k个位置。如果位置全为1,则表示该key存在,否则不存在。
如下图所示:
Bloomfilter
哈希函数会产生冲突,因此布隆过滤器会出现错误的判断。
这里的误报率指的是BloomFilter判断某个key存在但实际上不存在的概率,因为它存储的是key的hash值,而不是key的值。
所以有一定概率存在这样的钥匙。它们的内容不同,但是多次哈希后的哈希值是相同的。
对于 BloomFilter 判定不存在的 key,则其 100% 不存在。反之,如果这个key存在,那么每次hash后对应的hash值位置一定是1,而不是0。布卢姆的过滤判断并不一定真的存在。
缓存雪崩
缓存雪崩是指大量请求无法在Redis缓存系统中处理,所有请求都击中数据库,导致数据库压力激增甚至宕机。
造成这种情况的原因主要有两个:
- 大量热点数据同时过期,导致大量请求必须查询数据库并写入缓存;
- Redis错误宕机,缓存系统异常。
缓存大量数据同时过期。
系统将所有请求发送到数据库以获取数据。如果竞争量大,数据库的压力就会增大。
当大量数据同时失效时会发生缓存雪崩,而当数据在某个热点失效时会发生缓存击穿(失效)。这是他们之间最大的区别。
如下图:
缓存雪崩——大量缓存同时大量数据过期,过期时间=baes时间+随机时间(小随机数,比如随机增加1到5分钟) )。
这样所有热点数据不会同时过期,过期时间也不会相差太大,既保证过期时间相近,又满足业务需求。
接口限流
访问核心数据以外的数据时,在请求方法中添加接口限流保护。例如,设置 10000 Req/s。
访问核心数据接口时,不存在缓存以允许缓存来自数据库的查询。
这种情况下,只发送部分请求到数据库,减少了压力。
限流是指我们在业务系统的请求入口前端控制每秒进入系统的请求数量,避免向数据库发送过多的请求。
如下图所示:
当前限制缓存雪崩
Redis错误
Redis实例可以支持10万QPS,而数据库实例只能支持.
一旦Redis宕机,大量请求就会冲击数据库,导致缓存雪崩。
解决方案
缓存系统故障导致的缓存雪崩有两种解决方案:
- 服务融合和接口限流;
- 构建高可用的缓存集群系统。
业务断路器和限流
在业务系统中,业务断路器用于竞争激烈,降低业务供给,保证系统可用性。
服务熔断是指如果从缓存中检索数据时发现异常,则立即将错误数据返回到前端,以防止任何流量冲击数据库而导致停机。
业务断路器和限流是缓存雪崩发生时减少雪崩对数据库影响的解决方案。
搭建高可用缓存集群
因此,缓存系统必须搭建Redis高可用集群,如《Redis 哨兵集群》或《Redis Cluster 集群》。如果Redis的主节点崩溃宕机,从节点仍然可以切换。成为主节点,继续提供缓存服务,避免缓存实例宕机导致的缓存雪崩问题。
总结
- 缓存穿透是指数据库没有这个数据,查询直接到数据库,缓存系统就没用了。
- 缓存击穿(失效)是指数据库有数据,缓存也应该有数据,但是缓存已经过期了。 Redis的流量保护屏障已经被打破,请求直接进入数据库。
- 缓存雪崩是指大量热点数据无法在Redis缓存中处理(大区热点数据缓存失效、Redis宕机),全部流量打到数据库,对数据库造成较大压力。
https://segmentfault.com/a/1190000039688578
https://cloud.tencent.com/developer/article/1824584n
https://time.geekbang.org /
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网