Redisson 源码解析 - 分布式锁实现过程

一、Redisson 分布式锁源码解析

Redisson是架设在Redis基础上的一个Java驻内存数据网格。在基于NIONetty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。

其中比较具体特色的就是 Redisson 对分布式锁的支持,不仅简化了分布式锁的应用过程还支持 Fair Lock、MultiLock、RedLock、ReadWriteLock 等锁的实现。本文就 Redisson 分布式锁的加锁和解锁过程的源码进行大致的解析。

下面是Redisson 源码地址:

https://github.com/redisson/redisson

如果对 Redisson 的使用还不了解的小伙伴可以先看下下面这篇文章:

https://xiaobichao.blog.csdn.net/article/details/112726748

Redisson 中的分布式锁在使用起来非常简便,例如:

public class TestLock {

    @Resource
    RedissonClient redissonClient;

    @Test
    public void test() {
        RLock lock = null;
        try {
            // 获取可重入锁
            lock = redissonClient.getLock("redislock");
            // 获取锁,如果获取不到会等待
            lock.lock();
            Thread.sleep(30000000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (lock != null) {
                // 释放锁
                lock.unlock();
            }
        }
    }

    @Test
    public void test1() {
        RLock lock = null;
        try {
            // 获取可重入锁
            lock = redissonClient.getLock("redislock");
            // 尝试获取锁,返回获取锁的状态
            Boolean isLock = lock.tryLock();
            Thread.sleep(30000000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (lock != null) {
                // 释放锁
                lock.unlock();
            }
        }
    }
}

下面分别从 locktryLockunlock 、三个地方进行源码的解析。

二、lock 获取锁和看门狗机制

先看下 redissonClient.getLock 方法,它默认创建了一个 RedissonLock 对象,并将锁的key传递进来:

在这里插入图片描述

而 RedissonLock 对象又继承至RedissonBaseLock 类:

在这里插入图片描述

因此我们下面大多的源码分析都基于这两个类进行。

首先进到 RedissonLock 类下的 lock() 方法中:

在这里插入图片描述

这里主要又调用了 lock(long leaseTime, TimeUnit unit, boolean interruptibly) 方法,注意如果没有指定过期时间默认为 -1 ,下面看到 lock(long leaseTime, TimeUnit unit, boolean interruptibly) 方法中:

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
    // 当前线程ID
    long threadId = Thread.currentThread().getId();
    // 尝试获取锁,如果已经有锁的话返回锁的剩余时间
    Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
    // 获取锁成功
    if (ttl == null) {
        return;
    }
    // 如果获取锁失败,订阅当前线程,以便后续获取锁时得到通知。
    CompletableFuture<RedissonLockEntry> future = subscribe(threadId);
    //设置超时处理,当订阅的future完成时,触发超时处理。
    pubSub.timeout(future);
    //定义一个RedissonLockEntry对象,用于表示当前线程在分布式锁中的状态。
    RedissonLockEntry entry;
    if (interruptibly) {
        // 可中断
        entry = commandExecutor.getInterrupted(future);
    } else {
        entry = commandExecutor.get(future);
    }

    try {
        // 循环尝试获取锁
        while (true) {
            // 尝试获取锁
            ttl = tryAcquire(-1, leaseTime, unit, threadId);
            // 获取锁成功
            if (ttl == null) {
                break;
            }

            // 如果已经存在锁的过期时间大于等于0,需要等待通知
            if (ttl >= 0) {
                try {
                    // 通过Semaphore 的 tryAcquire方法等待指定时间
                    entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    if (interruptibly) {
                        throw e;
                    }
                    entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                }
            } else { //如果剩余时间小于0,就一直等待。
                if (interruptibly) {
                    entry.getLatch().acquire();
                } else {
                    entry.getLatch().acquireUninterruptibly();
                }
            }
        }
    } finally {
        // 无论加锁成功或失败,都取消订阅
        unsubscribe(entry, threadId);
    }
}

代码中加了注释,这里我总结下,首先调用 tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) 方法尝试获取锁,如果锁存在的话则返回过期时间,为 null 的话表示获取锁成功。如果获取锁失败,则将自己加入到订阅中,然后开启一个死循环,在循环中再次尝试获取锁,如果还是没有获取到的话则使用 Semaphore 的 tryAcquire 方法阻塞当前线程,如果其他线程释放了锁,则这里继续循环再次尝试获取锁。

