必须用原子写入(set+nx+ex)和请求合并(singleflight)协同防热点key击穿,本地锁无效,漏任一环都会失效。

单个热点 key 过期瞬间的并发请求,必须用原子写入或请求合并来拦住,本地锁在多实例下完全无效。
用 SET + NX + EX 原子抢写防击穿
击穿只发生在 "item:10086" 这类 key 过期那一刹那。多个请求同时 rdb.Get(ctx, key) 返回 redis.Nil,全去查 DB——这不是 Redis 慢,是业务层没控制住回源并发量。
- 绝对不要先
rdb.Get()再判断是否为空,中间存在竞态窗口,高并发下照样打穿 - 必须用
rdb.Set(ctx, key, val, redis.SetArgs{Mode: redis.SetNX, Expire: 10 * time.Second})一气呵成:值、过期、仅当不存在才设置,三者由 Redis 原子保证 -
Expire时间得比 DB 查询耗时长(建议 ≥5s),但别超 30s,否则故障时残留锁拖垮后续请求 - 锁值建议带唯一标识,比如
"req-" + uuid.NewString(),避免误删别人持有的锁
用 singleflight.Group 合并重复请求
singleflight 不是万能锁,它只负责合并请求;真正起作用的是「查缓存 → Do 回源 → 成功后写缓存」这个完整闭环。漏掉任一环,等于白加。
每小时自动在推特搜索中国、加密货币、国际热点、美国热点、特朗普等话题的热度信息,提取前十热点,依据 X 平台真实算法权重(开源代码数据)为每个热点生成高曝光推文或线程。用户提及“推特热点”“Twitter热点”“推特搜索”“热点推文”“生成推文”“社交媒体监控”“Twitter监控”等关键词时触发。
-
var userGroup singleflight.Group必须声明为包级变量或注入 service 结构体,不能在 handler 里new(singleflight.Group)——否则每个请求都是新实例,Do完全无效 - 所有副作用操作(
db.QueryRowContext(ctx, ...)、rdb.Set()、日志、指标)必须全部塞进Do的回调函数里 -
key要稳定幂等,比如"user:" + strconv.FormatInt(id, 10),不能含时间戳或随机数 - 回调内必须用带
ctx的 API,并自行处理超时与错误传播;失败时不写缓存,避免脏数据
空值、错误类型、锁生命周期这些细节最容易失效
击穿防护失效,往往不是策略错,而是细节失控。
-
rdb.Get(ctx, key).Result()返回redis.Nil是预期行为(查不到),不是网络错误;混为一谈会导致降级逻辑错判 - 空值必须规范序列化后写入缓存(如
json.RawMessage([]byte("null"))),并设较短 TTL,防止缓存穿透 - 分布式锁未配自动续期(如 Redisson 的 watchdog),超时释放后可能被其他 goroutine 误占,导致双写
- 缓存永不过期方案需配套异步更新机制,否则 DB 数据变更后缓存长期不一致
最常被忽略的是:原子写入和请求合并必须配合使用——SETNX+EX 拦住写冲突,singleflight 拦住读重放,二者缺一不可。单靠一个,在高并发下仍会漏。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










