【Redis】缓存雪崩问题及解决思路

缓存雪崩是指在同一时段大量的缓存key同时失效(例如过期)或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

1653327884526

解决方案:

  • key同时失效,说明它们的ttl一起到期了,为了避免它们ttl同时到期,我们可以给不同的Key的TTL添加随机值

    我们平时在做缓存的时候,有时候为了做缓存的预热,我们可能提前把数据库中的数据给它导入到我们的缓存中,即批量导入。

    在批量导入的过程中,因为是同一时刻导入的,它们的ttl设置是一样的值,很有可能将来时间一到,所有的都一起过期了,那么就会出现雪崩。为了解决这个问题,我们可以给ttl的后面跟上一个随机数。

    例如我们的时间是30分钟有效期,然后我们再在后面跟上一个随机的1~5的随机数,此时它的有效期就会在30~35之间有一个波动,当然你也可以扩大这个随机数,这样的话就可以让这些key过期的时间分散在一个时间段内,而不是一起失效。

上面是随机数失效的解决方法,这种还好,添加一个随机数,谁都会做。但redis宕机导致的问题才是最严重的,基于这个问题我们该如何解决呢?首先我们可能要尽可能避免redis宕机,即提高redis的高可用性,要想提高redis的高可用性,我们就必须借助于redis的这种集群、哨兵机制,redis哨兵可以实现服务的监控。

例如我们可以先搭建redis集群形成主从,如果有主宕机,哨兵此时可以自动从 从机中选出一个来替代原来的主,这样就可以确保reids能够一直正常的对外提供服务,而且我们的主从还可以实现数据的同步,如果主宕机了,从上面还会有数据,也不会导致数据的丢失,所以这样就可以在很大程度上保证redis的高可用性,这就是我们解决宕机导致的一种思路,即集群。

  • 利用Redis集群提高服务的可用性

第三个,我们还可以

  • 给缓存业务添加降级限流策略

    例如出现了人们无法抗拒的,超级严重的事故,导致整个服务器挂了,整个机房挂了,结果整个redis集群都完蛋了,此时就可以给服务添加一些降级、限流的策略了,即提前做好一些容错处理,当我们发现redis出现故障时,我们应该及时的去做服务降级(快速失败,拒绝服务,而不是将请求加到数据库中去),牺牲部分服务,保护数据库健康。(也是SpringCloud中的)

第四个,我们还可以

  • 给业务添加多级缓存,保证业务安全

所谓多级缓存:之前讲过,缓存的使用场景是多种多样的,不仅仅是说可以在应用层添加,请求从浏览器发出,浏览器是可以添加缓存的,但是浏览器的缓存一般缓存的是静态的数据,而对于需要从数据库中查询的动态数据是无法做缓存的,对于这部分我们还可以在反向代理服务器Nginx层面做缓存,Nginx缓存未命中,再去找我们的redis,redis未命中,到达我们的JVM,我们还可以在JVM内部建立本地缓存,最后是数据库,即在多个层面建立缓存,这样redis这一环崩了,还有很多别的缓存可以去弥补。(也是SpringCloud中的)

### Java 实现 Redis 缓存雪崩解决方案 缓存雪崩是指大量缓存数据在同一时间过期,导致请求直接打到数据库上,造成数据库压力骤增。为了避免这种情况发生,可以通过多种方式来缓解这个问题。 #### 方法一:设置不同的过期时间 通过给不同键设置随机的过期时间,可以避免所有缓存同时失效的情况。这可以在一定程度上减少因缓存集中失效带来的冲击[^1]。 ```java public class RedisCacheUtil { private static final int MIN_EXPIRE_TIME = 60; // 单位秒 private static final int MAX_EXPIRE_TIME = 300; /** * 设置带有随机过期时间的数据到Redis中 */ public void setWithRandomExpireTime(String key, String value) { Random random = new Random(); int expireSeconds = random.nextInt(MAX_EXPIRE_TIME - MIN_EXPIRE_TIME + 1) + MIN_EXPIRE_TIME; Jedis jedis = getJedisConnection(); // 获取连接方法需自行实现 try { jedis.setex(key, expireSeconds, value); } finally { closeJedis(jedis); // 关闭资源的方法也需自行实现 } } } ``` #### 方法二:锁机制防止击穿 当某个热点key即将过期时,先尝试获取分布式锁再更新该key对应的value值并重置其TTL(生存周期)。这样即使有多个线程试图刷新同一个key也不会引发连锁反应[^2]。 ```java public boolean refreshKeyIfExpired(String key){ Jedis jedis = null; try{ jedis = pool.getResource(); Long ttl = jedis.ttl(key); if(ttl!=null && ttl<=0){ // 如果ttl小于等于零则表示已经到期或者不存在此key // 尝试获得锁 String lockKey = "lock:" + key; long acquireLockTimeoutMs = 5_000L; if(!jedis.setnx(lockKey,"locked")){ return false; // 已经被其他客户端锁定,则返回false不处理 } // 入超时控制防死锁 jedis.expire(lockKey,(int)(acquireLockTimeoutMs/1000)); Object newValue = loadValueFromDBByKey(key); // 假设这是载新值的方式 if(newValue != null){ jedis.setex(key,DEFAULT_TTL,newValue.toString()); // 解除锁 jedis.del(lockKey); return true; } } }catch(Exception e){ log.error("Error occurred while refreshing expired key.",e); }finally{ if(jedis!=null){ jedis.close(); } } return false; } ``` #### 方法三:使用布隆过滤器预判是否存在 在查询之前利用布隆过滤器快速判断某条记录是否可能存在于缓存数据库内。如果布隆过滤器认为存在,则继续访问;反之可以直接告知用户未找到相应信息而无需进一步查找[^3]。 ```java // 这里仅提供思路示意,具体实现依赖于所使用的库 BloomFilter<String> bloomFilter = BloomFilters.create(...); if (!bloomFilter.mightContain(cacheKey)) { System.out.println("The item is definitely not present."); } else { // 可能存在,仍需实际验证 ... } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值