澄迈县网站建设_网站建设公司_React_seo优化
2025/12/25 21:27:39 网站建设 项目流程

写在开头:

上周跟一个想跳槽腾讯的朋友吃饭,他一脸郁闷。 三面的时候,面试官问了他一个经典的场景题:“双十一大促,某个爆款商品预计每秒有 100 万请求(QPS),库存只有 100 个。请设计一个限流方案。”

朋友自信满满地抛出了“Redis 分布式限流”方案: “用 Redis 的INCR或者Lua脚本做令牌桶,每秒发 100 个令牌,抢不到的直接返回失败。”

面试官听完冷笑了一声:“100 万 QPS 全部打到 Redis 上?你知道 Redis 单节点的极限吞吐是多少吗?哪怕是集群,针对同一个 Key(商品ID)的热点操作,你怎么分片?还没等扣库存,你的 Redis 网卡就被限流请求打爆了。”

朋友当场死机。

这就是典型的“工具思维”害死人。在海量流量面前,Redis 不是银弹,它甚至可能是瓶颈本身。

今天咱们把这个“漏斗式限流”拆透,告诉你为什么真正的秒杀系统,绝不敢把第一次限流放在 Redis 这一层。

一、 认清现实:Redis 扛不住 100 万 QPS 的“询问”

先算笔账。100 万 QPS,指的是“用户点击抢购”的请求量。 如果你让这 100 万个请求都去访问 Redis(哪怕只是读一下令牌),这叫“热点 Key 问题”

Redis 再强,单节点处理Lua脚本或简单命令的极限通常在 8-10 万 QPS 左右。 要把 100 万 QPS 扛下来,你得搞多大的 Redis 集群? 而且,因为是同一个商品(Key 是seckill:sku:1001),根据哈希算法,这些请求全部会打到同一个 Redis 分片上

结局:Redis 单节点 CPU 100%,整个缓存集群卡死,继而拖垮依赖缓存的订单、用户信息服务。全站崩溃。

结论:在真正的秒杀场景,千万别让所有流量都触达 Redis。

二、 核心架构:漏斗模型(The Funnel)

秒杀限流的核心心法是:“层层削减,把流量拦在最外层”。 就像一个漏斗,100 万请求进来,最后到底层数据库的,只能是两位数。

第 0 层:客户端/前端限流(让请求发不出来)

这是最容易被忽略,但效果最立竿见影的一层。 如果 100 万用户同时点按钮,前端不做控制,这 100 万个 HTTP 请求瞬间发起,光是建立 TCP 连接(握手)的开销和带宽成本就能把机房入口堵死。

  1. 按钮控制:活动开始前置灰,点击一次后强制置灰几秒(防止手速快的用户 1 秒点 10 次)。

  2. 随机丢弃:在极度火爆的场景下,前端 JS/App 本地可以直接Math.random()。如果是 100 万人抢 100 个货,前端直接丢弃 90% 的请求(直接弹窗“哎呀人太多了”),只有 10% 的幸运儿能真的发起 HTTP 请求。

  3. 错峰神器:引入验证码(滑动、计算题)。让用户花 3-5 秒去答题,把原本集中在 1 秒内的 100 万并发,拉平到 5-10 秒内,瞬间削峰。

第 1 层:Nginx/网关限流(拦住 90% 的流量)

这是服务端的“门神”。利用Nginx + Lua(OpenResty),在流量到达 Java 应用服务器之前,直接把它掐断。

  • 手段limit_req_zone或者自定义 Lua 脚本。

  • 逻辑:针对 IP 限流,或者粗粒度的总流控。

  • 效果:前端放行过来的 10 万请求,在这里再丢弃一部分非法或溢出的请求。为什么在这里做?因为 Nginx 是 C 写的,处理静态规则的性能是 Java 的几十倍。让 Nginx 返回 503,成本极低。

第 2 层:单机限流(Java 进程内限流)

这是保护微服务不崩盘的关键。 剩下的请求穿透了 Nginx,到达了你的 Java 微服务(Tomcat/Netty)。这时候,千万别急着调 Redis!先用 JVM 内部的锁(如Guava RateLimiterSentinel)再拦一道。

  • 逻辑:假设你有 100 台机器,后端总共想放行 5000 个请求。那么每台机器设置单机阈值 = 50 QPS。

  • 优势完全内存操作,无网络开销。比调 Redis 快 1000 倍。

  • 效果:经过这一层,只有 5000 个请求能通过。