下面主要看下 tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) 尝试获取锁的逻辑,看到该方法下:

在这里插入图片描述

tryAcquire 方法又调用了 tryAcquireAsync0 方法,然后又主要调用了 tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) 方法,下面主要看到这个方法下:

private RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    RFuture<Long> ttlRemainingFuture;
    if (leaseTime > 0) {
        //如果指定了锁持有时间,则根据指定的时间设置 key 的过期时间
        ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    } else {
        // 没指定,默认锁持有 30s
        ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
                TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    }
    // 执行 lua 操作
    CompletionStage<Long> s = handleNoSync(threadId, ttlRemainingFuture);
    ttlRemainingFuture = new CompletableFutureWrapper<>(s);

    CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {
        // 如果加锁成功
        if (ttlRemaining == null) {
            if (leaseTime > 0) {
                internalLockLeaseTime = unit.toMillis(leaseTime);
            } else {// 没指定的话
                // 启动看门狗,延长锁持有时间
                scheduleExpirationRenewal(threadId);
            }
        }
        // 返回锁的过期时间
        return ttlRemaining;
    });
    return new CompletableFutureWrapper<>(f);
}

这里其中 tryLockInnerAsync 方法主要是指定了 Lua 脚本,主要注意的是如果没有指定了锁的过期则默认为 30s 的时间,然后在 Lua 脚本执行后,同样的判断,如果获取到锁的话并且没有指定锁的过期时间则开启看门狗机制,为锁延长时间续命的操作。

这里先看下核心操作 tryLockInnerAsync 方法中 Lua 脚本:

<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    // lua 脚本
    return evalWriteSyncedAsync(getRawName(), LongCodec.INSTANCE, command,
            // 如果锁不存在,或者哈希表中锁对应的线程ID存在的话
            "if ((redis.call('exists', KEYS[1]) == 0) " +
                        "or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then " +
                    // 对hash中的内容值 +1
                    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                    // 设置过期时间
                    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                    //表示脚本执行成功,且不需要返回特定的值。
                    "return nil; " +
                "end; " +
                    // 如果if条件不满足,返回剩余过期时间(以毫秒为单位)
                "return redis.call('pttl', KEYS[1]);",
            // 对应这 lua 脚本中的参数,第一个参数就是 KEYS[1],以此类推
            Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
}

这里主要利于 Lua 的原子性将整个判断操作过程给原子化了,其中这里锁的结构是以 hash 的形式存放的,key为锁的名称,hash中的key为线程IDUUID+线程ID的形式),因为分布式情况下线程ID也有可能重复,value为数字表示锁重入的次数, lua 脚本如果执行加锁逻辑成功则返回 null,否则返回锁的过期时间,也就对应前面获取锁的时候判断的依据。

下面回到上面的 tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) 方法中,在 ttlRemainingFuture.thenApply 中如果获取锁成功,并且没有指定锁的过期时间则会开启看门狗机制为锁进行续命操作,主要调用的是 scheduleExpirationRenewal(long threadId) 方法,下面看到该方法下的逻辑:

protected void scheduleExpirationRenewal(long threadId) {
    ExpirationEntry entry = new ExpirationEntry();
    // 加入看门狗记录中
    ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
    // 如果已经存在
    if (oldEntry != null) {
        // 重新指定线程ID
        oldEntry.addThreadId(threadId);
    } else { // 如果不存在的话就开启看门狗
        entry.addThreadId(threadId);
        try {
            // 启动看门狗
            renewExpiration();
        } finally {
            // 如果线程已经终止,则关闭看门狗
            if (Thread.currentThread().isInterrupted()) {
                cancelExpirationRenewal(threadId);
            }
        }
    }
}

主要的逻辑在 renewExpiration() 方法下,继续看到该方法中:

private void renewExpiration() {
    // 获取当前信息
    ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
    if (ee == null) {
        return;
    }
    // 执行计时任务
    Timeout task = getServiceManager().newTimeout(new TimerTask() {
        @Override
        public void run(Timeout timeout) throws Exception {
            //再次获取信息
            ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
            if (ent == null) {
                return;
            }
            // 获取线程ID
            Long threadId = ent.getFirstThreadId();
            if (threadId == null) {
                return;
            }
            // 延长锁的过期时间
            CompletionStage<Boolean> future = renewExpirationAsync(threadId);
            future.whenComplete((res, e) -> {
                if (e != null) { //如果有异常删除该任务
                    log.error("Can't update lock {} expiration", getRawName(), e);
                    EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                    return;
                }
                
                if (res) { // 如果执行成功
                    // 递归继续执行
                    renewExpiration();
                } else { // 执行失败
                    // 关闭看门狗
                    cancelExpirationRenewal(null);
                }
            });
        }
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
    
    ee.setTimeout(task);
}

这里主要通过递归延时任务的方式实现循环执行的效果,其中延时的时间为 internalLockLeaseTime 的三分之一,也就是默认 10s 触发一次,在任务中主要通过 renewExpirationAsync(long threadId) 方法,对锁进行了延时续命操作,看到该方法中:

protected CompletionStage<Boolean> renewExpirationAsync(long threadId) {
    // lua 脚本
    return evalWriteSyncedAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            // 如果锁和线程ID存在
            "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                    // 重置过期时间
                    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                    // 成功返回 1
                    "return 1; " +
                    "end; " +
                    // 失败返回 0
                    "return 0;",
            // lua 脚本中对应的参数
            Collections.singletonList(getRawName()),
            internalLockLeaseTime, getLockName(threadId));
}

这里还是依靠 Lua 脚本的方式,如果锁存在的话就重置过期时间,达到续命的效果。

三、tryLock 获取锁

tryLocklock是两种获取分布式锁的方法,它们的主要区别在于获取锁的方式和阻塞行为。tryLock默认是一种非阻塞的获取锁的方法,也可以通过设置 waitTime 变成阻塞的。而lock默认就是一种阻塞的获取锁的方法。

他们俩的最终处理逻辑都是一样的,只不过默认的 tryLock 没有订阅阻塞的操作。

下面看下默认的 tryLock 的操作 ,进到 RedissonLock 下的 tryLock() 中:

在这里插入图片描述

再进入 tryLockAsync() 方法中:

在这里插入图片描述

这里调用了 tryLockAsync 方法,并将当前线程的ID传递了进来,继续看到 tryLockAsync 方法中:

在这里插入图片描述

在看到 tryAcquireOnceAsync 方法中,注意这里的等待时间和上面 lock() 默认一样,是 -1 :

private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    CompletionStage<Boolean> acquiredFuture;
    if (leaseTime > 0) {
        //如果指定了锁持有时间,则根据指定的时间设置 key 的过期时间
        acquiredFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
    } else {
        // 没指定,默认锁持有 30s
        acquiredFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
                TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
    }
    // 执行 lua 操作
    acquiredFuture = handleNoSync(threadId, acquiredFuture);

    CompletionStage<Boolean> f = acquiredFuture.thenApply(acquired -> {
        // 如果加锁成功
        if (acquired) {
            // 如果指定了锁持有时间
            if (leaseTime > 0) {
                internalLockLeaseTime = unit.toMillis(leaseTime);
            } else { // 没指定的话,
                // 看门狗,延长锁持有时间
                scheduleExpirationRenewal(threadId);
            }
        }
        // 返回获取锁的状态
        return acquired;
    });
    return new CompletableFutureWrapper<>(f);
}

这里的逻辑相比于前面 lock() 的逻辑就差不多了,只不过缺少了订阅和阻塞等待重试的操作,再下面的操作和lock() 的逻辑是一致的。

四、unlock 解锁和关闭看门狗

解锁的逻辑看到 RedissonBaseLock 下的 unlock() 方法中:

在这里插入图片描述

继续看到 unlockAsync 方法中:

