Redis客户端 Redisson(四)

Redis Client Redisson 四

  • 简介
  • 分布式锁
    • 基本特性
    • RedissonLock源码
  • Redisson加锁& 解锁实现过程
    • 常用的两种加锁方法
    • 给每次请求赋予唯一编号
    • 加锁 tryLockInnerAsync方法
    • 未抢到锁
    • 解锁 unlockInnerAsync方法
    • Redis分布式锁的非正常失效情况
  • 锁& 数据类型使用例子

  • 官网地址:https://redisson.org
  • 项目地址:https://github.com/redisson/redisson

简介

  • Redisson是基于 Netty框架的事件驱动通讯的, 所以方法调用是异步的同时线程安全的, Redisson提供一系列的分布式服务及数据结构, 如 Lock, BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, AtomicLong, CountDownLatch, Publish/Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service

分布式锁

  • 分布式系统有一个著名的理论 CAP, 指在一个分布式系统中, 最多只能同时满足一致性(Consistency), 可用性(Availability), 分区容错性(Partition tolerance)中的两项. 所以设计系统时, 需要权衡

基本特性

  1. 锁互斥: 高并发时, 保证同一时间只能有一个线程获得锁
  2. 重入锁: 高并发时, 在同一个线程不等待解锁, 可重复加锁(递归形式), 执行代码. 此时其它线程只能等到当前(被锁定)的线程的锁(包括重入锁), 执行完代码后解锁(递归形式), 方可开始抢锁.
  3. 释放锁: 高并发时, 某个线程加锁后, 因为系统故障或者其它原因导致无法执行解锁的命令, 造成死锁. 为了防止此种情况, 需要设置锁的有效时间, 如果指定的时间内未解锁, 系统主动释放锁

RedissonLock源码


public class RedissonLock extends RedissonExpirable implements RLock {
    private static final Logger log = LoggerFactory.getLogger(RedissonLock.class);
    private static final ConcurrentMap EXPIRATION_RENEWAL_MAP = new ConcurrentHashMap();
    protected long internalLockLeaseTime;
    final UUID id;
    final String entryName;
    protected final LockPubSub pubSub;
    final CommandAsyncExecutor commandExecutor;
    private static final RedisCommand HGET;

    public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {
        super(commandExecutor, name);
        this.commandExecutor = commandExecutor;
        this.id = commandExecutor.getConnectionManager().getId();
        this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
        this.entryName = this.id + ":" + name;
        this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();
    }

    protected String getEntryName() {
        return this.entryName;
    }

    String getChannelName() {
        return prefixName("redisson_lock__channel", this.getName());
    }

    protected String getLockName(long threadId) {
        return this.id + ":" + threadId;
    }

    public void lock() {
        try {
            this.lock(-1L, (TimeUnit)null, false);
        } catch (InterruptedException var2) {
            throw new IllegalStateException();
        }
    }

    public void lock(long leaseTime, TimeUnit unit) {
        try {
            this.lock(leaseTime, unit, false);
        } catch (InterruptedException var5) {
            throw new IllegalStateException();
        }
    }

    public void lockInterruptibly() throws InterruptedException {
        this.lock(-1L, (TimeUnit)null, true);
    }

