十万级低成本超详细的秒杀高并发设计,快收藏起来(代码片段)

ArthurKingYs ArthurKingYs     2022-12-09     638

关键词:

秒杀系统相信很多人见过,比如京东或者淘宝的秒杀,小米手机的秒杀,那么秒杀系统的后台是如何实现的呢?我们如何设计一个秒杀系统呢?对于秒杀系统应该考虑哪些问题?如何设计出健壮的秒杀系统?本文我们就来探讨一下这个问题。

秒杀应该考虑哪些问题

超卖问题

分析秒杀的业务场景,最重要的有一点就是超卖问题,假如备货只有100个,但是最终超卖了200,一般来讲秒杀系统的价格都比较低,如果超卖将严重影响公司的财产利益,因此首当其冲的就是解决商品的超卖问题。

高并发

秒杀具有时间短、并发量大的特点,秒杀持续时间只有几分钟,而一般公司都为了制造轰动效应,会以极低的价格来吸引用户,因此参与抢购的用户会非常的多。短时间内会有大量请求涌进来,后端如何防止并发过高造成缓存击穿或者失效,击垮数据库都是需要考虑的问题。

接口防刷

现在的秒杀大多都会出来针对秒杀对应的软件,这类软件会模拟不断向后台服务器发起请求,一秒几百次都是很常见的,如何防止这类软件的重复无效请求,防止不断发起的请求也是需要我们针对性考虑的。

秒杀 URL

对于普通用户来讲,看到的只是一个比较简单的秒杀页面,在未达到规定时间,秒杀按钮是灰色的,一旦到达规定时间,灰色按钮变成可点击状态。这部分是针对小白用户的,如果是稍微有点电脑功底的用户,会通过F12看浏览器的network看到秒杀的url,通过特定软件去请求也可以实现秒杀。或者提前知道秒杀url的人,一请求就直接实现秒杀了。这个问题我们需要考虑解决。

数据库设计

秒杀有把我们服务器击垮的风险,如果让它与我们的其他业务使用在同一个数据库中,耦合在一起,就很有可能牵连和影响其他的业务。如何防止这类问题发生,就算秒杀发生了宕机、服务器卡死问题,也应该让他尽量不影响线上正常进行的业务。

大量请求问题

按照「高并发」的考虑,就算使用缓存还是不足以应对短时间的高并发的流量的冲击。如何承载这样巨大的访问量,同时提供稳定低时延的服务保证,是需要面对的一大挑战。我们来算一笔账,假如使用的是 Redis 缓存,单台 Redis 服务器可承受的 QPS 大概是 4W 左右,如果一个秒杀吸引的用户量足够多的话,单 QPS 可能达到几十万,单体 Redis 还是不足以支撑如此巨大的请求量。缓存会被击穿,直接渗透到 DB,从而击垮MySQL,后台会将会大量报错。

秒杀系统的设计和技术方案

秒杀系统数据库设计

针对「数据库设计」提出的秒杀数据库的问题,因此应该单独设计一个秒杀数据库,防止因为秒杀活动的高并发访问拖垮整个网站。这里只需要两张表,一张是秒杀订单表,一张是秒杀货品表。

其实应该还有几张表,商品表:可以关联goods_id查到具体的商品信息,商品图像、名称、平时价格、秒杀价格等,还有用户表:根据用户user_id可以查询到用户昵称、用户手机号,收货地址等其他额外信息,这个具体就不给出实例了。

秒杀 URL 的设计

为了避免有程序访问经验的人通过下单页面url直接访问后台接口来秒杀货品,我们需要将秒杀的 URL 实现动态化,即使是开发整个系统的人都无法在秒杀开始前知道秒杀的URL 。具体的做法就是通过 md5 加密一串随机字符作为秒杀的 URL,然后前端访问后台获取具体的 URL,后台校验通过之后才可以继续秒杀。

秒杀页面静态化

将商品的描述、参数、成交记录、图像、评价等全部写入到一个静态页面,用户请求不需要通过访问后端服务器,不需要经过数据库,直接在前台客户端生成,这样可以最大可能的减少服务器的压力。具体的方法可以使用freemarker模板技术,建立网页模板,填充数据,然后渲染网页。

单体 Redis 升级为集群 Redis

秒杀是一个读多写少的场景,使用 Redis 做缓存再合适不过。不过考虑到缓存击穿问题,我们应该构建 Redis 集群,采用哨兵模式,可以提升Redis的性能和可用性。