在这里插入图片描述

主要逻辑在 unlockAsync0 方法中:

private RFuture<Void> unlockAsync0(long threadId) {
    // 解锁
    CompletionStage<Boolean> future = unlockInnerAsync(threadId);
    CompletionStage<Void> f = future.handle((opStatus, e) -> {
        // 关闭看门狗
        cancelExpirationRenewal(threadId);

        if (e != null) { // 如果执行有异常
            if (e instanceof CompletionException) {
                throw (CompletionException) e;
            }
            throw new CompletionException(e);
        }
        if (opStatus == null) { // 如果结果为空的话,表示锁不存在
            IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
                    + id + " thread-id: " + threadId);
            throw new CompletionException(cause);
        }

        return null;
    });

    return new CompletableFutureWrapper<>(f);
}

主要做了两件事,解锁和关闭看门狗,先看下 unlockInnerAsync(long threadId) 方法解锁的过程:

protected final RFuture<Boolean> unlockInnerAsync(long threadId) {
    String id = getServiceManager().generateId();
    MasterSlaveServersConfig config = getServiceManager().getConfig();
    int timeout = (config.getTimeout() + config.getRetryInterval()) * config.getRetryAttempts();
    timeout = Math.max(timeout, 1);
    // 解锁
    RFuture<Boolean> r = unlockInnerAsync(threadId, id, timeout);
    CompletionStage<Boolean> ff = r.thenApply(v -> {
        CommandAsyncExecutor ce = commandExecutor;
        if (ce instanceof CommandBatchService) {
            ce = new CommandBatchService(commandExecutor);
        }
        ce.writeAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.DEL, getUnlockLatchName(id));
        if (ce instanceof CommandBatchService) {
            ((CommandBatchService) ce).executeAsync();
        }
        // 释放锁的结果
        return v;
    });
    return new CompletableFutureWrapper<>(ff);
}

这里的重点主要关注 unlockInnerAsync 方法,通过使用 Lua 脚本进行解锁的操作:

protected RFuture<Boolean> unlockInnerAsync(long threadId, String requestId, int timeout) {
    // lua 脚本
    return evalWriteSyncedAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                          // 从Redis中获取锁的状态。
                          "local val = redis.call('get', KEYS[3]); " +
                                  //如果不是false
                                "if val ~= false then " +
                                  //将其转换为数字并返回,也就是 true 返回 1
                                    "return tonumber(val);" +
                                "end; " +
                                // 如果哈希表锁中不存在线程ID,表示锁已经被释放,返回nil。
                                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                                    "return nil;" +
                                "end; " +
                                  //对锁中的线程ID的值减1,并将结果存储在 counter 变量中。这是一个计数器的操作。
                                "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                                  //如果计数器值大于0,表示锁仍然被持有。
                                "if (counter > 0) then " +
                                    // 更新哈希表锁的过期时间。
                                    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                                  // 设置键锁的状态值为0,并设置过期时间,表示锁仍然被持有。
                                    "redis.call('set', KEYS[3], 0, 'px', ARGV[5]); " +
                                  //返回0,表示锁仍然被持有
                                    "return 0; " +
                                "else " + //如果计数器值不大于0,表示锁即将被释放。
                                    //删除锁
                                    "redis.call('del', KEYS[1]); " +
                                    "redis.call(ARGV[4], KEYS[2], ARGV[1]); " +
                                    // 设置键锁的状态值为1,并设置过期时间,表示锁已经被释放。
                                    "redis.call('set', KEYS[3], 1, 'px', ARGV[5]); " +
                                    //返回1,表示锁已经被释放
                                    "return 1; " +
                                "end; ",
                            Arrays.asList(getRawName(), getChannelName(), getUnlockLatchName(requestId)),
                            LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime,
                            getLockName(threadId), getSubscribeService().getPublishCommand(), timeout);
}

需要注意的是,在 Lua 脚本中,如果锁还存在的话,就对 hash 中的 value 减一,如果此时 value 结果还大于 0 的话,则表示这是重入锁的场景,此时不能直接删除锁,而是对重入的次数进行减一,并且要重置过期时间。

