秒杀模块-业务分析参考

本文详述了电商秒杀模块的设计思路,包括利用Redis缓存减轻数据库压力,通过静态化页面和并发请求队列处理高并发,以及使用分布式锁确保库存更新的准确性。此外,还探讨了防止恶意请求的措施,如动态URL和访问频率限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

宝家-秒杀模块-业务分析参考

1. 秒杀模式介绍

秒杀卖场一直是电商项目中最热门的存在,其目的就是以超低价格商品吸引消费者参加活动。这是一种很常见的促销方式。在电商中秒杀的销售模式最大的挑战就是我们需要面临短时间段的恐怖并发流量,因为存在时间和库存的限制,参与秒杀的用户会在固定的时间段访问我们的服务器,突然的高并发流量如果不经过处理很有可能造成系统崩溃。

注意,这里需要明白的是,秒杀只是网站营销的一个活动,如果因为秒杀而导致整个服务器瘫痪不能使用,得不偿失。介于秒杀的特殊性,我们一般会将秒杀模块独立出来,单独的运行一个微服务,使用独立域名,将它与其他功能隔离开,避免影响到网站正常功能的使用。

2. 场景分析

**秒杀业务的原则就是处理高并发,保证系统不被拖垮,并且尽可能快的处理所有请求。**首先,我们需要把请求尽量的拦截在系统上游,减少真正达到应用服务器的请求。

  • 用户在秒杀开始前,通过不停的刷新页面以保证不会错过秒杀。这些流量如果是访问应用服务器,连接数据库的话,会给服务器和数据库造成负载压力。

    针对这种场景我们的处理方案有两种:

    1. 重新设计秒杀详情页,将秒杀详情页静态化到nginx中,让nginx去处理这些静态资源的请求。但是这里有一点需要注意,我们秒杀详情页中的抢购按钮必须到秒杀时间才能点击,时间未到我们将按钮显示灰色无法点击。
    2. **(我们项目中使用的方案)**在第一次请求秒杀商品列表的时候,将秒杀商品存放到redis中,这样后面的所有请求秒杀商品的信息都可以由redis去处理。redis的数据在内存中,让redis去处理这种高并发场景比mysql效率高很多。而且秒杀商品一般都有限制每个用户只能抢购一次,也就是说每个用户只有一次写数据的请求,大多数都是读取数据,这种频繁的数据读取的场景刚好适用redis。
    3. 需要注意的是,有一些高端用户可能会直接去请求抢购下单的url,这种类似于作弊的行为也需要考虑,我们可以将url动态化。办法就是在下单页面url加入由服务器生成的随机数作为参数,让参数在秒杀开始时才能被得到。这个随机数只会产生一个,服务器端可以使用redis来存储这个参数。
  • 多台服务器处理下单请求的时候需要做一个前置检查:

    • 如果当前服务器已成功处理十个下单请求,那么对于后面的请求,直接返回结束页面。
    • 如果当前所有服务器已成功处理的等同于秒杀商品数量的请求,那就相当于,所有商品都已被下单,此时后面的请求全部返回结束页面。
  • 秒杀商品是定时上架的,通常来说我们不管在前端还是后台都会对时间进行严格把控,防止有人作弊,提前秒杀。定时秒杀还需要注意避免商家在秒杀前对商品进行编辑,从而造成不可预期的影响,所以我们还需要禁止商家在秒杀前进行秒杀商品的编辑。当然如果是数据出现错误,商家可以走数据订正的流程,由运营商来订正数据。

  • 目前市场上有许多针对于秒杀的秒杀器的使用,秒杀器下单及其迅速,堪称作弊利器。我们可以通过校验码的方式来应对秒杀器。一般采用的有:秒杀专用验证码,秒杀答题等。

3. 业务分析

秒杀的限制有两个,一个是时间,一个是库存。

  • 秒杀开始前,我们将秒杀商品存放到redis中,将所有查询秒杀商品信息的请求交给redis处理。并且我们需要禁止商家对秒杀商品的编辑。

  • 秒杀开始时,用户点击抢购进入填写订单页面,此时我们会将redis中对应商品的数量减一操作,并将订单信息存放到redis中。等到用户支付订单后,我们再将订单信息写入数据库,并修改秒杀商品的库存量。

    • 通常情况库存会带来超卖订单问题,也就是售出数量大于库存数量。我们在修改库存量的时候--------

      1.乐观锁

      update  seckills set count=#扣减后的库存量 where goodsId=#goodId and count=#扣减前的库存量
      

      2.尝试扣减库存操作**(我们项目中使用的方案)**

      update  seckills set count=count-#扣减库存数 where goodsId=#goodId and count>=#扣减库存数
      
  • 由于请求并发量太大,且秒杀一开始99.9%的请求都是下单请求,我们需要一个并发请求队列去保证请求的有序性。

    这里我们可以将下单的并发请求放到一个并发队列中,处理请求之前进行一个预处理—判断redis中还有库存时,如果有,再拿取下一个请求进行下单处理。(也可以在后台再次进行一个用户重下单的判断,如果是已经下过单,返回失败)

    • java的并发包提供了三个常用并发队列的实现:

      ConcurrentLinkedQueue:入队无锁,出队加锁。

      LinkedBlockingQueue:阻塞型,入队出队都加锁。

      ArrayBlockingQueue:初始量固定,阻塞型

    • 我们这里选用ConcurrentLinkedQueue实现请求队列,因为系统入队请求远大于出队请求。

