redis缓存击穿

分布式锁+lua解锁,原子性

拿java来做对比,工具类

java 复制代码
@Component
public class RedisUtil {

    private static StringRedisTemplate redisTemplate;

    @Autowired
    public void setRedisTemplate(StringRedisTemplate redisTemplate) {
        RedisUtil.redisTemplate = redisTemplate;
    }

    private static final String LOCK_PREFIX = "lock:";
    private static final String NULL_MARKER = "NULL"; // 空值标记

    // 雪花算法生成唯一锁值
    private static String lockValue() {
        return IdUtil.getSnowflakeNextIdStr();
    }

    // 加锁
    public static String lock(String key, int expireSeconds) {
        String lockKey = LOCK_PREFIX + key;
        String value = lockValue();
        Boolean success = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, value, expireSeconds, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success) ? value : null;
    }

    // 解锁(Lua 原子)
    public static void unlock(String key, String value) {
        String lockKey = LOCK_PREFIX + key;
        String lua = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                     "then return redis.call('del', KEYS[1]) " +
                     "else return 0 end";

        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(lua);
        script.setResultType(Long.class);
        redisTemplate.execute(script, Collections.singletonList(lockKey), value);
    }

    // 获取(自动处理空值标记)
    public static <T> T get(String key, Class<T> clazz) {
        Object value = redisTemplate.opsForValue().get(key);
        if (value == null) {
            return null;
        }
        if (NULL_MARKER.equals(value)) {
            return null; // 空值缓存,返回 null
        }
        return objectMapper.convertValue(value, clazz);
    }

    // 存数据
    public static void set(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    // 存空值(缓存穿透保护)
    public static void setNull(String key, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, NULL_MARKER, timeout, unit);
    }

    // 缓存数据(自动序列化)
    private static final ObjectMapper objectMapper = new ObjectMapper()
            .registerModule(new JavaTimeModule());

    public static void set(String key, Object value, long timeout, TimeUnit unit) {
        try {
            String json = objectMapper.writeValueAsString(value);
            redisTemplate.opsForValue().set(key, json, timeout, unit);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("序列化失败", e);
        }
    }
}

业务层

java 复制代码
public Product getProduct(Long productId) {
    String cacheKey = "product:info:" + productId;
    String lockKey = "lock:product:" + productId;

    // 1. 先查缓存(包含空值缓存)
    Product product = redisUtil.get(cacheKey, Product.class);
    if (product != null) {
        return product;
    }

    // 2. 自旋重试
    int maxRetry = 5;
    for (int i = 0; i < maxRetry; i++) {
        // 再次检查缓存
        product = redisUtil.get(cacheKey, Product.class);
        if (product != null) {
            return product;
        }

        // 3. 获取分布式锁
        String lockValue = redisUtil.lock(lockKey, 10);
        if (lockValue != null) {
            try {
                // 双重检查
                product = redisUtil.get(cacheKey, Product.class);
                if (product != null) {
                    return product;
                }

                // 4. 查数据库
                product = productMapper.selectById(productId);

                // 5. 缓存结果
                if (product != null) {
                    redisUtil.set(cacheKey, product, 30, TimeUnit.MINUTES);
                } else {
                    // 缓存空值,短过期
                    redisUtil.setNull(cacheKey, 1, TimeUnit.MINUTES);
                }

                return product;

            } finally {
                redisUtil.unlock(lockKey, lockValue);
            }
        }

        // 6. 等待重试(指数退避)
        try {
            Thread.sleep(50L * (i + 1));
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    // 7. 兜底:直接查库
    return productMapper.selectById(productId);
}
相关推荐
青柠代码录10 分钟前
【Redis】数据类型:Stream
redis
Bert.Cai21 分钟前
Oracle INSTR函数详解
数据库·oracle
Yeats_Liao1 小时前
Feed流系统设计(三):数据模型与存储设计,从表结构到Redis收件箱
java·javascript·redis
IronMurphy2 小时前
【算法五十七】146. LRU 缓存
算法·缓存
茉莉玫瑰花茶2 小时前
综合案例 - AI 智能租房助手 [ 5 ]
服务器·数据库·人工智能·python·ai
ywl4708120872 小时前
jwt生产token,简单版helloworld
java·数据库·spring
器灵科技2 小时前
AI视频工具实测:Seedance/可灵/HappyHorse谁最能打?
java·运维·数据库·人工智能·github
huangdong_3 小时前
京东商品图片视频批量下载与m3u8视频合并技术完整实现方案
大数据·前端·数据库
倒流时光三十年3 小时前
PostgreSQL CASE 条件表达式详解
数据库·postgresql
字节跳动数据平台3 小时前
营销视频进入工业化时代,火山引擎多模态数据湖如何助力多米实现内容生产提效 100+ 倍
数据库