Java解决高并发秒杀商品
在看本文章之前,需要了解Spring boot搭建和使用 ,本篇文章核心问题是如何解决高并发问题。
开发环境:redis缓存4.0.1,Rabbitmq消息队列,Erlang(这个跟MQ环境有关,先安装Erlang,再安装MQ),mysql5.7,JDK1.8(Spring boot要用1.8以上的版本)
开发框架:Spring boot 2.0.4 , mybatis,前端页面:thymeleaf展示信息
一:问题
首先我们要考虑的是为什么要解决高并发,高并发瓶颈出现在哪里,有了解过的朋友肯定知道是在数据库,因为在大量请求去操作数据库时会出现数据的错乱,超卖,系统崩溃,mysql死锁等现象。
二:思路
既然知道问题出现在哪之后,就要对症下药,减少对数据的访问。在这里提供一下解决思路:
1. 页面静态化:就是将整个页面存储到redis中,下次访问时去读取redis中的页面值
2. cdn:主要对整个网站的静态资源文件进行加速,如图片,css,js等(去阿里看教程)
3.数学验证码:用户在计算验证码结果时可以减少大量请求同时进入,减少redis, mysql,服务器的压力。
4:库存标识:这是一个巨大优化,通过标识来判断redis的库存是否足够,如不足就中断去读取redis库存。例:boolean over = map.get(goodsId);当我们map通过key读取到value值为true的时候,就返回错误提示给用户, if(over) { return Result.error(‘库存不足’); }.....这样不管以后有多个请求进入都只运行两行代码,以下的操作无法进入。
5.生成动态url:主要是防止恶意用户通过固定url进行提前秒杀商品(安全方面问题这个不可掉以轻心,你连安全措施都没做好以下的那些操作都是白搭的)
6. redis预减库存::在用户秒杀商品前去redis获取当前的库存数量,然后在秒杀时候直接减去redis存储的库存(大家放心这里Redis和MySQL数据是同步的,只要进入MQ队列操作完成下单,MySQL数据库会-1数量),从而避开去MySQL读取库存数据。
7. MQ消息队列:它是一个中间消息键,通过生产者发送消息给消费者,进行业务操作,而生产者无需知道执行结果,也就是用户点击秒杀之后等待处理结果,之后再去轮询查询处理结果(异步操作),这样就避开了不断请求去操作数据库。(这里的轮询查询也是直接从redis里面去查询,因为秒杀成功之后会将秒杀的结果放到redis中,轮询时候通过key去查询)
8. Nginx: 解决高并发的好方法,也就是我们多增加几个tomcat服务器。当用户访问的时候,请求可以提交到空闲的tomcat服务器上。
三:实现
1. 页面的静态化:很多人说怎么页面静态化,其实页面静态化就是将整个页面储到redis中,我们访问这个接口的时候就直接去获取redis的中页面,这样大大提高了访问页面的速度。如下图:
2. 预先获取秒杀商品信息,库存存储到redis中,怎么个预先法?在启动项目时在MiaoshaController调用InitializingBean接口,这个主要是在启动项目时去查询数据库中所有的秒杀商品的信息,库存等。大家别小看那一行标识代码,这可是一个巨大转折点的优化。为什么呢?接着看下去。如下图:
3. 数学公式验证码,这个也是减少大量请求的方法,因为你要去计算结果,想想几秒钟时间我可以减少多少访问请求,这个数学公司验证码自己去百度找下,同样把生成的验证码结果存储到redis中,判断时在去redis中获取结果对比验证码结果。
4.生成动态url,防止恶意用户提前秒杀商品! 我们应该先在访问秒杀业务时去生成一个动态的url,这样做可以防止恶意用户去通过固定url去提前秒杀商品。方法如下图:
html:
5.当我们生成动态Url之后,我们进行秒杀业务的操作,将我们的url传递到后台进行验证是否与之前所生存的一致,并且进行MQ消息队列处理请求 如下图:
我们执行完秒杀业务层之后,将结果0返回到js中,其实这时候mq已经异步进行操作去下订单操作了,js去轮询查询数据库中是否已经下订单成功,相当是说下订单操作和js去查询下订单信息是互不相关的,这也就说不管有没有秒杀是否成功都先返回一个0结果消息。当然这里还有个问题,就是我们如何区分秒杀的操作是在排队中呢还是库存已经秒杀完毕了呢?请接着继续看下去!
(异步操作,js查询和下订单各自执行)下面来重点将下MQ这个过程,在我们平时的超市中购物也是一样,当我们在结算的时候,并不会一窝蜂一样涌入收银台,而是排队结算。这也是队列机制。我们在使用MQ前先要去配置信息如下图:
然后我们在启动MQ时要自动加载定义通信消息,用于生产者发送消息给消费者的信息
e当我们在执行秒杀业务验证都通过的话,生产者会发送一条"miaosha.queue"消息给消费者
当然消费者收到信息之后,进行秒杀下订单操作,不过为了数据正确性,这时候需要去数据库查询下当前的库存是否足够,而不是前面判断就可以了,前面redis判断只是为了减少mysql的访问。如下图:
这里我详细说下在下订单时,会出现向上面说怎么区分卖完了和排队中,假如秒杀商品被卖完了我们记录下,将商品Id当作K,true为V存储在redis中,在redis轮询查询的时候就可以判断是在排队还是商品卖完了。(没看明白的去看下轮询查询的代码)
在减库存的时候需要注意下sql语句 ,需要库存数量-1 并且库存数量必须>0才能操作下订单 ,至于添加订单详情数据我就不贴出来了,一般就是平常增加数据操作就行了,如下图:
还没有写完 。。。。。。。。还在更新完善中
我先给下源代码 :https://pan.baidu.com/s/1gpdFvcalAC53mq1L2BV3uw