前言

该博文主要记录学习IT老齐的<<千万级电商高并发与秒杀实战项目>>课程的学习笔记。

1.并发问题

代码1中的项目是通过SpringBoot编写的一个B/S架构项目,前端采用freemarker模板引擎,功能非常简单就是通过Mybatis建立和数据库的映射,从数据库中获取前端网页需要展示的数据,并和模板引擎的数据进行绑定,实现网页的动态展示。

在这里插入图片描述

项目结构如下:

在这里插入图片描述

通过游览器访问 http://localhost:8969/goods?gid=1333 地址,运行效果如下:

在这里插入图片描述

上面的访问一切都正常,如果该网站同时有100人进行访问,每人访问100次会怎么样呢,我们通过Apache JMeter进行模拟:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
启动项目项目启动后,启动Apache JMeter进行访问:

  • 项目启动成功

在这里插入图片描述

  • 启动Apache JMeter

在这里插入图片描述

  • 运行情况如下

如下图所示,100个用户同时访问时最长时间为1s。(最长时间为1s表示100个用户中会有一个用户访问时需要等待1s钟才能够获取网页的信息)

在这里插入图片描述

如果网站有1万人访问,每人访问1次,又会怎么样?

在这里插入图片描述

通过下面测试的结果,我们可以看出最长时间达到了23.669s,并且吞吐为270.8,意味着某个用户访问网站时要等待23.669s才能够访问到该网站,并且吞吐率270.8实在是太低了,这是一件非常可怕的问题。

在这里插入图片描述

2.使用缓存技术Redis

像一些购物的商品信息,上架后基本上不怎么进行修改,修改的往往是一些局部的数据,比如价格这些,我们可以把这些不怎么进行变化的用户又经常需要进行访问的数据可以放到缓存中去,当缓存中有用户需要访问的数据时就从缓存中取数据,如果缓存中没有数据就从数据库中获取,并更新数据到缓存中,这样可以大大的减少对数据库服务器的访问。

代码2在代码1的基础上引入了缓存技术Redis,使用的是SpringBoot中的SpringCache声明式缓存。

在这里插入图片描述

具体的步骤如下:

导入缓存的依赖:

在这里插入图片描述
在application.yaml中配置redis:

在这里插入图片描述
在启动类中使用@EnableCaching注解开启声明式缓存

在这里插入图片描述
在业务层的业务代码中使用 @Cacheable 注解:

在这里插入图片描述

启动Reedis服务器:

在代码2中的Redis文件下有一个redis-server.exe,点击运行即可:

在这里插入图片描述
启动项目:

在这里插入图片描述

再次进行测试:

第一次运行缓存中没有数据,Max达到了79.3049秒,吞吐315.2:

在这里插入图片描述
缓存中有数据了:

在这里插入图片描述
然后再次进行访问,会发现max只有1.222秒了,并且吞吐达到1318:

在这里插入图片描述

备注:如果应用的用户访问量比较大的话,我们可以将一些用户不经常做操作的数据放入到缓存中,这样就可以大大的提高访问效率。

3.采用静态化处理

除了采用缓存技术以外.,我们可以通过网页静态化处理的方式提高访问效率,就是将用户需要访问的网页生成静态网页,用户访问静态网页少了网页和数据的交互的过程,访问速度会非常的快。

在这里插入图片描述

代码3就是在代码2的基础上多了一个生成静态网页的方法:

  • 生成静态网页

在这里插入图片描述

在这里插入图片描述

  • 生成全部静态网页

在这里插入图片描述

运行项目:

在这里插入图片描述

生成静态网页(先删除缓存中的数据):

在这里插入图片描述

生成成功(需要在该目录下放入网站的样式文件):

在这里插入图片描述

使用Nginx进行映射,当用户访问时访问静态网页:

  • 修改nginx的配置参数:

在这里插入图片描述

  • 启动nginx

在这里插入图片描述

  • 访问 http://localhost/goods/1333.html (nginx采用的是80端口)

在这里插入图片描述

4.自动静态化处理

上面的方式虽然可以生成静态网页文件但是不够智能化,需要手动进行操作,这样非常的不方便,我们可以通过资源调度的方式,按时执行,并添加相应的条件,比如说用户修改数据后5分钟后的网页文件进行静态化生成。

在这里插入图片描述

代码4在代码3的基础上增加了一个Scheduled进行按时资源调度:

在这里插入图片描述

StaticTask的代码如下:

在这里插入图片描述

其中cron表达式表示每5分钟执行一次,其中的goodsService.getLast5M();返回的是最近5分钟的数据集合,具体的SQL映射如下:
在这里插入图片描述

在数据库中修改一下goods数据表中的last_update_time,增加几组数据:

在这里插入图片描述
启动应用:

在这里插入图片描述
等待5分钟:

在这里插入图片描述
生成成功:

在这里插入图片描述

5.动静数据分离

前二种静态化方式都是直接生成静态化文件,不涉及到一些动态的数据,如果一个网页中既有不经常进行修改的数据又有需要进行频繁修改的数据,比如商品中的评论,商品的数据不怎么进行修改,但是商品的评论会频繁的进行更新。这时候我们可以对不进行修改的数据和前端一起生成静态网页,需要进行修改的数据通过前端发送Ajax请求获取数据,这样就可以实现动静数据的分离。

在这里插入图片描述

