Redis 穿透 击穿 雪崩 认识

本文介绍了Redis的穿透、击穿、雪崩问题。穿透指key在库中不存在,请求全到数据库;击穿是key数据存在但Redis过期,大量并发请求压垮数据库;雪崩是缓存重启或大量集中失效给后端系统带来压力。同时给出了相应的解决方案。

穿透
key对应的数据在库中并不存在,每次针对此key的请求从缓存获取不到,请求都会到库中,每次都会进行请求,从而可能压垮数据库.
比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
击穿:key对应的数据存在,但在redis中过期了,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端数据库进行加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端数据库压垮。
雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如数据库)带来很大压力。

解决方案

穿透

采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。
对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。

击穿

利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。
也可以用判断当这个数据刚好失效时在访问时就加个条件只允许某一个数据进行访问,当缓存中有了这条数据后在允许大量数据并发访问
雪崩

在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀,也可以加个随机的过期时间,这样就不会出现大批量过期事件。

在Java与Redis的应用场景中,缓存击穿、缓存穿透、缓存雪崩是影响系统性能与稳定性的重要问题,下面分别介绍它们的原理及解决方案。 ### 缓存穿透 原理:指客户端请求的数据在缓存和数据库中都不存在,这样每次请求都会穿透缓存,直接访问数据库,当大量此类请求到来时,会对数据库造成巨大压力。 解决方案: - 缓存空对象:当数据库查询结果为空时,将空对象存入缓存,下次相同请求直接从缓存获取空对象,避免再次查询数据库。 - 布隆过滤器:在请求到达缓存之前,使用布隆过滤器判断请求的数据是否可能存在。如果布隆过滤器判定数据不存在,则直接返回,避免对缓存和数据库的无效访问。 - id格式校验:对请求的id进行格式校验,过滤掉明显非法的请求,减少无效请求对系统的影响 [^1][^2][^4]。 ### 缓存击穿 原理:部分热门key过期时,大量请求同时访问该key,由于缓存中该key已失效,这些请求会直接穿透到数据库,给数据库带来巨大压力。 解决方案: - 使用锁:对查询数据库的操作加锁,同一时间只有一个请求可以查询数据库,其他请求等待,查询到数据后存入缓存,后续请求可从缓存获取数据。 - 逻辑过期方案:不设置缓存的过期时间,而是在缓存对象中添加一个逻辑过期时间字段。当访问缓存时,检查逻辑过期时间,如果已过期,使用异步线程更新缓存,当前请求返回旧数据。 - 永不过期,主动更新:对于热门key,不设置过期时间,通过定时任务或其他机制主动更新缓存数据。 - 接口限流:对访问该key的接口进行限流,控制并发请求数量,避免大量请求同时穿透到数据库 [^1][^2][^4]。 ### 缓存雪崩 原理:在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,给数据库带来巨大压力 [^1][^2][^3]。 解决方案: - 分散过期时间:为不同的缓存key设置不同的过期时间,避免大量key在同一时间过期。 - 高可用架构:搭建Redis集群,提高Redis服务的可用性,避免因单点故障导致Redis服务宕机。 - 数据预热:在系统启动前,将一些热门数据提前加载到缓存中,减少缓存失效时的压力。 - 熔断降级:当数据库压力过大时,采取熔断降级策略,如返回默认值或提示信息,避免数据库被压垮 [^1][^2][^3][^4]。 以下是使用Java和Redis实现缓存穿透中缓存空对象的示例代码: ```java import redis.clients.jedis.Jedis; public class CachePenetrationExample { private static final String REDIS_HOST = "localhost"; private static final int REDIS_PORT = 6379; private static final String KEY_PREFIX = "data:"; public static String getData(String id) { Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT); String key = KEY_PREFIX + id; String data = jedis.get(key); if (data == null) { // 模拟从数据库查询数据 String dbData = queryFromDatabase(id); if (dbData == null) { // 缓存空对象,设置较短的过期时间 jedis.setex(key, 60, ""); } else { // 缓存查询到的数据 jedis.set(key, dbData); } data = dbData; } jedis.close(); return data; } private static String queryFromDatabase(String id) { // 模拟数据库查询,这里返回null表示数据不存在 return null; } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值