下面再回到 unlockAsync0(long threadId) 方法中,释放锁通过 Lua 脚本实现了,下面看下 cancelExpirationRenewal(Long threadId) 关闭看门狗的操作:

protected void cancelExpirationRenewal(Long threadId) {
    // 从记录中获取信息
    ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());
    if (task == null) {
        return;
    }
    
    if (threadId != null) {
        // 移除线程ID
        task.removeThreadId(threadId);
    }

    if (threadId == null || task.hasNoThreads()) {
        // 关闭计时任务
        Timeout timeout = task.getTimeout();
        if (timeout != null) {
            timeout.cancel();
        }
        // 从缓存记录中删除
        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
    }
}

这里就比较好理解了,停止计时任务,从缓存记录中移除。

分布式架构-Redisson 框架介绍使用

一、Redisson

Redisson是架设在Redis基础上的一个Java驻内存数据网格。在基于NIONetty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。

Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。如果您现在正在使用其他的RedisJava客户端,希望Redis命令和Redisson对象匹配列表能够帮助您轻松的将现有代码迁徙到Redisson框架里来。如果Redis的应用场景还仅限于作为缓存使用,您也可以将Redisson轻松的整合到像SpringHibernate这样的常用框架里。除此外您也可以间接的通过Java缓存标准规范JCache API (JSR-107)接口来使用Redisson

Redisson生而具有的高性能,分布式特性和丰富的结构等特点恰巧与Tomcat这类服务程序对会话管理器(Session Manager)的要求相吻合。利用这样的特点,Redisson专门为Tomcat提供了会话管理器(Tomcat Session Manager)。

二、Redisson 和Jedis性能对比

Redisson是吞吐量和延迟敏感系统的完美伴侣。比Jedis更有效的方式利用可用的系统资源。
下面的链接可以清晰的展示在并发量增加的时候,Redisson 和Jedis的性能变化。

https://dzone.com/articles/redisson-pro-vs-jedis-which-is-faster

三、基本使用

1. 引入

pom依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.11.1</version>
</dependency>

RedissonClient 注入 Spring

@Configuration
public class RedissonConfig {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private String port;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.database}")
    private int database;

    @Bean
    public RedissonClient getRedisson() {
        Config config = new Config();
        //线程定时间隔时间
//        config.setLockWatchdogTimeout(100000000);
        //设置单机版本redis
        config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password).setDatabase(database);
        //设置集群的方式
//        config.useClusterServers().addNodeAddress("redis://" + host + ":" + port);
        //添加主从配置
//        config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
        return Redisson.create(config);
    }
}

或者在现有基础上,增加 RedissonClient

spring:
  redis:
    timeout: 6000
    password:
    cluster:
      max-redirects:
      nodes:
        - 192.168.0.1:7001
        - 192.168.0.2:7002
        - 192.168.0.3:7003
@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient getRedisson(RedisProperties redisProperties) {
        Config config = new Config();
        String[] nodes = redisProperties.getCluster().getNodes().stream().filter(StringUtils::isNotBlank).map(node -> "redis://" + node).collect(Collectors.toList()).toArray(new String[]{});
        ClusterServersConfig clusterServersConfig = config.useClusterServers().addNodeAddress(nodes);
        if (StringUtils.isNotBlank(redisProperties.getPassword())) {
            clusterServersConfig.setPassword(redisProperties.getPassword());
        }
        clusterServersConfig.setConnectTimeout((int) (redisProperties.getTimeout().getSeconds() * 1000));
        clusterServersConfig.setScanInterval(2000);
        return Redisson.create(config);
    }
}

2. Key-Value形式

//添加数据  string
RBucket<String> bucket = redissonClient.getBucket("abc");
bucket.set("asd");

//异步添加数据
bucket.setAsync("asd");
bucket.setAsync("asdf");

//读取数据
RBucket<String> bucket1 = redissonClient.getBucket("abc");
String o = bucket1.get();
System.out.println(o);

//异步读取数据
RFuture<String> async = bucket1.getAsync();
System.out.println(async.get());

