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);
}
相关推荐
ss2735 小时前
食谱推荐系统功能测试如何写?
java·数据库·spring boot·功能测试
l1t5 小时前
DeepSeek总结的数据库外部表
数据库
m0_674294645 小时前
如何编写SQL存储过程性能对比_记录执行时间评估优化效果
jvm·数据库·python
014-code6 小时前
CompletableFuture 实战模板(超时、组合、异常链处理)
java·数据库
运气好好的6 小时前
怎样开启phpMyAdmin的操作审计日志_记录每条执行的SQL
jvm·数据库·python
それども6 小时前
DELETE 和 TRUNCATE TABLE区别
java·数据库·mysql
wenha6 小时前
数据库隔离级别
数据库·mysql·sqlserver·隔离级别
2401_871492857 小时前
Layui如何修改Layui默认的UI主题颜色(换肤功能实现)
jvm·数据库·python
Edward111111117 小时前
4.27mysql ,数据库,数据源
数据库·mysql
小徐敲java7 小时前
踩坑实录:MySQL8.0 导入SQL报错 2006 - MySQL server has gone away 完美解决
数据库·sql