【秒杀活动】
目录
- 秒杀采用什么方案,redis挂了怎么办?
- 秒杀系统痛点有哪些?
- 解决方案
- 1.高并发的解决方案
- 2.链接暴露的解决方案:
- 3.超卖问题的解决方案:
- 4.恶意请求的解决方案:
- 5.数据库层面的解决方案:
秒杀采用什么方案,redis挂了怎么办?
-100商品---》预热---》100这个数,放到redis中----》incrby--》来一个秒杀请求-1,在redis集合中把用户id放进去,最后100这个数变成了0,---》起个异步任务---》消费集合中的id,生成订单,扣减库存,扣减账户余额,提前充钱了
-用户真去看订单的时候---》异步任务完成了下单
秒杀系统痛点有哪些? 1.高并发: 时间极短、 瞬间用户量大,而且用户会在开始前不断刷新页面,还会积累一大堆重复请求的问题,请求过多把数据库打宕机了,系统响应失败,导致与这个系统耦合的系统也GG,一挂挂一片
2.链接暴露: 有人知道了你秒杀的url,然后在秒杀开始前通过这个url传要传递的参数来进行购买操作
3.超卖: 你只有一百件商品,由于是高并发的问题,一起拿到了最后一件商品的信息,都认为还有,全卖出去了,最终卖了一百十件,仓库里根本没这么多货
4.恶意请求: 因为秒杀的价格比较低,有人会用脚本来秒杀,全给一个人买走了,他再转卖,或者同时以脚本操作多个账号一起秒杀。(就是我们常见的黄牛党)
5.数据库: 一瞬间高QPS把数据库打宕机了,谁都抢不到,活动失败GG,这可能与高并发有重叠的点,不过着眼于数据库的具体方面
解决方案 1.高并发的解决方案
1.nginx做负载均衡(一个tomcat可能只能抗住几百的的并发,nginx还可以对一些恶意ip做限制)
2.资源静态化,把前端的模板页面放到CDN服务器中(放到别的服务器中,减轻自己服务器的压力)
3.页面的按钮,按一次后置灰X秒(防止一直用户一直点,同一个用户重复点击,虽然不会再卖给他,但是请求还是会到后端给系统压力,需要在前端按钮上做限制,比如点一下限制五秒内不能点击。)
4.同一个uid,限制访问频度,做页面缓存,x秒内到达站点层的请求,均返回同一页面(用来防止其他程序员跳过按钮,直接用for循环不断发起http请求,具体的话可以请求每次进来都去redis查有没有对应的id的key值,没有就在redis中设置X秒过期的key)
5.对于写请求,用消息队列(比如商品有一万件,就放一万个请求进来,当然要做好每秒几个的限制,不能一秒内全放进来,都成功了就继续放下一批,没成功就剩下的请求全部返回失败),
6.读请求用redis集群顶住(一个redis也只能顶住几万的并发,叫上兄弟)
7.记住一定一定要先把数据库里的东西提前加载到redis来,别等用户查了再加
2.链接暴露的解决方案: 1.有人会说,在上面已经做好了请求X秒一条的限制了嘛,为什么还要防止链接暴露?
? 其实在秒杀没开始前,这个秒杀的接口也是存在的,如果这个接口的url被人知道了,他直接可以在秒杀开始前就通过请求传输必要的参数来进行秒杀。我们要做的就是在秒杀开始前,谁都不知道秒杀(也就是付款、减少库存的接口)这个接口的url是什么。
2.如何防止秒杀的url暴露?
? 我们要做的是,在秒杀时间到的时候,才能获得url。而且秒杀场景中,肯定会有一个倒计时的模块,来告诉你还有几秒开始秒杀。我们逻辑如下:
①页面中有一个计时模块,是访问秒杀页面的时候去从服务器里拿的,计时结束,显示秒杀的按钮。 问题:(为什么不直接取用户的时间?用户的本机时间是可以修改的。)
②点击秒杀按钮后,再次请求服务器时间,与秒杀的时间对比,如果秒杀进行中,返回一个由加密过的秒杀url 问题:(为什么还要再次请求服务器时间?怎么加密url?,避免时间误差,md5加密)
③通过这个加密过的url来进行支付、减库存操作。
3.超卖问题的解决方案: 我们假设现在商品只剩下一件了,此时数据库中 num = 1;
但有100个线程同时读取到了这个 num = 1,所以100个线程都开始减库存了。
每一个用户线程进来,key值就减1,等减到0的时候,全部拒绝剩下的请求.所以一定不会出现超卖的现象
4.恶意请求的解决方案: 1.怎么限制让一个人只能秒杀一件商品?
秒杀成功,将用户id存入redis集合。通过集合来判断
2.如果一个人用脚本掌握了多个账号去执行秒杀,怎么办?
可以让用户付款的时候回答问题,防止脚本的操作。比如12306买火车票的时候,是不是会有按顺序点击图中相同的文字?这就是为了防止脚本。
5.数据库层面的解决方案: 1.用消息队列来削峰
比如一秒钟进来1W个写请求,我们数据库只能顶住一秒5000个,那我们就每秒放出来4000个。
2.用缓存来顶住大量的查询请求
引入redis,如果redis中有要查询的数据,就直接返回,如果没有,从数据库查询的时候,把查询的结果放到redis中,以后查询都会落到redis层。
在这里又会出现引用redis常见的问题问题①:一开始没有这个key,需要第一次查询才会到redis,此时秒杀开始,又是一堆并发进来把数据库打挂了,活动失败,GG。
解决方案:在活动没开始前,就把可能会访问到的数据都加载到redis中,虽然解决方法很简单,但是一开始没想到这个请况,会是很严重的问题。
问题②:如果这个数据特别热,突然这个key过期了,然后一堆并发请求过来查询这个数据,还是把数据库打挂了。
解决方案: 这也是我们常说的缓存击穿的问题,先看下这个问题的解决方案
1.设置这个key不过期(比如淘宝首页,只需要在有修改的时候去更新这个key就行)
2.数据库的查询时使用互斥锁,这个时候只有一个线程来查询,不会有一瞬间大量的查询sql,查询之后也放在了redis中,后面的查询就不会打到sql。(查询的互斥锁会把查询的结果集加入互斥锁,其他的不受影响,也就是说大大降低了相同查询的并发量)
引入redis中还会出现其他的问题,比如缓存雪崩、缓存穿透,想更深入了解redis