Redis缓存问题详解和处理

缓存更新策略


在这里插入图片描述

缓存穿透


缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库.

  • 常见的解决方案:
    • 缓存空对象
      • 优点: 实现简单, 维护方便
      • 缺点: 额外的内存消耗, 可能造成短期的不一致
    • 布隆过滤
      • 优点: 内存占用较少(保存的是数据的二进制位)
      • 缺点: 实现复杂, 存在误判可能

在这里插入图片描述

  • 缓存空对象业务逻辑
    在这里插入图片描述
  • 写入空值
redisTemplate.opsForValue().set("item:1001", "", 30L, TimeUnit.SECONDS);

缓存雪崩


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

解决方案:

  • 给不同的key的TTL添加随机值
  • 利用Redis集群提高服务的可用性
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存

缓存击穿


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

常见的解决方案:

  • 互斥锁
  • 逻辑过期

在这里插入图片描述

互斥锁

在这里插入图片描述

  • 定义获取锁和释放锁的方法
	private boolean tryLock(String key){
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10L, TimeUnit.SECONDS);
        return BooleanUtils.isTrue(flag); 
    }
    private void unlock(String key){
        redisTemplate.delete(key);
    }

在这里插入图片描述
在这里插入图片描述

逻辑过期

在这里插入图片描述

  • 逻辑过期业务逻辑

在这里插入图片描述

  • 缓存预热

在这里插入图片描述

  • RedisData.java
@Data
public class RedisData {

    private LocalDateTime expireTime;

    private Object data;

}
  • 判断是否逻辑过期

在这里插入图片描述

  • 开启独立线程重建缓存

在这里插入图片描述

封装Redis工具类


缓存工具类


import cn.hutool.json.JSONUtil;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Component
public class CacheClient {

    private final StringRedisTemplate stringRedisTemplate;

    private static final long NULL_TTL = 1L;

    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public void set(String key, Object value, Long time, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    public void delete(String key) {
        stringRedisTemplate.delete(key);
    }

    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)));
        // 写入redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }


    /**
     * 查询--缓存穿透处理
     */
    public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Long time, TimeUnit unit, Function<ID, R> dbFallBack) {
        String key = keyPrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)) {
            // 存在,直接返回
            return JSONUtil.toBean(json, type);
        }
        // 判断命中的是否为空值,缓存穿透处理会存""
        if (json != null) {
            // 返回一个错误信息
            return null;
        }
        R r = dbFallBack.apply(id);
        if (r == null) {
            // 将空值写入redis, 缓存穿透处理
            stringRedisTemplate.opsForValue().set(key, "", NULL_TTL, TimeUnit.SECONDS);
            return null;
        }
        // 存在, 写入redis
        this.set(key, JSONUtil.toJsonStr(r), time, unit);
        return r;
    }

}

@Data
class RedisData {
    // 逻辑过期时间
    private LocalDateTime expireTime;
    private Object data;
}

分布式锁类

  • ILock.java
public interface ILock {

    /**
     * 尝试获取锁
     * @param timeoutSec
     * @return
     */
    boolean tryLock(long timeoutSec);

    /**
     * 释放锁
     */
    void unlock();
}

  • SimpleRedisLock.java
import cn.hutool.core.lang.UUID;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

public class SimpleRedisLock implements ILock {

    private StringRedisTemplate stringRedisTemplate;
    private String name;
    private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";


    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }


    @Override
    public boolean tryLock(long timeoutSec) {
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unlock() {
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
        if (threadId.equals(id)) {
            // 释放锁
            stringRedisTemplate.delete(KEY_PREFIX + name);
        }
    }
}
  • 使用分布式锁

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值