Lazy loaded image
电商有个热点福利品,库存几十万,会触发秒杀,你作为秒杀链路负责人,怎么设计优化整条链路保证高TPS?
字数 1134阅读时长≈ 3 分钟
2026-10-14
type
Post
status
Published
date
Oct 14, 2026
slug
tps
summary
tags
技术探索
category
手写
icon
password
我的思路是:流量分层拦截、库存前置、异步削峰、热点分散、最终一致性。
第一,接入层削减无效流量。
通过 CDN 缓存商品页面和静态资源,避免所有请求进入后端。网关层进行限流、防刷、用户资格校验,尽量把无效请求拦截在前面。
第二,Redis 前置库存校验。
提前把几十万库存预热到 Redis,使用 Lua 脚本原子执行库存判断、扣减和一人一单校验,避免每个请求直接访问 MySQL。
第三,消息队列异步削峰。
Redis 预扣库存成功后,不同步创建订单,而是通过 MQ 把请求交给订单服务异步消费,控制数据库写入速度,避免瞬时流量压垮数据库。
第四,解决热点瓶颈。
由于所有用户都抢同一个商品,会产生 Redis 热 Key 和数据库热点行。我会根据实际压测结果考虑库存分桶,把库存拆分到多个 Redis Key 和数据库库存分片中,分散并发竞争。
第五,保证一致性与稳定性。
数据库通过条件更新、唯一索引和幂等处理兜底;通过可靠消息、超时释放、失败补偿和定期对账,避免超卖以及库存长期不一致。
最后通过压测,重点观察 TPS、P99 延迟、Redis/MQ/数据库瓶颈以及订单成功率,逐层优化。

Redis库存扣减为什么要用Lua脚本?

而 Lua 脚本可以把判断库存、扣减库存、一人一单校验放到一次原子执行中,避免其他请求在中间插入。 另外,还能减少多次网络往返,提高性能。

本地缓存和Redis、Redis和DB之间的一致性怎么保证?

第一,本地缓存和 Redis 之间:采用消息通知 + TTL 兜底。 本地缓存比如 Caffeine,Redis 是共享缓存。 当 Redis 数据发生变化时,通过 MQ 或 Redis Pub/Sub 通知各个服务实例删除本地缓存。下次访问再从 Redis 加载。同时设置 TTL,避免通知丢失导致脏数据长期存在。
第二,Redis 和 MySQL 之间:采用 Cache Aside(旁路缓存)模式。
  • 读数据:先查 Redis,未命中再查 MySQL,并回填 Redis。
  • 写数据:先更新 MySQL,事务提交成功后删除 Redis 缓存,而不是直接更新缓存。
  • 并发一致性:针对数据库更新与缓存删除之间的竞态,可以使用延迟双删、版本号校验等机制。
  • 可靠性:通过 MQ 重试或订阅 Binlog 变更,保证缓存删除失败后能够补偿。
核心思路是:数据库作为最终数据源,缓存允许短暂不一致,但要保证最终能够收敛。

Redis扣减后异步扣DB,单行有TPS上限,1万并发怎么解决?

分三步解决:
  1. MQ 削峰:Redis 扣减成功后写入消息队列,由消费者按数据库实际承载能力消费,避免瞬间打满 MySQL。
  1. 库存分桶:把一条库存记录拆成多条,例如 30 万库存拆成 100 个桶,每桶 3000 件。每个订单更新对应的库存桶,分散行锁竞争。
  1. 批量扣减:消费者把同一库存桶的多次扣减合并,例如 100 次扣减合并成一次 UPDATE,减少数据库写入和锁竞争。

怎么把100次请求聚合成一次写DB?

消费者维护一个按 skuId(如果分桶,还要按 bucketId)分组的内存缓冲区。
  • 每收到一条消息,放入对应分组。
  • 攒够 100 条,立即执行批量扣减。
  • 超过 50ms 还没攒够,也立即执行,避免低流量时一直等待。

怎么感知Redis数据变更?

在 Lua 扣库存成功时,主动记录一条扣减事件。
Redis Lua 扣减成功时,主动写入 Redis Stream,再由消费者处理事件。
回到首页