美文网首页基础知识我爱编程
redisson分布式锁与同步器教程

redisson分布式锁与同步器教程

作者: wine_5664 | 来源:发表于2018-06-11 10:35 被阅读89次

    简单使用

    • 基本
    // 1. 创建配置对象
    Config = ...
    
    // 2. 创建redisson实例
    RedissonClient redisson = Redisson.create(config);
    
    // 3. 获得锁对象
    RLock lock = redisson.getLock("myLock");
    
    // 4. 上锁
    lock.lock();
    
    // 做你想做的。。。。
    
    // 5.解锁
    lock.unlock();
    
    • 依赖
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.4.3</version>
    </dependency>
    
    • 可运行示例代码
    public class RedissonTest {
        private static Logger logger = LoggerFactory.getLogger(RedissonTest.class);
    
        private String address = "redis://localhost:6379";
        private int connectionMinimumIdleSize = 10;
        private int idleConnectionTimeout = 10000;
        private int pingTimeout = 1000;
        private int connectTimeout = 10000;
        private int timeout = 3000;
        private int retryAttempts = 3;
        private int retryInterval = 1500;
        private int reconnectionTimeout = 3000;
        private int failedAttempts = 3;
        private String password = null;
        private int subscriptionsPerConnection = 5;
        private String clientName = null;
        private int subscriptionConnectionMinimumIdleSize = 1;
        private int subscriptionConnectionPoolSize = 50;
        private int connectionPoolSize = 64;
        private int database = 0;
        private boolean dnsMonitoring = false;
        private int dnsMonitoringInterval = 5000;
    
        private int thread; //当前处理核数量 * 2
    
        private String codec = "org.redisson.codec.JsonJacksonCodec";
    
        RedissonClient redisson() throws Exception {
            Config config = new Config();
            config.useSingleServer().setAddress(address)
                    .setConnectionMinimumIdleSize(connectionMinimumIdleSize)
                    .setConnectionPoolSize(connectionPoolSize)
                    .setDatabase(database)
                    .setDnsMonitoring(dnsMonitoring)
                    .setDnsMonitoringInterval(dnsMonitoringInterval)
                    .setSubscriptionConnectionMinimumIdleSize(subscriptionConnectionMinimumIdleSize)
                    .setSubscriptionConnectionPoolSize(subscriptionConnectionPoolSize)
                    .setSubscriptionsPerConnection(subscriptionsPerConnection)
                    .setClientName(clientName)
                    .setFailedAttempts(failedAttempts)
                    .setRetryAttempts(retryAttempts)
                    .setRetryInterval(retryInterval)
                    .setReconnectionTimeout(reconnectionTimeout)
                    .setTimeout(timeout)
                    .setConnectTimeout(connectTimeout)
                    .setIdleConnectionTimeout(idleConnectionTimeout)
                    .setPingTimeout(pingTimeout)
                    .setPassword(password);
            Codec codec = (Codec) ClassUtils.forName("org.redisson.codec.JsonJacksonCodec", ClassUtils.getDefaultClassLoader()).newInstance();
            config.setCodec(codec);
            config.setThreads(thread);
            config.setEventLoopGroup(new NioEventLoopGroup());
            config.setUseLinuxNativeEpoll(false);
            return Redisson.create(config);
        }
    
        @Test
        public void testGetLock() throws Exception {
            RedissonClient redissonClient = redisson();
    
            RLock lock = redissonClient.getLock("TEST");
            try {
                lock.lock();
                logger.info("Request Thread - " + Thread.currentThread().getName() + " locked and begun...");
                Thread.sleep(5000); // 5 sec
                logger.info("Request Thread - " + Thread.currentThread().getName() + " ended successfully...");
            } catch (Exception ex) {
                logger.error("Error occurred");
            } finally {
                lock.unlock();
                logger.info("Request Thread - " + Thread.currentThread().getName() + " unlocked...");
            }
        }
    }
    

    集成spring boot

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.4.3</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
    • 配置
    @Configuration
    public class RedissonConfig {
    
        private String address = "redis://localhost:6379";
        private int connectionMinimumIdleSize = 10;
        private int idleConnectionTimeout = 10000;
        private int pingTimeout = 1000;
        private int connectTimeout = 10000;
        private int timeout = 3000;
        private int retryAttempts = 3;
        private int retryInterval = 1500;
        private int reconnectionTimeout = 3000;
        private int failedAttempts = 3;
        private String password = null;
        private int subscriptionsPerConnection = 5;
        private String clientName = null;
        private int subscriptionConnectionMinimumIdleSize = 1;
        private int subscriptionConnectionPoolSize = 50;
        private int connectionPoolSize = 64;
        private int database = 0;
        private boolean dnsMonitoring = false;
        private int dnsMonitoringInterval = 5000;
    
        private int thread; //当前处理核数量 * 2
    
        private String codec = "org.redisson.codec.JsonJacksonCodec";
    
        @Bean(destroyMethod = "shutdown")
        RedissonClient redisson() throws Exception {
            Config config = new Config();
            config.useSingleServer().setAddress(address)
                    .setConnectionMinimumIdleSize(connectionMinimumIdleSize)
                    .setConnectionPoolSize(connectionPoolSize)
                    .setDatabase(database)
                    .setDnsMonitoring(dnsMonitoring)
                    .setDnsMonitoringInterval(dnsMonitoringInterval)
                    .setSubscriptionConnectionMinimumIdleSize(subscriptionConnectionMinimumIdleSize)
                    .setSubscriptionConnectionPoolSize(subscriptionConnectionPoolSize)
                    .setSubscriptionsPerConnection(subscriptionsPerConnection)
                    .setClientName(clientName)
                    .setFailedAttempts(failedAttempts)
                    .setRetryAttempts(retryAttempts)
                    .setRetryInterval(retryInterval)
                    .setReconnectionTimeout(reconnectionTimeout)
                    .setTimeout(timeout)
                    .setConnectTimeout(connectTimeout)
                    .setIdleConnectionTimeout(idleConnectionTimeout)
                    .setPingTimeout(pingTimeout)
                    .setPassword(password);
            Codec codec = (Codec) ClassUtils.forName("org.redisson.codec.JsonJacksonCodec", ClassUtils.getDefaultClassLoader()).newInstance();
            config.setCodec(codec);
            config.setThreads(thread);
            config.setEventLoopGroup(new NioEventLoopGroup());
            config.setUseLinuxNativeEpoll(false);
            return Redisson.create(config);
        }
    }
    

    分布式锁使用场景

    • 场景1
    某集群服务提供一组任务,A请求随机从集群中的机器1任务组中获取一个任务;B请求随机从集群中的机器2任务组中获取一个任务。 
    在理想的情况下,A从任务组中挑选一个任务,任务组删除该任务,B从剩下的的任务中再挑一个,任务组删除该任务。 
    同样的,在真实情况下,如果不做任何处理,可能会出现A和B挑中了同一个任务的情况。
    
    • 场景2
    以流量业务场景中例子来说明,手机用户可以在手机App端、网上营业厅、wap手厅进行流量业务的操作
    如果发现该用户没有流量账户的时候,会首先给该用户创建一个专门的流量账户,如果用户在app端、网上营业厅端同时操作的时候,可能会给该用户创建2个账户;
    
    • 场景3
    一个公共集团账户,下面包含很多账户,给下面账户充值的时候,会对该集团账本进行资金扣减,高并发多请求的时候会到导致并发失败,这时候为了减少失败率,提升QPS/TPS,同样需要分布式锁 
    

    实质:在多线程中加锁是线程进程资源的分配,而到分布式锁这里是单个服务器进程)对集群共有资源的分配

    官方详细教程

    8.1. 可重入锁(Reentrant Lock)

    基于Redis的Redisson分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口。

    RLock lock = redisson.getLock("anyLock");
    // 最常见的使用方法
    lock.lock();
    

    大家都知道,如果负责储存这个分布式锁的Redis节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

    另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

    // 加锁以后10秒钟自动解锁
    // 无需调用unlock方法手动解锁
    lock.lock(10, TimeUnit.SECONDS);
    
    // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
    boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
    ...
    lock.unlock();
    

    Redisson同时还为分布式锁提供了异步执行的相关方法:

    RLock lock = redisson.getLock("anyLock");
    lock.lockAsync();
    lock.lockAsync(10, TimeUnit.SECONDS);
    Future<Boolean> res = lock.tryLockAsync(100, 10, TimeUnit.SECONDS);
    

    RLock对象完全符合Java的Lock规范。也就是说只有拥有锁的进程才能解锁,其他进程解锁则会抛出IllegalMonitorStateException错误。但是如果遇到需要其他进程也能解锁的情况,请使用分布式信号量Semaphore 对象.

    8.2. 公平锁(Fair Lock)

    基于Redis的Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。

    RLock fairLock = redisson.getFairLock("anyLock");
    // 最常见的使用方法
    fairLock.lock();
    

    大家都知道,如果负责储存这个分布式锁的Redis节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

    另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

    // 10秒钟以后自动解锁
    // 无需调用unlock方法手动解锁
    fairLock.lock(10, TimeUnit.SECONDS);
    
    // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
    boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
    ...
    fairLock.unlock();
    

    Redisson同时还为分布式可重入公平锁提供了异步执行的相关方法:

    RLock fairLock = redisson.getFairLock("anyLock");
    fairLock.lockAsync();
    fairLock.lockAsync(10, TimeUnit.SECONDS);
    Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
    

    8.3. 联锁(MultiLock)

    基于Redis的Redisson分布式联锁RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。

    RLock lock1 = redissonInstance1.getLock("lock1");
    RLock lock2 = redissonInstance2.getLock("lock2");
    RLock lock3 = redissonInstance3.getLock("lock3");
    
    RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
    // 同时加锁:lock1 lock2 lock3
    // 所有的锁都上锁成功才算成功。
    lock.lock();
    ...
    lock.unlock();
    

    大家都知道,如果负责储存某些分布式锁的某些Redis节点宕机以后,而且这些锁正好处于锁住的状态时,这些锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

    另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

    RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
    // 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
    lock.lock(10, TimeUnit.SECONDS);
    
    // 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
    boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
    ...
    lock.unlock();
    

    8.4. 红锁(RedLock)

    基于Redis的Redisson红锁RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。

    RLock lock1 = redissonInstance1.getLock("lock1");
    RLock lock2 = redissonInstance2.getLock("lock2");
    RLock lock3 = redissonInstance3.getLock("lock3");
    
    RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
    // 同时加锁:lock1 lock2 lock3
    // 红锁在大部分节点上加锁成功就算成功。
    lock.lock();
    ...
    lock.unlock();
    

    大家都知道,如果负责储存某些分布式锁的某些Redis节点宕机以后,而且这些锁正好处于锁住的状态时,这些锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

    另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

    RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
    // 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
    lock.lock(10, TimeUnit.SECONDS);
    
    // 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
    boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
    ...
    lock.unlock();
    

    8.5. 读写锁(ReadWriteLock)

    基于Redis的Redisson分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。同时还支持自动过期解锁。该对象允许同时有多个读取锁,但是最多只能有一个写入锁。

    RReadWriteLock rwlock = redisson.getLock("anyRWLock");
    // 最常见的使用方法
    rwlock.readLock().lock();
    // 或
    rwlock.writeLock().lock();
    

    大家都知道,如果负责储存这个分布式锁的Redis节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

    另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

    // 10秒钟以后自动解锁
    // 无需调用unlock方法手动解锁
    rwlock.readLock().lock(10, TimeUnit.SECONDS);
    // 或
    rwlock.writeLock().lock(10, TimeUnit.SECONDS);
    
    // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
    boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
    // 或
    boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
    ...
    lock.unlock();
    

    8.6. 信号量(Semaphore)

    基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。

    RSemaphore semaphore = redisson.getSemaphore("semaphore");
    semaphore.acquire();
    //或
    semaphore.acquireAsync();
    semaphore.acquire(23);
    semaphore.tryAcquire();
    //或
    semaphore.tryAcquireAsync();
    semaphore.tryAcquire(23, TimeUnit.SECONDS);
    //或
    semaphore.tryAcquireAsync(23, TimeUnit.SECONDS);
    semaphore.release(10);
    semaphore.release();
    //或
    semaphore.releaseAsync();
    

    8.7. 可过期性信号量(PermitExpirableSemaphore)

    基于Redis的Redisson可过期性信号量(PermitExpirableSemaphore)是在RSemaphore对象的基础上,为每个信号增加了一个过期时间。每个信号可以通过独立的ID来辨识,释放时只能通过提交这个ID才能释放。

    RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore");
    String permitId = semaphore.acquire();
    // 获取一个信号,有效期只有2秒钟。
    String permitId = semaphore.acquire(2, TimeUnit.SECONDS);
    // ...
    semaphore.release(permitId);
    

    8.8. 闭锁(CountDownLatch)

    基于Redisson的Redisson分布式闭锁(CountDownLatch)Java对象RCountDownLatch采用了与java.util.concurrent.CountDownLatch相似的接口和用法。

    RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
    latch.trySetCount(1);
    latch.await();
    
    // 在其他线程或其他JVM里
    RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
    latch.countDown();
    

    相关文章

      网友评论

        本文标题:redisson分布式锁与同步器教程

        本文链接:https://www.haomeiwen.com/subject/rnvceftx.html