基于SpringBoot的电商秒杀系统

项目地址:https://github.com/msk666666/homework/tree/master/imooc

项目描述
本项目是一个基于springBoot框架搭建的Web项目,使用mybatis实现持久层数据管理,动态页面部分使用thymeleaf代码。静态页面使用html、js结合layui框架实现。缓存功能基于redis数据库来实现,主要用来缓存登录用户的认证token,优化部分库存预减功能的各商品的库存数量,以及页面页面缓存,优化部分还包括静态页面资源优化。使用rabbitMQ消息队列实现了消息的异步处理,提高了响应速度,增加用户的体验感,同时减轻了服务端的并发量和数据库的负载。

1. 项目环境搭建

创建springboot项目

添加thymeleaf模板依赖

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

关于thymeleaf的配置

# 是否开启缓存
spring.thymeleaf.cache=false
spring.thymeleaf.servlet.content-type=text/html
spring.thymeleaf.enabled=true
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML5
#url前缀
spring.thymeleaf.prefix=classpath:/templates/
#url后缀
spring.thymeleaf.suffix=.html

集成Mybatis

  • 添加依赖
 <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!--mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.0.2</version>
        </dependency>
        <!--连接池依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.15</version>
        </dependency>
  • 添加配置
#druid
spring.datasource.url=jdbc:mysql://localhost:3306/imooc?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=westos
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.filters=stat
spring.datasource.maxActive=2
spring.datasource.initialSize=1
spring.datasource.maxWait=60000
spring.datasource.minIdle=1
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=select 'x'
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxOpenPreparedStatements=20

集成Redis

  • 添加Jedis依赖
  • 添加 fastjson依赖
<dependency>
	    <groupId>redis.clients</groupId>
	    <artifactId>jedis</artifactId>
	</dependency>
	
	<dependency>
	    <groupId>com.alibaba</groupId>
	    <artifactId>fastjson</artifactId>
	    <version>1.2.38</version>
	</dependency>

用户登录

两次MD5加密
表单到服务器加密一次,服务器到数据库加密一次;
JSR303参数检验+全局异常处理器
自定义IsMobile注解,来检验手机号码格式;
自定义全局异常处理器GlobalHandler捕获发生的异常并响应给浏览器。
分布式Session

通常情况下,用户登录成功之后,给这个用户生成一个JSESSIONID的东西,来标识这个用户,写到响应的cookie中,用户端拿到这个sessionId,以后每次发送请求时带上它,服务器根据这个sessionId来取到这个用户的session信息。以便服务器识别出这是个已经登陆过的用户,解决了http无状态问题。

分布式session,将session存入公共的缓存redis中,而不像之前单端登陆,将session存入tomcat容器中。

实现秒杀功能

数据库设计
商品表、秒杀商品表

订单表、秒杀订单表

商品列表页

  • 商品列表页
    处理请求中的参数问题 https://blog.csdn.net/mashaokang1314/article/details/89217885
    显式商品的信息、提供进入商品详情页的链接
    在这里插入图片描述

  • 商品详情页
    显示商品的详情,根据目前的时间和秒杀开始时间、秒杀结束时间来显示秒杀的状态。如果未到秒杀时间或者秒杀已经结束则不能点击秒杀按钮。
    在这里插入图片描述

订单详情页

商品详情页
订单详情页

JMeter压测

https://blog.csdn.net/mashaokang1314/article/details/89397613

页面优化技术

页面缓存+URL缓存+对象缓存
https://blog.csdn.net/mashaokang1314/article/details/89575445

  • 页面缓存:当客户端有请求页面时,先从本地缓存中看是否有页面的缓存,如果没有则向服务器发出请求,获取页面。同时将服务端响应的页面进行换存。
  • URL缓存:思路类似页面缓存,不同的url有不同的缓存;

页面静态化,前后端分离

  • 页面静态化
    原理:在客户端缓存页面,减少了客户端与服务端的通信。
    1、 常用技术AngularJS、Vue.js
    2、优点:利用浏览器的缓存

静态资源优化

1、JS/CSS压缩,减少流量;
2、多个JS/CSS组合,减少连接数

webback工具可以自动去除空白字符和注解从而减少页面的体积;

CDN优化(就近访问 )
内容分发网络,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。能够实时地很具网络流量和各节点的连接、负载情况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。

总结
对于整个服务程序的QPS瓶颈主要在于数据库,所以我们可以使用缓存来较少对数据库的访问。

  • 首先从用户的角度来说,可以使用静态页面本地缓存,使得用户不需要从服务器获取页面,减少了请求响应所带来的时间损耗。
  • 然后可以使用CDN就近访问,访问部署在用户和服务端之间的各个缓存节点,尽可能从更近的地方取得所需要的资源。
  • 服务端的页面缓存,当用户的请求过来的时候,先去查询redis中是否存在要响应的页面,如果有则直接返回,否则,查询数据库获取数据然后手动渲染静态页面再响应给用户。
  • 对象缓存、url缓存
  • 数据库

优缺点
优点:可以提高响应的速度,减少服务器和数据库的压力;
缺点:可能会造成读取的数据不一致,应该在满足数据一致的情况下使用缓存。

解决超卖问题

库存量减为负数
解决方法:在sql语句上判断,只有库存量大于0才能更新成功;
一个用户发出了两个秒杀请求,出现一个用户秒杀到两个商品的情况
解决方法:将秒杀表的userId和GoodsId设为唯一索引,如果一个用户已经秒杀到了,再来一个请求插不进秒杀表而发生事务回滚。

接口优化

Redis预减库存减少数据库访问

1. 系统初始化时,就把商品库存数量加载到Redis中
2. 收到请求,Redis预减库存,库存不足,直接返回,否则进入3
3. 请求入队,立即返回排队中
4. 请求出队,生成订单,减少库存
5. 客户端轮询,是否秒杀成功
客户端根据服务端返回的状态判断应该显示什么,如果为-1说明库存不足,秒杀失败。如果为0说明秒杀成功但是还在排队中,如果是orderId则说明秒杀成功,用户是否要查看订单。如果要查看订单则通过orderId取数据库中查询相应订单并跳转到订单详情页。

内存标记减少Redis访问

使用内存标记,服务器初始化的时候将所有商品标记为false表示可以秒杀的,每次预减库存前都判断标记是否为false,如果预减库存之后商品库存小于0则将标记置为true表示商品卖完了。在判断的时候直接返回库存不足的错误信息。

请求先入队缓冲,异步下单,增强用户体验

RabbitMQ安装与Spring Boot集成

https://blog.csdn.net/mashaokang1314/article/details/89742757

总结
在项目中,使用redis预减库存挡住了几乎所有的库存不足时的失败秒杀请求,避免了库存不足时请求继续访问数据库的无意义请求。减轻了数据库的压力,用户也能及时的知道秒杀失败;同时使用消息队列异步请求处理减轻了服务器的并发量,对服务器性能的要求可以降低。
意义
秒杀失败的请求及时返回客户端,秒杀成功的请求通过消息队列排队可以慢慢来处理,并且返回排队中,让用户及时的直到自己秒杀成功还是失败,增强了用户的体验感,同时降低了对服务器的要求。

Nginx水平扩展

压测

分库分表中间件mycat

安全优化

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

  1. 接口改造,带上PathVariable参数
  2. 添加生成地址的接口;
  3. 秒杀收到请求,先验证PathVariable

意义:由于前端的html会暴露在客户端,如果不隐藏秒杀地址,则可能会有恶意用户登陆后直接通过浏览器获取秒杀地址和商品id直接访问url秒杀到商品,而绕过了前端的验证。而隐藏之后则必须先获取秒杀的path路径,将这个path带入秒杀的url中,这个path是经过MD5加密后的,所以谁也不知道真正的秒杀路径是什么。在执行秒杀的时候通过对比url中的path和redis中之前存入的path,如果匹配上才可以进行秒杀。而这个path必须通过点击秒杀按钮才能获取到。

数学公式验证码
思路:点击秒杀之前,先输入验证码,一是分散用户的请求,减轻并发量,二是防止恶意用户使用机器或工具进行秒杀。

  1. 添加生成验证码的接口;
  2. 在获取秒杀路径的时候,验证验证码;
  3. ScriptEngine使用;

接口的限流防刷
限制某一个接口,指定时间内限制访问多少次

项目中出现的问题

pom依赖的版本问题
mybatis的xml和接口的对应问题
redis的连接失败问题
封装了redis处理键值对的接口,开始在处理过程中使用了JDK1.8的新特性try()语句获得连接,问题出现在如果在try中捕获到异常,此时连接还没有关闭处于异常等待状态,出现了poolMaxIdle不够用的问题。
解决方法:捕获异常后应该在finally块中关闭连接;
压测过程中的redis Timeout异常
redis配置时

redis.timeout=10 //设置过小
redis.poolMaxTotal=1000 //设置过小
redis.poolMaxIdle=500 
redis.poolMaxWait=500

超时时间和最大连接数、最小活跃数都设置的太小,而并发量较大,导致处理IO速度较慢,超时时间变长。

rabbitMQ接收器监听异常
在接收器中将接收到的消息写入秒杀订单中,因为秒杀订单表设置了用户id和商品id的唯一索引,所以如果有重复的记录再写入会出现记录重复异常,由于没有捕获导致接收器监听失败。

  • 3
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值