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请求接口,结果如下图:


8284499f5d364ca78c02e9a0ca964d5f.png

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请求接口,结果如下图,没有出现超卖情况:

9cfd057e30424b3f9e5390b4c15eb5cf.png

结论
通过Redis事务机制,能够有效的解决秒杀系统的超卖问题;

其他实现方式:Redis实现分布式锁机制

点赞(93)

评论列表共有 0 条评论

立即
投稿
返回
顶部