秒杀系统

时间:2023-01-12 17:57:57

秒杀是一个短时间并发暴增的场景,实际只有很小部分有效下单操作。秒杀是一个很热门的话题,甚至是体现能力的标志,包括很多面试都会问秒杀设计。

以下设计仅为案例分析说明,供日后参考,如有缺陷欢迎纠正。

秒杀系统类似漏斗结构,将大量的无用读操作过滤,保留少量的数据穿过执行写操作,并保证不会超写。其核心在于如何无限量的过滤掉无用读操作,常识中都知道使用缓存进行过滤,如何分布式搭建缓存是个棘手的问题。


秒杀处理

这里仅以库存处理及限流相关进行说明,不包含分布式数据库及事务、集群搭建、服务注册等。


秒杀库存处理

秒杀最忌讳超卖,但不要求绝对先到先得。实际上也很难做到绝对先到先得,请求进入业务处理到最终数据落地实际顺序受限于网络延时、进程调度差异、分布式处理等因素,并不是先请求的一定先处理,不过时间偏差不会太大。

通过缓存能有效的过滤多余请求,只保留少量请求穿过。实际上一个缓存实例并不能解决无限流量压力,因此需要多个分担。分担有两种方法:主从同步集群、各自独立。缓存在秒杀的过程中缓存读写量基本均衡,主从模式难以适应,因此使用各自独立会更容易扩展。


缓存处理位置

秒杀缓存处理可由业务处理服务处理也可以由HTTP服务处理,前者性能稍差,后者性能更佳。HTTP服务中处理需要在服务中调用缓存并进行相关判断处理,让符合的请求进入业务处理服务器。这样会降低HTTP服务性能但能提升秒杀处理性能。

在HTTP服务上处理缓存一般会借助lua,lua是一个高性能轻量级开发语言(整个包只有几百K大小),可以内嵌到很多软件中。

nginx有openresty衍射软件,openresty提供了nginx与lua结合的扩展,并提供了一些常用的lua库,非常方便快速开发一个秒杀缓存处理lua程序。

apache也有lua扩展,编译安装时指定 --enable-lua、--enable-luajit、--with-lua=PATH选项进行安装,安装后会有一个mod_lua.so模块,不同的是apache将lua响应脚本运行(类似运行PHP),需要作额外的处理进行转发重定向操作。


缓存库存

将库存数量备份到缓存中,进行预扣减,能有效过滤掉无效请求,缓存中的库存数量不需要作为最终有效库存数量。

示例:峰值100W并发量、秒杀10件商品、每件商品10件。假设一个缓存实例最高处理1W并发量,那最少需要100个独立缓存实例(实际需要留余量,比如提供120个缓存实例)。每个实例保存10件商品并记录库存数10件,则穿过缓存的请求最大数为120*10=1200。显然请求数量已经远远超过秒杀限制的库存量,但这不是最终的秒杀库存数量,最终的库存数量还需要业务处理进行再次过滤。穿过请求的数量级在业务处理及数据库压力上是能接受的,为可靠还可以增加队列分流处理。库存最终扣减核实处理由业务处理和落地数据库上限制超卖,如果数据库使用了分布式或不能保证库存超卖,则最终库存还可以再放到一个专用主从同步缓存集群或指定数据库中扣减处理。

各自独立能保证各自的数据独立,当部分节点异常时不影响其它节点数据,只要能抗住并发压力即可。当以数倍库存量的请求进来时通过业务处理能可靠的再次过滤掉多余的请求,并将指定的库存数生成对应订单。订单生成满时可释放所有缓存库存,当订单有失效时则可增加缓存数量,以便再放入请求进行处理。

从示例中可以看出秒杀只需要过滤掉大量无用请求,剩下的请求进入穿过业务处理基本都能直接胜任,如果并发量再大还可以分多级缓存过滤,保证最终进入业务处理的请求量在一个接受范围内,同时也可以结合队列进行分流处理。

缺点:所有秒杀请求均需要查询缓存,缓存压力大。

优点:能适应秒杀数量很多的场景,并占用较少的缓存空间。


令牌限流

生成指定库存数量的令牌,用户在秒杀前先自动获取令牌,有令牌才认为是有效的请求并允许穿过。令牌数量也可以超过库存数量数倍,用来缓解部分用户有令牌不用或超时占用指标的情况。

示例:峰值100W并发量、秒杀10件商品、每件商品10件。总共最少需要生成 10 *10 = 100个令牌,为缓解令牌被无效占用,可生成3000个令牌,分布在30个独立的缓存实例中(注意:令牌需要包含缓存实例的标识及期限)。假设一个缓存实例最高处理1W并发量,那么令牌校验处理最高可达30W的并发。无令牌会直接跳过(终端也可以禁止发送无令牌的秒杀请求),但需要防止恶意伪造令牌进行欺骗处理,因此还需要增加队列进行分流缓冲。