使用 Nginx

Nginx 是一个高性能 Web 服务器,它的并发能力可以达到几万,而 Tomcat 只有几百。通过 Nginx 映射客户端请求,再分发到后台 Tomcat 服务器集群中可以大大提升并发能力。

精简 SQL

典型的一个场景是在进行扣减库存的时候,传统的做法是先查询库存,再去update。这样的话需要两个SQL,而实际上一个SQL我们就可以完成的。可以用这样的做法:update miaosha_goods set stock =stock-1 where goos_id =#goods_id and version = #version and sock>0;这样的话,就可以保证库存不会超卖并且一次更新库存,还有注意一点这里使用了版本号的乐观锁,相比较悲观锁,它的性能较好。

Redis 预减库存

很多请求进来,都需要后台查询库存,这是一个频繁读的场景。可以使用Redis来预减库存,在秒杀开始前可以在 Redis 设值,比如 redis.set(goodsId,100),这里预放的库存为100可以设值为常量,每次下单成功之后,Integer stock = (Integer)redis.get(goosId); 然后判断 sock 的值,如果小于常量值就减去1;不过注意当取消的时候,需要增加库存,增加库存的时候也得注意不能大于之间设定的总库存数(查询库存和扣减库存需要原子操作,此时可以借助 lua 脚本)下次下单再获取库存的时候,直接从Redis里面查就可以了。

接口限流

秒杀最终的本质是数据库的更新,但是有很多大量无效的请求,我们最终要做的就是如何把这些无效的请求过滤掉,防止渗透到数据库。限流的话,需要入手的方面很多:

前端限流

首先第一步就是通过前端限流,用户在秒杀按钮点击以后发起请求,那么在接下来的5秒是无法点击(通过设置按钮为disable)。这一小举措开发起来成本很小,但是很有效。

同一个用户xx秒内重复请求直接拒绝

具体多少秒需要根据实际业务和秒杀的人数而定,一般限定为10秒。具体的做法就是通过Redis的键过期策略,首先对每个请求都从 String value = redis.get(userId);如果获取到这个 value 为空或者为 null,表示它是有效的请求,然后放行这个请求。如果不为空表示它是重复性请求,直接丢掉这个请求。如果有效,采用redis.setexpire(userId,value,10).value 可以是任意值,一般放业务属性比较好,这个是设置以 userId 为 key,10秒的过期时间(10秒后,key对应的值自动为null)。

令牌桶算法限流

接口限流的策略有很多,我们这里采用令牌桶算法。令牌桶算法的基本思路是每个请求尝试获取一个令牌,后端只处理持有令牌的请求,生产令牌的速度和效率我们都可以自己限定,Guava 提供了 RateLimter 的 API 供我们使用。以下做一个简单的例子,注意需要引入Guava:

public class TestRateLimiter 


    public static void main(String[] args) 
        //1秒产生1个令牌
        final RateLimiter rateLimiter = RateLimiter.create(1);
        for (int i = 0; i < 10; i++) 
            //该方法会阻塞线程,直到令牌桶中能取到令牌为止才继续向下执行。
            double waitTime= rateLimiter.acquire();
            System.out.println("任务执行" + i + "等待时间" + waitTime);
        
        System.out.println("执行结束");
    

上面代码的思路就是通过RateLimiter来限定我们的令牌桶每秒产生1个令牌(生产的效率比较低),循环10次去执行任务。acquire会阻塞当前线程直到获取到令牌,也就是如果任务没有获取到令牌,会一直等待。那么请求就会卡在我们限定的时间内才可以继续往下走,这个方法返回的是线程具体等待的时间。执行如下:

可以看到任务执行的过程中,第1个是无需等待的,因为已经在开始的第1秒生产出了令牌。接下来的任务请求就必须等到令牌桶产生了令牌才可以继续往下执行。如果没有获取到就会阻塞(有一个停顿的过程)。不过这个方式不太好,因为用户如果在客户端请求,如果较多的话,直接后台在生产token就会卡顿(用户体验较差),它是不会抛弃任务的,我们需要一个更优秀的策略:如果超过某个时间没有获取到,直接拒绝该任务。接下来再来个案例:

public class TestRateLimiter2 


    public static void main(String[] args) 
        final RateLimiter rateLimiter = RateLimiter.create(1);


        for (int i = 0; i < 10; i++) 
            long timeOut = (long) 0.5;
            boolean isValid = rateLimiter.tryAcquire(timeOut, TimeUnit.SECONDS);
            System.out.println("任务" + i + "执行是否有效:" + isValid);
            if (!isValid) 
                continue;
            
            System.out.println("任务" + i + "在执行");
        
        System.out.println("结束");
    

其中用到了tryAcquire方法,这个方法的主要作用是设定一个超时的时间,如果在指定的时间内预估(注意是预估并不会真实的等待),如果能拿到令牌就返回true,如果拿不到就返回false。然后我们让无效的直接跳过,这里设定每秒生产1个令牌,让每个任务尝试在0.5秒获取令牌,如果获取不到,就直接跳过这个任务(放在秒杀环境里就是直接抛弃这个请求)。程序实际运行如下:

只有第1个获取到了令牌,顺利执行了,下面的基本都直接抛弃了,因为0.5秒内,令牌桶(1秒1个)来不及生产就肯定获取不到返回false了。

这个限流策略的效率有多高呢?假如我们的并发请求是400万瞬间的请求,将令牌产生的效率设为每秒20个,每次尝试获取令牌的时间是0.05秒,那么最终测试下来的结果是,每次只会放行4个左右的请求,大量的请求会被拒绝,这就是令牌桶算法的优秀之处。

异步下单

为了提升下单的效率,并且防止下单服务的失败。需要将下单这一操作进行异步处理。最常采用的办法是使用队列,队列最显著的三个优点:异步、削峰、解耦。这里可以采用 RabbitMQ,在后台经过了限流、库存校验之后,流入到这一步骤的就是有效请求。然后发送到队列里,队列接受消息,异步下单。下完单,入库没有问题可以用短信通知用户秒杀成功。假如失败的话,可以采用补偿机制,重试。

服务降级

假如在秒杀过程中出现了某个服务器宕机,或者服务不可用,应该做好后备工作。之前的博客里有介绍通过Hystrix进行服务熔断和降级,可以开发一个备用服务,假如服务器真的宕机了,直接给用户一个友好的提示返回,而不是直接卡死,服务器错误等生硬的反馈。

总结

秒杀流程图:

这就是我设计出来的秒杀流程图,当然不同的秒杀体量针对的技术选型都不一样,这个流程可以支撑起几十万的流量,如果是成千万破亿那就得重新设计了。比如数据库的分库分表、队列改成用Kafka、Redis增加集群数量等手段。通过本次设计主要是要表明的是我们如何应对高并发的处理,并开始尝试解决它,在工作中多思考、多动手能提升我们的能力水平,加油!如果本篇博客有任何错误,请麻烦指出来,不胜感激。

利用乐观锁及redis解决电商秒杀高并发基本逻辑

解决秒杀高并发问题方法很多,如悲观锁,消息队列等……利用乐观锁及redis解决秒杀高并发基本逻辑//初始化redis$redis=newRedis();//设置键值,起到监视作用 执行事务之前,判断被修改,回滚$redis->watch(‘sales‘);//获... 查看详情

阿里面试官:高并发大流量秒杀系统如何正确的解决库存超卖问题?(建议收藏)

...网最强秒杀系统架构解密!!》一文中,冰河详细的阐述了高并发秒杀系统的架构设计,也简单提到了如何扣减商品的库存。也许不少小伙伴会问:扣减商品的库存很简单啊,用户下单的时候扣除对应的商... 查看详情

阿里面试官:高并发大流量秒杀系统如何正确的解决库存超卖问题?(建议收藏)

...网最强秒杀系统架构解密!!》一文中,冰河详细的阐述了高并发秒杀系统的架构设计,也简单提到了如何扣减商品的库存。也许不少小伙伴会问:扣减商品的库存很简单啊,用户下单的时候扣除对应的商... 查看详情

如何设计一个能抗高并发的秒杀场景(代码片段)

...要考虑的问题,这里只是将如何设计一个能抗高并发的秒杀设计的主流程进行分析!!!一、秒杀的特点商品库存总量固定先到先得,瞬时并发极大商品数量和库存数量有限二、如何设计秒杀场景1.秒杀开始前... 查看详情

解密秒杀系统架构:不是所有的秒杀都是秒杀

