问题

电商网站中,50W-100W高并发,秒杀功能是怎么实现的?

回答
电商网站处理50W100W的并发量,再去做秒杀这种瞬时流量洪峰,那真是一场硬仗。秒杀听起来简单,就是“比谁快”,但背后涉及到方方面面的技术调优和架构设计,绝不是简单地加几台服务器就能搞定的。

首先得明确一点,秒杀拼的不仅仅是“库存”本身,更多的是如何高效地处理海量请求,确保公平性,并且不把整个系统给拖垮。

咱们一步步来捋捋,秒杀这个功能是怎么在这样的量级下被“喂饱”的。

一、前期准备:防患于未然

在秒杀活动开始前,就已经有很多工作要做,这才是稳定性的基础。

1. 容量规划与压测: 这不是事后诸葛亮,而是事前必须做的功课。提前计算好秒杀活动的预期流量,模拟远超预期的峰值,对每一个环节进行压测,找出瓶颈并进行优化。服务器、数据库、网络、中间件,一个都不能少。

2. 库存管理: 这是秒杀的核心痛点。直接操作数据库减库存,在高并发下极易出现超卖或少卖(虽然少卖也是一种超卖,只是没卖出去)。

预减库存(或锁定库存): 在用户提交订单前,先从数据库中预扣除一部分库存,或者使用分布式锁来标记库存。这有一个问题,如果用户最终没下单,这个库存就被占用了,需要有补偿机制。
使用Redis等内存数据库进行库存缓存和扣减: 这是目前最主流的做法。将秒杀商品库存放在Redis中,利用Redis的原子性操作(如`INCRBY`配合负数)来执行减库存操作。即使有并发请求,Redis也能保证操作的原子性,防止超卖。秒杀开始前,将商品库存一次性加载到Redis中。

3. 接口设计: 秒杀接口需要极致的性能。

轻量化: 接口请求参数尽量少,返回数据也尽量精简。避免不必要的数据库查询或复杂计算。
幂等性: 虽然秒杀过程中用户操作是独立的,但如果因为网络问题,用户重复提交了同一请求,应该保证后端只处理一次。

二、秒杀核心流程及技术实现

秒杀开始后,才是真正考验系统功力的时刻。

1. 前端防刷与流量削峰(Guarding the Front Door):

验证码: 在抢购前设置验证码,虽然不能完全挡住机器人,但能过滤掉大部分低效的恶意请求。
IP限制/设备限制: 对同一IP或设备在短时间内频繁请求进行限制。
前端定时器与预加载: 秒杀开始前,前端页面可以有一个倒计时,并在接近秒杀时间时预加载商品详情和抢购按钮。
CDN加速: 将静态资源(图片、JS、CSS)部署到CDN,减少源服务器压力。

2. 请求入口层: 这是第一道防线。

负载均衡(LB): Nginx、LVS、HAProxy等,将流量分散到多个应用服务器上。
API Gateway: 统一的入口,可以进行路由、认证、限流等操作。
漏斗机制/秒杀链接提前生成:
秒杀开始前不直接暴露秒杀接口: 而是生成一个临时的、唯一的秒杀链接(例如,包含一个随机的token)。用户点击后,先通过这个链接访问一个页面,这个页面再跳转到真实的秒杀接口。这样可以有效过滤掉大量非正常访问的请求。
秒杀活动页面预热: 在秒杀开始前,将秒杀页面的热度“预热”起来,让用户在点击购买时,一些基础信息已经被加载。
Sentinel/Hystrix 等服务降级与熔断机制: 当系统负载过高时,自动触发服务降级,例如关闭非核心功能,或者返回一个友好的提示信息(“您来得太晚了,商品已售罄”),避免雪崩效应。

3. 应用层(The Core Logic):

