来一个最常见的 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) {}
});
}
}
为什么这么做?
更新 DB → 删除缓存:避免 DB 新值写好,但缓存还是旧的。
延时双删:如果有并发请求在“DB更新 → 缓存删除”之间读了一次数据并写回缓存,就可能产生脏数据。再删一次缓存,保证最终一致。
读缓存回填:只在缓存未命中时去查 DB,减少压力。
执行流程示例
假设 用户1 改名:
请求调用
updateUser(1, "Tom")
。DB 更新成功。
立即删掉 Redis 的
user:1
。假如这时另一个线程来
getUser(1)
:读缓存 miss → 去 DB 拿到最新的
Tom
→ 回填缓存。
0.5 秒后,延迟任务再次删除缓存,确保不会残留旧值。