    public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
        this.lock(leaseTime, unit, true);
    }

    private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
        Long ttl = this.tryAcquire(leaseTime, unit, threadId);
        if (ttl != null) {
            RFuture future = this.subscribe(threadId);
            this.commandExecutor.syncSubscription(future);

            try {
                while(true) {
                    ttl = this.tryAcquire(leaseTime, unit, threadId);
                    if (ttl == null) {
                        return;
                    }

                    if (ttl >= 0L) {
                        try {
                            this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                        } catch (InterruptedException var13) {
                            if (interruptibly) {
                                throw var13;
                            }

                            this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                        }
                    } else if (interruptibly) {
                        this.getEntry(threadId).getLatch().acquire();
                    } else {
                        this.getEntry(threadId).getLatch().acquireUninterruptibly();
                    }
                }
            } finally {
                this.unsubscribe(future, threadId);
            }
        }
    }

    private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
        return (Long)this.get(this.tryAcquireAsync(leaseTime, unit, threadId));
    }

    private RFuture tryAcquireOnceAsync(long leaseTime, TimeUnit unit, long threadId) {
        if (leaseTime != -1L) {
            return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
        } else {
            RFuture ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
            ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
                if (e == null) {
                    if (ttlRemaining) {
                        this.scheduleExpirationRenewal(threadId);
                    }

                }
            });
            return ttlRemainingFuture;
        }
    }

    private  RFuture tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
        if (leaseTime != -1L) {
            return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
            RFuture ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
            ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
                if (e == null) {
                    if (ttlRemaining == null) {
                        this.scheduleExpirationRenewal(threadId);
                    }

                }
            });
            return ttlRemainingFuture;
        }
    }

    public boolean tryLock() {
        return (Boolean)this.get(this.tryLockAsync());
    }

    private void renewExpiration() {
        RedissonLock.ExpirationEntry ee = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
        if (ee != null) {
            Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
                public void run(Timeout timeout) throws Exception {
                    RedissonLock.ExpirationEntry ent = (RedissonLock.ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());
                    if (ent != null) {
                        Long threadId = ent.getFirstThreadId();
                        if (threadId != null) {
                            RFuture future = RedissonLock.this.renewExpirationAsync(threadId);
                            future.onComplete((res, e) -> {
                                if (e != null) {
                                    RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);
                                } else {
                                    if (res) {
                                        RedissonLock.this.renewExpiration();
                                    }

                                }
                            });
                        }
                    }
                }
            }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
            ee.setTimeout(task);
        }
    }

    private void scheduleExpirationRenewal(long threadId) {
        RedissonLock.ExpirationEntry entry = new RedissonLock.ExpirationEntry();
        RedissonLock.ExpirationEntry oldEntry = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
        if (oldEntry != null) {
            oldEntry.addThreadId(threadId);
        } else {
            entry.addThreadId(threadId);
            this.renewExpiration();
        }

    }

    protected RFuture renewExpirationAsync(long threadId) {
        return this.commandExecutor.evalWriteAsync(this.getName(), 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(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
    }

    void cancelExpirationRenewal(Long threadId) {
        RedissonLock.ExpirationEntry task = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
        if (task != null) {
            if (threadId != null) {
                task.removeThreadId(threadId);
            }

            if (threadId == null || task.hasNoThreads()) {
                task.getTimeout().cancel();
                EXPIRATION_RENEWAL_MAP.remove(this.getEntryName());
            }

        }
    }

     RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) {
        this.internalLockLeaseTime = unit.toMillis(leaseTime);
        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
    }

    private void acquireFailed(long threadId) {
        this.get(this.acquireFailedAsync(threadId));
    }

    protected RFuture acquireFailedAsync(long threadId) {
        return RedissonPromise.newSucceededFuture((Object)null);
    }

    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
        long threadId = Thread.currentThread().getId();
        Long ttl = this.tryAcquire(leaseTime, unit, threadId);
        if (ttl == null) {
            return true;
        } else {
            time -= System.currentTimeMillis() - current;
            if (time <= 0L) {
                this.acquireFailed(threadId);
                return false;
            } else {
                current = System.currentTimeMillis();
                RFuture subscribeFuture = this.subscribe(threadId);
                if (!this.await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {
                    if (!subscribeFuture.cancel(false)) {
                        subscribeFuture.onComplete((res, e) -> {
                            if (e == null) {
                                this.unsubscribe(subscribeFuture, threadId);
                            }

                        });
                    }

                    this.acquireFailed(threadId);
                    return false;
                } else {
                    try {
                        time -= System.currentTimeMillis() - current;
                        if (time <= 0L) {
                            this.acquireFailed(threadId);
                            boolean var20 = false;
                            return var20;
                        } else {
                            boolean var16;
                            do {
                                long currentTime = System.currentTimeMillis();
                                ttl = this.tryAcquire(leaseTime, unit, threadId);
                                if (ttl == null) {
                                    var16 = true;
                                    return var16;
                                }

                                time -= System.currentTimeMillis() - currentTime;
                                if (time <= 0L) {
                                    this.acquireFailed(threadId);
                                    var16 = false;
                                    return var16;
                                }

                                currentTime = System.currentTimeMillis();
                                if (ttl >= 0L && ttl < time) {
                                    this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                                } else {
                                    this.getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                                }

                                time -= System.currentTimeMillis() - currentTime;
                            } while(time > 0L);

                            this.acquireFailed(threadId);
                            var16 = false;
                            return var16;
                        }
                    } finally {
                        this.unsubscribe(subscribeFuture, threadId);
                    }
                }
            }
        }
    }

    protected RedissonLockEntry getEntry(long threadId) {
        return (RedissonLockEntry)this.pubSub.getEntry(this.getEntryName());
    }

    protected RFuture subscribe(long threadId) {
        return this.pubSub.subscribe(this.getEntryName(), this.getChannelName());
    }

    protected void unsubscribe(RFuture future, long threadId) {
        this.pubSub.unsubscribe((PubSubEntry)future.getNow(), this.getEntryName(), this.getChannelName());
    }

    public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {
        return this.tryLock(waitTime, -1L, unit);
    }

    public void unlock() {
        try {
            this.get(this.unlockAsync(Thread.currentThread().getId()));
        } catch (RedisException var2) {
            if (var2.getCause() instanceof IllegalMonitorStateException) {
                throw (IllegalMonitorStateException)var2.getCause();
            } else {
                throw var2;
            }
        }
    }

    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }

    public boolean forceUnlock() {
        return (Boolean)this.get(this.forceUnlockAsync());
    }

    public RFuture forceUnlockAsync() {
        this.cancelExpirationRenewal((Long)null);
        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('del', KEYS[1]) == 1) then redis.call('publish', KEYS[2], ARGV[1]); return 1 else return 0 end", Arrays.asList(this.getName(), this.getChannelName()), new Object[]{LockPubSub.UNLOCK_MESSAGE});
    }

    public boolean isLocked() {
        return this.isExists();
    }

    public RFuture isLockedAsync() {
        return this.isExistsAsync();
    }

    public RFuture isExistsAsync() {
        return this.commandExecutor.writeAsync(this.getName(), this.codec, RedisCommands.EXISTS, new Object[]{this.getName()});
    }

    public boolean isHeldByCurrentThread() {
        return this.isHeldByThread(Thread.currentThread().getId());
    }

    public boolean isHeldByThread(long threadId) {
        RFuture future = this.commandExecutor.writeAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.HEXISTS, new Object[]{this.getName(), this.getLockName(threadId)});
        return (Boolean)this.get(future);
    }

    public RFuture getHoldCountAsync() {
        return this.commandExecutor.writeAsync(this.getName(), LongCodec.INSTANCE, HGET, new Object[]{this.getName(), this.getLockName(Thread.currentThread().getId())});
    }

    public int getHoldCount() {
        return (Integer)this.get(this.getHoldCountAsync());
    }

    public RFuture deleteAsync() {
        return this.forceUnlockAsync();
    }

    public RFuture unlockAsync() {
        long threadId = Thread.currentThread().getId();
        return this.unlockAsync(threadId);
    }

    protected RFuture unlockInnerAsync(long threadId) {
        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil;end; local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); return 0; else redis.call('del', KEYS[1]); redis.call('publish', KEYS[2], ARGV[1]); return 1; end; return nil;", Arrays.asList(this.getName(), this.getChannelName()), new Object[]{LockPubSub.UNLOCK_MESSAGE, this.internalLockLeaseTime, this.getLockName(threadId)});
    }

    public RFuture unlockAsync(long threadId) {
        RPromise result = new RedissonPromise();
        RFuture future = this.unlockInnerAsync(threadId);
        future.onComplete((opStatus, e) -> {
            if (e != null) {
                this.cancelExpirationRenewal(threadId);
                result.tryFailure(e);
            } else if (opStatus == null) {
                IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + this.id + " thread-id: " + threadId);
                result.tryFailure(cause);
            } else {
                this.cancelExpirationRenewal(threadId);
                result.trySuccess((Object)null);
            }
        });
        return result;
    }

    public RFuture lockAsync() {
        return this.lockAsync(-1L, (TimeUnit)null);
    }

    public RFuture lockAsync(long leaseTime, TimeUnit unit) {
        long currentThreadId = Thread.currentThread().getId();
        return this.lockAsync(leaseTime, unit, currentThreadId);
    }

    public RFuture lockAsync(long currentThreadId) {
        return this.lockAsync(-1L, (TimeUnit)null, currentThreadId);
    }

    public RFuture lockAsync(long leaseTime, TimeUnit unit, long currentThreadId) {
        RPromise result = new RedissonPromise();
        RFuture ttlFuture = this.tryAcquireAsync(leaseTime, unit, currentThreadId);
        ttlFuture.onComplete((ttl, e) -> {
            if (e != null) {
                result.tryFailure(e);
            } else if (ttl == null) {
                if (!result.trySuccess((Object)null)) {
                    this.unlockAsync(currentThreadId);
                }

            } else {
                RFuture subscribeFuture = this.subscribe(currentThreadId);
                subscribeFuture.onComplete((res, ex) -> {
                    if (ex != null) {
                        result.tryFailure(ex);
                    } else {
                        this.lockAsync(leaseTime, unit, subscribeFuture, result, currentThreadId);
                    }
                });
            }
        });
        return result;
    }

    private void lockAsync(long leaseTime, TimeUnit unit, RFuture subscribeFuture, RPromise result, long currentThreadId) {
        RFuture ttlFuture = this.tryAcquireAsync(leaseTime, unit, currentThreadId);
        ttlFuture.onComplete((ttl, e) -> {
            if (e != null) {
                this.unsubscribe(subscribeFuture, currentThreadId);
                result.tryFailure(e);
            } else if (ttl == null) {
                this.unsubscribe(subscribeFuture, currentThreadId);
                if (!result.trySuccess((Object)null)) {
                    this.unlockAsync(currentThreadId);
                }

            } else {
                final RedissonLockEntry entry = this.getEntry(currentThreadId);
                if (entry.getLatch().tryAcquire()) {
                    this.lockAsync(leaseTime, unit, subscribeFuture, result, currentThreadId);
                } else {
                    AtomicReference futureRef = new AtomicReference();
                    final Runnable listener = () -> {
                        if (futureRef.get() != null) {
                            ((Timeout)futureRef.get()).cancel();
                        }

                        this.lockAsync(leaseTime, unit, subscribeFuture, result, currentThreadId);
                    };
                    entry.addListener(listener);
                    if (ttl >= 0L) {
                        Timeout scheduledFuture = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
                            public void run(Timeout timeout) throws Exception {
                                if (entry.removeListener(listener)) {
                                    RedissonLock.this.lockAsync(leaseTime, unit, subscribeFuture, result, currentThreadId);
                                }

                            }
                        }, ttl, TimeUnit.MILLISECONDS);
                        futureRef.set(scheduledFuture);
                    }
                }

            }
        });
    }

    public RFuture tryLockAsync() {
        return this.tryLockAsync(Thread.currentThread().getId());
    }

    public RFuture tryLockAsync(long threadId) {
        return this.tryAcquireOnceAsync(-1L, (TimeUnit)null, threadId);
    }

    public RFuture tryLockAsync(long waitTime, TimeUnit unit) {
        return this.tryLockAsync(waitTime, -1L, unit);
    }

    public RFuture tryLockAsync(long waitTime, long leaseTime, TimeUnit unit) {
        long currentThreadId = Thread.currentThread().getId();
        return this.tryLockAsync(waitTime, leaseTime, unit, currentThreadId);
    }

    public RFuture tryLockAsync(long waitTime, long leaseTime, TimeUnit unit, long currentThreadId) {
        RPromise result = new RedissonPromise();
        AtomicLong time = new AtomicLong(unit.toMillis(waitTime));
        long currentTime = System.currentTimeMillis();
        RFuture ttlFuture = this.tryAcquireAsync(leaseTime, unit, currentThreadId);
        ttlFuture.onComplete((ttl, e) -> {
            if (e != null) {
                result.tryFailure(e);
            } else if (ttl == null) {
                if (!result.trySuccess(true)) {
                    this.unlockAsync(currentThreadId);
                }

            } else {
                long el = System.currentTimeMillis() - currentTime;
                time.addAndGet(-el);
                if (time.get() <= 0L) {
                    this.trySuccessFalse(currentThreadId, result);
                } else {
                    long current = System.currentTimeMillis();
                    AtomicReference futureRef = new AtomicReference();
                    final RFuture subscribeFuture = this.subscribe(currentThreadId);
                    subscribeFuture.onComplete((r, ex) -> {
                        if (ex != null) {
                            result.tryFailure(ex);
                        } else {
                            if (futureRef.get() != null) {
                                ((Timeout)futureRef.get()).cancel();
                            }

                            long elapsed = System.currentTimeMillis() - current;
                            time.addAndGet(-elapsed);
                            this.tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId);
                        }
                    });
                    if (!subscribeFuture.isDone()) {
                        Timeout scheduledFuture = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
                            public void run(Timeout timeout) throws Exception {
                                if (!subscribeFuture.isDone()) {
                                    subscribeFuture.cancel(false);
                                    RedissonLock.this.trySuccessFalse(currentThreadId, result);
                                }

                            }
                        }, time.get(), TimeUnit.MILLISECONDS);
                        futureRef.set(scheduledFuture);
                    }

                }
            }
        });
        return result;
    }

    private void trySuccessFalse(long currentThreadId, RPromise result) {
        this.acquireFailedAsync(currentThreadId).onComplete((res, e) -> {
            if (e == null) {
                result.trySuccess(false);
            } else {
                result.tryFailure(e);
            }

        });
    }

    private void tryLockAsync(AtomicLong time, long leaseTime, TimeUnit unit, RFuture subscribeFuture, RPromise result, long currentThreadId) {
        if (result.isDone()) {
            this.unsubscribe(subscribeFuture, currentThreadId);
        } else if (time.get() <= 0L) {
            this.unsubscribe(subscribeFuture, currentThreadId);
            this.trySuccessFalse(currentThreadId, result);
        } else {
            long curr = System.currentTimeMillis();
            RFuture ttlFuture = this.tryAcquireAsync(leaseTime, unit, currentThreadId);
            ttlFuture.onComplete((ttl, e) -> {
                if (e != null) {
                    this.unsubscribe(subscribeFuture, currentThreadId);
                    result.tryFailure(e);
                } else if (ttl == null) {
                    this.unsubscribe(subscribeFuture, currentThreadId);
                    if (!result.trySuccess(true)) {
                        this.unlockAsync(currentThreadId);
                    }

                } else {
                    long el = System.currentTimeMillis() - curr;
                    time.addAndGet(-el);
                    if (time.get() <= 0L) {
                        this.unsubscribe(subscribeFuture, currentThreadId);
                        this.trySuccessFalse(currentThreadId, result);
                    } else {
                        final long current = System.currentTimeMillis();
                        final RedissonLockEntry entry = this.getEntry(currentThreadId);
                        if (entry.getLatch().tryAcquire()) {
                            this.tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId);
                        } else {
                            AtomicBoolean executed = new AtomicBoolean();
                            AtomicReference futureRef = new AtomicReference();
                            final Runnable listener = () -> {
                                executed.set(true);
                                if (futureRef.get() != null) {
                                    ((Timeout)futureRef.get()).cancel();
                                }

                                long elapsed = System.currentTimeMillis() - current;
                                time.addAndGet(-elapsed);
                                this.tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId);
                            };
                            entry.addListener(listener);
                            long t = time.get();
                            if (ttl >= 0L && ttl < time.get()) {
                                t = ttl;
                            }

                            if (!executed.get()) {
                                Timeout scheduledFuture = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
                                    public void run(Timeout timeout) throws Exception {
                                        if (entry.removeListener(listener)) {
                                            long elapsed = System.currentTimeMillis() - current;
                                            time.addAndGet(-elapsed);
                                            RedissonLock.this.tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId);
                                        }

                                    }
                                }, t, TimeUnit.MILLISECONDS);
                                futureRef.set(scheduledFuture);
                            }
                        }

                    }
                }
            });
        }
    }

    static {
        HGET = new RedisCommand("HGET", ValueType.MAP_VALUE, new IntegerReplayConvertor(0));
    }

    public static class ExpirationEntry {
        private final Map threadIds = new LinkedHashMap();
        private volatile Timeout timeout;

        public ExpirationEntry() {
        }

        public void addThreadId(long threadId) {
            Integer counter = (Integer)this.threadIds.get(threadId);
            if (counter == null) {
                counter = 1;
            } else {
                counter = counter + 1;
            }

            this.threadIds.put(threadId, counter);
        }

        public boolean hasNoThreads() {
            return this.threadIds.isEmpty();
        }

        public Long getFirstThreadId() {
            return this.threadIds.isEmpty() ? null : (Long)this.threadIds.keySet().iterator().next();
        }

        public void removeThreadId(long threadId) {
            Integer counter = (Integer)this.threadIds.get(threadId);
            if (counter != null) {
                counter = counter - 1;
                if (counter == 0) {
                    this.threadIds.remove(threadId);
                } else {
                    this.threadIds.put(threadId, counter);
                }

            }
        }

        public void setTimeout(Timeout timeout) {
            this.timeout = timeout;
        }

        public Timeout getTimeout() {
            return this.timeout;
        }
    }
}

