秒杀系统技术点

举报
i进击的攻城狮 发表于 2022/06/27 21:36:40 2022/06/27
【摘要】 ​ 一、高并发下带来的服务器请求压力问题既然是秒杀系统,那肯定是非常多的人一起买一个商品,首先要做的到的是你的网站需要能承受上万的访问压力,不能像学校的抢课系统那样,几千人人抢课,系统就崩溃进不去了。要解决这个问题:1:前端的页面静态化,什么是页面静态化?比如说在秒杀页面,可能除了秒杀按钮,秒杀倒计时,还有比如背景图,商品推荐什么的,像这些页面的数据,应该避免重复去想服务器请求数据,在刷新秒...

 一、高并发下带来的服务器请求压力问题

既然是秒杀系统,那肯定是非常多的人一起买一个商品,首先要做的到的是你的网站需要能承受上万的访问压力,不能像学校的抢课系统那样,
几千人人抢课,系统就崩溃进不去了。
要解决这个问题:
1:前端的页面静态化
,什么是页面静态化?比如说在秒杀页面,可能除了秒杀按钮,秒杀倒计时,还有比如背景图,商品推荐什么的,像这些页面的数据,应该避免重复去想服务器请求数据,在刷新秒杀页面时,应该用局部刷新的方式,以最小的数据量请求到最新数据。

2:前端接口限流
比如说按钮只能提交一次,之后就置灰色,这样可以简单的防止一般用户页面上的重复提交(无法避免使用工具的请求访问);
3:后端接口限流
后端可以通过自定义自定义注解的方式,通过把用户的id缓存到redis,限制用户在多少秒只能请求多少次
4:后端商品数据缓存
,用户查询商品的数据,如果每个用户都重数据库去查询,那数据库肯定是顶不住,这个时候就需要使用redis去缓存商品数据
(需要注意redis的缓存击穿,缓存穿透等问题)
5:集群部署,如果是高并发的环境下,单台服务器,单台redis肯定是难以满足需求的,需要做系统的集群部署,reids的集群部署
6:分布式session
 用户登录就不能简单的用session去缓存数据,这个时候可以用redis做分布式session

二、秒杀时间到时,多用户下单的问题 

秒杀时间一到,这个时候是用户请求量最大的时候,应该如何去处理这些请求呢?

 1、流量削峰; 什么是流量削峰,正常情况下,我们秒杀系统的流量访问大小应该是,

在秒杀前几分钟,需要参与秒杀的人陆陆续续的进来,请求的流量慢慢增大,到秒杀开始时间的一瞬间,流量达到最大(这就是峰), 然后流量在慢慢的降下来,

削峰就是让它的访问流量不要这一瞬间突然变大,而是在控制在一个区间里慢慢变大 为什么要削峰 通常秒杀系统为什么要进行削峰呢?或者说峰值会带来哪些坏处? 我们知道服务器的处理资源是恒定的,你用或者不用它的处理能力都是一样的,所以出现峰值的话, 会让服务器在那一个瞬间特别忙,然后有闲下去,但是由于要保证服务质量,我们很多的处理资源只能按照忙的时候来预估,而这会导致资源的一个浪费。 比如它的峰值需要6台服务器集群才能处理,之后的请求只需要3台就能处理,那大部分时间就有三台服务器是处于闲置状态的 所以我们需要让用户的请求尽可能的平缓 削峰的一些操作思路:排队、答题、分层过滤

 1.消息队列解决削峰;2.流量削峰漏斗:层层削峰、3、验证码解决削峰

1.消息队列解决削峰 要对流量进行削峰,最容易想到的解决方案就是用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送, 中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去。 不过这样虽然保护了系统处流量请求的平缓,但消息队列的入口处流量依然很大,有压垮消息队列的风险

2.流量削峰漏斗:层层削峰 针对秒杀场景还有一种方法,就是对请求进行分层过滤,从而过滤掉一些无效的请求。 系统可以通过一些校验,判断哪些请求可能来自于脚本,哪些请求是重复的无效请求,哪些是来自异常状态用户的请求 或者是哪些请求是是专门的羊毛党,把这些请求过滤出去 分层过滤其实就是采用“漏斗”式设计来处理请求的,如下图所示:

编辑


3、验证码 在用户秒杀按钮时,弹出验证码后在发起请求,这样做有什么好处呢? 用户输入验证码可能会需要1-3s,这样之后服务器收到的请求就在1-3s这个区间

编辑


三、使用mq异步下单 异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。

比如这段代码,在确认库存量后,没有直接调用secKill方法生成订单,而是发送一条消息到mq消息队列,让消息队列去异步处理下订单

编辑



四、用带有原子性的redis锁解决超卖问题


每次下单前先检查redis里缓存的商品数量,如果大于0,就扣减库存,下订单,如一下代码

decrement方法的作用是读取这个redis的key的值并减1,在减1的过程中,其他线程读取redis的值时,不会读到相同的

//预减库存
Long decrement = valueOperations.decrement("seckillGoods:" + goodsId);
if (decrement<0){
//如果小于0,说明没有库存了,内存中标记商品为true,表示卖完
emptyStockMap.put(goodsId,true);
valueOperations.increment("seckillGoods:" + goodsId);
return RespBean.error(RespBeanEnum.EMPTY_STOCK);
}

单redis做分布式锁的时候需要注意其原则性的问题,比如如下代码: 这就是一段有问题的锁,如果在商品还剩下1个的时候,在第三行代码执行前,有多个线程执行了第一行, 那就会有多个线程以为还有1个商品,同时走下单的流程,导致商品超卖 

Integer o = (Integer)valueOperations.get("seckillGoods:" + goodsId);
o--;
valueOperations.set("seckillGoods:" + goodsId,o);
if (o < 0) {
//如果小于0,说明没有库存了,内存中标记商品为true,表示卖完
emptyStockMap.put(goodsId,true);
valueOperations.increment("seckillGoods:" + goodsId);
return RespBean.error(RespBeanEnum.EMPTY_STOCK);
}

 五、链接暴露问题

抢购接口的url不应该是固定的,如果是固定的,那如果有人知道抢购的接口地址,那么他就能提前开脚本,写定时任务,在时间到的时候就发起请求, 一般脚本的速度是远大于人手指点击的数度的,所以很有可能导致抢购商品全部都被黄牛抢走 如何去解决,可以通过动态生成url的方式,每个用户动态生成自己唯一的url 实现步骤是,当发起抢购的时候,不去请求真正的访问地址,而是去获取真正的动态访问地址,再去请求真正的地址 



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。