29人参与 • 2026-07-27 • Redis
缓存(redis)和数据库(mysql)是两个独立的存储。更新数据时,就像同时给两座房子送信。
核心结论:我们只能追求“最终一致”(短暂允许不一样,但很快修复),无法低成本实现绝对强一致。
假设两个线程同时更新数据:
而“删除缓存”则没有这个烦恼:管你谁先谁后,删掉就没了。下次读取时,读库再回填,保证回填的是最新的 db 值。
极罕见情况下:
解决办法:延迟双删。即在更新 mysql 并删除 redis 后,等待 1~2 秒(等待可能的并发读回填完成),再删除一次 redis,把旧值清理掉。
先更新 db,事务提交后删除缓存,并设置 ttl 兜底。
代码示例:
@service
@slf4j
public class userservice {
@autowired
private userdao userdao;
@autowired
private redistemplate<string, object> redistemplate;
private static final string cache_key = "user:";
// 查询:旁路缓存
public user getbyid(long id) {
string key = cache_key + id;
// 1. 查缓存
user user = (user) redistemplate.opsforvalue().get(key);
if (user != null) {
return user;
}
// 2. 查数据库
user = userdao.selectbyid(id);
if (user != null) {
// 3. 回填缓存,必须设置过期时间(兜底)
redistemplate.opsforvalue().set(key, user, 30, timeunit.minutes);
}
return user;
}
// 更新:先更新db,后删缓存
@transactional
public void updateuser(user user) {
// 1. 更新数据库
userdao.updatebyid(user);
// 2. 删除缓存(注意:必须确保事务提交后再删,避免db回滚导致缓存被误删)
string key = cache_key + user.getid();
redistemplate.delete(key);
log.info("更新用户 id={}, 缓存已删除", user.getid());
}
}利用事务同步器确保在 db 真正提交后才删缓存,并开启延迟二次删除。
代码示例:
@service
@slf4j
public class userservicestrict {
@autowired
private userdao userdao;
@autowired
private redistemplate<string, object> redistemplate;
// 用于延迟任务的线程池
private final scheduledexecutorservice scheduler = executors.newscheduledthreadpool(4);
@transactional
public void updateuser(user user) {
// 1. 更新数据库
userdao.updatebyid(user);
string key = cache_key + user.getid();
// 2. 注册事务同步器,确保事务提交后再操作缓存
transactionsynchronizationmanager.registersynchronization(
new transactionsynchronization() {
@override
public void aftercommit() {
// 2.1 立即删除缓存
redistemplate.delete(key);
log.info("事务提交后立即删除缓存 key={}", key);
// 2.2 延迟 1.5 秒后再次删除(防止并发读回填旧数据)
scheduler.schedule(() -> {
try {
redistemplate.delete(key);
log.info("延迟删除缓存 key={}", key);
} catch (exception e) {
log.error("延迟删除缓存失败", e);
}
}, 1500, timeunit.milliseconds);
}
}
);
}
}如果业务要求极高的最终一致性,引入 canal 监听 binlog,异步删除缓存。应用层只负责更新 db,canal 解析变更事件后自动删除 redis。
代码示例(此为 canal 消费端伪代码,由框架回调触发):
@component
@slf4j
public class canalcachelistener {
@autowired
private redistemplate<string, object> redistemplate;
public void onuserchange(long userid, string eventtype) {
if ("update".equals(eventtype) || "delete".equals(eventtype)) {
string key = "user:" + userid;
redistemplate.delete(key);
log.info("binlog 异步清理缓存 userid={}", userid);
}
}
}| 你的业务场景 | 推荐采用方案 |
|---|---|
| 普通管理后台,并发低 | 基础版(5.1)+ ttl 即可 |
| 用户端高并发读,要求数据较新 | 严谨版(5.2)延迟双删 |
| 金融/库存强校验,绝不能错 | 不使用缓存,直接读数据库 |
| 分布式微服务,解耦优先 | binlog 异步删除(5.3) |
以上就是redis缓存与数据库的一致性的原理与最佳实践的详细内容,更多关于redis缓存与数据库一致性的资料请关注代码网其它相关文章!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论