当前位置: 首页>后端>正文

分布式锁和Rdisson

在业务场景中,我们要防范高并发带来的,超卖,Redis操作时的原子性,死锁,锁失效等问题。

如有以下场景,电商业务中用户提交订单,此时后台扣减Redis中的商品id。
如果写个最简单的分布式锁可以使用Redis命令

    /**
     * 提交商品生成订单
     * @param orderItemsDtos
     * @return
     */
    @PostMapping("/reduceInventory")
    @SentinelResource(value = "order",blockHandler = "orderblockHandler") //资源名
    public ApiResponse<List<ProductDto>> updateProduct(@RequestBody List<OrderItemsDto> orderItemsDtos){
        String lockKey = "lock:product1";
        String clientId = UUID.randomUUID().toString();
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
        if (!result){
            return new ApiResponse(400,"当前库存紧张请重试",null);
        }
        try {
            //比对是否有redis里的秒杀商品,如果有取出对应id值
            for (OrderItemsDto orderItems : orderItemsDtos){
                System.out.println(orderItems);
                //根据传入的商品id去redis查询
                String productStock = redisTemplate.opsForValue().get(String.valueOf(orderItems.getProductId()));
                if (Integer.parseInt(productStock) >= orderItems.getQuantity()){
                    //传入对应商品的数量,去redis减少对应商品的秒杀数量
                    int setRedisProductStock =  Integer.parseInt(productStock) - orderItems.getQuantity();
                    redisTemplate.opsForValue().set(String.valueOf(orderItems.getProductId()), String.valueOf(setRedisProductStock));
                    System.out.println( redisTemplate.opsForValue().get(String.valueOf(orderItems.getProductId())));

                }else {
                    redisTemplate.delete(lockKey);
                    return new ApiResponse(400,"订单中的商品库存不足",orderItems.getProductId());
                }
            }
            return new ApiResponse(200,"提交订单成功",null);
        }finally {
            //比较是否为自己的锁
            if (clientId.equals(redisTemplate.opsForValue().get(lockKey))){
                redisTemplate.delete(lockKey);
            }

        }

    }

这个分布式锁是基于Redis命令完成的基础锁,有存在原子性的问题。
在释放锁时,可能会出现当前线程删除了别的线程的锁,因为线程处理业务逻辑的时间不一致,为了避免这种情况,可以开一个子线程,写定时任务去监听主线程的redis中的键,是否存在,如果存在就延期,不存在就结束子线程。

或者也可以使用Rdisson

       <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.23.0</version>
        </dependency>

引入依赖后配置Redisson配置类

@Configuration
public class RedissonCogfiguratin {
    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();

        config.useSingleServer().setAddress("redis://youhost:6379").setPassword("youpassword");
        return Redisson.create(config);
    }
}

然后在controller中使用

  /**
     * 提交商品生成订单
     * @param orderItemsDtos
     * @return
     */
    @PostMapping("/reduceInventory")
    @SentinelResource(value = "order",blockHandler = "orderblockHandler") //资源名
    public ApiResponse<List<ProductDto>> updateProduct(@RequestBody List<OrderItemsDto> orderItemsDtos){
//        List<ProductDto> productDtos = productClient.updateProduct(orderItemsDtos);
        String lockKey = "lock:product1";
        RLock redissonLock = redissonClient.getLock(lockKey);
        //加锁
        redissonLock.lock();
        try {
            //比对是否有redis里的秒杀商品,如果有取出对应id值
            for (OrderItemsDto orderItems : orderItemsDtos){
                System.out.println(orderItems);
                //根据传入的商品id去redis查询
                String productStock = redisTemplate.opsForValue().get(String.valueOf(orderItems.getProductId()));
                if (Integer.parseInt(productStock) >= orderItems.getQuantity()){
                    //传入对应商品的数量,去redis减少对应商品的秒杀数量
                    int setRedisProductStock =  Integer.parseInt(productStock) - orderItems.getQuantity();
                    redisTemplate.opsForValue().set(String.valueOf(orderItems.getProductId()), String.valueOf(setRedisProductStock));
                    System.out.println( redisTemplate.opsForValue().get(String.valueOf(orderItems.getProductId())));

                }else {
                    redisTemplate.delete(lockKey);
                    return new ApiResponse(400,"订单中的商品库存不足",orderItems.getProductId());
                }
            }
            return new ApiResponse(200,"提交订单成功",null);
        }finally {
            //释放锁
          redissonLock.unlock();

        }

    }

使用压力测试软件模拟100个线程,请求5秒,每秒20个并发,资源被处理的很好,没有死锁,锁失效等情况。
Rdisson生成锁时使用了lua脚本,保证原子性,向redis发送脚本。


https://www.xamrdz.com/backend/3q71938267.html

相关文章: