Redis缓存击穿问题

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

基于互斥锁的解决方式

原理:Redis有个命令:sexnx

centos7-192.168.163.129:0>setnx lock 1
"1"
centos7-192.168.163.129:0>setnx lock 2
"0"
centos7-192.168.163.129:0>setnx lock 3
"0"
centos7-192.168.163.129:0>get lock
"1"
centos7-192.168.163.129:0>del lock
"1"
centos7-192.168.163.129:0>get lock
null
centos7-192.168.163.129:0>setnx lock 2
"1"

setnx的时候,当key=lock,存在时,不能更新,只有不存在的时候才能set

1.0版本

/**
 * 互斥锁解决缓存击穿(热点key)
 * @param id
 * @return
 */

public Shop queryWithMutex(Long id){
    Shop shop =null;
    String key =CACHE_SHOP_KEY + id;
    String JSONStr = stringRedisTemplate.opsForValue().get(key);
    if (StringUtils.isNotBlank(JSONStr)){
        return JSONUtil.toBean(JSONStr, Shop.class);
    }
    if ("".equals(JSONStr)){
        return null;
    }
    try {
        boolean isLock = tryLock(LOCK_SHOP_KEY + id);
        if (!isLock) {
            Thread.sleep(50);
            return queryWithMutex(id);
        }
        shop = getById(id);
        Thread.sleep(200);
        if (shop ==null){
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
            return null;
        }
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,TimeUnit.MINUTES);
    } catch (InterruptedException e) {
        unLock(key);
    }
    return shop;
}

/**
 *  加互斥锁
 * @param key
 * @return
 */
public boolean tryLock(String key){
    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
    return BooleanUtil.isTrue(flag);
}

/**
 *  释放锁
 * @param key
 * @return
 */
public void unLock(String key){
    stringRedisTemplate.delete(key);
}

2.0版本,封装

/**
     * 缓存击穿-->互斥锁解决
     * @param prefix  查询的前缀
     * @param id
     * @param type
     * @param function
     * @param time
     * @param unit
     * @return
     * @param <R>
     * @param <ID>
     */
    public <R,ID> R queryWithMutex(String prefix, ID id, Class<R> type, Function<ID,R> function, Long time, TimeUnit unit  ){
        String key = prefix + id;
        String lockKey = LOCK_SHOP_KEY+id;
        String JSON = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(JSON)){
            return JSONUtil.toBean(JSON,type);
        }
        if ("".equals(JSON)){
            return null;
        }
        R r = null;
        try {
            boolean isLock = tryLock(lockKey);
            if (!isLock){
                Thread.sleep(50);
                return queryWithMutex(prefix, id, type, function, time, unit);
            }
            r = function.apply(id);
            log.debug("r--->" + r);
//        代表数据库没有需要的数据
            if (r==null){
                set(key,"", time, unit);
                return null;
            }
            set(key,JSONUtil.toJsonStr(r),time,unit);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            unLock(lockKey);
        }
        return r;
    }


/**
 * 互斥锁
 * @param key
 * @return
 */
public boolean tryLock(String key){
    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
    return BooleanUtil.isTrue(flag);
}

/**
 * 释放锁
 * @param key
 */
public void unLock(String key){
    stringRedisTemplate.delete(key);
}

基于逻辑过期的解决方式

1.0版本

/**
 * 逻辑过期处理 预热
 * @param id
 */
public void saveShopHot(Long id,Long seconds){
    RedisData redisData = new RedisData();
    redisData.setData(getById(id));
    redisData.setExpireTime(LocalDateTime.now().plusSeconds(seconds));
    stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
}
//    创建线程池
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
    /**
     * 逻辑过期解决缓存击穿
     * @param id
     * @return
     */
    public Shop queryWithExpiration(Long id){
        String JSONStr = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
//        未命中
        if (StringUtils.isBlank(JSONStr)){
            return null;
        }
//        命中
        LocalDateTime now = LocalDateTime.now();
        RedisData redisData = JSONUtil.toBean(JSONStr, RedisData.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
//            没过期,返回商铺信息
        if (expireTime.isAfter(now)){
            return shop;
        }
//            过期
        if (tryLock(LOCK_SHOP_KEY + id)){
//                获取到了锁,开启一个新线程,查询数据库,写入redies,重新设置过期时间
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    saveShopHot(id,30L);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
                    unLock(LOCK_SHOP_KEY + id);
                }
            });
        }
        return shop;
    }

2.0版本,封装

private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    /**
     * 缓存击穿,逻辑过期解决
     * @param prefix
     * @param id
     * @param type
     * @param function
     * @param time
     * @param unit
     * @return
     * @param <R>
     * @param <ID>
     */
    public <R,ID> R queryWithLogicalExpire(String prefix, ID id, Class<R> type, Function<ID,R> function, Long time, TimeUnit unit){
        String key = prefix + id;
        String lockKey = LOCK_SHOP_KEY + id;
        String JSON = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isBlank(JSON)){
            return null;
        }
        RedisData redisData = JSONUtil.toBean(JSON, RedisData.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        LocalDateTime now = LocalDateTime.now();
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        if (expireTime.isAfter(now)){
//            没过期 
            return r;
        }
//        过期
        try {
            if (tryLock(lockKey)) {
                CACHE_REBUILD_EXECUTOR.submit(()->{
                    log.debug("竞争到了");
                    R rDb = function.apply(id);
                    setWithLogicalExpire(key,rDb,time,unit);
                });
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            unLock(key);
        }
        return r;
    }

/**
  * 重载缓存
  * @param key
  * @param value
  * @param time
  * @param unit
  */
 public void set(String key, Object value, Long time, TimeUnit unit){
     stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);
 }

 /**
  * 设置逻辑过期缓存
  * @param key
  * @param value
  * @param time
  * @param unit
  */
public void setWithLogicalExpire(String key,Object value, Long time, TimeUnit unit){
    RedisData redisData = new RedisData();
    redisData.setData(value);
    redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
    stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(redisData));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值