暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

使用 Redisson 实现分布式锁

PiPiD 2020-01-08
846

1. Redisson 简介

Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格 (In-Memory Data Grid)。它不仅提供一系列的分布式的 Java 常用对象,还提供了许多分布式服务。Redisson 提供了使用 Redis 的最简单的和最便捷的方法。Redisson 的宗旨是促进使用者对 Redis 的关注分离 (Separation of Concern),从而让使用者能够将精力更集中地放到处理业务逻辑上。

为了防止用户重复提交请求,我们需要对相同参数的请求增加分布式锁。因此考虑使用 Redisson 来实现此功能。

2. 如何使用 Redisson?

1) pom 中加入依赖包
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.12.0</version>
</dependency>
2)  配置

Redisson 支持单节点、集群、哨兵等模式的配置。此处,我们使用单节点的配置方式。

    @Bean
   public RedissonClient getRedisson() throws Exception {
       Config config = new Config();
       String redisAddress = "redis://" + host + ":" + port;
       config.useSingleServer()
              .setAddress(redisAddress).setPassword(password);
       return Redisson.create(config);
  }
3) 分布式锁

接口:定义两个方法。

tryLock
方法,尝试获取锁,直到 waitTime
,如果获取到锁,返回 true;否则,返回 false。等到 leaseTime
, 此锁会自动释放。

unlock
方法,尝试释放锁。释放锁的时候会检查当前线程是否持有这个锁 (isHeldByCurrentThread()
),如果没有持有锁,则抛出异常。

public interface DistributedLocker {
   
   /**
    * 获取锁
    * @param lockKey   分布式锁的key
    * @param unit     时间单位
    * @param waitTime 等待时间
    * @param leaseTime 自动释放时间
    * @return 是否获取到锁
    */
   boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime);

   void unlock(String lockKey);

}

实现类

@Component
public class DistributedLockerImpl implements DistributedLocker {

   @Autowired
   private RedissonClient redissonClient;

   @Override
   public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
       try {
           RLock lock = redissonClient.getFairLock(lockKey);
          ;
           return lock.tryLock(waitTime, leaseTime, unit);
      } catch (Exception e) {
           log.error("tryLock error, lockKey: {}, error: ", lockKey, e);
           return true;
      }
  }

   @Override
   public void unlock(String lockKey) {
       try {
           RLock lock = redissonClient.getFairLock(lockKey);
           lock.unlock();
      } catch (Exception e) {
           log.error("unlock error, lockKey: {}, error: ", lockKey, e);
      }

  }
}
4)使用分布式锁
String key = "test_key#123";

// 在5s内,如果获取到锁,返回 true,否则返回 false;
// 在10s后,主动释放锁
boolean isGetLock = distributedLocker.tryLock(key, TimeUnit.SECONDS, 5, 10);


if (isGetLock) {
   // 具体的业务逻辑
  ...
   // 只在获取到锁的情况下,才需要释放锁
   distributedLocker.unlock(key);  
} else {
   // 具体的业务逻辑
  ...
}

3. 使用过程中遇到的问题

1) 只处理了单一接口的情况,未处理组合接口的情况,导致组合接口请求都被锁住等待 (因为没有主动释放锁,只有等到 releaseTime );

总结:对功能修改的评估需要全面,防止出现遗漏的情况

  • 2) 第一次上线之后出现重复释放锁的情况

总结:尽量减少不必要的操作。尤其是对于分布式锁的操作,需要保证加锁-释放锁的操作成对出现,避免出现重复释放锁导致的开销以及锁操作异常问题。

3)第一次上线之后出现:某线程解锁之后,其他线程继续等待直到超时的情况。

原因分析:分布式锁所依赖的组件 Redisson 在阿里云集群 Redis 环境下会出现这种情况  github issue

解决方案:修改到单机版的 Redis,解决此问题。

总结: 1) 引入新的技术组件,需要经过充分的评估和测试。包括其对于环境的依赖的测试。

2) 尽量保持线上环境和开发测试环境的一致,包括redis、mysql等的版本等。使得能在测试环节可以提前发现问题。



文章转载自PiPiD,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论