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);
}
相关推荐
Python自动化直播8 分钟前
自动化笔记:素材管理、命名、轮换的几条土办法
linux·运维·数据库
xiaomuzuirenzhen10 分钟前
2027校招ERP实施顾问:大数据管理与应用专业如何用SQL、BI证明业务能力
大数据·数据库
底层玩家老张28 分钟前
从副本集到分片:百亿级文档数据的KES架构边界
数据库·分布式架构·分片·架构选型·文档数据库
ss27330 分钟前
AI时代首先数据库-PostgreSQL实战笔记 | 16 PostgreSQL子查询与嵌套查询(下)
数据库·笔记·postgresql
川石课堂软件测试44 分钟前
涨薪技术|0到1学会性能测试第8课-Analysis分析图
服务器·数据库·python·sql·jmeter·mysql·容器
SelectDB技术团队1 小时前
云数仓成本结构拆解:五层计费模型与 Apache Doris 托管形态对照
大数据·数据库·elk·全文检索·snowflake
夕除1 小时前
redis--ziplist
redis
我命由我123451 小时前
SQL 检测脚本
java·数据库·sql·功能测试·单元测试·压力测试·可用性测试
Zenova EdgeOS1 小时前
工业网关数据全链路加密实战:从传输、静态数据到密钥轮
数据库·oracle·边缘计算·工业网关
太理摆烂哥2 小时前
MySQL表的约束
数据库·mysql