缓存击穿问题

缓存击穿发生在访问热点数据,大量请求访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用。

解决方案:

1、使用同步锁或分布式锁控制。

2、热点数据永不过期。

3、缓存预热,分为提前预热、定时预热

4、降级处理

1. 什么是缓存击穿

缓存击穿是指当大量的请求同时访问某个热点数据,而该数据在缓存中失效或到期的瞬间,所有请求同时绕过缓存直接请求数据库,导致数据库瞬间负载过高,严重时可能导致数据库宕机或服务崩溃。

缓存击穿通常发生在以下场景:

  • 某个热点数据有大量的请求,而这个数据正好在缓存中失效。
  • 缓存失效后,所有请求同时到达数据库,导致数据库压力瞬间暴增。

2. 解决方案

2.1 使用同步锁或分布式锁控制

为了解决缓存失效后多个线程同时访问数据库的问题,可以通过加锁来控制访问。在数据失效时,只有一个线程能去数据库查询,其他线程等待这个线程将数据加载到缓存后,再从缓存读取数据。常见的两种锁机制有:

(1)单体架构下(单进程内)可以使用同步锁控制查询数据库的代码,只允许有一个线程去查询数据库,查询得到数据库存入缓存。

synchronized(obj){
  //查询数据库
  //存入缓存
}

(2)分布式架构下(多个进程之间)可以使用分布式锁进行控制。分布式锁确保同一时刻只有一个实例可以去数据库查询,其他实例等待查询结果。

// 获取分布式锁对象
RLock lock = redisson.getLock("myLock");
try {
    // 尝试加锁,最多等待100秒,加锁后自动解锁时间为30秒
    boolean isLocked = lock.tryLock(100, 30, java.util.concurrent.TimeUnit.SECONDS);
    if (isLocked) {
          //查询数据库
          //存入缓存
    } else {
        System.out.println("获取锁失败,可能有其他线程持有锁");
    }
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    // 释放锁
    lock.unlock();
    System.out.println("释放锁...");
}

2.2 热点数据永不过期

对于一些非常重要且访问量大的数据,可以通过设置这些热点数据永不过期的方式,避免其从缓存中失效。这样的话,缓存击穿的问题就不会发生。

redisTemplate.opsForValue().set("hotData", value, Duration.ofDays(Long.MAX_VALUE));

需要注意的是,虽然设置了永不过期,但还是要设计相应的后台机制来手动更新缓存中的数据,以保持数据的及时性。

2.3 缓存预热

缓存预热是指在系统启动或缓存失效之前,提前将热点数据加载到缓存中,避免缓存失效时发生大量请求同时访问数据库的情况。缓存预热可以通过以下两种方式实现:

(1)提前预热:在系统上线前,提前将已知的热点数据加载到缓存中。通常可以通过后台程序或者脚本,预先将这些热点数据从数据库中查询出来并缓存。

@PostConstruct
public void preheatCache() {
    List<User> hotUsers = database.getHotUsers();  // 获取热点数据
    for (User user : hotUsers) {
        cache.put(user.getId(), user);  // 预热缓存
    }
}

(2)定时预热:对于某些数据可以设置定时任务,在缓存即将到期时,自动刷新缓存,避免缓存过期导致的击穿。

@Scheduled(fixedDelay = 60000)  // 每隔一分钟执行一次
public void refreshCache() {
    List<User> hotUsers = database.getHotUsers();
    for (User user : hotUsers) {
        cache.put(user.getId(), user);  // 更新缓存
    }
}

2.4 降级处理

当缓存失效并且数据库负载过高,系统可选择进行降级处理,避免继续增加数据库压力。这种策略可以通过以下方式实现:

  • 返回默认值:当缓存和数据库都不可用时,返回一些默认的静态数据,或者告知用户系统繁忙稍后再试,避免对数据库造成更大压力。

  • 限流:在缓存失效时,对某些重要的接口进行限流,只允许一部分请求通过,其余的请求返回降级响应。这样可以保护数据库不被大量请求压垮。

@Service
public class UserService {

    private RateLimiter rateLimiter = RateLimiter.create(10);  // 每秒最多允许10个请求

    public User getUserById(Long id) {
        if (!rateLimiter.tryAcquire()) {
            return new User();  // 降级,返回默认值
        }

        User user = cache.get(id);
        if (user == null) {
            user = database.getUserById(id);
            cache.put(id, user);
        }
        return user;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cyt涛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值