集群化部署: 部署多个应用服务器实例,通过负载均衡器分发请求。
无状态化设计: 应用服务器尽量无状态,便于水平扩展。用户会话信息可以存储在Redis或Cookie中。
异步化处理: 对于非核心的、耗时的操作,比如发送邮件、生成订单详情页缓存等,可以放到消息队列(如Kafka、RabbitMQ)中异步处理。这样主流程能够快速响应用户请求。
秒杀下单流程: 用户点击“抢购” > 请求打到应用服务器 > 应用服务器先校验库存(Redis)> 如果库存足够,生成一个预订单(此时还不是最终订单,是为了占位)> 异步将下单请求推送到消息队列 > 消费者从消息队列中取出请求,进行最终的库存扣减、生成订单、扣款等操作。
分布式锁: 在秒杀核心的库存扣减环节,为了保证原子性,可以使用分布式锁(如基于Redis的Redlock,或者ZooKeeper)。当一个请求需要扣减库存时,先尝试获取锁,成功后执行扣减操作,然后释放锁。这样确保同一时刻只有一个请求能成功扣减该件商品的库存。
库存预扣与最终确认:
用户请求到来,先在Redis中尝试“预减”库存(例如,使用`INCRBY commodity_id 1`)。
如果预减成功,返回给前端一个“抢购成功”的提示,并生成一个抢购凭证(例如一个临时令牌),前端携带这个凭证去进行下一步下单流程。
后台通过消息队列异步处理真正的订单创建和库存最终扣减(如果用户没有在规定时间内完成支付,需要将预扣的库存释放回Redis)。
限流(Rate Limiting): 在应用层或API网关层,对每个用户、每个IP、甚至整个接口的请求速率进行限制。可以使用令牌桶(Token Bucket)或漏桶(Leaky Bucket)算法。

4. 数据存储层(The Database Under Pressure):

库存与订单数据分离: 秒杀期间,订单数据会激增。可以将秒杀订单与普通订单分开存储,或者使用专门的秒杀库。
读写分离: 利用主从复制,读操作(如展示商品列表、查询订单状态)可以导向从库,减少主库压力。
数据库连接池优化: 调整数据库连接池大小,避免连接耗尽。
分库分表(Sharding): 对于海量的订单数据,可以进行分库分表,提高查询和写入效率。

5. 缓存策略(Caching is King):

CDN缓存: 静态资源。
Redis缓存: 秒杀商品信息、库存数量、用户信息、热点数据。
本地缓存(JVM缓存): 在应用服务器内存中缓存一些不经常变动的数据。

6. 消息队列(The Decoupler):

Kafka、RabbitMQ、RocketMQ: 用于异步处理下单请求、扣库存、发优惠券、通知用户等。将高并发的下单请求暂时“堰塞”在队列中,由下游的消费者慢慢处理。这是一种非常有效的削峰填谷的手段。
保证消息的可靠性: 确保消息不丢失,并能按顺序处理(如果顺序很重要)。

三、秒杀后的善后处理

1. 数据一致性保证:
分布式事务: 秒杀的整个流程(库存扣减、生成订单、支付)涉及到多个步骤,需要考虑分布式事务的保证。通常会采用最终一致性的方案,例如通过消息队列的重试机制和补偿机制来保证。
事后对账: 秒杀结束后,对库存、订单、支付等环节进行对账,发现并修正不一致的地方。

2. 流量回落: 秒杀结束后,流量会迅速下降,系统也需要能平滑地从高并发状态恢复到正常状态。

核心思路总结一下:

削峰填谷: 通过各种技术手段(前端控制、API网关限流、消息队列)将瞬时高并发的请求“打散”和“缓冲”。
防刷防作弊: 增加机器人的获取难度。
异步化: 将非核心的、耗时的操作放到后台异步处理。
缓存: 最大化利用缓存减少数据库压力。
原子性: 保证库存扣减等核心操作的原子性。
服务降级与熔断: 避免系统整体崩溃。
分布式: 充分利用多台机器的能力。

说到底,50W100W并发秒杀,考验的是整个系统的韧性和伸缩性。从前端到后端,从网络到存储,每一个环节都要考虑到高并发下的表现,并且要以一种“宁可慢一点也不出错”的思路去设计。它不是一个单一的技术点,而是一个系统工程。

网友意见

user avatar

正文

首先设计一个系统之前,我们需要先确认我们的业务场景是怎么样子的,我就带着大家一起假设一个场景好吧。

场景

