秒杀系统架构设计
程序员文章站
2022-03-03 14:35:12
...
“秒杀”,就是在同一个时刻有大量请求争抢购买同一个商品,并完成交易的过程,其间涉及大量的并发读和并发写,并要求高可靠和高性能的系统支持。
关键词:稳、准、快。
要求:高性能,一致性,高可用。
高性能:秒杀涉及大量的并发读和写,需要支持高并发,数据动静分离,热点的发现与隔离,请求的削峰与分层过滤、服务端的优化
一致性:商品库存需要一致性,不可超卖或少卖,保证数据的准确性。
高可用:做兜底方案,一但系统故障,不影响系统使用。
架构原则:
1、数据要尽量少
2、请求数要尽量少
3、路径要尽量短
4、依赖要尽量少
5、不要单机系统
一、动静分离
把用户请求的数据划分为动态数据和静态数据。
静态数据做缓存,常见的有浏览器,CDN,服务端Cache。
静态化改造就是要直接缓存HTTP连接。
改造内容:
1、URL唯一化。为啥要URL唯一呢?缓存整个HTTP连接,就可以用URL作为缓存的Key。
2、分离浏览者相关的因素。浏览者相关的因素包括是否已登录,以及登录身份等,这些相关因素我们可以单独拆分出来,通过动态请求来获取。
3、分离时间因素。服务端输出的时间也通过动态请求获取。
4、异步化地域因素。详情页面上与地域相关的因素做成异步方式获取,当然你也可以通过动态请求方式获取,只是这里通过异步获取更合适。
5、去掉Cookie。服务端输出的页面包含的Cookie可以通过代码软件来删除,如Web服务器Varnish可以通过unset req.http.cookie 命令去掉Cookie。注意,这里说的去掉Cookie并不是用户端收到的页面就不含Cookie了,而是说,在缓存的静态数据中不含有Cookie。
改造方案:
1、ESI方案(或者SSI):即在Web代理服务器上做动态内容请求,并将请求插入到静态页面中,当用户拿到页面时已经是一个完整的页面了。这种方式对服务端性能有些影响,但是用户体验较好。
2、CSI方案。即单独发起一个异步JavaScript 请求,以向服务端获取动态内容。这种方式服务端性能更佳,但是用户端页面可能会延时,体验稍差。
动静分离3种架构方案:
(1)实体机单机部署
1、没有网络瓶颈,能使用大内存
2、能提升命中率,减少Gzip压缩
3、减少Cache失效压力
4、hash 分组越少,缓存的命中率肯定就会越高,但短板是也会使单个商品集中在一个分组中,容易导致 Cache 被击穿,所以我们应该适当增加多个相同的分组,来平衡访问热点和命中率的问题
(2)统一Cache层
优点:
1、减少运维成本,方便接入其他静态化系统
2、可以共享内存
缺点:
1、Cache层内部交换网络成为瓶颈
2、缓存服务器网卡瓶颈
3、风险较大
(3)CDN
1、失效问题,CDN上可以主动失效,存储在浏览器不可控,只有用户主动刷新才可以失效。
2、命中率问题
3、发布更新问题
CDN部署有以下几个特点:
1、整个页面缓存在用户浏览器中
2、强制刷新整个页面,也会请求CDN
3、实际有效请求,只是用户对按钮的点击
二、实时热点发现与处理
1、发现热点数据
发现静态热点数据与动态数据
静态数据方式:
1、提前报名筛选
2、TOPN商品
动态数据方式:
1、构建一个异步的系统,它可以收集交易链路上各个环节中的中间件产品的热点Key,如Nginx、缓存、RPC服务框架等这些中间件(一些中间件产品本身已经有热点统计模块)。
2、建立一个热点上报和可以按照需求订阅的热点服务的下发规范,主要目的是通过交易链路上各个系统(包括详情、购物车、交易、优惠、库存、物流等)访问的时间差,把上游已经发现的热点透传给下游系统,提前做好保护。比如,对于大促高峰期,详情系统是最早知道的,在统一接入层上Nginx模块统计的热点URL。
3、将上游系统收集的热点数据发送到热点服务台,然后下游系统(如交易系统)就会知道哪些商品会被频繁调用,然后做热点保护。
2、处理热点数据
(1)优化
优化热点数据最有效的办法就是缓存热点数据,如果热点数据做了动静分离,那么可以长期缓存静态数据。但是,缓存热点数据更多的是“临时”缓存,即不管是静态数据还是动态数据,都用一个队列短暂地缓存数秒钟,由于队列长度有限,可以采用LRU淘汰算法替换。
(2)限制
限制更多的是一种保护机制,限制的办法也有很多,例如对被访问商品的ID做一致性Hash,然后根据Hash做分桶,每个分桶设置一个处理队列,这样可以把热点商品限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源,而使其他请求始终得不到服务器的处理资源。
(3)隔离
1、业务隔离(域名)
2、系统应用隔离
3、数据隔离
三、削峰
削峰的3种处理方式:
一个是通过队列来缓冲请求,即控制请求的发出;
一个是通过答题来延长请求发出的时间,在请求发出后承接请求时进行控制,最后再对不符合条件的请求进行过滤;
最后一种是对请求进行分层过滤。
1、排队
通过引入消息队列(MQ),将请求放入队列中,通过队列先进先出的特点,来达到削峰。
队列选型:大型选火箭(RocketMQ),中小型选兔子(RabbitMQ)。
2、答题
目的:
1、防止部分买家使用秒杀器在参加秒杀时使用。
2、延缓请求,起到对请求流量进行削峰的作用,从而让系统能够更好地支持瞬时的流量高峰。
加入答题环节,需要引入题库系统:
1、题库生成模块
2、题库的推送模块
3、题目的图片生成模块,用于把题目生成为图片格式,并且在图片里增加一些干扰因素。
3、分层过滤(漏斗式)
大部分数据和流量在用户浏览器或者CDN上获取,这一层可以拦截大部分数据的读取;
经过第二层(即前台系统)时数据(包括强一致性的数据)尽量走Cache,过滤一些无效的请求;
再到第三层后台系统,主要做数据的二次检验,对系统做好保护和限流,这样数据量和请求就进一步减少;
最后在数据层完成数据的强一致性校验。
分层校验的基本原则是:
将动态请求的读数据缓存(Cache)在Web端,过滤掉无效的数据读
对读数据不做强一致性校验,减少因为一致性校验产生瓶颈的问题
对写数据进行基于时间的合理分片,过滤掉过期的失效请求
对写请求做限流保护,将超出系统承载能力的请求过滤掉
对写数据进行强一致性校验,只保留最后有效的数据
分层校验的目的:对读系统,尽量减少由于一致性校验带来的系统瓶颈,尽量将不影响性能的检查条件提前;对写系统对写数据做一致性检查,在数据库层保证数据的最终准确性。
四、扣减库存核心逻辑
1、下单减库存,即当买家下单后,在商品的总库存中减去买家购买数量。下单减库存是最简单的减库存方式,也是控制最精确的一种,下单时直接通过数据库的事务机制控制商品库存,这样一定不会出现超卖的情况。但是你要知道,有些人下完单可能并不会付款。
2、付款减库存,即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库存,否则库存一直保留给其他买家。但因为付款时才减库存,如果并发比较高,有可能出现买家下单后付不了款的情况,因为可能商品已经被其他人买走了。
3、预扣库存,这种方式相对复杂一些,买家下单后,库存为其保留一定的时间(如10分钟),超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买。在买家付款前,系统会校验该订单的库存是否还有保留:如果没有保留,则再次尝试预扣;如果库存不足(也就是预扣失败)则不允许继续付款;如果预扣成功,则完成付款并实际地减去库存。
针对秒杀场景,采用下单减库存,针对秒杀场景,下单成功不付款几乎不存在;其他的电商一般采用预留库存的方式。
五、系统可用性保障
所谓兜底保护就是上线后,系统一旦出现故障,需要怎么应急处理,既然出现故障,当然就会体验不好的,但是为了活动,还得把任务完成。
1、服务降级
2、限流
3、拒绝服务
老王推荐阅读:
1、《深入分析Java Web技术内幕》。
2、《架构演进与性能优化》。
https://mp.weixin.qq.com/s/gnW5Yom6Vf7Wii2kCN1qKQ
关键词:稳、准、快。
要求:高性能,一致性,高可用。
高性能:秒杀涉及大量的并发读和写,需要支持高并发,数据动静分离,热点的发现与隔离,请求的削峰与分层过滤、服务端的优化
一致性:商品库存需要一致性,不可超卖或少卖,保证数据的准确性。
高可用:做兜底方案,一但系统故障,不影响系统使用。
架构原则:
1、数据要尽量少
2、请求数要尽量少
3、路径要尽量短
4、依赖要尽量少
5、不要单机系统
一、动静分离
把用户请求的数据划分为动态数据和静态数据。
静态数据做缓存,常见的有浏览器,CDN,服务端Cache。
静态化改造就是要直接缓存HTTP连接。
改造内容:
1、URL唯一化。为啥要URL唯一呢?缓存整个HTTP连接,就可以用URL作为缓存的Key。
2、分离浏览者相关的因素。浏览者相关的因素包括是否已登录,以及登录身份等,这些相关因素我们可以单独拆分出来,通过动态请求来获取。
3、分离时间因素。服务端输出的时间也通过动态请求获取。
4、异步化地域因素。详情页面上与地域相关的因素做成异步方式获取,当然你也可以通过动态请求方式获取,只是这里通过异步获取更合适。
5、去掉Cookie。服务端输出的页面包含的Cookie可以通过代码软件来删除,如Web服务器Varnish可以通过unset req.http.cookie 命令去掉Cookie。注意,这里说的去掉Cookie并不是用户端收到的页面就不含Cookie了,而是说,在缓存的静态数据中不含有Cookie。
改造方案:
1、ESI方案(或者SSI):即在Web代理服务器上做动态内容请求,并将请求插入到静态页面中,当用户拿到页面时已经是一个完整的页面了。这种方式对服务端性能有些影响,但是用户体验较好。
2、CSI方案。即单独发起一个异步JavaScript 请求,以向服务端获取动态内容。这种方式服务端性能更佳,但是用户端页面可能会延时,体验稍差。
动静分离3种架构方案:
(1)实体机单机部署
1、没有网络瓶颈,能使用大内存
2、能提升命中率,减少Gzip压缩
3、减少Cache失效压力
4、hash 分组越少,缓存的命中率肯定就会越高,但短板是也会使单个商品集中在一个分组中,容易导致 Cache 被击穿,所以我们应该适当增加多个相同的分组,来平衡访问热点和命中率的问题
(2)统一Cache层
优点:
1、减少运维成本,方便接入其他静态化系统
2、可以共享内存
缺点:
1、Cache层内部交换网络成为瓶颈
2、缓存服务器网卡瓶颈
3、风险较大
(3)CDN
1、失效问题,CDN上可以主动失效,存储在浏览器不可控,只有用户主动刷新才可以失效。
2、命中率问题
3、发布更新问题
CDN部署有以下几个特点:
1、整个页面缓存在用户浏览器中
2、强制刷新整个页面,也会请求CDN
3、实际有效请求,只是用户对按钮的点击
二、实时热点发现与处理
1、发现热点数据
发现静态热点数据与动态数据
静态数据方式:
1、提前报名筛选
2、TOPN商品
动态数据方式:
1、构建一个异步的系统,它可以收集交易链路上各个环节中的中间件产品的热点Key,如Nginx、缓存、RPC服务框架等这些中间件(一些中间件产品本身已经有热点统计模块)。
2、建立一个热点上报和可以按照需求订阅的热点服务的下发规范,主要目的是通过交易链路上各个系统(包括详情、购物车、交易、优惠、库存、物流等)访问的时间差,把上游已经发现的热点透传给下游系统,提前做好保护。比如,对于大促高峰期,详情系统是最早知道的,在统一接入层上Nginx模块统计的热点URL。
3、将上游系统收集的热点数据发送到热点服务台,然后下游系统(如交易系统)就会知道哪些商品会被频繁调用,然后做热点保护。
2、处理热点数据
(1)优化
优化热点数据最有效的办法就是缓存热点数据,如果热点数据做了动静分离,那么可以长期缓存静态数据。但是,缓存热点数据更多的是“临时”缓存,即不管是静态数据还是动态数据,都用一个队列短暂地缓存数秒钟,由于队列长度有限,可以采用LRU淘汰算法替换。
(2)限制
限制更多的是一种保护机制,限制的办法也有很多,例如对被访问商品的ID做一致性Hash,然后根据Hash做分桶,每个分桶设置一个处理队列,这样可以把热点商品限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源,而使其他请求始终得不到服务器的处理资源。
(3)隔离
1、业务隔离(域名)
2、系统应用隔离
3、数据隔离
三、削峰
削峰的3种处理方式:
一个是通过队列来缓冲请求,即控制请求的发出;
一个是通过答题来延长请求发出的时间,在请求发出后承接请求时进行控制,最后再对不符合条件的请求进行过滤;
最后一种是对请求进行分层过滤。
1、排队
通过引入消息队列(MQ),将请求放入队列中,通过队列先进先出的特点,来达到削峰。
队列选型:大型选火箭(RocketMQ),中小型选兔子(RabbitMQ)。
2、答题
目的:
1、防止部分买家使用秒杀器在参加秒杀时使用。
2、延缓请求,起到对请求流量进行削峰的作用,从而让系统能够更好地支持瞬时的流量高峰。
加入答题环节,需要引入题库系统:
1、题库生成模块
2、题库的推送模块
3、题目的图片生成模块,用于把题目生成为图片格式,并且在图片里增加一些干扰因素。
3、分层过滤(漏斗式)
大部分数据和流量在用户浏览器或者CDN上获取,这一层可以拦截大部分数据的读取;
经过第二层(即前台系统)时数据(包括强一致性的数据)尽量走Cache,过滤一些无效的请求;
再到第三层后台系统,主要做数据的二次检验,对系统做好保护和限流,这样数据量和请求就进一步减少;
最后在数据层完成数据的强一致性校验。
分层校验的基本原则是:
将动态请求的读数据缓存(Cache)在Web端,过滤掉无效的数据读
对读数据不做强一致性校验,减少因为一致性校验产生瓶颈的问题
对写数据进行基于时间的合理分片,过滤掉过期的失效请求
对写请求做限流保护,将超出系统承载能力的请求过滤掉
对写数据进行强一致性校验,只保留最后有效的数据
分层校验的目的:对读系统,尽量减少由于一致性校验带来的系统瓶颈,尽量将不影响性能的检查条件提前;对写系统对写数据做一致性检查,在数据库层保证数据的最终准确性。
四、扣减库存核心逻辑
1、下单减库存,即当买家下单后,在商品的总库存中减去买家购买数量。下单减库存是最简单的减库存方式,也是控制最精确的一种,下单时直接通过数据库的事务机制控制商品库存,这样一定不会出现超卖的情况。但是你要知道,有些人下完单可能并不会付款。
2、付款减库存,即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库存,否则库存一直保留给其他买家。但因为付款时才减库存,如果并发比较高,有可能出现买家下单后付不了款的情况,因为可能商品已经被其他人买走了。
3、预扣库存,这种方式相对复杂一些,买家下单后,库存为其保留一定的时间(如10分钟),超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买。在买家付款前,系统会校验该订单的库存是否还有保留:如果没有保留,则再次尝试预扣;如果库存不足(也就是预扣失败)则不允许继续付款;如果预扣成功,则完成付款并实际地减去库存。
针对秒杀场景,采用下单减库存,针对秒杀场景,下单成功不付款几乎不存在;其他的电商一般采用预留库存的方式。
五、系统可用性保障
所谓兜底保护就是上线后,系统一旦出现故障,需要怎么应急处理,既然出现故障,当然就会体验不好的,但是为了活动,还得把任务完成。
1、服务降级
2、限流
3、拒绝服务
老王推荐阅读:
1、《深入分析Java Web技术内幕》。
2、《架构演进与性能优化》。
https://mp.weixin.qq.com/s/gnW5Yom6Vf7Wii2kCN1qKQ