3. hash

RMap<String, Object> rMap = redissonClient.getMap("testmap");
//清空
rMap.clear();

//添加,返回之前的值
Object o = rMap.put("abc", "asd");
Object o1 = rMap.putIfAbsent("abc", "ghh");

//移除key
rMap.remove("abc");

//添加并返回之前关联过的值
Object o2 = rMap.putIfAbsent("aa", "tem");

//添加数据,如果已经有key 返回false
boolean third = rMap.fastPut("bb", "tem");
boolean third1 = rMap.fastPut("bb", "tem");
System.out.println(third);
System.out.println(third1);

//异步添加数据,如果已经有key 返回false
RFuture<Boolean> booleanRFuture = rMap.fastPutAsync("cc", "tem");
System.out.println(booleanRFuture.get());
RFuture<Boolean> booleanRFuture1 = rMap.fastPutAsync("cc", "tem");
System.out.println(booleanRFuture1.get());

// 异步移除key
rMap.fastRemoveAsync("cc");

//遍历集合
for(String key :rMap.keySet()){
    System.out.println(key+":"+rMap.get(key));
}

4. Set / Zset

//获取不排序的set集合
RSet<Object> set = redissonClient.getSet("");
//获取排序的set集合
RSortedSet<String> rSortedSet = redissonClient.getSortedSet("listtest");

//清空集合
rSortedSet.clear();

//追加数据
boolean abc = rSortedSet.add("abc");
System.out.println(abc);

//异步追加数据
RFuture<Boolean> bb = rSortedSet.addAsync("bb");
System.out.println(bb.get());

//删除数据
boolean abc1 = rSortedSet.remove("abc");
System.out.println(abc1);

System.out.println(Arrays.toString(rSortedSet.toArray()));

5. list

RList<String> listtest = redissonClient.getList("listtest");

listtest.clear();

listtest.add("abc");
listtest.add("asd");

boolean asd = listtest.remove("asd");
System.out.println(asd);

listtest.fastRemove(0);

System.out.println(listtest.toString());
System.out.println(listtest.size());

6. 队列模式 先进先出List

RQueue<String> rQueue = redissonClient.getQueue("testqueue");

rQueue.clear();

rQueue.add("abc1");
rQueue.add("abc2");
rQueue.add("abc3");
rQueue.add("abc4");

//获取队列第一个元素
String a = rQueue.peek();
System.out.println("--"+a);
String b = rQueue.element();
System.out.println("--"+b);

//获取队列第一个元素 并移除队列
String c = rQueue.poll();
System.out.println(c);
String d = rQueue.remove();
System.out.println(d);

System.out.println(rQueue.toString());

7. 双端队列,对头和队尾都可添加或者移除,也是先进先出List

RDeque<String> deque = redissonClient.getDeque("deque");
deque.addFirst("aa");
deque.addLast("bb");

8. 原子类

RAtomicLong abc = redissonClient.getAtomicLong("abclong");
long andAdd = abc.getAndAdd(1L);
System.out.println(andAdd);
RAtomicDouble atomicDouble = redisson.getAtomicDouble("double");
atomicDouble.set(1.0);
atomicDouble.addAndGet(2.0);
atomicDouble.get();

9. 订阅

RTopic qwe = redissonClient.getTopic("qwe");

qwe.addListener(String.class, new MessageListener<String>() {
   @Override
   public void onMessage(CharSequence charSequence, String s) {
       System.out.println("message-->"+s);
   }
});

qwe.publish("acs1");
qwe.publish("acs2");
qwe.publish("acs3");

10.分布式锁

Redisson 分布式锁过程如下:

  1. 获取锁:

    多个 jvm使用lua脚本在redis中 setnx 写入一个相同的key,谁能够写成功谁就获取锁成功。 如果写入key成功,会单独开启一个看门狗的线程(续命定时任务线程) 默认的情况下每隔10s时间不断续命延迟,key默认是30s 有效期。

  2. 释放锁

    调用lua脚本,修改重入的次数,如果重入次数小于0的情况下,直接将该key删除。

