前言
Redis事务是一种将多个Redis命令打包成一个原子操作的机制。在执行事务期间,Redis会按照顺序执行所有命令,并确保在执行事务期间不会被其他客户端的命令所打断。
Redis事务有两个重要的特性:
原子性:在执行事务期间,Redis保证事务内的所有命令要么全部执行成功,要么全部执行失败,不会出现部分执行的情况。
隔离性:Redis事务具有隔离性,即在一个事务执行过程中,其他客户端发送的命令不会被插入到该事务的命令队列中,确保事务的执行是独立的。
Redis事务的基本操作:
- 开启事务:使用MULTI命令开启一个事务块。之后的命令都将被添加到事务队列中等待执行。
- 命令入队:在事务块中,使用常规的Redis命令(如SET、GET、HSET等)将命令添加到事务队列中。这些命令并不会立即执行,而是在事务执行时按顺序批量执行。
- 执行事务:使用EXEC命令执行事务。Redis会按照事务队列中命令的顺序执行,并返回一个包含每个命令执行结果的数组。
1、场景
秒杀系统存在高并发的场景,在对商品进行秒杀时,由于并发过高可能会导致库存超卖的情况,那么可以通过Redis提供的事务机制超卖问题;Redis事务实际就是将所有命令都按顺序地执行。事务在执行时不会被其他的命令所打断。
2、复现超卖场景
2.1 初始化库存接口
@RestController
@RequestMapping("/redis")
@Slf4j
public class RedisController {
@Resource
private RedisTemplate redisTemplate;
//记录实际卖出的商品数量
private AtomicInteger successNum = new AtomicInteger(0);
@GetMapping(value = "/init")
public String init() {
// 初始化库存数量,模拟库存只要5个商品,写入到redis中
redisTemplate.opsForValue().set("stock", 5);
successNum.set(0);
log.info("===>>>库存初始化成功,库存数为" + 5);
return "初始化库存成功";
}
}
2.2 库存扣减接口
@RestController
@RequestMapping("/redis")
@Slf4j
public class RedisController {
@Resource
private RedisTemplate redisTemplate;
//记录实际卖出的商品数量
private AtomicInteger successNum = new AtomicInteger(0);
@GetMapping(value = "/reduce")
public String reduce() {
int stock = (Integer) redisTemplate.opsForValue().get("stock");
log.info("===>>>当前数量" + stock);
// 模拟只减少一个库存
stock = stock - 1;
if (stock < 0) {
log.info("===>>>库存不足");
return "库存不足";
}
// 将剩余数量回写到redis
redisTemplate.opsForValue().set("stock", stock);
// 记录实际卖出的商品数量(线程安全每个请求都会记录)
log.info("===>>>减少库存成功,共出售" + successNum.incrementAndGet());
return "减少库存成功";
}
}
2.3 测试
使用工具JMeter
模拟并发请求,此处模拟每秒200次
;JMeter
工具使用参考博客:https://blog.csdn.net/tianqingmuyu/article/details/108401543
注意:测试前先执行初始化库存接口
,保证库存写入到Redis中
使用JMeter
请求接口,结果如下图:
3、解决超卖实现
3.1 初始化库存接口
@RestController
@RequestMapping("/redis")
@Slf4j
public class RedisController {
@Resource
private RedisTemplate redisTemplate;
//记录实际卖出的商品数量
private AtomicInteger successNum = new AtomicInteger(0);
@GetMapping(value = "/init")
public String init() {
// 初始化库存数量,模拟库存只要5个商品,写入到redis中
redisTemplate.opsForValue().set("stock", 5);
successNum.set(0);
log.info("===>>>库存初始化成功,库存数为" + 5);
return "初始化库存成功";
}
}
3.2 库存扣减接口
@RestController
@RequestMapping("/redis")
@Slf4j
public class RedisController {
@Resource
private RedisTemplate redisTemplate;
//记录实际卖出的商品数量
private AtomicInteger successNum = new AtomicInteger(0);
@GetMapping(value = "/reduce")
public String reduce() {
// 开启事务
redisTemplate.setEnableTransactionSupport(true);
List<Object> results = (List<Object>) redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
// 监视key
operations.watch("stock");
Integer stock = (Integer) operations.opsForValue().get("stock");
operations.multi();
stock = stock - 1;
if (stock < 0) {
log.info("===>>>库存不足");
return null;
}
operations.opsForValue().set("stock", stock);
return operations.exec();
}
});
if (results != null && results.size() > 0) {
log.info("===>>>减少库存成功,共出售" + successNum.incrementAndGet());
return "减少库存成功";
}
return "库存不足";
}
}
3.3 测试
使用工具JMeter
模拟并发请求,此处模拟每秒200次
;JMeter
工具使用参考博客:https://blog.csdn.net/tianqingmuyu/article/details/108401543
注意:测试前先执行初始化库存接口
,保证库存写入到Redis中
使用JMeter
请求接口,结果如下图,没有出现超卖情况:
结论
通过Redis事务机制,能够有效的解决秒杀系统的超卖问题;
其他实现方式:《RedisTemplate解决高并发下秒杀系统库存超卖方案 — Redis实现分布式锁机制》
声明:JMeter
工具使用参考博客:https://blog.csdn.net/tianqingmuyu/article/details/108401543 — @前端SkyRain