redis分布式锁

1、使用条件:依赖

<dependency>
      <groupId>monkey.do</groupId>
      <artifactId>data-bus-redis-client</artifactId>
</dependency>

本文分布式锁,基于Redis的方式去实现分布式锁,具体实现用到的是 Redisson。通俗来讲,就是在Redis基础上实现的分布式工具集合。 分布式锁的使用分成以下 3 步: 1. 获取锁:根据唯一的 key 去 redis 获取锁。 2. 加锁:拿到锁后在指定的等待时间内不断尝试对其加锁,超过等待时间则加锁失败。 3. 解锁:分成两种情形: • 第一如果在加锁的时候指定了自动释放时间,那么在此时间范围内业务提前完成的话就在 finally 手动释放锁,而如果业务没有完成也会自动释放锁,所以指定自动释放时间需要做非常仔细的考量; • 第二就是没有指定自动释放时间,由于 redisson 有 watch dog (看门狗)机制,watch dog 默认的 releaseTime 是 30s,给锁加上 30s 的自动释放时间,并且每隔 releaseTime / 3 即 10 s 去检查业务是否完成,如果没有完成重置 releaseTime 为 30 s, 即锁的续约,所以一个业务严重阻塞的话会造成系统资源的极大浪费。

2、使用示例:一般情况,没有异步方法的情况下

@Autowired
private RedissonClient redissonClient;

public  void test(Long id) throws InterruptedException {
    //获取锁
    RLock lock = redissonClient.getLock("my-lock-name-" + id);
    //加锁,参数1:获取锁的最大等待时间(期间会重试),参数2:锁自动释放时间,参数3:时间单位
    //注意:如果指定锁自动释放时间,不管业务有没有执行完,锁都不会自动延期,即没有 watch dog 机制。
    boolean isLock = lock.tryLock(1, 2, TimeUnit.SECONDS);
    try {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        if (isLock) {
            System.out.println(format.format(System.currentTimeMillis()) + "获取分布式锁成功");
            dowork();//模拟业务处理
            System.out.println(id);
            System.out.println(format.format(System.currentTimeMillis()) + "业务完成");
        } else {
            System.out.println(format.format(System.currentTimeMillis()) + "获取分布式锁失败");
        }
    } catch (Exception e) {
        throw new RuntimeException("业务异常");
    } finally {
        //当前线程未解锁
        if (lock.isHeldByCurrentThread() && lock.isLocked()) {
            //释放锁
            System.out.println("解锁");
            lock.unlock();
        }
    }
}


private void doWork() {
    // 模拟业务逻辑处理
    try {
        Thread.sleep(5000); // 让线程持有锁5秒
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

3、使用过程中遇到的问题:

@Async当把Rlock对象传入异步方法的时候会无法解锁,才发现redis分布式锁只能解锁同一个线程加的锁,在项目中因业务逻辑限制,需要在异步方法外部创建锁对象,把锁对象传入异步方法内部后无法解锁,这个时候需要用异步解锁的方法:

先创建redis分布式锁对象,然后在异步方法外部获取线程ID,把线程ID传入异步方法,然后通过异步解锁方法解锁

//获取线程ID
long threadId = Thread.currentThread().getId();

//传参传进异步方法

if(lock.isLocked() && lock.isHeldByThread(threadId)){
                    // 异步解锁--指定线程id
                    lock.unlockAsync(threadId);
                }

①第一步


public Result reCommit(long userId, @RequestBody ReserveNoticeRequest request)  throws InterruptedException  {

         RLock redislock = redissonClient.getLock(read_record_batch_key+ resource_id_read);
//                      //加锁,参数1:获取锁的最大等待时间(期间会重试),参数2:锁自动释放时间,参数3:时间单位
//                      //注意:如果指定锁自动释放时间,不管业务有没有执行完,锁都不会自动延期,即没有 watch dog 机制。
                        boolean isLock = redislock.tryLock(1, 60*60, TimeUnit.SECONDS);

                        if (!isLock) {
                            return new Result<>(901, "已被锁定,不能导入!");
                        }
                        KoiCsvResponse csvResponse;
                        try (InputStreamReader inputStreamReader = new InputStreamReader(reserveNoticeUtil.execute(request.getFileId()).getData(), "GBK")) {
                            Result<KoiCsvResponse> result = readLogService.parseCsvData(inputStreamReader);
                            if (!result.isSuccess()) {
                                //当前线程未解锁
                                if (redislock.isHeldByCurrentThread() && redislock.isLocked()) {
                                    //释放锁
                                    redislock.unlock();
                                }
                                log.info("【预约导入】抄表录入文件流解析失败: " + ObjectMapper.toJson(result));
                                return result;
                            }
                            csvResponse = result.getData();
                            if (CollectionUtils.isEmpty(csvResponse.getDataList())) {
                                //当前线程未解锁
                                if (redislock.isHeldByCurrentThread() && redislock.isLocked()) {
                                    //释放锁
                                    redislock.unlock();
                                }
                                return new Result<>(902, "无上传数据!");
                            }
                        } catch (Exception e) {
                            //当前线程未解锁
                            if (redislock.isHeldByCurrentThread() && redislock.isLocked()) {
                                //释放锁
                                redislock.unlock();
                            }
                            return new Result<>(903, "导入失败!");
                        }
                        //获取线程ID
                        long threadId = Thread.currentThread().getId();
                        if (csvResponse.getType() == 1) {
                            readLogService.createBatchCsv(userId,csvResponse.getDataList(), financeMonth,request,read_record_batch_key, resource_id_read,redislock,threadId);
                        } else if (csvResponse.getType() == 2) {
                            readLogService.reviewBatchCsv(userId, csvResponse.getDataList(), financeMonth, request, read_record_batch_key, resource_id_read,redislock,threadId);
                        }
                        return Result.success();
}

②第二步 

 @Async
    public void createBatchCsv(Long memberId, List<KoiReadRecordCsvResponse> responses,
                               String accountingMonth, ReserveNoticeRequest request,
                               String batchKey, String resourceId, RLock remoteResourceLock,long threadId) {
 try {
            Long taskId = request.getId();

//代码逻辑

}catch (Exception e) {
            log.info("抄表导入异常: " + JSONObject.toJSONString(e));
            throw new RuntimeException();
        } finally {
            if (remoteResourceLock.isLocked()&&remoteResourceLock.isHeldByThread(threadId)){
                remoteResourceLock.unlockAsync(threadId);
            }
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值