Redisson加锁& 解锁实现过程

常用的两种加锁方法

  1. lock(long leaseTime, TimeUnit unit), 可设置过期时间, 当业务处理超过过期时间或系统出了故障未执行解锁命令时, 系统自动释放锁
  2. lock(), 无参方法默认过期时间为 -1, 此方法加锁同时会启动一个看门狗(Watch dog)在后台线程监控当前锁, 不断的延长锁的生命周期, 避免业务处理超出预期时间时, 保证在业务执行完后释放锁

给每次请求赋予唯一编号


    protected String getLockName(long threadId) {
	# id是 UUID.randomUUID()
	# threadId是线程编号
	# 类似返回 d2518228-aad9-4fd8-b814-f450541cab3c:1
        return this.id + ":" + threadId;
    }

  • 请求多个命令的代码块时, 为了保证原子性使用 Lua脚本(Redis对 Lua脚本的执行具有原子性)

加锁 tryLockInnerAsync方法

  • KEYS[1]是 LockKey. 获取锁时指定的键, 如 redissonClient.getLock(“LockKey”);
  • ARGV[1]是锁的生存时间, 如 30000L(单位为毫秒)
  • ARGV[2]是请求者的唯一编号, 通过方法 String getLockName(long threadId)来获取, 类似 d2518228-aad9-4fd8-b814-f450541cab3c:1

