淘东电商项目(74) -秒杀系统(库存超卖解决方案)
引言
本文代码已提交至Github(版本号:
4a1e952df7a06cb764166262b02c8c23962e6084
),有兴趣的同学可以下载来看看:https://github.com/ylw-github/taodong-shop
在上一篇博客《淘东电商项目(73) -秒杀系统(前端优化)》主要讲解了秒杀系统的前端优化,本文开始讲解后端的秒杀系统设计。
本文目录结构:
l____引言
l____ 1.什么是库存超卖?
l____ 2.库存超卖的解决方案
l________ 2.1 解决方案
l________ 2.2 数据库表设计
l________ 2.3 使用DB行锁(悲观锁)
l________ 2.4 使用version控制(乐观锁)
l____ 3. 测试
l________ 3.1 测试悲观锁
l________ 3.2 测试乐观锁
1.什么是库存超卖?
在秒杀系统中,同一时刻大量的用户会并发访问秒杀接口,此时数据库会相应的减少库存,举个例子:
比如一件商品有100件,此时有10万个用户同时访问秒杀接口,当数据库还剩一件商品时,A用户和B用户同时进入接口,操作数据库,都做扣减库存操作(set sum=sum-1),由于数据库的行锁机制,A用户先获取到行锁,所以A用户获取后,库存应该为0(即当前库存-1)。A用户操作完后,释放行锁,B用户进行操作,库存变为-1(即当前库存-1),这很明显是不符合需求的,那该如何解决呢?下面来讲解。
2.库存超卖的解决方案
2.1 解决方案
为了应对库存超卖的问题,有两种解决方案:
- 使用DB行锁,也就是悲观锁(WHERE控制)。
- 使用version控制,也就是乐观锁(CAS无锁机制)。
2.2 数据库表设计
讲解代码前,先看看秒杀系统数据库的表设计:
①订单表:
CREATE TABLE `order` (
`seckill_id` bigint(20) NOT NULL COMMENT '秒杀商品id',
`user_phone` bigint(20) NOT NULL COMMENT '用户手机号',
`state` tinyint(4) NOT NULL DEFAULT '-1' COMMENT '状态标示:-1:无效 0:成功 1:已付款 2:已发货',
`create_time` datetime NOT NULL COMMENT '创建时间',
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表';
②秒杀库存表:
CREATE TABLE `seckill` (
`seckill_id` bigint(20) NOT NULL COMMENT '商品库存id',
`name` varchar(120) NOT NULL COMMENT '商品名称',
`inventory` int(11) NOT NULL COMMENT '库存数量',
`start_time` datetime NOT NULL COMMENT '秒杀开启时间',
`end_time` datetime NOT NULL COMMENT '秒杀结束时间',
`create_time` datetime NOT NULL COMMENT '创建时间',
`version` bigint(20) NOT NULL DEFAULT '0' COMMENT '乐观锁',
PRIMARY KEY (`seckill_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀库存表';
2.3 使用DB行锁(悲观锁)
首先看看秒杀接口的代码逻辑:
@Transactional
public BaseResponse<JSONObject> spike(String phone, Long seckillId) {
// TODO 1.参数验证
// TODO 2.用户频率限制 setnx 如果key存在话
// TODO 3.修改数据库对应的库存 1万中只有100个抢购成功 提前生成好100个token 谁能够抢购成功toen放入到mq中实现异步修改库存
// TODO 4.添加秒杀成功订单 基于MQ实现异步形式
}
库存超卖逻辑在第3个步骤,下面直接贴出Mybatis SQL语句:
update
seckill
set
inventory=inventory-1
where
seckill_id=#{seckillId} and inventory>0;
上面的语句主要是由where来控制,在inventory
(库存数量)大于0的情况下,才允许修改库存减一。
缺点:由于DB里面使用的是行锁,所以效率比较低,要等一个更新操作完才能进行下一个更新操作,在用户并发量高的情况下,效率非常慢。
解决方案:使用version控制,即乐观锁,下面讲解。
2.4 使用version控制(乐观锁)
注意:乐观锁CAS无锁机制主要的两个变量:“预期值"和"结果值”。
下面看看使用乐观锁之后的MyBatis SQL语句:
①首先获取当前乐观锁的version版本号:
SELECT
seckill_id AS seckillId,name as name,inventory as inventory,start_time as startTime,end_time as endTime,create_time as createTime,version as version
from
seckill
where
seckill_id=#{seckillId}
②然后传入查询的乐观锁的version版本号,并更新库存:
update
seckill
set
inventory=inventory-1, version=version+1
where
seckill_id=#{seckillId} and inventory>0 and version=#{version} ;
优点:效率高同时也防止库存超卖。
3. 测试
首先数据库模拟插入一条数据:
INSERT INTO `seckill`(`seckill_id`, `name`, `inventory`, `start_time`, `end_time`, `create_time`, `version`) VALUES (100001, 'iphoneX', 100, '2020-05-25 17:16:11', '2020-05-25 17:16:13', '2020-05-25 17:16:16', 1);
使用JMeter测试,定义200个用户访问:
3.1 测试悲观锁
调用悲观锁接口pessimisticDeduction
,核心代码如下:
@Transactional
public BaseResponse<JSONObject> spike(String phone, Long seckillId) {
// 1.参数验证
if (StringUtils.isEmpty(phone)) {
return setResultError("手机号码不能为空!");
}
if (seckillId == null) {
return setResultError("商品库存id不能为空!");
}
SeckillEntity seckillEntity = seckillMapper.findBySeckillId(seckillId);
if (seckillEntity == null) {
return setResultError("商品信息不存在!");
}
// 2.用户频率限制 setnx 如果key存在话
// 3.(悲观锁 )修改数据库对应的库存 1万中只有100个抢购成功 提前生成好100个token 谁能够抢购成功token放入到mq中实现异步修改库存
int inventoryDeduction = seckillMapper.pessimisticDeduction(seckillId);
if (!toDaoResult(inventoryDeduction)) {
log.info(">>>修改库存失败>>>>inventoryDeduction返回为{} 秒杀失败!", inventoryDeduction);
return setResultError("亲,请稍后重试!");
}
// 4.添加秒杀成功订单 基于MQ实现异步形式
OrderEntity orderEntity = new OrderEntity();
orderEntity.setUserPhone(phone);
orderEntity.setSeckillId(seckillId);
int insertOrder = orderMapper.insertOrder(orderEntity);
if (!toDaoResult(insertOrder)) {
return setResultError("亲,请稍后重试!");
}
log.info(">>>修改库存成功>>>>inventoryDeduction返回为{} 秒杀成功", inventoryDeduction);
return setResultSuccess("恭喜您,秒杀成功!");
}
运行JMeter,可以看到数据库的库存减为0,并新增了100条订单:
运行前 | 运行后 |
---|---|
![]() | ![]() |
![]() | ![]() |
3.2 测试乐观锁
调用乐观锁接口optimisticDeduction
,核心代码如下:
@Transactional
public BaseResponse<JSONObject> spike(String phone, Long seckillId) {
// 1.参数验证
if (StringUtils.isEmpty(phone)) {
return setResultError("手机号码不能为空!");
}
if (seckillId == null) {
return setResultError("商品库存id不能为空!");
}
SeckillEntity seckillEntity = seckillMapper.findBySeckillId(seckillId);
if (seckillEntity == null) {
return setResultError("商品信息不存在!");
}
// 2.用户频率限制 setnx 如果key存在话
// 3.(乐观锁 )修改数据库对应的库存 1万中只有100个抢购成功 提前生成好100个token 谁能够抢购成功token放入到mq中实现异步修改库存
Long version = seckillEntity.getVersion();
int inventoryDeduction = seckillMapper.optimisticDeduction(seckillId, version);
if (!toDaoResult(inventoryDeduction)) {
log.info(">>>修改库存失败>>>>inventoryDeduction返回为{} 秒杀失败!", inventoryDeduction);
return setResultError("亲,请稍后重试!");
}
// 4.添加秒杀成功订单 基于MQ实现异步形式
OrderEntity orderEntity = new OrderEntity();
orderEntity.setUserPhone(phone);
orderEntity.setSeckillId(seckillId);
int insertOrder = orderMapper.insertOrder(orderEntity);
if (!toDaoResult(insertOrder)) {
return setResultError("亲,请稍后重试!");
}
log.info(">>>修改库存成功>>>>inventoryDeduction返回为{} 秒杀成功", inventoryDeduction);
return setResultSuccess("恭喜您,秒杀成功!");
}
运行JMeter,可以看到数据库的库存减少了24个,并新增了24条订单:
运行前 | 运行后 |
---|---|
![]() | ![]() |
![]() | ![]() |
相关文章
- 自然语言交流系统 phxnet团队 创新实训 项目博客 (八)
- 自然语言交流系统 phxnet团队 创新实训 项目博客 (五)
- 自然语言交流系统 phxnet团队 创新实训 项目博客 (一)
- 新闻发布项目——后台JSP界面adminManage/modifyNews.jsp
- Maven父子项目配置-多模块(multi-modules)结构
- C#写的NoSQL开源项目/系统(系列)
- python-django项目-Linux系统建立django项目_20191117
- 项目部署、配置、查错常用到的Linux命令
- 基于Springboot2.0的Dubbo入门项目(dubbo-spring-boot-starter)
- C# 项目开发笔记
- atitit.研发管理--标准化流程总结---java开发环境与项目部署环境的搭建工具包总结
- 【项目实战】Java 8中表示日期和时间的类
- 【项目实战】并发编程之ScheduledExecutorService(Java提供的一个定时任务框架)入门介绍
- 【项目实战】MySQL日期与字符串之间处理,MySQL日期函数大全
- Python实现GWO智能灰狼优化算法优化随机森林回归模型(RandomForestRegressor算法)项目实战
- [h5棋牌项目]-07-系统调用delete时,会在delete内调用对象的析构函数.
- [手游项目4]-sql server数据库
- 项目架构理解
- 将项目安装到Maven本地资源库
- Django项目在linux系统中虚拟环境部署
- 使用VSCode创建第一个VUE项目
- Opencv项目实战:07 人脸识别和考勤系统
- 【课题总结】OpenCV 抠图项目实战(6)色彩范围抠图
- Gitlab----部署Docker类型的项目级别的gitlab-runner
- 慕课网前端项目:Vue2.0+Node.js+MongoDB全栈打造商城系统 笔记 整理【6/18】
- 有哪些优秀的Android开源项目最值得阅读?这里Android中近百个优秀开源库,包你提高效率
- 【项目实战】性能测试之压力测试的系统性能指标基本介绍
- 基于MYSQL的新闻发布系统数据库设计项目实战
- 02 从头开始atac项目 ubuntu20 install r4.2 Linux系统环境配置 服务器版本的rstudio r install in linux /ubuntu/centos