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);
}
相关推荐
不像程序员的程序媛4 小时前
系统cpu内存负载资源分析
运维·服务器·数据库
Jane Chiu5 小时前
Oracle DBMS_REDEFINITION(在线重定义)
数据库·oracle·分区表
全栈前端老曹5 小时前
【MongoDB】安全与权限管理 —— 用户认证、角色权限、SSL 加密
前端·javascript·数据库·安全·mongodb·nosql·ssl
怕孤单的草丛5 小时前
缓存管理面临的主要问题
java·数据库·缓存
我会尽全力 乐观而坚强6 小时前
MySQL零基础入门(二)
数据库·mysql·adb
kali-Myon7 小时前
某校园门禁系统高危 SQL 注入漏洞挖掘复盘
数据库·sql·安全·web安全
程序员在囧途8 小时前
likeadmin-api API 中转站怎么做统一报价?从 /pricing、/user/balance 到 task_id 回执的落地方法
运维·服务器·数据库·likeadmin-api·api中转站·token计费
龙石数据9 小时前
MySQL 全量同步到 Hive 怎么做?三步配置教程
数据库·hive·mysql·数据治理·数据中台
程序员在囧途10 小时前
likeadmin-api API 算力超市怎么做供应商切换?统一鉴权、task_id 和 callback_url 才能稳交付
java·服务器·数据库·开放api·likeadmin-api·api算力超市