Java双重检测锁解决MySQL和Redis数据一致性问题

Java双重检测锁解决MySQL和Redis数据一致性问题

双重检测锁(Double-Checked Locking)是一种在多线程环境下优化性能的设计模式,可以用于解决MySQL和Redis之间的数据一致性问题。下面我将介绍如何实现这一方案。

问题背景

在MySQL和Redis双存储系统中,常见的一致性问题包括:

  1. 缓存穿透:查询不存在的数据,导致每次请求都打到数据库
  2. 缓存击穿:热点key失效瞬间,大量请求直接访问数据库
  3. 缓存雪崩:大量key同时失效,导致数据库压力骤增
  4. 数据不一致:数据库更新后缓存未同步

双重检测锁实现方案

public class CacheService {
    private final RedisTemplate<String, Object> redisTemplate;
    private final DataMapper dataMapper;
    private final ReentrantLock lock = new ReentrantLock();
    
    public CacheService(RedisTemplate<String, Object> redisTemplate, DataMapper dataMapper) {
        this.redisTemplate = redisTemplate;
        this.dataMapper = dataMapper;
    }
    
    public Object getData(String key) {
        // 第一次检测:查询缓存
        Object value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            return value;
        }
        
        // 加锁防止缓存击穿
        lock.lock();
        try {
            // 第二次检测:再次查询缓存(其他线程可能已经更新了缓存)
            value = redisTemplate.opsForValue().get(key);
            if (value != null) {
                return value;
            }
            
            // 查询数据库
            value = dataMapper.selectByKey(key);
            if (value != null) {
                // 写入缓存并设置过期时间
                redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
            } else {
                // 防止缓存穿透:缓存空值但设置较短过期时间
                redisTemplate.opsForValue().set(key, "", 1, TimeUnit.MINUTES);
            }
            return value;
        } finally {
            lock.unlock();
        }
    }
    
    @Transactional
    public void updateData(String key, Object newValue) {
        // 更新数据库
        dataMapper.updateByKey(key, newValue);
        
        // 删除缓存(采用延迟双删策略)
        redisTemplate.delete(key);
        
        // 异步延迟再次删除(可选,解决可能的脏数据)
        new Thread(() -> {
            try {
                Thread.sleep(500); // 延迟500ms
                redisTemplate.delete(key);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
    }
}

关键点解析

  1. 双重检测

    • 第一次检测:无锁状态下快速检查缓存
    • 第二次检测:加锁后再次检查,防止其他线程已经更新了缓存
  2. 锁机制

    • 使用ReentrantLock确保只有一个线程能执行数据库查询
    • 防止缓存击穿(热点key失效时大量请求直接访问数据库)
  3. 缓存策略

    • 缓存空值防止缓存穿透
    • 设置合理过期时间防止数据长期不一致
    • 延迟双删策略确保数据一致性
  4. 事务处理

    • 数据库更新使用@Transactional确保原子性
    • 先更新数据库再删除缓存(Cache Aside Pattern)

优化建议

  1. 可以使用分布式锁(如Redis的SETNX)替代本地锁,适用于分布式环境
  2. 考虑引入消息队列异步更新缓存,降低数据库压力
  3. 对于热点数据,可以设置永不过期,通过后台任务定期刷新
  4. 使用布隆过滤器(Bloom Filter)进一步防止缓存穿透

注意事项

  1. 锁的粒度要尽可能小,避免性能瓶颈
  2. 要考虑锁超时情况,避免死锁
  3. 双重检测锁在Java中需要配合volatile关键字使用(但在Spring管理的Bean中通常不需要)
  4. 高并发场景下需要充分测试性能表现

这种方案在大多数场景下能较好地平衡性能与一致性需求,但需要根据具体业务场景进行调整。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值