代码5在代码4的基础上对前端网页进行了简单的修改,并新增了一个获取评论数据的后端接口:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

后端响应前端Ajax的请求:

在这里插入图片描述

获取数据库中的评论数据的SQL映射语句如下:

在这里插入图片描述

动态数据并不是用户直接获取,而是通过nginx服务器代理获取:

在这里插入图片描述

启动应用程序,并通过static_all生成所有的静态文件:

在这里插入图片描述

生成成功,并且生成的静态网页是包括静态数据和动态数据(执行前需要启动redis):

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

启动nginx服务器,访问http://localhost/goods/739.html:

在这里插入图片描述

动态数据评论通过前端发送的Ajax请求获取:

在这里插入图片描述

6.解决超卖问题

超卖问题就是一件商品本来只卖10件,但是由于并发问题卖出了20件,这就是我们所说的超卖问题。

在这里插入图片描述

代码6中有二个项目,其中seckill-sample为展示超卖问题和上锁方式解决超卖的项目,而babytun-seckill为通过redis方式解决超卖问题项目。

  • seckill-sample项目

项目代码非常简单,在ProductDAO类中用static修饰的count为某个商品的库存情况,前端用户抢购的时候调用getProduct()方法较少count的数量:

在这里插入图片描述

用户通过业务层的getProduct()实现抢购:

在这里插入图片描述
在这里插入图片描述

运行项目:

在这里插入图片描述
用户通过访问http://localhost:8969/pr抢购商品:

在这里插入图片描述

执行10次,感觉一切都正常:

在这里插入图片描述
在这里插入图片描述

如果有10000个用户同时进行访问呢?

在这里插入图片描述
在这里插入图片描述

重启应用,启动JMeter进行测试:

在这里插入图片描述

观察控制台发现出现超卖:

在这里插入图片描述

在学习多线程的时候,我们知道这个原因就是执行时同一个状态下(如:count=10)的多个线程同时执行了减1操作,没有在前一个状态改变后(10减1后的状态)再进行减1操作,

在这里插入图片描述

在多线程中我们可以使用同步锁的方式解决该问题,就是对count这个资源进行上锁来保持状态的一致性,等上一个修改后再进行修改避免在同一个状态下进行修改,上锁后的代码:

在这里插入图片描述

再次重启应用,并使用jMeter再次进行测试:

在这里插入图片描述

观察控制台发现问题解决了:

在这里插入图片描述

  • babytun-seckill项目

除了上锁解决超卖外,我们还可以通过缓存技术解决超卖,执行过程就是获取用户需要进行售卖的商品并分别把商品一件一件的放入到redis缓存中的list中去,用户抢购的时候再通过list弹出数据,并将抢购商品的用户的信息放入到redis缓存中的set中去这样我们就可以利用redis通过单线程处理的方式解决并发问题,而且redis对高并发,分布式有很好的支持。

抢购商品的数据表:

在这里插入图片描述

步骤如下:

在这里插入图片描述

业务层中的processSeckill方法的逻辑很简单,首先判断商品是否可以进行抢购,不能够抢购的条件有抢购商品不存在,商品秒杀还没开始,商品秒杀活动已经结束,用户已经进行了抢购;如果都不满足上面的条件,就让存放在缓存中的list集合中的数据弹出,如果弹出成功表示用户已经抢购了此商品,并将用户的信息存放在缓存中的set集合中;如果弹出数据失败说明缓存中的list集合已经没有数据了,表示商品已经被抢完了。

在这里插入图片描述

list中的数据如何存放的呢,其实是通过资源调度实现每5秒中判断一下数据库中商品的抢购信息是否开始,如果开始就把商品一件一件的存放到List集合中:

在这里插入图片描述

获取抢购商品列表数据的SQL映射如下:

在这里插入图片描述

启动应用:

在这里插入图片描述

修改数据库中抢购商品中的结束时间和status模拟商品准备开始上线抢购:

在这里插入图片描述

修改后,控制台就会输出描述活动已启动:

在这里插入图片描述

redis缓存中就会多了这10件抢购商品的list集合信息:

在这里插入图片描述

网页访问 http://localhost:8969/secKill.html抢购商品 :

在这里插入图片描述

立即抢购按钮绑定了点击事件如下,会向后端发送一个Ajax的请求:

在这里插入图片描述

点击立即抢购后弹出抢购情况:

在这里插入图片描述

此时redis缓存数据中就会多了一个用户抢购商品的用户set集合数据,下图表明用户u03抢购了抢购id=1的商品:

在这里插入图片描述
并且存放商品的list集合中就会少一条商品信息:
在这里插入图片描述

当u03用户再次点击抢购时就会出现用户已经抢购的提示:

在这里插入图片描述

下面通过Jmeter模拟万人访问的情况:

首先添加一个CSV的数据配置文件,存放在代码6中的userid.csv:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

启动JMeter进行模拟:

在这里插入图片描述

观察redis缓存中的数据会发现没有抢购商品的list集合了,只有一个存放抢购用户信息的set集合数据,数据里刚好存放了10记录,这10条记录就是1万人中抢购成功的用户:

在这里插入图片描述

7.代码下载

在我的微信公众号后台回复 并发实战 就可以获取本篇博文相关的源代码了,如果有什么疑问后台给为留言,我看见会第一时间回复你的。

在这里插入图片描述

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