简单实用的redis分布式锁

627 阅读1分钟

`这是我参与更文挑战的第14天,活动详情查看: 更文挑战

立个Flag每天写点东西,坚持下去。

基于setnx加锁,基于lua实现锁释放。

实现了根据 key 及唯一标识 requestId进行加锁,并设置了超时时间;释放锁时根据key及requestId进行释放。

SET_IF_ABSENT 没有key才设置,expire 设置超时时间,作为原子性操作

RedisCallback<Boolean> callback = (connection) -> {
    return connection.set(lockKey.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8")), Expiration.seconds(timeUnit.toSeconds(expire)), RedisStringCommands.SetOption.SET_IF_ABSENT);
};
return (Boolean)redisTemplate.execute(callback)

直接上代码,可以直接用的工具类

@Slf4j
public class RedisDistributedLock {
 
  private RedisTemplate redisTemplate;
 
  public RedisDistributedLock(RedisTemplate redisTemplate){
    this.redisTemplate = redisTemplate;
  }
 
  public static final String UNLOCK_LUA;
 
  /**
   * 释放锁脚本,原子操作
   */
  static {
    StringBuilder sb = new StringBuilder();
    sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
    sb.append("then ");
    sb.append("    return redis.call(\"del\",KEYS[1]) ");
    sb.append("else ");
    sb.append("    return 0 ");
    sb.append("end ");
    UNLOCK_LUA = sb.toString();
  }
 
 
  /**
   * 获取分布式锁,原子操作
   * @param lockKey
   * @param requestId 唯一ID, 可以使用UUID.randomUUID().toString();
   * @param expire
   * @param timeUnit
   * @return
   */
  public boolean tryLock(String lockKey, String requestId, long expire, TimeUnit timeUnit) {
    try{
      RedisCallback<Boolean> callback = (connection) -> {
        return connection.set(lockKey.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8")), Expiration.seconds(timeUnit.toSeconds(expire)), RedisStringCommands.SetOption.SET_IF_ABSENT);
      };
      return (Boolean)redisTemplate.execute(callback);
    } catch (Exception e) {
      log.error("redis lock error.", e);
    }
    return false;
  }
 
  /**
   * 释放锁
   * @param lockKey
   * @param requestId 唯一ID
   * @return
   */
  public boolean releaseLock(String lockKey, String requestId) {
    RedisCallback<Boolean> callback = (connection) -> {
      return connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN ,1, lockKey.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8")));
    };
    return (Boolean)redisTemplate.execute(callback);
  }
 
  /**
   * 获取Redis锁的value值
   * @param lockKey
   * @return
   */
  public String get(String lockKey) {
    try {
      RedisCallback<String> callback = (connection) -> {
        return new String(connection.get(lockKey.getBytes()), Charset.forName("UTF-8"));
      };
      return (String)redisTemplate.execute(callback);
    } catch (Exception e) {
      log.error("get redis occurred an exception", e);
    }
    return null;
  }
 
}

此工具简易轻量,基本满足加锁的一些场景。

VS redission分布式锁

redisson实现了加锁尝试时间

看门狗延长锁持有时间。

redission支持多样锁如可重入锁、读写锁、信号量等。

直接用setNx的问题,无超时时间程序意外挂掉,导致key不释放,好像新版本有setnx+过期时间的方法。

释放锁直接用delete 可能删除非当前线程的锁,增加唯一value校验,两步操作非原子性。