基于SSM框架的秒杀系统开发过程

利用 Spring + SpringMVC + Mybatis框架完成的的商品秒杀系统API,通过对SSM框架配置整合,实现商品秒杀功能。主要分为数据库的设计与实现,开发环境的搭建,编程实现秒杀功能接口,利用Redis完成后端缓存优化,通过事务控制完成并发优化等过程,下面是的详细记录。

1、数据库的设计

根据对功能的分析,主要实现的是的秒杀商品的展示,以及秒杀成功的一个明细表。所以只需要创建两个表,用于数据存储。

秒杀商品表设计:

秒杀id 秒杀商品名 商品库存 秒杀开始时间 秒杀结束时间 记录创建时间

秒杀成功表设计:

因为每个秒杀商品的秒杀id是不同的,所以根据id可以定位到商品,然后根据id和用户电话复合主键,来保证每一个用户只能够秒杀一件商品,并且创建秒杀成功明细单。

秒杀id 用户电话 秒杀状态 记录创建时间

将数据库表的定义和操作写成规范的sql脚本,方便维护和更新。SQL脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
-- 数据库初始化脚本
-- 创建数据库
CREATE DATABASE seckill;
-- 使用数据库
use seckill;
CREATE TABLE seckill(
`seckill_id` BIGINT NOT NUll AUTO_INCREMENT COMMENT '商品库存ID',
`name` VARCHAR(120) NOT NULL COMMENT '商品名称',
`number` int NOT NULL COMMENT '库存数量',
`start_time` TIMESTAMP NOT NULL COMMENT '秒杀开始时间',
`end_time` TIMESTAMP NOT NULL COMMENT '秒杀结束时间',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (seckill_id),
key idx_start_time(start_time),
key idx_end_time(end_time),
key idx_create_time(create_time)
)ENGINE=INNODB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表';

-- 初始化数据
INSERT into seckill(name,number,start_time,end_time)
VALUES
('2000元秒杀iphone6',100,'2019-08-21 00:00:00','2019-08-22 00:00:00'),
('1000元秒杀小米9',100,'2019-08-21 00:00:00','2019-08-22 00:00:00'),
('2000元秒杀华为P30',100,'2019-08-21 00:00:00','2019-08-22 00:00:00'),
('2000元秒杀OPPO Reno',100,'2019-08-21 00:00:00','2019-08-22 00:00:00');

-- 秒杀成功明细表
-- 用户登录认证相关信息(简化为手机号)
CREATE TABLE success_killed(
`seckill_id` BIGINT NOT NULL COMMENT '秒杀商品ID',
`user_phone` BIGINT NOT NULL COMMENT '用户手机号',
`state` TINYINT NOT NULL DEFAULT -1 COMMENT '状态标识:-1:无效 0:成功 1:已付款 2:已发货',
`create_time` TIMESTAMP NOT NULL COMMENT '创建时间',
PRIMARY KEY(seckill_id,user_phone),/*联合主键*/
KEY idx_create_time(create_time)
)ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表';

-- SHOW CREATE TABLE seckill;#显示表的创建信息

2、开发环境的搭建

maven项目构建:

1
mvn archetype:generate -DgroupId=cn.ydlan.seckill -DartifactId=seckill -Dpackage=cn.ydlan.seckill -Dversion=1.0-SNAPSHOT -DarchetypeArtifactId=maven-archetype-webapp

关于maven构建项目:

archetype是指maven的项目模板,有(cocoon-22-archetype-webapp,maven-archetype-quickstart,maven-archetype-webapp)三种,maven-archetype-webapp模板,生成的项目包WEB-INF目录,并且有web.xml和一个index.jsp。

pom.xml相关依赖配置:

  • junit依赖,junit是使用编程的方式来进行测试,而junit4是使用注解的方式来运行junit。

  • 日志管理输出依赖,slf4j,log4j,logback,common-logging。

  • 数据库相关依赖,数据库连接与数据池配置。

  • MyBatis依赖,实现dao框架与mybatis自身实现的spring整合依赖。

  • Servlet web相关依赖。

  • spring依赖,包括spring核心依赖,dao依赖,web依赖,以及test依赖。

3、DAO层的设计与开发

创建实体类和DAO接口:

  • 创建Seckill.java和SuccessKilled.java 两个实体类
  • 分别创建这个两个类对应的DAO接口SeckillDao.java和SuccessKilledDao.java
  • 创建映射文件SeckillDao.xml和SuccessKilledDao.xml

mybatis与spring的整合:

  • 创建mybatis-config.xml配置文件,主要负责mybatis的配置。
  • 创建一个jdbc.properties文件,保存jdbc数据库连接的配置。
  • 创建一个spring文件夹存放spring的相关配置(spring-dao.xml)。

