Redis + MySQL 的缓存一致性

发布于:2025-09-13 ⋅ 阅读:(17) ⋅ 点赞:(0)

来一个最常见的 Redis + MySQL 的缓存一致性例子,用 旁路缓存(Cache Aside)+ 延时双删策略。这样你能直观看到“缓存一致性怎么玩”。


场景

  • 用户表 user(id, name, age)

  • 我们在 Redis 里缓存用户信息,key 规则是:user:{id}

  • 目标:保证 更新用户信息时,缓存不会读到旧值


代码示例(Spring + RedisTemplate + MyBatis)

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper; // MyBatis接口,操作MySQL

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    private String buildCacheKey(Long userId) {
        return "user:" + userId;
    }

    // 读操作:先读缓存,没命中再查DB并回填
    public User getUser(Long userId) {
        String key = buildCacheKey(userId);
        User user = (User) redisTemplate.opsForValue().get(key);
        if (user != null) {
            return user;
        }
        // 缓存没命中 -> 查DB
        user = userMapper.findById(userId);
        if (user != null) {
            redisTemplate.opsForValue().set(key, user, 10, TimeUnit.MINUTES);
        }
        return user;
    }

    // 写操作:更新DB + 删除缓存(延时双删)
    @Transactional
    public void updateUser(User user) {
        // 1. 先更新数据库
        userMapper.update(user);

        String key = buildCacheKey(user.getId());

        // 2. 立即删除缓存
        redisTemplate.delete(key);

        // 3. 延时双删,避免并发脏读
        executor.submit(() -> {
            try {
                Thread.sleep(500); // 延迟0.5秒
                redisTemplate.delete(key);
            } catch (InterruptedException ignored) {}
        });
    }
}

为什么这么做?

  1. 更新 DB → 删除缓存:避免 DB 新值写好,但缓存还是旧的。

  2. 延时双删:如果有并发请求在“DB更新 → 缓存删除”之间读了一次数据并写回缓存,就可能产生脏数据。再删一次缓存,保证最终一致。

  3. 读缓存回填:只在缓存未命中时去查 DB,减少压力。


执行流程示例

假设 用户1 改名:

  1. 请求调用 updateUser(1, "Tom")

  2. DB 更新成功。

  3. 立即删掉 Redis 的 user:1

  4. 假如这时另一个线程来 getUser(1)

    • 读缓存 miss → 去 DB 拿到最新的 Tom → 回填缓存。

  5. 0.5 秒后,延迟任务再次删除缓存,确保不会残留旧值。