秒杀问题分为两部分:用户查看商品详情页、用户下单
项目简介:
模拟了高并发场景的商城系统,它具备秒杀功能,为了解决秒杀场景下的高并发问题。引入了 redis 作为缓存中间件,1.主要作用是缓存预热、预减库存等等。2.针对高并发场景进行了页面优化,缓存页面至浏览器,前后端分离降低服务器压力,加快用户访问速度。
先不答:在安全性问题上,我使用双重MD5密码校验,隐藏了秒杀接口地址,设置了接口限流防刷。
最后还使用数学公式验证码不仅可以防恶意刷访问,还起到了削峰的作用。通过 Jmeter
压力 测试 ,系统的QPS
从150/s提升到2000/s。
QPS:【Queries-per-second】每秒处理事务数(每秒查询率)
一、查看商品详情页
1.初始化流程图
介绍:若redis中有数据就把数据返回到前端,若没有数据则请求数据库;
一条查询操作,在service层首先走Redis,若命中直接返回,否则去底层数据库中查,查询到了,返回给service层,并将查询结果保存在redis中,后返回给Controller
问:有无更快一点的方法?
答:利用多级缓存:查询redis会有网络请求,所以会产生一些i/o带宽的浪费,所以可以考虑把热点数据存储到jvm内存中去(热点数据:基本不变的数据),此时前端请求直接走jvm,没有一些I/O读写的操作,所以速度就会变快一些;若jvm内存中没有数据,再去redis中去查,redis中没有再去数据库查。
注意:但是jvm的内存是非常宝贵的,所以我们只存放非常热点的数据,不经常容易变的数据;
问:jvm并发有限,比如要抗住10万并发量怎么办?
答:1.利用服务器集群,比如我们一个机器可以抗住1w并发,那么我就可以利用10个机器去解决10万并发的问题;用Nginx来做负载均衡:用上一些轮询算法等(左图所示);**2.**还可以利用缓存来解决,我们把这个缓存放在距离用户最近的地方(这样子系统就会很快,用户体验也会很好),从目前这个流程图看,Nginx是距离用户最近的地方,所以我们可以用Nginx去访问这个redis(下两个图所示)
关于优化的问题
问:如商品详情页会有很多静态资源,这些静态资源怎么处理?
答:商品详情页有很多静态资源:比如我们写好的css,jss,html等,若去服务器上请求会浪费性能,这个时候我们可以把这个静态资源缓存到这个CDN上面,然后浏览器就可以直接去CDN上取这个静态资源,此时速度就会提升很多;
关于CDN:cdn优化的是用户访问静态资源的速度;
问:但是页面上显示的数据还得去后端接口查询出来,而我又不想去后端接口查询数据,如何处理?
答:在后端把页面完全静态化,然后把整个这个静态化好的资源放到CDN上面去,浏览器直接请求CDN上的详情页就可以直接显示了;
再问:那什么是页面静态化呢?
答:后端数据实时得渲染到前端页面上,才能显示不同的页面数据;因为有些商品详情的信息是基本不会发生改变的,页面静态化就是提前把这个数据渲染到页面上,然后把页面提供给用户访问。
二、秒杀下单
在活动开始之前,提前先把这个商品的库存缓存到redis中去,在redis里面进行一个预减库存的操作。(因为高并发下修改数据库的数据,会引发锁竞争);这样的话就不用频繁的去修改数据库中的数据,也就有效减少了锁竞争的问题
问:接上:那你是如何保证这个redis和数据库的数据一致性呢?
答:我们在redis和数据库之间加上MQ,用mq异步的方式同步这个信息。也就是说:扣减库存的操作在redis里面执行,然后把扣减库存的信息告诉MQ,然后MQ再通知MySQL扣减了多少库存;
队列排队下单
问:两个小问题
①mysql消费mq失败怎么办?②在数据库中创建订单失败了怎么办?
答:我们可以利用分布式事务来解决一下,如MQ中的rocket mq去解决;(因为rockt mq对事务比较友好)
问:那么不用mq,且redis挂了怎么办?
答:----(暂未写好答案)
问:一千万人秒杀商品,但是库存只有100w件怎么办?其余的人还在不断地请求我们的系统,这样子好吗?
做了什么流量削峰的措施?秒杀令牌
答:首先这样子肯定不好,一个是对系统的性能有影响,再一个就是用户的体验性不好;
如何解决:我么可以添加一个秒杀令牌的概念,但凡是想要参加这个秒杀活动的用户,必须先获得这个秒杀令牌(类似于活动入场券,有了入场券才能参加活动),我们在用户进行秒杀之前先判断他有无这个令牌,有令牌的则让进来进行秒杀;并且这个令牌的数量是有限的,当我们令牌发放完毕,我们就不让其他用户在继续请求我们的后端接口,直接拒绝用户的请求,减少系统资源的消耗和压力;
我们可以设置为库存量的5倍,500w个令牌;也就是说500万用户来抢夺这个100w件商品;我们不能把令牌的数量设置=库存数量,是因为有很多用户抢到了商品,但是最后还是没有付款,所以设置的多一些,可以让其他想要买的用户去抢;
还可以作为优化的地方:
1.限流操作
同时因为我们秒杀的商品种类不止一件,所以我们虽然设置了令牌机制,但是我们的秒杀流量依然很大,所以我们要进行一些限流的操作,如下图,我们在后端接口上用jmeter做后端系统的压测;比如我们这个秒杀的接口最多只能承受1w的并发,我们就要限流,比如限流8000,限流的好处就是防止系统被压垮,使得系统可以继续对外提供服务;
2.队列泄洪
接口里面进行一个队列泄洪操作;
因为在我们的接口里,会有大量的线程进来,去竞争创建订单,也就是说同时竞争这个cpu资源;而Redis是单线程的(redis是单线程的,是非常快的,因为没有cpu的上下文切换和i/o的多路复用);假使我们的cpu同时只能处理100个请求,而此时已经进来10000个线程来竞争cpu,此时的话会产生很多的性能消耗;
所以我们可以考虑把这10000个线程放在一个队列里面,然后让cpu从队列里面进行消费线程,一次只拿100个,执行完以后再从队列里面取100个,这样子cpu能处理的线程数和我们提供的线程数相等,此时就不会产生一个竞争问题。
3.熔断机制
利用熔断降级的逻辑保护系统,防止意外的大量的请求压垮系统,从而造成整个系统的瘫痪;
具体介绍:当出现系统无法承受的流量时候,我们利用熔断暂时先把请求全部拒绝,等到系统慢慢恢复再重新打开;
当熔断出现后,我们拒绝其他的请求,只剩下一小部分请求过来,看系统是否可以响应,等到系统恢复完全,我们再把接口全部开放;
同时配合这个降级的逻辑,也就是说,拒绝用户请求的同时返回一个友好提示:如,系统繁忙,请稍后再试