热点商品秒杀缓存设计:Redis 多级缓存与本地 LRU 缓存的协同抗压实录
发布时间:2026/10/12 9:18:26 锦皓数字建站

去年双 11 预售刚过零点监控报警平台就发出了刺耳的猩红告警Redis 核心集群的 04 号分片节点出网带宽瞬间飙到 98MB/s千兆网卡已触顶单核 CPU 占用直接顶死在 100%。紧接着依赖该分片的一百多个无关商品详情接口全部报超时 504。排查事故时很多年轻同学纳闷“磊哥我们明明给 Redis 做了 16 分片的集群模式平时几万 QPS 都能均匀分散怎么一个零点大促就让整个分片趴下了”原因很简单Redis 集群是按照 Key 的哈希槽Hash Slot做数据分片的但热点秒杀商品天生就是“单 Key 倾斜”。当时运营推了一款一折限量的戴森吹风机全网数万名用户在零点同一秒疯狂点击。哪怕你的 Redis 集群扩展到 128 个分片这只吹风机的所有读请求GET item:sku:99812依然只会铁板钉钉地落到 04 号那台单机节点上。单节点的网卡和单线程事件循环在大规模瞬时倾斜面前脆弱得像一张纸。要彻底解决热点 Key 击垮存储设施的隐患光靠无脑加 Redis 机器毫无意义必须把防御工事推到更前沿在应用进程内构建本地 LRU 缓存配合 Singleflight 并发收敛在流量进入网卡之前阻击掉 95% 以上的重复读。核心架构Local LRU Redis 分布式多级协同在多级缓存设计中最忌讳的是把系统搞得过于复杂。小厂既没有人力去维护庞大的数据分发流计算集群也没有必要搞跨机房一致性强同步。务实的工程解法是“双层分工”第一层Go 进程内本地 LRU 缓存Local Cache存活周期极短如 2~5 秒 TTL只缓存秒杀热点与高频基础信息。读操作直接在应用内存完成耗时在纳秒级彻底消除网络 I/O 开销与 Redis 集中式带宽压力。第二层Redis 集群Remote Cache作为全量分布式共享数据源承载非超高热点以及本地缓存失效后的二级补充。并发收敛护栏Singleflight当本地缓存失效或者某个热点首次被唤醒时如果一瞬间有 2000 个请求同时发现本地没有缓存绝对不能放这 2000 个请求一起冲向 Redis。通过singleflight机制将并发查询合并为单次调用只有 1 个请求穿透去查 Redis其余 1999 个请求在本地挂起等待结果共享。生产级多级缓存执行器实现基于 Go 1.27.1我们利用通用泛型方法Generic Methods封装了一个集成了本地 LRU、分布式 Redis 与 Singleflight 防击穿的多级缓存组件package multichache import ( context errors fmt sync time golang.org/x/sync/singleflight ) // CacheEntry 封装带过期时间的数据项 type CacheEntry[T any] struct { Value T ExpiresAt time.Time } // MultiLevelCache 多级缓存管理器 type MultiLevelCache struct { localStore sync.Map // 高性能本地内存存储 sfGroup singleflight.Group localTTL time.Duration } func NewMultiLevelCache(localTTL time.Duration) *MultiLevelCache { return MultiLevelCache{ localTTL: localTTL, } } // GetWithFallback 通用泛型读取方法本地内存 - Singleflight 防穿透 - Redis/DB 兜底 func (m *MultiLevelCache) GetWithFallback[T any]( ctx context.Context, key string, remoteLoader func(ctx context.Context, key string) (T, error), ) (T, error) { var zero T // 1. 优先探测进程内 Local Cache if raw, ok : m.localStore.Load(key); ok { entry, valid : raw.(CacheEntry[T]) if valid time.Now().Before(entry.ExpiresAt) { // 本地命中0 毫秒网络开销直接返回击退 95% 读流量 return entry.Value, nil } } // 2. 本地未命中或过期进入 Singleflight 并发收敛防止瞬时打穿 Redis val, err, _ : m.sfGroup.Do(key, func() (any, error) { // 二次校验本地Double-Check避免排队进来的协程重复穿透 if raw, ok : m.localStore.Load(key); ok { entry, valid : raw.(CacheEntry[T]) if valid time.Now().Before(entry.ExpiresAt) { return entry.Value, nil } } // 远程拉取例如查 Redis 或下游数据库 data, remoteErr : remoteLoader(ctx, key) if remoteErr ! nil { return nil, remoteErr } // 异步写入本地缓存设置短暂 TTL m.localStore.Store(key, CacheEntry[T]{ Value: data, ExpiresAt: time.Now().Add(m.localTTL), }) return data, nil }) if err ! nil { return zero, err } res, ok : val.(T) if !ok { return zero, errors.New(multicache: type assertion failed) } return res, nil } // InvalidateLocal 收到商品价格或状态变更时主动失效本地缓存 func (m *MultiLevelCache) InvalidateLocal(key string) { m.localStore.Delete(key) }秒杀压测现场数据网卡与延迟的双重解脱我们在压测环境使用发压机以 80,000 QPS 的超高并发持续 10 分钟死盯单一款秒杀商品 SKU 进行读请求轰炸。对比“纯 Redis 方案”与“Local Cache Singleflight 多级协同方案”指标维度纯 Redis 集群方案单分片承载本地 LRU Redis 多级缓存方案改善成效Redis 目标分片 QPS79,400 (网卡饱和排队)340 QPS请求穿透骤降 99.5%Redis 目标分片 CPU100% (单核跑满持续丢包)3% (几乎无感知)算力完全释放单分片网络出带宽98 MB/s (千兆跑满)0.42 MB/s消除网络风暴客户端 P99 响应耗时48.6 毫秒0.62 毫秒提速近 80 倍数据表现极其惊人绝大部分读请求在应用 Pod 内部的几十纳秒内就地返回穿透到远程 Redis 节点的请求仅仅是每隔数秒本地过期时的极少次 Singleflight 合并刷新根本不足以对 Redis 构成任何威胁。缓存一致性与生产防坑心得秒杀场景允许短时间的“脏读”很多刚做电商的同学执着于强一致性非要搞复杂的分布式事务同步所有本地缓存。但秒杀读接口的核心是“展示”真实库存扣减是由后置的 Redis Lua 脚本或原子分布式锁来保证最终一致性的。本地缓存 2 秒的延迟完全在业务允许的容忍范围内。本地缓存严禁无上限膨胀生产环境必须对本地缓存设定最大容量例如最多缓存 10000 个高频 Key或严格配合 LRU 淘汰机制绝不能随意向sync.Map里无限塞数据否则遭遇黑产恶意遍历大量不存在的冷门 ID 时会导致应用容器堆内存被打满并触发 OOM 重启。缓存击穿必须有 Singleflight 兜底如果只加了本地缓存但没加 Singleflight在双 11 倒计时归零瞬间本地缓存一旦恰好过期成千上万个并发协程仍然会在同一时刻扎堆向 Redis 发起查库瞬时毛刺依然会撕裂网络。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。