...并发系统?今天,我们就一起解密高并发业务场景下典型的秒杀系统的架构。本文分享自华为云社区《​​【高并发】秒杀系统架构解密,不是所有的秒杀都是秒杀(升级版)!!​​》,作者:冰河。究竟什么样的系统算是高... 查看详情

如何设计一个高可用高并发秒杀系统

...海量服务。微视春节项目中的集卡瓜分活动,是一个典型的秒杀场景,自己参与其中,分享一些心得和总结。如今的互联网已经在海量服务领域有了很成熟的理论,因此自己也很庆幸,能够从0到1完整践行海量服务。微视春节项... 查看详情

解密秒杀系统架构:不是所有的秒杀都是秒杀(代码片段)

...?今天,我们就一起解密高并发业务场景下典型的秒杀系统的架构。本文分享自华为云社区《【高并发】秒杀系统架构解密,不是所有的秒杀都是秒杀(升级版)!!》,作者:冰河。究竟什么... 查看详情

解密秒杀系统架构:不是所有的秒杀都是秒杀(代码片段)

...?今天,我们就一起解密高并发业务场景下典型的秒杀系统的架构。本文分享自华为云社区《【高并发】秒杀系统架构解密,不是所有的秒杀都是秒杀(升级版)!!》,作者:冰河。究竟什么... 查看详情

高并发系统的设计及秒杀实践

.../接口实现时应该注意的原则,以及通过工作中一个6万QPS的秒杀活动,来介绍一下秒杀业务 查看详情

我要手把手教你搭建一套抗瞬时百万流量的秒杀系统(代码片段)

...套支持瞬时百万流量的高并发、高性能、高可用、可扩展的秒杀系统,更重要的是要学会大厂处理高并发、大流量场景的技术方案和架构设计思想,并学会如何将这些技术方案和架构设计思想落地到实际项目中。一、秒杀专栏背... 查看详情

2023春招面试专题:高并发解决方案

...、微博大V的热点新闻等。除了这些典型事情,每秒几十万请求的秒杀系统、每天千万级的订单系统、每天亿级日活的信息流系 查看详情

2023春招面试专题:高并发解决方案

...、微博大V的热点新闻等。除了这些典型事情,每秒几十万请求的秒杀系统、每天千万级的订单系统、每天亿级日活的信息流系 查看详情

基于springboot+rabbitmq+redis开发的秒杀系统(异步下单热点数据缓存解决超卖)(代码片段)

基于SpringBoot+RabbitMQ+Redis开发的秒杀系统一、简易版秒杀SeckillProject系统简介开发技术二、实现细节记录1、用户密码两次MD5加密2、分布式session维持会话3、异常统一处理4、页面缓存+对象缓存5、页面静态化6、内存标记+... 查看详情

千万级用户的大型网站,应该如何设计其高并发架构?

...始建立的时候,用户量是很少的,大概可能就几万或者几十万的用户量,每天 查看详情

秒杀系统:如何打造并维护一个超大流量的秒杀系统?(代码片段)

...高一致、高可用的三高系统。而打造并维护一个超大流量的秒杀系统需要进行哪些关注,就是本文讨论的话题。整体思考首先从高维度出发,整体思考问题。秒杀无外乎解决两个核心问题,一是并发读,一是并发写 查看详情

实践出真知:全网最强秒杀系统架构解密,不是所有的秒杀都是秒杀!!(代码片段)

大家好,我是冰河~~很多小伙伴反馈说,高并发专题学了那么久,但是,在真正做项目时,仍然不知道如何下手处理高并发业务场景!甚至很多小伙伴仍然停留在只是简单的提供接口(CRUD)阶段ÿ... 查看详情

实践出真知:全网最强秒杀系统架构解密,不是所有的秒杀都是秒杀!!(代码片段)

大家好,我是冰河~~很多小伙伴反馈说,高并发专题学了那么久,但是,在真正做项目时,仍然不知道如何下手处理高并发业务场景!甚至很多小伙伴仍然停留在只是简单的提供接口(CRUD)阶段ÿ... 查看详情

消息队列使用场景

...请求达到网关(软硬件负载均衡:软件负载均衡nginx,支持十万级并发;硬件负载均衡f5,支持百万级并发)后,所有的请求到达服务端,会压垮服务端,所以在网关和服务器端加一中间过程:消息队列。所有请求入队列,此时不... 查看详情