我们现场要卖100件下面这个婴儿纸尿裤,然后我们根据以往这样秒杀活动的数据经验来看,目测来抢这100件纸尿裤的人足足有10万人。(南极人打钱!)

你一听,完了呀,这我们的服务器哪里顶得住啊!说真的直接打DB肯定挂。但是别急嘛,有暖男敖丙在,我们在开始之前应该先思考下会出现哪些问题

问题

高并发:

是的高并发这个是我们想都不用想的一个点,一瞬间这么多人进来这不是高并发什么时候是呢?

是吧,秒杀的特点就是这样时间极短瞬间用户量大

正常的店铺营销都是用极低的价格配合上短信、APP的精准推送,吸引特别多的用户来参与这场秒杀,爽了商家苦了开发呀

秒杀大家都知道如果真的营销到位,价格诱人,几十万的流量我觉得完全不是问题,那单机的Redis我感觉3-4W的QPS还是能顶得住的,但是再高了就没办法了,那这个数据随便搞个热销商品的秒杀可能都不止了。

大量的请求进来,我们需要考虑的点就很多了,缓存雪崩缓存击穿缓存穿透这些我之前提到的点都是有可能发生的,出现问题打挂DB那就很难受了,活动失败用户体验差,活动人气没了,最后背锅的还是开发

超卖:

但凡是个秒杀,都怕超卖,我这里举例的只是尿不湿,要是换成100个华为MatePro30,商家的预算经费卖100个可以赚点还可以造势,结果你写错程序多卖出去200个,你不发货用户投诉你,平台封你店,你发货就血亏,你怎么办?
(没事看了敖丙的文章直接不怕)

那最后只能杀个开发祭天解气了,秒杀的价格本来就低了,基本上都是不怎么赚钱的,超卖了就恐怖了呀,所以超卖也是很关键的一个点。

恶意请求:

你这么低的价格,假如我抢到了,我转手卖掉我不是血赚?就算我不卖我也不亏啊,那用户知道,你知道,别的别有用心的人(黑客、黄牛…)肯定也知道的。

那简单啊,我知道你什么时候抢,我搞个几十台机器搞点脚本,我也模拟出来十几万个人左右的请求,那我是不是意味着我基本上有80%的成功率了。

真实情况可能远远不止,因为机器请求的速度比人的手速往往快太多了,在贵州的敖丙我每年回家抢高铁票都是秒光的,我也不知道有没有黄牛的功劳,我要Diss你,黄牛。杰伦演唱会门票抢不到,我也Diss你。

Tip:科普下,小道消息了解到的,黄牛的抢票系统,比国内很多小公司的系统还吊很多,架构设计都是顶级的,我用顶配的服务加上顶配的架构设计,你还想看演唱会?还想回家?

不过不用黄牛我回家都难,我们云贵川跟我一样要回家过年的仔太多了555!

链接暴露:

前面几个问题大家可能都很好理解,一看到这个有的小伙伴可能会比较疑惑,啥是链接暴露呀?

相信是个开发同学都对这个画面一点都不陌生吧,懂点行的仔都可以打开谷歌的开发者模式,然后看看你的网页代码,有的就有URL,但是我写VUE的时候是事件触发然后去调用文件里面的接口看源码看不到,但是我可以点击一下查看你的请求地址啊,不过你好像可以对按钮在秒杀前置灰。

不管怎么样子都有危险,撇开外面的所有的东西你都挡住了,你卖这个东西实在便宜得过分,有诱惑力,你能保证开发不动心?开发知道地址,在秒杀的时候自己提前请求。。。(开发:怎么TM又是我)

数据库:

每秒上万甚至十几万的QPS(每秒请求数)直接打到数据库,基本上都要把库打挂掉,而且你服务不单单是做秒杀的还涉及其他的业务,你没做降级、限流、熔断啥的,别的一起挂,小公司的话可能全站崩溃404

反正不管你秒杀怎么挂,你别把别的搞挂了对吧,搞挂了就不是杀一个程序员能搞定的。

程序员:我TM好难啊!

问题都列出来了,那怎么设计,怎么解决这些问题就是接下去要考虑的了,我们对症下药。

服务单一职责:

设计个能抗住高并发的系统,我觉得还是得单一职责

什么意思呢,大家都知道现在设计都是微服务的设计思想,然后再用分布式的部署方式

也就是我们下单是有个订单服务,用户登录管理等有个用户服务等等,那为啥我们不给秒杀也开个服务,我们把秒杀的代码业务逻辑放一起。

单独给他建立一个数据库,现在的互联网架构部署都是分库的,一样的就是订单服务对应订单库,秒杀我们也给他建立自己的秒杀库。

至于表就看大家怎么设计了,该设置索引的地方还是要设置索引的,建完后记得用explain看看SQL的执行计划。(不了解的小伙伴也没事,MySQL章节我会说的)

单一职责的好处就是就算秒杀没抗住,秒杀库崩了,服务挂了,也不会影响到其他的服务。(强行高可用)

秒杀链接加盐:

我们上面说了链接要是提前暴露出去可能有人直接访问url就提前秒杀了,那又有小伙伴要说了我做个时间的校验就好了呀,那我告诉你,知道链接的地址比起页面人工点击的还是有很大优势

我知道url了,那我通过程序不断获取最新的北京时间,可以达到毫秒级别的,我就在00毫秒的时候请求,我敢说绝对比你人工点的成功率大太多了,而且我可以一毫秒发送N次请求,搞不好你卖100个产品我全拿了。

那这种情况怎么避免?

简单,把URL动态化,就连写代码的人都不知道,你就通过MD5之类的加密算法加密随机的字符串去做url,然后通过前端代码获取url后台校验才能通过。

暖男我呢,又准备了一个简单的url加密给大家尝尝鲜,还不点个赞

Redis集群:

之前不是说单机的Redis顶不住嘛,那简单多找几个兄弟啊,秒杀本来就是读多写少,那你们是不是瞬间想起来我之前跟你们提到过的,Redis集群主从同步读写分离,我们还搞点哨兵,开启持久化直接无敌高可用!

Nginx:

Nginx大家想必都不陌生了吧,这玩意是高性能的web服务器,并发也随便顶几万不是梦,但是我们的Tomcat只能顶几百的并发呀,那简单呀负载均衡嘛,一台服务几百,那就多搞点,在秒杀的时候多租点流量机

Tip:据我所知国内某大厂就是在去年春节活动期间租光了亚洲所有的服务器,小公司也很喜欢在双十一期间买流量机来顶住压力。

这样一对比是不是觉得你的集群能顶很多了。

恶意请求拦截也需要用到它,一般单个用户请求次数太夸张,不像人为的请求在网关那一层就得拦截掉了,不然请求多了他抢不抢得到是一回事,服务器压力上去了,可能占用网络带宽或者把服务器打崩、缓存击穿等等。

资源静态化:

秒杀一般都是特定的商品还有页面模板,现在一般都是前后端分离的,所以页面一般都是不会经过后端的,但是前端也要自己的服务器啊,那就把能提前放入cdn服务器的东西都放进去,反正把所有能提升效率的步骤都做一下,减少真正秒杀时候服务器的压力。

按钮控制:

大家有没有发现没到秒杀前,一般按钮都是置灰的,只有时间到了,才能点击。

这是因为怕大家在时间快到的最后几秒秒疯狂请求服务器,然后还没到秒杀的时候基本上服务器就挂了。

这个时候就需要前端的配合,定时去请求你的后端服务器,获取最新的北京时间,到时间点再给按钮可用状态。

按钮可以点击之后也得给他置灰几秒,不然他一样在开始之后一直点的。你敢说你们秒杀的时候不是这样的?

限流:

限流这里我觉得应该分为前端限流后端限流

前端限流:这个很简单,一般秒杀不会让你一直点的,一般都是点击一下或者两下然后几秒之后才可以继续点击,这也是保护服务器的一种手段。

后端限流:秒杀的时候肯定是涉及到后续的订单生成和支付等操作,但是都只是成功的幸运儿才会走到那一步,那一旦100个产品卖光了,return了一个false,前端直接秒杀结束,然后你后端也关闭后续无效请求的介入了。

