如果让你设计一个秒杀系统,你会怎么做?
mhr18 2025-05-03 15:26 27 浏览 0 评论
这个算是一个经典面试题了,虽说是一个场景题,但是也算是老八股了。
今天就从系统设计的角度来和小伙伴们聊一聊这个话题。
一般来说秒杀系统需要考虑到下面这样一些问题:
- 瞬时高并发流量
- 热点商品数据
- 库存管理
- 重复下单
- 黄牛
接下来我们就这里提到的点逐一进行分析。
本文主要和大家讲思路,不讲具体做法,具体做法在松哥之前的文章中很多已经和大家聊过了。
一 瞬时高并发流量
应对瞬时高并发流量,不是某一种方案就可以,是一个组合拳。另外大家要记得,系统设计没有银弹。
1.1 动静分离部署
这算是一个基本要求了,引入 Nginx,将静态资源和动态资源利用 Nginx 分流,静态资源直接返回,动态资源则转发给后端服务器去处理。
这一点其实还蛮重要,松哥之前就有遇到这个问题,一开始没有动静分离部署,后来动静分离部署之后,系统并发能力提升 2 倍以上。
不过如果愿意花点钱,把静态资源都交给云服务商的 CDN 来处理,那就更好了。
一般来说使用 CDN 是比较划算的,因为 CDN 流量费往往比云主机的流量费便宜。
1.2 数据库独立部署
这个也算是基操了,将应用程序和数据库部署到一起,往往无法让数据库发挥自己的极限性能。正常来说,一台 1C2G 的服务器上只部署 MySQL,就能做到每秒处理 200 次查询请求,这样的数据基本上就能满足一个每天 100W PV 的小网站了。
但是你想想,1C2G 的服务器部署 MySQL 和应用程序的话,估计卡的没法用了。
将 MySQL 和应用程序部署到一台服务器上,往往会因为两者互相影响而降低整体的并发性能,具体来说可能会发生这些问题:
- 高并发导致 CPU 被耗尽,进而 MySQL 响应变慢。
- 应用程序处理请求的时候需要等待更长的时间获取数据库的数据,这个过程占用了大量的内存。
- 系统内存紧张导致 MySQL 中缓存的数据被回收,进而拖慢 MySQL。
- 如此循环往复,系统最终越来越慢甚至崩溃。
因此我们要做的第二件事情就是将数据库和应用程序独立分开部署。
1.3 流量过滤
秒杀本来就是一个看运气的事,谁秒到算谁的,没秒到就算失败,产品数量往往有限,秒到的必然是少数人,所以在请求从客户端到达服务端并处理的过程中,可以对流量进行层层过滤。
一般来说,请求主要经过如下节点:
由于秒杀的随机性,我们可以这么做:
- Client 处也就是用户请求发起的地方,我们就可以随机丢弃一些请求,直接弹出秒杀失败、网络阻塞等等。
- 当请求到达 Nginx 之后,可以在 Nginx 处进行限流,利用像 limit_req_zone、limit_req_conn 等模块来实现不同的限流策略。
- 当请求从 Nginx 上转发到 Java 服务上之后,我们可以继续使用一些限流工具,比如 Sentinel,或者自己利用 Redis 写限流工具也可以,在这里继续进行限流。
- 当请求突破层层关卡到达业务层之后,对于实时性要求不高的数据,直接从缓存查询,缓存优先查本地缓存,其次是远程分布式缓存如 Redis,缓存中没有数据的话,最后再是 MySQL。
1.4 页面静态化
对于热点数据页面可以进行静态化处理。
比如秒杀商品页、秒杀商品详情页等等这些热点页面直接自动进行静态化处理,这样用户每次访问的时候,直接返回现成的页面,就不用走数据库了。
如果页面数据发生变化,重新自动生成静态页面即可。
二 热点商品数据
接下来就是热点商品数据的处理了。
秒杀这种事情,在秒杀活动开始之前,我们基本上就能够确定哪些数据是热点数据了,所以处理处理起来相对来说并不难。
不过需要注意的是,能缓存的数据肯定是一些商品信息类的数据,对于像库存这类实时性要求极高的数据,是不适合缓存的。
2.1 缓存预热
缓存预热主要从两方面入手:
- 本地缓存预热
- Redis 缓存预热
查询的时候先查本地缓存,没有再查 Redis 缓存,这样能够有效避免 Redis 的热 Key 问题。
2.2 数据拆分
另一方面就是我们要避免热点数据聚集到一起,将热点数据进行拆分。避免从一个缓存处去获取多个热点数据,这样就能降低缓存的压力。
比如:
- 商品详情数据
- 价格数据
- 秒杀规则数据
- 。。。
可以对这些热点数据进行拆分,其实拆分之后,热点数据也就不那么“热”了。
三 库存管理
库存因为实时性要求比较高,因此就不方便用缓存。
库存管理要是做不好,可能会发生超卖或者少卖。
那么库存管理怎么做呢?保险的方案当然就是直接去数据库扣减,但是数据库并发能力有限,所以往往还需要结合缓存来做。
我们分别来看。
3.1 数据库扣减
数据库扣减,为了避免把库存扣成负数,一般来说我们有两种思路:
- 悲观锁
- 乐观锁
在高并发场景下,悲观锁会导致更新效率降低很多;而乐观锁则会导致大量的失败。似乎都不是一个很好的选择。
其实我们只是要保证库存不被减为负数而已,那么其实就可以在更新 SQL 中添加一个条件就行了,像下面这样:
***** and 库存>=0
大致上这样就可以了。
不过只是这样做还不够,因为数据库的并发能力在哪摆着呢。所以我们还是要利用缓存。
3.2 缓存扣减
由于 Redis 本身就是单线程执行的,因此我们再结合上 Lua 脚本,就可以保证扣减库存这个操作的原子性。
在 Lua 脚本中我们可以获取到库存数据,然后判断库存,没问题再进行扣减。
Redis 本身的高性能+单线程执行+Lua 脚本的原子性,这三点结合起来就可以确保上述操作是没有问题的。
3.3 最佳实践
在具体实践中,往往是 3.1 和 3.2 结合起来。
具体流程是这样:
首先 Redis 做扣减,扣减完了之后,发送一条消息给 MQ,应用程序再去消费这条消息,消费消息时完成数据库的扣减。
这个过程中我们需要确保好 MQ 消息的可靠性和幂等性,处理好消息积压。
当然,稳妥起见还需要有对账机制,定时拉取 Redis 中的数据和数据库中的数据进行对比,保证数据的一致性。
四 重复下单
秒杀场景下用户由于比较焦急,频繁点击可能造成重复下单,因此我们需要处理好下单操作的幂等性。
这个也有很多思路,需要多管齐下。
4.1 前端置灰
前端用户点击之后,就对秒杀按钮进行置灰操作,同时提醒用户目前正在进行秒杀。
这是基操,但是不能从根本上解决问题,还得配合后段幂等性处理。
4.2 后端幂等性处理
后段幂等性处理有很多方案,可以利用 Token 机制,这个松哥之前也有很多文章介绍,不多说。
同时因为秒杀这种场景往往是限购的,因此在用户下单的时候可以判断是否有在途订单或者用户是否已经下单,进而决定当前下单操作是否能够成功。
五 黄牛
薅羊毛的黄牛也是我们要考虑的一个问题。
5.1 识别黄牛
首先我们要识别出来哪些用户可能是黄牛,一般来说,我们可以通过如下方式来识别:
- 请求频率:监测用户的请求频率,若某一账户的请求过于频繁,则可能是黄牛使用自动化工具发出的。
- 访问模式:分析用户的访问模式,例如短时间内大量的重复请求或者非正常人类行为的访问模式。
- IP 地址:检查请求来源的 IP 地址,对于同一 IP 地址下频繁的请求进行限制或标记。
如果公司有足够的人力资源,这块可以建立预测模型,通过模型去分析哪些人可能是黄牛。
5.2 防止黄牛
当我们识别出来黄牛之后,一般来说有如下一些办法:
- 图形验证码(CAPTCHA):在关键环节加入图形验证码,要求用户识别并输入相应的字符,以防止自动化工具的使用。
- 滑动验证:在关键环节采用滑动验证等交互式验证方式,这类验证方式难以被自动化工具模拟,这也是大家目前见到的最多的验证方式了。
- 行为验证:基于用户的行为轨迹(如鼠标移动轨迹、键盘输入模式等)来进行验证,这个目前松哥只在京东图书上见过这种验证方式。
- 请求频率限制:对识别出来的用户或 IP 地址的请求频率进行限制,超出限制则暂时禁止访问,这块利用 Nginx 或者 Sentinel 就能实现。
- 黑名单:对于已知的黄牛 IP 地址或账户进行封禁处理,这块可以直接在 Nginx 上处理,也可以在网关如 Spring Cloud Gateway 上处理。
- 动态调整:根据系统的实时负载情况动态调整限流阈值。
六 小结
秒杀是一个大工程,以上是松哥和大家分享的一些实现思路,具体落实下来还有很多细节需要处理。
借助本文希望小伙伴们在面试的时候不怯场,能够回答出来。
欢迎小伙伴们在评论区分享自己的方案或者提出补充。
相关推荐
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
- 备份软件调用rman接口备份报错RMAN-06820 ORA-17629 ORA-17627
-
一、报错描述:备份归档报错无法连接主库进行归档,监听问题12541RMAN-06820:WARNING:failedtoarchivecurrentlogatprimarydatab...
- 增量备份修复物理备库gap(增量备份恢复数据库步骤)
-
适用场景:主备不同步,主库归档日志已删除且无备份.解决方案:主库增量备份修复dg备库中的gap.具体步骤:1、停止同步>alterdatabaserecovermanagedstand...
- 一分钟看懂,如何白嫖sql工具(白嫖数据库)
-
如何白嫖sql工具?1分钟看懂。今天分享一个免费的sql工具,毕竟现在比较火的NavicatDbeaverDatagrip都需要付费才能使用完整功能。幸亏今天有了这款SQLynx,它不仅支持国内外...
- 「开源资讯」数据管理与可视化分析平台,DataGear 1.6.1 发布
-
前言数据齿轮(DataGear)是一款数据库管理系统,使用Java语言开发,采用浏览器/服务器架构,以数据管理为核心功能,支持多种数据库。它的数据模型并不是原始的数据库表,而是融合了数据库表及表间关系...
- 您还在手工打造增删改查代码么,该神器带你脱离苦海
-
作为Java开发程序,日常开发中,都会使用Spring框架,完成日常的功能开发;在相关业务系统中,难免存在各种增删改查的接口需求开发。通常来说,实现增删改查有如下几个方式:纯手工打造,编写各种Cont...
- Linux基础知识(linux基础知识点及答案)
-
系统目录结构/bin:命令和应用程序。/boot:这里存放的是启动Linux时使用的一些核心文件,包括一些连接文件以及镜像文件。/dev:dev是Device(设备)的缩写,该目录...
- PL/SQL 杂谈(二)(pl/sql developer使用)
-
承接(一)部分。我们从结构和功能这两个方面展示PL/SQL的关键要素。可以看看PL/SQL的优雅的代码。写出一个好的代码,就和文科生写出一篇优秀的作文一样,那么赏心悦目。1、与SQL的集成PL/S...
- 电商ERP系统哪个好用?(电商erp哪个好一点)
-
电商ERP系统哪个好用?做电商的,谁还没被ERP折腾过?有老板说:“我们早就上了ERP,订单、库存、财务全搞定,系统用得飞起。”也有运营吐槽:“系统是上了,可库存老不准,订单漏单错单天天有,财务对账还...
- 汽车检测线系统实例,看集中控制与PLC分布控制
-
PLC可编程控制器,上个世纪70年代初,为取代早期继电器控制线路,开始采取存储指令方式,完成顺序控制而设计的。开始仅有逻辑运算、计时、计数等简单功能。随着微处理的发展,PLC可编程能力日益提高,已经能...
- 苹果五件套成公司年会奖品主角,几大小技巧教你玩转苹果新品
-
钱江晚报·小时新闻记者张云山随着春节的临近,各家大公司的年会又将陆续上演。上周,各大游戏公司的年会大奖,苹果五件套又成了标配。在上海的游戏公司中,莉莉丝奖品列表拉得相当长,从特等奖到九等奖还包含了特...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)