令牌生成方式能有效防止伪造,但不能防止欺骗(比如:重复使用令牌)。令牌生成格式最少包含:节点标识、期限、唯一标识。令牌信息建议通过可逆加密处理,比如:对称或非对称加密处理。

最终库存处理与缓存库存处理方式一样,一般也建议最终库存由业务处理另行再核验,防止超卖。

缺点:令牌占用较大的缓存空间不适用秒杀数量太多的场景。

优点:能过快速跳过无令牌或无效令牌请求,降低并发压力。


防刷单

防刷单能防止不公平秒杀,类似火车票抢票工具一样,通过工具进行不公平操作,导致正常用户无法下单。防刷也是一个伪命题,实际上处理只是相对性的防刷单。防刷单不能保证绝对可靠,但能阻止一些行为,比如:单纯的暴力下单。

防止刷单主要方法有:令牌分发、验证码校验、黑名单、区域限制等。

令牌分发

令牌分发增加限制,尽可能的均衡分发。

比如:

  1. IP地址前两位进行限制,给每个地址区段每秒最多2令牌。
  2. 同一个ip地址区段一分钟最多5个令牌。
  3. 增加验证码校验


验证码校验

验证码程序解码可能性比较大,需要持续更新结构才能有效阻止短时间内解码。只有通过验证码的请求方可进入秒杀操作,能有效延缓请求,同时验证码会会影响交互体验,可考虑在秒杀前几分钟内进行验证码识别确认是人为操作,然后进行许可秒杀操作并限制秒杀操作次数。

验证码的结构主要有:三方滑动(收费安全性较高)、图片内找标识、图片识别选择、图片文字识别输入。


黑名单

黑名单主要是针对用户或IP地址而言,用户限制会比较简单合理,IP限制会产生很大的IP库并且容易造成误伤。


区域限制

区域限制可使用类似令牌分发IP地址区段限制,也可以使用不同的用户信息或提前获取IP地址区域进行限制。


高可用

高可用不仅仅在秒杀系统要有要求,一般要求高的系统均会提出高可用。一般需要高可用的是单一入口的服务,入口单一旦故障将无法提供服务,比如负载均衡器、数据库、单节点服务等。其它可以做成集群的均可通过服务检测或服务注册和方式处理,只要不是批量故障均不太影响服务。一般讲的高可用只针对故障切换,并不是压力超载、业务异常、被暴力等造成异常的解决方案。

高可用分两种:主备模式、多节点模式。

主备模式

主备模式一般是使用虚拟路由冗余协议(Virtual Router Redundancy Protocol,简称VRRP)工具,典型软件有:keepalived、heartbeat等。

虚拟路由器会在局域网内生成一个虚拟路由器,分为主备两种,当主路由器异常不可用时会自动启动备路由器继续提个服务。实现故障快速切换备用节点,VRRP是一种冗余高可用方案,实际应用中需要提供一个以上的备用节点等待故障切换,而备用节点只在故障切换后才对外供服务的。

主备模式能保证故障快速切换,避免后端服务停摆,后续再人工干预恢复故障服务器。

适用于所有场景。


多节点模式

多节点模式需要连接端(比如:用户终端程序)配合,当检测到某个节点不可用时自动切换到其它节点上,尽可能保证服务正常。

比如:app中可以自动切换接口负载均衡器的入口地址,提供服务。

多节点模式不需要冗余节点,所有节点均正常提供服务,一旦出现故障异常后端服务将停摆并需要人工尽快干预恢复正常,所有单一服务承担的压力不可过大,保证少量节点故障时其它节点能正常分担并提供服务。

适用于连接端可自动切换连接入口的场景。


硬件相关

目前除了超级计算机外(实际就是N多个子计算机组合的),即使是专业商用服务器也不能保证无限量性能(实际上应用很贵),多数使用一般通用商业服务器(性能很有限,性价比高)。一般通用商业服务器需要进行多台并行运行,以分担压力。

硬件不作为重点说明。


云服务器

使用方便,无需专业运维人员管理,能快速组建集群和高性能负载均衡器,还能快速实现动态伸缩集群数量应对高峰时期。但有一点要说明的是,云服务器实际上也是由很多商业服务器组成的一个超大的机房,通过云管理系统进行管理,还必需有盈利,因此在小业务应用时很适用,在大型业务应用时很多公司会自己组建机房。


组建机房

未曾组建过机房无相关经验。按一般常识理解必需项有:无尘恒温干燥机房(空调、除尘机等)、光纤宽带(电信、移动等)、硬件负载器(F5、A10、citrix Netscaler等)、网络设备(路由器、交换机等)、商业服务器(若干)、服务器机柜、机房报警系统(烟雾报警、温度报警、湿度报警、消防报警等)、机房电路系统(防雷击、防浪涌、防过载、防漏电、防短路等)、机房备用电源(停电时自动启用)等。

机房开支很大,而且需要电工、运维等专业人员管理。