# 判断锁存在与否 LockKey, 
if (redis.call('exists', KEYS[1]) == 0) then
## 如果不存在, 使用 hset命令加锁: hset LockKey d2518228-aad9-4fd8-b814-f450541cab3c:1 1
redis.call('hset', KEYS[1], ARGV[2], 1);
## 同时设置过期时间
redis.call('pexpire', KEYS[1], ARGV[1]);
## 返回空值, 结束此脚本
return nil; 
end;

# 判断当前线程是否已持有锁
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then 
## 如果已持有锁, 递增重入锁次数
redis.call('hincrby', KEYS[1], ARGV[2], 1); 
## 重置过期时间
redis.call('pexpire', KEYS[1], ARGV[1]); 
## 返回空值, 结束此脚本
return nil; 
end; 

# 加锁失败, 返回当前锁的剩余时间给其它线程的请求者
return redis.call('pttl', KEYS[1]);

未抢到锁

  • 获取锁消息键: 请求未获取到锁时线程进入等待, 通过此消息键订阅释放锁消息, 当锁拥有者释放锁时通过此键发送释放锁消息(一个锁只有一个消息键)

    String getChannelName() {
	# 类似返回 redisson_lock__channel:{LockKey}
        return prefixName("redisson_lock__channel", this.getName());
    }

  • 通过 lock方法加锁, 当未抢到锁进入等待释放锁消息
    
    # 获取锁, 如返回 null意思是成功抢到锁, 否者返回当前锁的剩余时间
    Long ttl = this.tryAcquire(leaseTime, unit, threadId);
    if (ttl != null) {
	# 当前请求者线程, 订阅释放锁消息
        RFuture future = this.subscribe(threadId);
	# 阻塞获取订阅结果
        this.commandExecutor.syncSubscription(future);
    
        try {
            while(true) {
     		# 循环获取锁, 如返回 null意味着已抢到锁
                ttl = this.tryAcquire(leaseTime, unit, threadId);
                if (ttl == null) {
                    return;
                }
		...
		...
            } finally {
		# 取消订阅
                this.unsubscribe(future, threadId);
            }
	...
	...

解锁 unlockInnerAsync方法

  • KEYS[1]是 LockKey. 获取锁时指定的键, 如 redissonClient.getLock(“LockKey”);
  • KEYS[2]是 通过方法 String getChannelName()来获取, 类似 redisson_lock__channel:{LockKey}
  • ARGV[1]是解锁消息, LockPubSub.UNLOCK_MESSAGE, 默认值为 0
  • ARGV[2]是锁的生存时间, 如 30000L(单位为毫秒)
  • ARGV[3]是请求者的唯一编号, 通过方法 String getLockName(long threadId)来获取, 类似 d2518228-aad9-4fd8-b814-f450541cab3c:1

# 判断当前线程是否没有锁
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
## 如果没有锁, 返回空值, 结束此脚本
return nil;
end;

# 将当前线程的锁数递减, 不能直接释放, 因为可能有重入锁
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);

# 判断递减后是否还存在锁
if (counter > 0) then
# 当还存在锁, 重置过期时间
redis.call('pexpire', KEYS[1], ARGV[2]);
# 返回0, 结束此脚本
return 0;

else # 否者
# 如果递减后没有额外的锁, 删除锁键, 释放锁!
redis.call('del', KEYS[1]);
# 发送释放锁消息
redis.call('publish', KEYS[2], ARGV[1]);
# 返回1,结束此脚本
return 1;
end;

## 返回空值, 结束此脚本
return nil;

Redis分布式锁的非正常失效情况

  1. 系统时钟漂移, 改动系统时间会影响 Redis的过期时间就是锁的有效时间
  2. Redis在多节点模式时, 当其中某主节点加锁后宕机, 未来得及将锁同步到从节点

以上第二种情况, 可以用 Zookeeper实现分布式锁来防止锁失效情况, 因为 Zookeeper也支持主从结构, 它的主叫 leader(领导者), 从叫做 follower(跟随者), 与 Redis不同的是, 当加锁时, 将锁加到 leader后不会马上告知客户端成功信息, 而是先同步到 *所有的 follower的(半数以上)节点之后, 告知客户端.

  • Zookeeper集群架构是更多保证了 CAP中的(CP, 一致性和分区容错性), 而 Redis集群架构是更多保证了 CAP中的(AP, 可用性和分区容错性), 所以 Zookeeper牺牲了性能更多保证了一致

  • 当然 Redis也可以通过类似的机制保证一致性, 如使用 Redisson的 getRedLock方法, 将多个锁合并成一个大锁, 并且统一的做加解锁. 不过这样做了已经牺牲了性能所以不如使用 Zookeeper

锁& 数据类型使用例子

  • 锁例子: https://blog.csdn.net/qcl108/article/details/101039657
  • RAtomicLong(分布式长整型原子类)

        RAtomicLong atomicLong = redissonClient.getAtomicLong("MyAtomicLong");
        System.out.println("value 1: " + atomicLong.get()); // value 1: 0
        atomicLong.incrementAndGet();
        System.out.println("value 2: " + atomicLong.get()); // value 2: 1
        atomicLong.addAndGet(10L);
        System.out.println("value 3: " + atomicLong.get()); // value 3: 11
        System.out.println("value 4: " + atomicLong.getAndAdd(20L)); // value 4: 11
        System.out.println("value 5: " + atomicLong.getAndIncrement()); // value 5: 31
        System.out.println("value 6: " + atomicLong.getAndSet(30L)); // value 6: 32
        System.out.println("value 7: " + atomicLong.getAndDelete()); // value 7: 30
        System.out.println("value 8: " + atomicLong.get()); // value 8: 0

  • RBitSet
  1. 4294967295L为最大数, 如超出此数值将会报错
  2. 它占用的内存大小, 不在于存了多少, 而在于最大的数值是多少

        RBitSet bitset = redissonClient.getBitSet("log_20200719");
        bitset.set(0L,true);
        bitset.set(1L,true);
        bitset.set(4L,true);
        bitset.set(999L,true);
        System.out.println("value 1: " + bitset.cardinality()); // value 1: 4
        System.out.println("value 2: " + bitset.get(4L)); // value 2: true
        System.out.println("value 3: " + bitset.get(5L)); // value 3: false
        bitset.set(4L,false);
        System.out.println("value 4: " + bitset.get(4L)); // value 4: false
        System.out.println("value 5: " + bitset.cardinality()); // value 5: 3
        bitset.set(8L,false);
        System.out.println("value 6: " + bitset.cardinality()); // value 6: 3
        //bitset.set(4294967295L,true); // 大约占512M左右内存空间, 4294967295L为最大数, 如超出此数值将会报错
        bitset.set(1000000000L,true); // 大约占120M左右内存空间
        System.out.println("value 7: " + bitset.cardinality()); // value 7: 4
        bitset.clear();
        System.out.println("value 8: " + bitset.cardinality()); // value 8: 0

  • RHyperLogLog(基数统计)
  1. 每个键只需要花费12KB内存, 就可以计算接近2的64次方(2^64)个不同元素的基数, 且所需空间是固定的也很小的, 这和元素越多耗费内存就越多的集合形成鲜明对比. 但它只会按输入的元素来计算基数, 而不会储存输入元素本身, 所以不能像集合那样, 返回输入的各个元素
  2. 去重计数有误差, 误差是0.81%

        RHyperLogLog log = redissonClient.getHyperLogLog("log_20200719");
        log.add(0L);
        log.add(2L);
        log.add(3L);
        log.add(3L);
        System.out.println("count 1: " + log.count()); // count 1: 3
        log.add(4294967296L);
        log.add(999999999999999999L);
        System.out.println("count 2: " + log.count()); // count 2: 5
        log.delete();
        System.out.println("count 3: " + log.count()); // count 3: 0

  • RBloomFilter(布隆过滤器)
  1. 优点为它是二进制组成的数组, 内存占用少, 且插入和查询速度都非常快
  2. 缺点为, 随着数据量的增加, 误判率会增加, 且无法判断数据一定存在, 还有就是无法删除已插入的数据

        RBloomFilter bloomFilter = redissonClient.getBloomFilter("bloomFilter");
        bloomFilter.tryInit(294967294L,0.03);

        for (long i = 0; i < 1000L; i++) {
            bloomFilter.add("全大爷 " + i);
        }
        System.out.println("全大爷 1: " + bloomFilter.contains("全大爷 " + 1)); // 全大爷 1: true
        System.out.println("全大爷 1001: " + bloomFilter.contains("全大爷 " + 1001)); // 全大爷 1001: false
        System.out.println("预计可插入数量: " + bloomFilter.getExpectedInsertions()); // 预计可插入数量: 294967294
        System.out.println("容错率: " + bloomFilter.getFalseProbability()); // 容错率: 0.03
        System.out.println("hash函数的个数: " + bloomFilter.getHashIterations()); // hash函数的个数: 5
        System.out.println("已插入的对象的个数: " + bloomFilter.count()); // 已插入的对象的个数: 1000

如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!

你可能感兴趣的:(Redis,Java,Redisson)