美文网首页redis
高并发项目 redis解决库存问题

高并发项目 redis解决库存问题

作者: Raral | 来源:发表于2021-05-18 08:40 被阅读0次

高并发单体项目 库存问题

1. 简单的业务逻辑需求

@RestController
@RequestMapping("/api/product")
public class ProdcutController extends BaseController {

    @Autowired
    private RedisCache redisCache;

    @PostMapping("/del_stock")
    public AjaxResult delductStock() {

        int stock =Integer.parseInt(redisCache.getCacheObject("stock"));
        if(stock > 0) {
            int realStock = stock - 1;
            redisCache.setCacheObject("stock", realStock + "" );
            System.out.println("扣减成功,剩余库存:" + realStock);
            return AjaxResult.success(realStock);

        }else {
            System.out.println("扣减失败,库存不足,");
            return AjaxResult.error("扣减失败,库存不足");
        }
}

}

bug:在高并发中多个用户会超卖

2. 使用java 内置锁

@RestController
@RequestMapping("/api/product")
public class ProdcutController extends BaseController {

    @Autowired
    private RedisCache redisCache;

    @PostMapping("/del_stock")
    public AjaxResult delductStock() {

        synchronized (this) {//代码块
            int stock =Integer.parseInt(redisCache.getCacheObject("stock"));
            if(stock > 0) {
                int realStock = stock - 1;
                redisCache.setCacheObject("stock", realStock + "" );
                System.out.println("扣减成功,剩余库存:" + realStock);
                return AjaxResult.success(realStock);

            }else {
                System.out.println("扣减失败,库存不足,");
                return AjaxResult.error("扣减失败,库存不足");
            }
        }

}

}

在集群中,jdk,synchronized,只能控制单机下的并发,但是大部分项目都是多台服务器部署项目,就控制不了并发问题,出现超卖

redis实现分布式锁

@RestController
@RequestMapping("/api/product")
public class ProdcutController extends BaseController {

    @Autowired
    public RedisTemplate redisTemplate;

    @Autowired
    private RedisCache redisCache;

    @GetMapping("/del_stock")
    public AjaxResult delductStock() {
        String lockKey = "prodcut_001";
        //第一个请求加锁
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge");
        if(!flag) {
            return  AjaxResult.error("当前系统繁忙,请稍后再试");
        }


        try {
            int stock =Integer.parseInt(redisCache.getCacheObject("stock"));
            if(stock > 0) {
                int realStock = stock - 1;
                redisCache.setCacheObject("stock", realStock + "" );
                System.out.println("扣减成功,剩余库存:" + realStock);
                return AjaxResult.success(realStock);

            }else {
                System.out.println("扣减失败,库存不足,");
                return AjaxResult.error("扣减失败,库存不足");
            }
        } finally {
            //释放当前业务完成后释放锁
            redisTemplate.delete(lockKey);
        }

    }

}

当系统宕机,出现死锁显现

非高并发

@RestController
@RequestMapping("/api/product")
public class ProdcutController extends BaseController {

    @Autowired
    public RedisTemplate redisTemplate;

    @Autowired
    private RedisCache redisCache;

    @GetMapping("/del_stock")
    public AjaxResult delductStock() {
        String lockKey = "prodcut_001";
//        //第一个请求加锁
//        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge");//redis.setnx
//        //添加延时时间
//        redisTemplate.expire(lockKey,10, TimeUnit.SECONDS);

        //原子命令
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge", 10, TimeUnit.SECONDS);
        if(!flag) {
            return  AjaxResult.error("当前系统繁忙,请稍后再试");
        }


        try {
            int stock =Integer.parseInt(redisCache.getCacheObject("stock"));
            if(stock > 0) {
                int realStock = stock - 1;
                redisCache.setCacheObject("stock", realStock + "" );
                System.out.println("扣减成功,剩余库存:" + realStock);
                return AjaxResult.success(realStock);

            }else {
                System.out.println("扣减失败,库存不足,");
                return AjaxResult.error("扣减失败,库存不足");
            }
        } finally {
            //释放当前业务完成后释放锁
            redisTemplate.delete(lockKey);
        }

    }

}

只能针对非高并发业务场景,可以不会超卖;如果在超高并发时候,可能会出现线程1释放锁,是线程2加的锁

超高并发

@RestController
@RequestMapping("/api/product")
public class ProdcutController extends BaseController {

    @Autowired
    public RedisTemplate redisTemplate;

    @Autowired
    private RedisCache redisCache;

    @GetMapping("/del_stock")
    public AjaxResult delductStock() {
        String lockKey = "prodcut_001";
//        //第一个请求加锁
//        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge");//redis.setnx
//        //添加延时时间
//        redisTemplate.expire(lockKey,10, TimeUnit.SECONDS);

        //给每一个线程标识一个唯一id
        String clientId = IDGeneratorUtils.getUUID().toString();
        //原子命令
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey,clientId, 10, TimeUnit.SECONDS);
        if(!flag) {
            return  AjaxResult.error("当前系统繁忙,请稍后再试");
        }
        try {
            int stock =Integer.parseInt(redisCache.getCacheObject("stock"));
            if(stock > 0) {
                int realStock = stock - 1;
                redisCache.setCacheObject("stock", realStock + "" );
                System.out.println("扣减成功,剩余库存:" + realStock);
                return AjaxResult.success(realStock);

            }else {
                System.out.println("扣减失败,库存不足,");
                return AjaxResult.error("扣减失败,库存不足");
            }
        } finally {
            //在释放锁时候,保证是当前线程id加的锁
            if(clientId.equals(redisTemplate.opsForValue().get(lockKey))) {
                 //假如这个卡顿一下,可能会删除 下一个线程2加的锁

                //释放当前业务完成后释放锁
                redisTemplate.delete(lockKey);
            }

        }

    }

}

假如在删除时候,这个卡顿一下,可能会删除 下一个线程2加的锁

  • 解决方案:
  1. 锁续命:假如在10s定时任务,检测当前线程id锁是否存在,如果redis存在,就再延时 超时时间。通过redission框架自动实现 锁续命
   <dependency>
    <groupId>org.redission</groupId>
    <artifactId>redisson</artifactId>
    <version>3.6.5</version>
</dependency>
  1. redis框架模式
    单机模式,集群模式,主从模式

相关文章

网友评论

    本文标题:高并发项目 redis解决库存问题

    本文链接:https://www.haomeiwen.com/subject/edlejltx.html