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);
}
相关推荐
L·S·P4 小时前
25MB 管理 100+ 种数据库:开源轻量客户端 DBX 介绍与上手
数据库·mysql·开源·database·dbx
笃行3506 小时前
KingbaseES KSQL Developer 的连接管理、数据处理与对象管理
数据库
ha_lydms6 小时前
MaxCompute中加密与解密函数
大数据·数据库·数据仓库·hadoop·dataworks·maxcompute·odps
Shadow(⊙o⊙)6 小时前
MySQL内置函数
数据库·mysql
笃行3507 小时前
金仓 KingbaseES PL/SQL 开发全攻略:异常处理、数据类型、调试优化与过程语句
数据库
快乐的木子李8 小时前
mysql-26.7.0-linux26.7.0下载安装
数据库·mysql
excel9 小时前
prisma 如何处理数据库竞态
前端·数据库·后端
知行产研9 小时前
易控智驾:毛利率跃升17.2个百分点,矿山无人驾驶从“规模换增长“迈入“盈利拐点“
数据库·mysql
千里码aicood9 小时前
基于CART算法的图书分类系统设计与实现
数据库·算法·分类
饺子大魔王的男人10 小时前
Redis 内存和连接异常怎么排查?用 redis_exporter 搭一套可远程抓取的监控链路
数据库·redis·bootstrap