连载中 8/15

热点库存:一万个人抢同一行

2026-09-06 · 293 阅读 · 0 评论 · 0 赞

单 Key 的天花板

Redis 单线程模型是原子性的来源,也是热点的天花板:所有命令排队执行,单个实例十万级 QPS——听起来够,但秒杀的请求全挤在一个 key 上,一个 key 的处理速度就是系统的总吞吐。开篇事故里数据库热点行的问题,换到 Redis 只是从"必死"变成"逼近极限"——十万请求打一个 key,排队延迟肉眼可见地涨。热点要拆,这是唯一出路。

库存分片:一个热点拆成 N 个桶

把一个库存 key 拆成 N 个分片桶:总库存 1000 拆成 10 个桶各 100,扣减时按随机路由选桶执行 Lua,单 key 的压力变成 N 分之一,总吞吐直接乘 N。选桶策略要随机或轮询——按用户 ID 哈希选桶会让"热门用户"永远撞同一个桶。扣减失败(桶空了)要换下一个桶重试,直到所有桶都空才算售罄。

// 分桶扣减:随机起点轮询,桶空换下一个
public long deduct(String skuId, int num) {
    int start = ThreadLocalRandom.current().nextInt(BUCKET_N);
    for (int i = 0; i < BUCKET_N; i++) {
        String key = "stock:" + skuId + ":" + ((start + i) % BUCKET_N);
        long r = redis.eval(DEDUCT_LUA, key, String.valueOf(num));
        if (r >= 0) return r;               // 当前桶扣减成功
    }
    return -2;                              // 所有桶都空:售罄
}
// 预热时:总库存 1000 -> 十个桶各 SET 100

桶不平衡:分片的经典副作用

随机路由不保证均匀:活动结束时可能出现 3 号桶早卖光、7 号桶还剩 40 个——库存没卖完,但一部分用户却被判了售罄,这就是少卖。缓解手段:换桶重试本身就是第一道平衡;再进一步,扣减失败的请求顺手把"空桶"状态记录下来,后续请求跳过已知空桶;规模更大的场景做二次再平衡——某桶余量过少时异步把它合并进其他桶。

售罄后的快速失败

秒杀最有意思的时刻是售罄那一秒:库存归零后,洪峰还在持续涌入——明知会失败,还是来了几万次。这时 Redis 压力已经毫无意义,本地缓存接力:应用内存里维护售罄标记,收到 Redis 返回售罄后置位,后续请求在应用层直接拒绝——连 Redis 都不用碰。标记要有失效机制(回补库存后清除),标记粒度到 SKU 级即可。

热点探测与动态分桶

分多少桶?拍脑袋 10 个可能不够也可能浪费。热点探测来自历史数据与预热监控:历史秒杀的 QPS 曲线、收藏加购数预估这次的热度,预期越高分桶越多;开抢后观察单桶 QPS,逼近阈值可以动态追加桶(从总库存里切份额给新桶)。热点探测这件事,本质是把"这次会多热"从玄学变成可配置的参数。资格扣完了,订单还没影——下一篇讲MQ 异步下单:削峰与订单落库。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#热点库存#库存分片#热Key#售罄快速失败#分桶

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