下面是基于MySQL乐观锁实现秒杀的完整攻略:
背景介绍
在高并发场景下,主要涉及到的两个问题是:安全性与性能。乐观锁技术可以在不加锁的情况下保证多个并发请求对同一资源进行操作时,不会发生数据覆盖的情况。
技术方案
- 在MySQL中,通过对update语句设置where条件来实现乐观锁控制。
- 在应用层面,可以通过重试机制来实现乐观锁。
示例说明
下面通过两个示例来详细讲解如何基于MySQL乐观锁实现秒杀。
示例一
首先创建一个名为seckill
的数据表,包含以下字段:
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '秒杀id',
seckill_name VARCHAR(120) NOT NULL COMMENT '秒杀名称',
start_time TIMESTAMP NOT NULL COMMENT '秒杀开始时间',
end_time TIMESTAMP NOT NULL COMMENT '秒杀结束时间',
total INT NOT NULL COMMENT '秒杀商品总数量',
version INT NOT NULL COMMENT '秒杀版本号',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间'
其中,version
字段表示当前秒杀数据的版本号。
接下来,编写Java代码,实现对秒杀商品进行秒杀的功能:
public class SeckillService {
private final AtomicInteger state = new AtomicInteger(0);
private final SeckillMapper seckillMapper;
public SeckillService(SeckillMapper seckillMapper) {
this.seckillMapper = seckillMapper;
}
/**
* 秒杀商品
*
* @param seckillId 秒杀id
* @param userPhone 用户手机号
* @return 秒杀结果
*/
public SeckillExecution executeSeckill(long seckillId, long userPhone) {
Seckill seckill = seckillMapper.queryById(seckillId);
if (seckill == null) {
return new SeckillExecution(seckillId, SeckillStateEnum.NOT_FOUND);
}
long nowTime = System.currentTimeMillis();
if (nowTime < seckill.getStartTime().getTime() || nowTime > seckill.getEndTime().getTime()) {
return new SeckillExecution(seckillId, SeckillStateEnum.REPEATED);
}
// 查询库存数量,如果库存不足,则不进行秒杀
int stockCount = seckillMapper.getStockCount(seckillId);
if (stockCount == 0) {
return new SeckillExecution(seckillId, SeckillStateEnum.NO_STOCK);
}
int updCount = 0;
while (updCount == 0) {
updCount = seckillMapper.reduceStock(seckillId, seckill.getVersion() + 1);
}
SeckillSuccess success = new SeckillSuccess();
success.setSeckillId(seckillId);
success.setUserPhone(userPhone);
success.setState(SeckillStateEnum.SEC_KILLED.getState());
success.setCreateTime(new Date());
int insertCount = seckillMapper.insertSuccessKilled(success);
if (insertCount == 0) {
updCount = seckillMapper.reduceStock(seckillId, seckill.getVersion() - 1);
return new SeckillExecution(seckillId, SeckillStateEnum.REPEATED);
}
SeckillSuccess querySuccess = seckillMapper
.querySuccessKilledWithSeckill(seckillId, userPhone, success.getCreateTime());
return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, querySuccess);
}
}
在上述代码中,查询库存数量后,通过while循环进行乐观锁的实现,如果更新库存行数为0,则重试。
示例二
此示例中,使用Spring Boot 2.3.7版本进行开发。
首先创建以下两个表:
创建秒杀商品表,包含以下字段:
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '秒杀id',
seckill_name VARCHAR(120) NOT NULL COMMENT '秒杀名称',
start_time TIMESTAMP NOT NULL COMMENT '秒杀开始时间',
end_time TIMESTAMP NOT NULL COMMENT '秒杀结束时间',
total INT NOT NULL COMMENT '秒杀商品总数量'
创建秒杀商品记录表,包含以下字段:
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '秒杀记录id',
seckill_id INT NULL COMMENT '秒杀id',
user_phone BIGINT NULL COMMENT '用户手机号',
state INT NULL COMMENT '秒杀状态',
create_time TIMESTAMP NULL COMMENT '秒杀时间',
version INT NOT NULL COMMENT '乐观锁版本号'
其中,version
字段表示当前秒杀记录的版本号。
接下来,编写Java代码,实现基于Spring Boot 2.3.7版本的秒杀功能:
@Service
@Slf4j
public class SeckillService {
private final SeckillMapper seckillMapper;
private final SeckillRecordMapper seckillRecordMapper;
public SeckillService(SeckillMapper seckillMapper,
SeckillRecordMapper seckillRecordMapper) {
this.seckillMapper = seckillMapper;
this.seckillRecordMapper = seckillRecordMapper;
}
@Transactional(rollbackFor = Exception.class)
public int seckill(long seckillId, long userPhone) {
Seckill seckill = seckillMapper.queryById(seckillId);
if (seckill == null) {
throw new RuntimeException("秒杀活动不存在");
}
Date now = new Date();
if (seckill.getEndTime().getTime() < now.getTime() ||
seckill.getStartTime().getTime() > now.getTime()) {
throw new RuntimeException("秒杀活动未开始或已结束");
}
int result = seckillMapper.reduceInventory(seckillId,
seckill.getVersion(),
new Date());
if (result == 0) {
throw new RuntimeException("秒杀商品已经被抢光了!");
} else {
int insResult = seckillRecordMapper.insertRecord(seckillId, userPhone);
log.info("插入秒杀记录{},结果{}", seckillId, insResult);
return result;
}
}
}
在上述代码中,通过@Transactional注解来实现对数据库的事务管理,从而保证秒杀过程中的数据一致性。其中,SeckillMapper
类和SeckillRecordMapper
类分别用于操作秒杀商品表和秒杀商品记录表。
至此,就完成了基于MySQL乐观锁实现秒杀的示例代码的完整攻略。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:基于mysql乐观锁实现秒杀的示例代码 - Python技术站