“先更新db再删缓存”是go-zero唯一推荐且默认生效的路径,因其仅导致一次短暂脏读且能快速收敛至最终一致;cachedconn硬编码该顺序,不支持配置,也不在事务内执行缓存操作。

“先更新 DB,再删缓存”是 go-zero 中唯一推荐且默认生效的缓存一致性路径,其他顺序(比如先删缓存)在并发下极易导致旧值回填、长期不一致。
go-zero 默认 cachedConn 的更新顺序为什么是安全的
go-zero 生成的 model 层代码(如通过 goctl model)中,Update 方法底层调用的是 cachedConn,其执行逻辑固定为:sqlx.NamedExec → 成功后 → delCache。这个顺序不是可配置项,而是硬编码在 core/stores/sqlx/sql.go 和 core/stores/cache/cache.go 中。
它之所以能规避大部分脏数据问题,关键在于:即使 B 在 A 更新 DB 后、删缓存前读到了旧缓存值,也只是短暂脏读;只要 delCache 执行成功,后续所有请求都会命中新数据。而“先删缓存再更新 DB”则可能让 B 把旧值重新写入缓存,A 更新 DB 后缓存仍为旧值——这种不一致无法自动收敛。
- 该策略不依赖事务包裹缓存操作(
delCache不在 SQL 事务内),避免事务回滚导致缓存误删 - 删缓存失败时,
cachedConn不重试,也不会抛异常中断业务流程,需上层兜底 - 适用于读多写少场景(如商品详情、用户资料),对写频次高的表(如订单状态流转)要额外评估
删缓存失败后如何补偿:消息队列 vs Redis 延迟信号
go-zero 本身不提供缓存删除失败的自动重试机制。直接在 handler 里加 for 循环重试 redis.Del 是危险的:网络抖动可能导致超时阻塞,放大接口延迟。
生产环境应解耦失败处理:
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- 推荐方案:DB 更新成功后,向消息队列(如 Kafka/RocketMQ)投递一条
cache_delete消息,由独立 job service 消费并重试,失败时记录日志+告警 - 轻量替代:用
redis.SetEX写一个带 TTL 的信号 key(如cache:order:123_signal),TTL 设为 300s;另起 goroutine 监听 Redis 的__keyevent@0__:expired事件(需开启notify-keyspace-events Ex),事件触发后执行真实缓存 key 删除 - 禁用方案:在事务内调用
redis.Del—— 若事务回滚,缓存已删但 DB 未改,造成“缓存空、DB 有值”的不可恢复不一致
延时双删在 go-zero 里为什么不能用 time.Sleep
所谓“更新 DB 后 time.Sleep(500 * time.Millisecond) 再删一次缓存”,是常见误用。它会阻塞当前 handler goroutine,不仅拖慢接口响应,还可能因进程重启导致第二次删除永远不执行。
真正可用的延时双删必须满足两个条件:解耦 + 可持久化。
- 不能用
time.Sleep,因为它是同步阻塞,且无容错能力 - 不能把延时逻辑写在 HTTP handler 里,必须下沉到异步任务层
- 若监听 Redis expired 事件失败(如 Redis 重启、通知未开启),需退化为定时轮询 ZSET(如
delay_queue)中到期任务 - 延时窗口不是拍脑袋定的:需基于业务打点统计“DB 更新 → 缓存回填完成”的 P99 时间,再加 buffer
本地缓存(LRU)和 Redis 缓存的一致性怎么协同
go-zero 的多级缓存(一级 LRU + 二级 Redis)不是自动同步的。本地缓存更新/删除不会触发 Redis 缓存变化,反之亦然。这意味着:如果只删 Redis 缓存,本地缓存仍可能返回旧值;如果只清本地缓存,其他节点的 Redis 缓存还在。
实际做法是分层清理:
- 写操作时,必须同时调用
lru.Del和redis.Del(go-zero 的cachedConn默认已做) - 本地缓存过期时间(
expire)建议设得比 Redis TTL 短(如 Redis 设 30min,LRU 设 10min),降低本地残留旧值概率 - 高一致性要求场景(如账户余额),可关闭本地缓存(传
WithNoLocalCache()),只用 Redis + 单点 barrier 防击穿
最易被忽略的点是:本地缓存淘汰靠时间轮,但时间轮精度默认为 1 秒;如果你设了 expire: 500ms,实际可能 1 秒后才清理——这会导致本地缓存比 Redis 多存几百毫秒旧值。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










