秒杀系统——整体流程

技术点:Thymeleaf    springboot  jsr303 mybatis rabbitmq   redis  druid

总体流程:

                

大并发的瓶颈:数据库。所以我们处理大并发的出发点就是怎么减少对数据库的访问,现在能够想到的就是加各种各样的缓存来减少对数据库的访问。比如页面缓存,url缓存,对象缓存,redis缓存等等

优化的方向:缓存优化,消息队列

登录模块:

  

秒杀模块:

          

页面优化技术:

     

接口优化:

   

安全优化:

   

为什么明文密码要进行两次md5?

   1.用户/客户端:pass = md5(明文+固定salt)

  2.服务端:pass = md5(用户输入+随机salt)

        如果用户登录时不做任何处理密码是明文的,那么密码就会在网络上传输,假如被别人截取到了,那么就能得到用户的密码,所以要对用户的密码进行一次md5,然后加密后的密码再发送给服务端,服务端接收到用户端传过来的经过md5的密码后要再对密码随机生成一个salt,然后和用户的md5的密码进行一个拼装,再做一次md5,然后把再次md5后的密码和salt一起写入数据库中。主要是基于安全方面的考虑,第一次md5是为了防止用户的密码在网络中进行传输,第二次md5是因为假如数据库的数据出现了泄露,为了避免这个时候用户的密码被通过反MD5推出来,所以就再进行了一次md5,双重保险

分布式session?

   首先登录成功后会为用户生成一个token,我这里用的是uuid,然后会将token存储在redis里也就是缓存中,因此就实现了分布式session

数据库设计?

  商品表      秒杀商品表          订单表      秒杀订单表

  设置秒杀商品表而不是直接在商品表后面增加一次字段是因为秒杀系统很有可能不只一次,而是有很多次,所以假如是在商品表里的话,以后这样的活动越来越多,那么表就越来越难以维护,所以就新起了一个秒杀商品表

压力测试?

    当并发量达到1000时,qps是80左右(也就是说1000个请求进来,这个时候只有80多个请求能够访问成功),因为此时我做的就是查询商品,查询商品是直接去的mysql查询的,所以mysql的性能成了瓶颈。我在进行压测的时候同时运行了top指令,然后看到cpu的绝大部分都是使用在了mysql上,所以可以确定qps低的原因就是mysql的原因。然后我查询用户信息时发现qps达到了800,性能提高了10倍左右,原因就是用户所需要的token是存放在redis里的,由此说明了redis的速度比mysql快,性能更强,此时是同一个用户发出多次请求

 redis的压测:redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000       也就是100个并发连接,100000个请求,经过压测发现1.6s左右可以完成100000个请求,qps达到了60000多

  最开始:当线程数为5000,循环数10次时(也就是一共50000),访问商品列表时qps为1300左右。

                    然后开始压秒杀接口,5000个并发,跑了10次(一共50000),这个时候qps为1300左右。

超卖问题?

   在上面的高并发情况下进行秒杀发现了数据库的库存变为负数了,也就是出现了超卖的情况。超卖的原因就是在判断库存的时候假如这个时候只有1个商品,但是两个线程同时到达,这个时候进行判断因为库存为1大于0,所以两个线程都认为自己可以继续进行秒杀,然后假如两个线程都没有创建订单,那么就都会秒杀成功,减库存,所以就出现了超卖现象

接口优化?

   首先是解决超卖问题,先在sql语句中设置stock>0,这样首先保证商品不会变为负数,然后就是为了避免一个用户多次秒杀,再在数据库上加上唯一索引。这两步做完后就是秒杀的优化了,为了优化秒杀,我做了这几步:在秒杀系统启动时将库存预缓存到redis中,然后当秒杀请求到达时先在redis中进行预减库存,这个时候使用了消息队列rabbitmq来实现异步操作,当用户的请求到达消息队列中就返回正在秒杀中,这样就能够实现异步操作,同时通过消息队列避免大量的请求同时到达数据库导致数据库承受不住,在等待过程中消费者会判断是否已经秒杀过了或者是否卖完,如果是上面两种情况就直接返回秒杀失败,这里设置了3个状态码,-1就表示秒杀失败,0就是正在秒杀(也就是客户端正在做轮询),1就是秒杀成功。当轮询到某个客户端时,才会真正为它生成订单,然后订单支付以后,mysql库存才会减1。我这里也定义了一个本地标识(map)来减少对redis的访问,因为假如我只有10件库存,然而有10000个请求,那么其实11个以后的请求都是无效的,假如这10000个请求都去访问redis,说实话还是会有消耗的,为了解决这个问题,我设置了一个hashMap来标识,key为goodsId,value为boolean类型的变量,初始值为false,当第一次防线库存小于0时,就设置boolean为true,然后后面进行秒杀的请求直接判断发现该goods的value为true,就直接返回了,节约了去访问redis的时间和消耗。

      但是我发现其实性能只是从800提升到了2400,大概提升了3倍,我自我觉得qps提升有点小,不应该这么少的,后来通过top命令去查看cpu使用情况,发现java,jmeter,redis,mysql都占用了很多的cpu,估计是因为我是单机进行的,所以四核cpu都快被压榨完了,假如mysql,jmeter,redis是放在不同的机器上,应该qps提升会大更多

安全方面的优化?

  1.秒杀接口地址的隐藏

  2.数学公式验证码

  3.接口限流防刷

因为前端的html的源码是透明的,可以直接看到源码,也就是能看到你的url请求,参数,访问方式都能看到,这个时候就算开始秒杀按钮变灰了,但是也能够直接通过输入真正的秒杀url地址来实现在秒杀时间段外的秒杀,仅仅靠前端是不能避免这种情况发生的,所以前端的校验不完美,安全校验依然是主要放在后端进行

  秒杀接口地址的隐藏?

     思路:秒杀开始之前,先去请求接口获取秒杀地址

                 1.接口改造,带上PathVariable参数

                 2.添加生成地址的接口

                3.秒杀收到请求,先验证PathVariable

每次进行一次秒杀都会生成一个不同的秒杀地址,我是通过uuid来生成一个额外的path,然后在秒杀的时候url要带上这个path,大多数情况下uuid随机生成的能保证不一样,然后再将这个path给缓存到redis中去,这样当用户访问的时候就不是直接暴露原来的地址了,而是加上了url后的地址,就实现了秒杀接口地址的隐藏,然后每个用户想要访问秒杀接口时都要先从redis中获得该path,真正秒杀的时候判断是否有该地址,有的话才能进行下面的秒杀活动

      

数学公式验证码?

    思路:点击秒杀之前,先输入验证码,分散用户的请求

                   1.添加生成验证码的接口

                   2.在获取秒杀路径的时候,验证验证码

                   3.ScriptEngine的使用

前端的操作:首先验证码都给隐藏起来,假如是在秒杀进行中,那么验证码就都给显示出来,加入秒杀结束了,那么又隐藏起来

接口防刷限流?

   思路:对接口做限流

                 将用户对接口的访问次数缓存在redis中

                 可以通过拦截器来进行

                 自定义一个注解,通过注解的方式来设置,因为其实这个限流是与业务无关的但是却会对业务产生影响的,这里使用了切面的思想,定义一个AccessLimit注解,然后里面参数是时间,最大访问次数,是否需要登录

               

  

  整体完成

发布了208 篇原创文章 · 获赞 0 · 访问量 5951

猜你喜欢

转载自blog.csdn.net/qq_40058686/article/details/105042573
今日推荐