秒杀系统设计架构 秒杀系统流程_限流

秒杀系统是学习“三高”(高性能、高并发、高可用)的一个非常好的例子,具有读多写少、瞬时流量、高并发读、高并发写以及高可用等特点。


一、什么是秒杀

秒杀系统是网络商家为了促销等目的进行的网上限时抢购活动。比如某宝某东某夕夕上的秒杀。用户在规定的时间内,定时定量的秒杀,无论商品是否秒杀完毕,该场次的秒杀活动都会结束。

秒杀系统具有瞬时流量、高并发读、高并发写以及高可用等特点。秒杀时会有大量用户在同一时间进行抢购,瞬时并发访问量突然增加10倍,甚至100倍以上都有可能。

秒杀系统的架构设计思想主要有:

(1)缓存

把部分业务逻辑迁移到内存的缓存或者Redis中,从而极大地提高并发读效率。

 

(2)削峰

杀开始的一瞬间,会有大量用户冲进来,所以在开始时会有一个瞬间流量峰值。如何使瞬间的流量峰值变得更平缓,是成功设计秒杀系统的关键。要实现流量的削峰填谷,一般的方法是采用缓存和MQ中间件。

 

(3)异步

将同步业务设计成异步处理的任务,以提高网站的整体可用性。

 

(4)限流

由于活动库存量一般都很少,只有少部分用户才能秒杀成功,所以需要限制大部分用户流量,只准少量用户流量进入后端服务器。

 

二、秒杀系统的工作流程

秒杀系统的整体工作流程

秒杀系统设计架构 秒杀系统流程_秒杀系统设计架构_02

 

三、秒杀系统的简单实现

3.1 创建Spring Boot项目

        项目名称为 shopping-kill-system。

3.2 库表设计与Model实体类

      创建数据库shopping-kill-system并设计秒杀系统的库表,这里的库表分别是lc_product(商品表)、lc_client(客户表)、lc_order_details(秒杀订单明细表),同时,初始化用户和商品数据。具体的SQL语句如下:

1 -- ----------------------
 2 -- 商品表 lc_product
 3 -- ----------------------
 4 CREATE TABLE `lc_product` (
 5   `pro_id` bigint(20) NOT NULL COMMENT '商品id',
 6   `pro_name` varchar(255) NOT NULL DEFAULT '' COMMENT '商品名称',
 7   `pro_num` int(11) NOT NULL DEFAULT '0' COMMENT '商品数量',
 8   `pro_img` varchar(255) DEFAULT NULL COMMENT '商品图片',
 9   `create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
10   `start_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP COMMENT '秒杀开始时间',
11   `end_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP COMMENT '秒杀结束时间',
12   PRIMARY KEY (`pro_id`),
13   KEY `idx_name` (`pro_name`) USING BTREE
14 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
15  
16 -- 商品表新增数据
17 INSERT INTO lc_product VALUES ('1', '安家存', '100', '/anjiecun1.jpg','2021-01-01 00:00:00', '2021-01-01 06:00:00', '2021-01-01 12:00:00');
18 INSERT INTO lc_product VALUES ('2', '新婚宝', '100', '/xinhunbao.jpg','2021-01-01 00:00:00', '2021-01-01 06:00:00', '2021-01-02 00:00:00');
19 INSERT INTO lc_product VALUES ('3', '月薪宝', '100', '/yuexinbao.jpg','2021-01-01 00:00:00', '2021-01-02 06:00:00', '2021-01-03 00:00:00');
20  
21  
22 -- ----------------------
23 -- 客户表 lc_client
24 -- ----------------------
25 CREATE TABLE `lc_client` (
26   `client_id` bigint(20) NOT NULL COMMENT '客户id',
27   `client_name` varchar(255) NOT NULL COMMENT '客户名称',
28   `phone_num` VARCHAR(11) DEFAULT NULL COMMENT '电话号码',
29   PRIMARY KEY (`client_id`)
30 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
31  
32 -- 客户表新增数据
33 INSERT into lc_client VALUES ('1', '貂蝉', '15020000000');
34 INSERT into lc_client VALUES ('2', '伽罗', '15027777777');
35 INSERT into lc_client VALUES ('3', '韩信', '15029999999');
36  
37  
38 -- -------------------------------------
39 -- 秒杀订单明细表 lc_order_details
40 -- -------------------------------------
41 CREATE TABLE `lc_order_details` (
42   `order_id` bigint(20) NOT NULL auto_increment COMMENT '订单id',
43   `client_id` bigint(20) NOT NULL COMMENT '客户id',
44   `client_name` varchar(255) NOT NULL COMMENT '客户名称',
45     `pro_id` bigint(20) NOT NULL COMMENT '商品id',
46   `pro_name` varchar(255) NOT NULL DEFAULT '' COMMENT '商品名称',
47   `order_state` TINYINT(4) DEFAULT NULL COMMENT '订单状态(-1:无效,0:成功,1:已付款,2:待付款)',
48   `create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
49   PRIMARY KEY (`order_id`)
50 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
51

 

在页面中,引入jquery.min.js和bootstrap.min.js文件,jQuery是一个JavaScript库,可极大地简化JavaScript编程。Bootstrap是Twitter公司推出的一个用于前端开发的开源工具包,它由Twitter的设计师Mark Otto和Jacob Thornton合作开发,是一个CSS/HTML框架,使用Bootstrap,可以简单快速地让页面变得更漂亮,而Bootstrap依赖jQuery,所以我们将jQuery也引入进来。更多Bootstrap和jQuery的知识,详见Bootstrap官方网站地址:https://www.bootcss.com/jQuery官方网站地址:https://jquery.com/

 

四、秒杀系统读优化

4.1 高并发读优化-使用Redis缓存

秒杀系统本质上就是一个满足大并发数、高性能和高可用的分布式系统,主要解决的问题是大量的并发读和并发写,本节主要解决高并发读的问题。简单的秒杀系统架构其每次请求先通过应用服务再到数据库查询数据,然后将查询的数据由下及上返回。高并发流量及过长的请求路径对应用服务和数据库会造成巨大的压力,可以考虑通过引入缓存,缩短请求路径,让请求流量不要直接查询数据库来解决大流量的。可以将上述简单的秒杀系统架构演化为如图21-5所示的系统架构。

秒杀系统设计架构 秒杀系统流程_限流_03

 

用户查询秒杀商品列表时,先到缓存查询数据,如果缓存有用户需要的数据,直接返回给用户;否则,查询数据库,将数据存入到缓存中,最后将数据返回给用户。因为秒杀商品属于不经常修改的数据,所以非常适合存放在缓存中。

 

4.2 流量削峰-采用缓存和MQ中间件

由于秒杀请求在短时间内高度集中于某一特定的时间点,会导致一个特别高的流量峰值,它对资源的消耗是瞬时的。一台服务器处理资源的能力是固定的,如果出现流量峰值的话,很容易造成系统的瓶颈,况且最终能够秒杀到商品的请求是固定的,比如1万个秒杀请求到最后真正能成功的请求可能只有1000个。因此,我们需要设计一些原则,让并发的请求更加平缓有序地进行,这也是为什么需要流量削峰的原因。

 

流量削峰,最容易想到的就是消息队列。可使用消息队列来缓冲瞬时流量,将同步请求转换为异步请求。

 

4.3 业务优化

4.3.1 答题/验证码

答题抢票的主要目的是,除了防止黄牛和抢票软件,区分人工和机器外,深层次的原因是增加用户购买的难度,延缓请求。抢票难度增大后,用户下单的时间会增加,从之前的1~2秒之内延长到2秒之后,这个时间的延缓对于后端服务处理并发非常重要,会大大减少压力。

4.3.2 分时分段

细心的用户可能会注意到,12306放票不是一次性放完,而是分成几个时间段来放的。例如,在原来8:00至18:00(除14:00外)每整点放票的基础上,增加9:30、10:30、12:30、13:30、14:00、14:30 6个放票时间点,每隔半小时放出一批,将流量摊匀。

4.3.3 禁用秒杀按钮

用户在单击秒杀按钮后,如果后端服务压力大,系统响应时间长,用户基本都会再次单击,一直单击秒杀按钮。每单击一次秒杀按钮,都会向后端服务请求一次。这样请求越来越多,系统压力越来越大,最后会造成任何用户都没法秒杀成功。

一种很简单的解决方法是,用户单击“秒杀”按钮后,按钮为灰色,表示禁止用户继续提交秒杀请求。同时限制用户在x秒内只能提交一次请求(根据具体的业务,设置合理的x时间,比如5秒、10秒等)。

 

4.4 降级、限流、拒绝服务

4.4.1 降级

在业务高峰时,为了保证服务的高可用,往往需要服务或者页面有策略地不处理或换一种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。这种技术在分布式微服务架构中称为服务降级。

例如在线购物系统,整个购买流程是重点业务,比如支付功能在流量高峰时,为了保证购买流程的正常运行,往往会关闭一些不太重要的业务(如广告业务等)。

降级方案可以这样设计:当秒杀流量达到10万/s时,把成交记录的获取从展示20条降级到只展示10条。“从20改到5”这个操作由一个开关来实现,也就是设置一个能够从开关系统动态获取的系统参数。

4.4.2 限流

限流是指通过对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致系统运行缓慢或宕机,限流的根本目的就是为了保障服务的高可用。当系统容量达到瓶颈时,我们需要通过限制一部分流量来保护系统,并做到既可以人工执行开关,也支持自动化保护的措施。限流是比降级更极端的一种保护措施。

限流既可以是在客户端限流,也可以是在服务端限流。限流的实现方式既要支持URL以及方法级别的限流,也要支持基于 QPS(每秒查询率)和线程的限流。例如,我们的系统最高支持1万QPS时,可以设置8000QPS来进行限流保护。

4.4.3 拒绝服务

如果系统流量实在太大,严重超出了系统的负载,系统可以直接拒绝所有的请求。比如,HTTP请求直接返回503错误码。拒绝服务是一种不得已的方案,同时也是最暴力最有效的系统保护方法。

系统虽然在过载时无法提供服务,但是仍然可以运作,当负载下降时又很容易恢复,所以每个系统和每个环节都应该设置这个方案,以对系统做最坏情况下的保护。

 

4.5 避免单点

所谓单点,即单机部署。单点意味着没有备份,风险不可控,一旦单点出问题,整个服务将不可用。

单点并不单指服务,也包括服务依赖的资源,比如数据库、缓存、消息中间件等。

 

一个系统不仅仅是单个应用为用户提供服务,而是采用集群的方法统一对外提供服务。如果服务A宕机,服务B、服务C及服务D仍可以继续提供服务,并不会影响整个系统。除了应用服务需要避免单点外,数据库以及缓存都可以通过改造(比如数据库主从设计、缓存主从设计等)来避免单点服务。

 

4.6 总结

秒杀系统是学习“三高”(高性能、高并发、高可用)的一个非常好的例子,具有读多写少等特性。为了保证系统的高可用,使用避免单点措施(应用服务、数据库、缓存等)可保证服务的稳定性;对于高并发读,引入Redis缓存可避免流量直接穿透到数据库,同时,引入消息中间件则可对流量进行削峰。除了技术上的设计,业务方法上使用答题/验证码、分时分段以及禁用秒杀按钮等措施,可将请求流量尽量拦截在上游。最后,在最坏的情况下,使用系统降级、限流、拒绝服务等错误,可起到保护系统以防宕机的作用。

 

 

 

 

 

愿你一生努力·一生被爱