Redisson分布式锁总结

1、分布式锁目前可能存在的问题(基于redis客户端jedis)
加锁: set key value [expiration EX seconds|PX milliseconds] [NX|XX]
该加锁方式是从Redis2.8之后便支持这种原子性加锁方式,之前设置setnx和设置过期时间不是原子性的。

String ret = jedis.set(getKey(key), 
                       new CacheEntity<T>(serializable, version, System.currentTimeMillis()).getBytes(), 
                       "NX".getBytes(),
                       "EX".getBytes(), 
                       expireSecond);
return StringUtils.equals("OK", ret);

参数解释如下:

  • EX second :设置key的过期时间为 second 秒
  • PX millisecond :设置key的过期时间为 millisecond 毫秒
  • NX :只在key不存在时,才对key进行设置操作
  • XX :只在key已经存在时,才对key进行设置操作

解锁

ret = jedis.del(getKey(key));

使用

if (!lock.tryLockXxx(key)) {
  return ;
}
try {
  // TODO
} finally { lock.unlockXxx(key); }

基于jedis实现的分布式锁缺点

  • 不支持重入
  • 加锁时设置的value无意义
  • 锁超时时间不能自动续期,所以不好取值
  • 锁超时时间 > 业务执行时间,业务正常执行完成释放锁,没问题,业务节点奔溃,尚未释放锁,会导致其他业务系统线程最多等待整个超时时间
  • 锁超时时间 < 业务执行时间,锁超时自动释放,业务执行完成,再执行unlock,可能会错误的解锁
  • 加锁key设置在Master节点上,尚未同步到slave就宕机了,salve提升为新的master,没有上一个锁的信息,其他线程依然可以获取同一个key的锁

2、Redisson客户端优势

  • 支持 SSL
  • 线程安全的实现
  • Reactive Streams API
  • 异步 API
  • 异步连接池
  • Lua 脚本
  • 分布式Java对象
  • Object holder, Binary stream holder, Geospatial holder, BitSet, AtomicLong, AtomicDouble, PublishSubscribe, Bloom filter, HyperLogLog
  • 分布式Java集合
  • Map, Multimap, Set, List, SortedSet, ScoredSortedSet, LexSortedSet, Queue, Deque, Blocking Queue, Bounded Blocking Queue, Blocking Deque, Delayed Queue, Priority Queue, Priority Deque
  • 分布式锁和同步器
  • Lock, FairLock, MultiLock, RedLock, ReadWriteLock, Semaphore, PermitExpirableSemaphore, CountDownLatch

Redisson的基本用法参考这篇博客《基于redission的分布式锁

自动续期源码:

// tryAcquireAsync -> scheduleExpirationRenewal -> renewExpiration
// 自动续期
private void renewExpiration() {
    ...

    Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
        @Override
        public void run(Timeout timeout) throws Exception {
            ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
            if (ent == null) {
                return;
            }
            Long threadId = ent.getFirstThreadId();
            if (threadId == null) {
                return;
            }

            RFuture<Boolean> future = renewExpirationAsync(threadId);
            future.onComplete((res, e) -> {
                if (e != null) {
                    log.error("Can't update lock " + getRawName() + " expiration", e);
                    EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                    return;
                }

                if (res) {
                    // reschedule itself
                    renewExpiration();
                }
            });
        }
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

    ...
}

真正执行续期的lua脚本

protected RFuture<Boolean> renewExpirationAsync(long threadId) {
    return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                          "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                          "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                          "return 1; " +
                          "end; " +
                          "return 0;",
                          Collections.singletonList(getRawName()),
                          internalLockLeaseTime, getLockName(threadId));
}

Netty中的时间轮算法 HashedWheelTimer

 

 

posted @ 2021-06-28 15:16  郭慕荣  阅读(685)  评论(0编辑  收藏  举报