Tip:真正的限流还会有限流组件的加入例如:阿里的Sentinel、Hystrix等。我这里就不展开了,就说一下物理的限流。

库存预热:

秒杀的本质,就是对库存的抢夺,每个秒杀的用户来你都去数据库查询库存校验库存,然后扣减库存,撇开性能因数,你不觉得这样好繁琐,对业务开发人员都不友好,而且数据库顶不住啊。

开发:你tm总算为我着想一次了。

那怎么办?

我们都知道数据库顶不住但是他的兄弟非关系型的数据库Redis能顶啊!

那不简单了,我们要开始秒杀前你通过定时任务或者运维同学提前把商品的库存加载到Redis中去,让整个流程都在Redis里面去做,然后等秒杀介绍了,再异步的去修改库存就好了。

但是用了Redis就有一个问题了,我们上面说了我们采用主从,就是我们会去读取库存然后再判断然后有库存才去减库存,正常情况没问题,但是高并发的情况问题就很大了。

这里我就不画图了,我本来想画图的,想了半天我觉得语言可能更好表达一点。

多品几遍!!!就比如现在库存只剩下1个了,我们高并发嘛,4个服务器一起查询了发现都是还有1个,那大家都觉得是自己抢到了,就都去扣库存,那结果就变成了-3,是的只有一个是真的抢到了,别的都是超卖的。咋办?

Lua:

之前的文章就简单的提到了他,我今天就多一定点篇幅说一下吧。

Lua 脚本功能是 Reids在 2.6 版本的最大亮点, 通过内嵌对 Lua 环境的支持, Redis 解决了长久以来不能高效地处理 CAS (check-and-set)命令的缺点, 并且可以通过组合使用多个命令, 轻松实现以前很难实现或者不能高效实现的模式。

Lua脚本是类似Redis事务,有一定的原子性,不会被其他命令插队,可以完成一些Redis事务性的操作。这点是关键。

知道原理了,我们就写一个脚本把判断库存扣减库存的操作都写在一个脚本丢给Redis去做,那到0了后面的都Return False了是吧,一个失败了你修改一个开关,直接挡住所有的请求,然后再做后面的事情嘛。

限流&降级&熔断&隔离:

这个为啥要做呢,不怕一万就怕万一,万一你真的顶不住了,限流,顶不住就挡一部分出去但是不能说不行,降级,降级了还是被打挂了,熔断,至少不要影响别的系统,隔离,你本身就独立的,但是你会调用其他的系统嘛,你快不行了你别拖累兄弟们啊。

削峰填谷:

一说到这个名词,很多小伙伴就知道了,对的MQ,你买东西少了你直接100个请求改库我觉得没问题,但是万一秒杀一万个,10万个呢?服务器挂了,程序员又要背锅的

Tip:可能小伙伴说我们业务达不到这个量级,没必要。但是我想说我们写代码,就不应该写出有逻辑漏洞的代码,至少以后公司体量上去了,别人一看居然不用改代码,一看代码作者是敖丙?有点东西!

你可以把它放消息队列,然后一点点消费去改库存就好了嘛,不过单个商品其实一次修改就够了,我这里说的是某个点多个商品一起秒杀的场景,像极了双十一零点。

总结

到这里我想我已经基本上把该考虑的点还有对应的解决方案也都说了一下,不知道还有没有没考虑到的,但是就算没考虑到我想我这个设计,应该也能撑住一个完整的秒杀流程。

最后我就画个完整的流程图给大家收个尾吧!


Tip:这个链路还是比较简单的,很多细节的点全部画出来就太复杂了,我上面已经提到了所有的注意点了,大家都看看,真正的秒杀有比我这个简单的,也有比我这个复杂N倍的,之前的电商老东家就做的很高级,有机会也可以跟你们探讨,不过是面试嘛,我就给思路,让你理解比较关键的点。


秒杀这章我脑细胞死了很多,考虑了很多个点,最后还是出来了,忍不住给自己点赞

这章是真的不要白嫖,每次都看了不点赞,你们想白嫖我么?你们好坏喲,不过我好喜欢


文章每周持续更新,可以微信搜索「 敖丙 」第一时间阅读

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有