秒杀系统的架构设计

最近为了提升自己,学习了极客时间许老师的秒杀课程,本文为学习秒杀系统课程整理。

秒杀关键词:稳、准、快。

秒杀要求:高性能,一致性,高可用。

高性能:秒杀涉及大量的并发读和写,需要支持高并发,数据动静分离,热点的发现与隔离,请求的削峰与分层过滤、服务端的优化

一致性:商品库存需要一致性,不可超卖或少卖,保证数据的准确性。

高可用:做兜底方案,一但系统故障,不影响系统使用。

架构原则:

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,过滤一些无效的请求;

再到第三层后台系统,主要做数据的二次检验,对系统做好保护和限流,这样数据量和请求就进一步减少;

最后在数据层完成数据的强一致性校验。

分层校验的基本原则是:

1、将动态请求的读数据缓存(Cache)在Web端,过滤掉无效的数据读

2、对读数据不做强一致性校验,减少因为一致性校验产生瓶颈的问题

3、对写数据进行基于时间的合理分片,过滤掉过期的失效请求

4、对写请求做限流保护,将超出系统承载能力的请求过滤掉

5、对写数据进行强一致性校验,只保留最后有效的数据

分层校验的目的:对读系统,尽量减少由于一致性校验带来的系统瓶颈,尽量将不影响性能的检查条件提前;对写系统对写数据做一致性检查,在数据库层保证数据的最终准确性。

四、扣减库存核心逻辑

1、下单减库存,即当买家下单后,在商品的总库存中减去买家购买数量。下单减库存是最简单的减库存方式,也是控制最精确的一种,下单时直接通过数据库的事务机制控制商品库存,这样一定不会出现超卖的情况。但是你要知道,有些人下完单可能并不会付款。

2、付款减库存,即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库存,否则库存一直保留给其他买家。但因为付款时才减库存,如果并发比较高,有可能出现买家下单后付不了款的情况,因为可能商品已经被其他人买走了。

3、预扣库存,这种方式相对复杂一些,买家下单后,库存为其保留一定的时间(如10分钟),超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买。在买家付款前,系统会校验该订单的库存是否还有保留:如果没有保留,则再次尝试预扣;如果库存不足(也就是预扣失败)则不允许继续付款;如果预扣成功,则完成付款并实际地减去库存。

针对秒杀场景,采用下单减库存,针对秒杀场景,下单成功不付款几乎不存在;其他的电商一般采用预留库存的方式。

五、系统可用性保

所谓兜底保护就是上线后,系统一旦出现故障,需要怎么应急处理,既然出现故障,当然就会体验不好的,但是为了活动,还得把任务完成。

1、服务降级

2、限流

3、拒绝服务

老王推荐阅读:

1、《深入分析Java Web技术内幕》。

2、《架构演进与性能优化》。

你可能感兴趣的:(秒杀系统的架构设计)