RLock lock = null;
try {
   // 获取可重入锁
   lock = redissonClient.getLock("redislock");
   lock.lock();
   Thread.sleep(30000000);
} catch (Exception e) {

} finally {
   if (lock != null) {
       lock.unlock();
   }
}

设置持有锁过期时间

lock.lock(5, TimeUnit.SECONDS);

尝试获取锁等待时间,持有锁最大时间

boolean b = lock.tryLock(5,6, TimeUnit.SECONDS);

示例:

@GetMapping("/GetTest3")
public String GetTest3() throws InterruptedException {
    System.out.println(">>>>>>>>>>>GetTest3");
    RLock lock =  redissonClient.getLock("redislock");
    try{
        while (true){
            boolean b = lock.tryLock(5,6, TimeUnit.SECONDS);
            if (b){
                break;
            }
            System.out.println(">>>>>>>>>>>GetTest3 >>>>>>尝试");
        }
    }catch (Exception e){
        e.printStackTrace();
    }
    System.out.println(">>>>>>>>>>>GetTest3 >>>>>>开始执行");
    Thread.sleep(5000);
    System.out.println(">>>>>>>>>>>GetTest3 >>>>>>执行结束");
    lock.unlock();
    return "success";
}

11、RedLock

Jvm01连接到主的redis 做setnx操作的时候,异步将数据同步给从redis;意味着jvm01获取锁成功,正好在这时候主redis宕机了,redis集群自动开启哨兵机制选举,就会选举剩余从节点中某个redis为主redis,就会导致两个jvm获取锁成功,违背分布式锁原子特征。

而红锁机制就需要至少三个以上Redis独立节点,这些节点相互之间可以不需要存在主从之分,每个Redis保证独立即可。

  1. 客户端会在每个redis实例创建锁,只需要满足一半的Redis节点能够获取锁成功,就表示加锁成功。
  2. 客户端使用相同的key,在从所有的Redis节点获取锁;
  3. 客户端需要设置超时时间,连接redis设置不成功的情况下立即切换到下一个Redis实例,防止一直阻塞;
  4. 客户端需要计算获取锁的总耗时,客户端至少要有N/2+1节点获取锁成功
    且总耗时时间小于锁的过期时间才能获取锁成功。
  5. 如果客户端最终获取锁失败,必须所有节点释放锁。
RLock lock1 = redissonClient1.getLock("lock1");
RLock lock2 = redissonClient2.getLock("lock2");
RLock lock3 = redissonClient3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
try {
   //获取锁
   boolean res = lock.tryLock(10, 10, TimeUnit.SECONDS);
} catch (Exception e) {

} finally {
   if (lock != null) {
       // 释放锁
       lock.unlock(); 
   }
}

12、布隆过滤器

RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("phoneList");
//初始化布隆过滤器:预计元素为100000000L,误差率为3%
bloomFilter.tryInit(100000000L,0.03);
//将号码10086插入到布隆过滤器中
bloomFilter.add("10086");

//判断下面号码是否在布隆过滤器中
System.out.println(bloomFilter.contains("123456"));//false
System.out.println(bloomFilter.contains("10086"));//true

13、ReadWriteLock

RReadWriteLock rwlock = redisson.getLock("lock");
//尝试获取锁
rwlock.readLock().lock();
rwlock.writeLock().lock();
rwlock.readLock().lock(10, TimeUnit.SECONDS);
rwlock.writeLock().lock(10, TimeUnit.SECONDS);
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
// 释放锁
lock.unlock();

14、Semaphore

RSemaphore semaphore = redisson.getSemaphore("sem");
// 获取信号量
semaphore.acquire();
semaphore.acquire(23);
semaphore.tryAcquire();
semaphore.tryAcquire(23, TimeUnit.SECONDS);
//释放信号量
semaphore.release(10);
semaphore.release();

15、CountDownLatch

RCountDownLatch latch = redisson.getCountDownLatch("cdl");
latch.trySetCount(1);
latch.countDown();
latch.await();
posted @ 2024-12-11 15:31  CharyGao  阅读(63)  评论(0编辑  收藏  举报