第 3 层:分布式限流(Redis 最终一致性)

只有那 5000 个“幸运儿”,才有资格去请求 Redis 做精准的库存校验和令牌扣减。 这时候,Redis 面临的压力只有 5000 QPS,简直是挠痒痒,完全 hold 得住。

三、 算法选择:别只会说“令牌桶”

面试官如果问:“用什么算法?” 别光背“令牌桶(Token Bucket)”和“漏桶(Leaky Bucket)”的定义,要结合业务体验说。

  1. 漏桶算法(Leaky Bucket)

    • 特点:出水速率恒定。即使瞬间来了 1 万个请求,它也按每秒 10 个慢慢处理,多的直接扔。

    • 缺点太死板。秒杀场景用户就想要“快”,你让他排队慢吞吞地处理,体验极差。

  2. 令牌桶算法(Token Bucket)

    • 特点:以恒定速率存入令牌,允许突发流量(只要桶里有存货)。

    • 秒杀首选:它允许系统在刚开始的一瞬间处理一波高峰,符合秒杀“瞬时爆发”的特征。

四、 最后的“防杠”指南(高阶避坑)

这一块是专门留给 P8/P9 面试官的“投名状”。当你说完上面的漏斗模型,对方可能会挑战你:

杠点 1:“单机限流导致流量不均怎么办?”

  • 挑战:“你用 Nginx 轮询,但如果某台机器处理得慢,堆积了请求,而另一台机器很空闲,单机限流(Guava)会不会导致整体通过量不足?”

  • 回击:“你说得对,单机限流确实有‘总体精度误差’。但在秒杀场景下,系统存活 > 精准控制。 我们宁可少卖出几个(最后靠 Redis 兜底),也不能让单台机器被流量打死。而且,通过 Nginx 的Least Connections策略可以尽量保证流量均匀。”

杠点 2:“Redis 还是热点 Key 怎么办?”

  • 挑战:“就算只有 5000 QPS 打到 Redis,如果这 5000 个都是写操作(扣库存),Redis 的单线程依然可能响应不过来。”

  • 回击:“对于极致热点,我们会采用‘本地售罄标记(Local Stock Flag)’策略。 我们不在 Java 内存里做库存分片(容易导致数据倾斜),而是做一个‘售罄标记’

    1. Redis 扣减库存成功 -> 返回成功。

    2. Redis 返回库存为 0 -> 所有应用服务器同步在本地内存(比如AtomicBoolean soldOut = true)做标记(可以通过 MQ 广播或 ZooKeeper 监听,甚至简单的短 TTL 本地缓存)。

    3. 关键点:后续请求在 JVM 层检查到soldOut = true直接返回失败,连 Redis 都不用访问。这样,Redis 只需要处理库存卖完之前的那一点点流量,后续的无效流量全被 JVM 挡住了。”

杠点 3:“如果有羊毛党用脚本刷接口怎么办?”

  • 回击:“限流解决的是‘流量’问题,解决不了‘人性’问题。 防刷必须在限流之前,通过WAF(Web防火墙)风控系统黑名单复杂验证码来解决。单纯靠技术限流是拦不住模拟正常请求的机器人的。”

总结一下

下次面试被问到“秒杀限流”,千万别上来就写 Lua 脚本。请按这个剧本走:

  1. 否定单层架构:直接怼回去,100 万 QPS 打 Redis 肯定死。

  2. 抛出漏斗模型: 客户端(随机丢弃/置灰) -> Nginx(IP限流/鉴权) -> 单机限流(Guava保命) -> Redis(处理最后 1%)。

  3. 防御性补充:提出“本地售罄标记”解决热点 Key 问题,承认单机限流的误差是为了“保命”做的取舍。

做秒杀系统,不是为了让所有人都买到东西,而是为了在流量洪峰下,保证系统能 “有尊严地活着”。

https://mp.weixin.qq.com/s/myYTZ_YAT0Wl7dHhfIGgGg

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询