前台设计

秒杀开始前某个时间到秒杀开始的时间段,被称为准备阶段。秒杀开始到所有用户获得秒杀结果是秒杀阶段。

  • 在准备阶段,用户会不停的刷新页面。前端需要保证秒杀页面的抢购按钮只有在固定时间才能被点击,并且秒杀页面和下单页面都尽可能的简单设计,减少不必要的网络开销。

  • 秒杀页面需要有秒杀倒计时的显示,这个一般是由js调用客户端本地时间。

  • 当用户点击下单或者购买之后,按钮变成灰色,js限制用户在x秒内只能提交一个请求。

  • 下单数量只能为1且不可修改。送货的地址和收件信息使用默认信息,允许提交订单后修改。

  • 用户第一次提交的订单成功后,再次点击只能看到抢购结束页面。

    • 针对非正常访问,例如某些人写一个for,不停的对服务器发起http请求,我们需要限制同一个uid,或者对同一个item的查询的访问频率,做页面缓存,x秒内的所有请求都返回同一个页面。

5. 流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SsLloGXi-1622031526747)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210507202321060.png)]

6.设计思路分析与总结

我们整个秒杀业务中,业务流程很简单,全部围绕着利用缓存分担数据库压力。而这个模块中最重要的是如何在秒杀前准备阶段和秒杀阶段快速且安全的处理高并发流量。

初步设想是让nginx通过静态资源分担后台应用的压力,但是这样会暴露我们下单的url,所以我们需要做一个特殊处理,给下单的url设置一个参数,由系统随机生成字符串,存储到redis中,在秒杀开始时再将redis中的字符串查询出来,传递给前端,形成一个固定的完整的下单url。(所有人看到的都会是同一个url,但是在秒杀开始之前,所有人都不会知道参数是什么,也就是url不完整,避免一些程序员提前下单)

不过通过nginx分担流量有个问题,我们生成的是静态页面,其中的库存量显示是秒杀前的。那我们在秒杀阶段再访问这个详情页就无法动态显示我们的库存量。(目前没有想到解决方案,先放这里,当然,如果不用实时显示库存量就没有这个问题)

秒杀开始时为了快速处理用户请求,我们的初步设想是做一个并发请求队列,所有请求都进到队列中。这里进一步优化设想是针对每个秒杀商品做出多个并发请求队列,我们的秒杀商品不止一个,如果只做一队列,就等同与对所有商品的下单请求全部放一起了,我们无法进行准确判断,只能一个个请求去处理。但是如果是一个队列处理一种商品的下单请求,我们就能很明确的知道,这个队列全是对a商品的下单操作。而这么做的好处是,作为服务端,我们很明确自己的a商品库存量有多少,假设库存量十件,我们可以将队列大小设置为固定的二十个容量,其他没有进入队列的全部返回已售罄。这样可以避免后面的用户长时间等待请求处理后,得到一个下单失败的信息,影响体验。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5y9uBRFg-1622031526751)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210507211433486.png)]

需要注意的是,我们的大队列可以使用java并发队列的ConcurrentLinkedQueue,这个队列的特点就是入队快,出队需要上锁。

而分队列可以使用ArrayBlockingQueue,它的初始量固定,属于阻塞性队列。

这种方案下相当于我们进行了一个下单请求的筛选,将没有可能下单成功的请求全部直接返回失败,不给他们请求下单接口的机会,也最大程度上节省了系统资源。

再然后就是处理筛选后有可能下单成功的请求了,这个请求数量我们在上图是放宽了限制了,以上面举例的,请求数是库存量倍数的情况下,肯定会有一半的人下单失败。而且我们是多个微服务进行下单操作,这里扣减redis的库存–下单操作必须加上分布式锁

分布式锁以redis实现。(详细查看分布式锁)

下单后需要将用户转至支付页面,提醒一分钟内支付,如果我们一分钟内没有接收到用户的成功支付信息,我们会将这个订单取消,库存回滚一个。并返回秒杀失败信息。

一旦用户成功支付,我们就确定他秒杀成功,直接返回结果,不需要等待信息录入mysql数据库。因为基于秒杀快速返回的原则,录入数据库这个跟用户无关,我们只要确定用户秒杀成功或者失败,就立马返回结果,剩下的业务交给系统慢慢处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值