Junit测试:

  • 分别创建SeckillDao.java和SuccessKilledDao.java两个接口的单元测试
  • 单元测试需要使用注解进行配置,以及使用@Resource完成依赖注入。
1
2
3
4
5
//配置spring和junit整合,这样junit在启动时就会加载spring容器
@RunWith(SpringJUnit4ClassRunner.class)
//告诉junit spring的配置文件
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class SeckillDaoTest {}

4、Service层的设计与开发

接口设计:(如无必要,勿增实体)

  • 创建SeckillService接口,提供四个功能分别是查询全部秒杀记录,查询单个秒杀记录,获取秒杀接口,执行秒杀功能。
  • 使用DTO来承接返回结果,创建Exposer实体类封装暴露的秒杀接口,创建SeckillExecution实体类封装秒杀执行情况。
  • 处理秒杀重复和秒杀关闭异常,跳过编译期异常,适当的处理运行期异常情况。

接口实现:

  • 根据秒杀id以及系统时间,判断秒杀接口是否暴露,并通过MD5对秒杀接口进行加密处理。
  • 执行秒杀过程,验证消息,执行秒杀逻辑,修改数据库信息,处理异常情况。
  • 创建枚举型保存常量字典,替换代码中的常量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum SeckillStatEnum {

SUCCESS(1,"秒杀成功"),
END(0,"秒杀结束"),
REPEAT_KILL(-1,"重复秒杀"),
INNER_ERROR(-2,"系统异常"),
DATE_REWRITE(-3,"数据篡改");

private int state;
private String info;

SeckillStatEnum(int state, String info) {
this.state = state;
this.info = info;
}

配置spring托管service:

  • 创建spring-service.xml文件,扫描service包下所有使用注解的类型。
  • 配置事务管理器,注入数据库连接池,配置基于注解的声明式事务,默认使用注解来管理事务行为。
1
2
3
4
5
6
7
8
9
10
<!--扫描service包下所有使用注解的类型-->
<context:component-scan base-package="cn.ydlan.seckill.service"/>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据库连接池-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置基于注解的声明式事务
默认使用注解来管理事务行为-->
<tx:annotation-driven transaction-manager="transactionManager"/>
  • 采用注解的方式将Service的实现类加入到Spring IOC容器中。

创建service层的单元测试:

  • 创建SeckillServiceTest Junit测试,自动注入对象,测试接口中每个方法的正确性。

5、Web层的设计与开发

SpringMVC 的理论部分:

  • SpringMVC的运行流程(Handler和Controller)
  • HTTP请求地址映射原理,设计优雅的RESTful
  • 注解映射技巧(RequestMapp注解),请求方法细节处理

配置SpringMVC 的整合:

  • 在web.xml 中配置DispatcherServlet,将配置文件加载到DispatcherServlet里,完成Mybites -> spring -> springMVC的无法整合。
  • 创建spring-web.xml文件,配置spring MVC(开启springmvc注解模式,静态资源默认servlet配置,配置JSP 显示ViewResolver,扫描web相关的bean)。
  • 实现SeckillController,使用注解将方法注入容器,通过@RequestMapping注解定义URL 请求和Controller 方法之间的映射,使Controller 就能够被外界访问到。

前端交互过程:

  • Bootstrap开发界面,利用开发框架提供的CSS样式,可以完成简单而漂亮的前端界面。
  • jQuery开发交互界面,使用CDN 获取公共js,获取jQuery Cookie操作插件和countDown倒计时插件,自定义交互逻辑脚本。
  • 交互逻辑脚本实现,封装秒杀相关ajax的url,模块化JS,执行交互逻辑以及数据回传显示。

6、高性能优化

使用Redis缓存:

  • Redis的使用:下载解压,进入解压文件里,使用命令行执行make,然后在启动Redis server,然后使用执行命令’redis-cli -p 6379’查看运行情况,这里的端口需要自己设置。
  • 项目中使用Redis:添加相关的依赖,包括jedis和protostuff-core以及protostuff-runtime序列化的依赖。
  • 在DAO层设计一个RedisDao,作为数据的缓冲cache。并通过序列化和反序列化操作,实现对象的存取。改造exportSeckillUrl方法,一定要先注入redisDao。
  • 写存储过程,然后去,Mysql控制台执行储存过程。

事务控制优化:

  • 调整事务的执行顺序(SQL的顺序),通过的事务的调整控制,减少虚拟机的GC操作,提高系统的运行效率。
  • 改造SeckillController中的execute方法调用,把一开始调用普通方法的改成调用储存过程的那个方法。