zoukankan      html  css  js  c++  java
  • Redisson分布式锁在Java应用中的使用

    一、引入Redisson依赖,并配置相关的Bean

    a. Spring 应用

    通过Maven引入依赖

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

    配置相关的Bean

    创建配置类的Bean:

    Config config = new Config();
    config.useClusterServers()
           // use "rediss://" for SSL connection
          .addNodeAddress("redis://127.0.0.1:7181");
    

    创建 Redisson 实例:

    // 同步与异步API
    RedissonClient redisson = Redisson.create(config);
    

    此外还有Reactive API和RxJava2 API相关的客户端,具体查看Redisson在GitHub上的说明

    b. Spring Boot 应用

    通过Maven引入依赖

    <dependency>
       <groupId>org.redisson</groupId>
       <artifactId>redisson-spring-boot-starter</artifactId>
       <version>3.14.0</version>
    </dependency>
    

    注意,如果通过其他方式引入了redisson-spring-data模块,则需要根据Spring Boot的版本,调整redisson-spring-data的版本,具体的版本适配见这里

    在application.properties中添加配置

    基本的Redis配置(其中host、port、password必须配置):

    spring:
      redis:
        database: 
        host:
        port:
        password:
        ssl: 
        timeout:
        cluster:
          nodes:
        sentinel:
          master:
          nodes:
    

    根据需要添加Redisson相关的配置:

      # path to config - redisson.yaml
      redisson: 
        file: classpath:redisson.yaml
        config:
          clusterServersConfig:
            idleConnectionTimeout: 10000
            connectTimeout: 10000
            timeout: 3000
            retryAttempts: 3
            retryInterval: 1500
            failedSlaveReconnectionInterval: 3000
            failedSlaveCheckInterval: 60000
            password: null
            subscriptionsPerConnection: 5
            clientName: null
            loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
            subscriptionConnectionMinimumIdleSize: 1
            subscriptionConnectionPoolSize: 50
            slaveConnectionMinimumIdleSize: 24
            slaveConnectionPoolSize: 64
            masterConnectionMinimumIdleSize: 24
            masterConnectionPoolSize: 64
            readMode: "SLAVE"
            subscriptionMode: "SLAVE"
            nodeAddresses:
            - "redis://127.0.0.1:7004"
            - "redis://127.0.0.1:7001"
            - "redis://127.0.0.1:7000"
            scanInterval: 1000
            pingConnectionInterval: 0
            keepAlive: false
            tcpNoDelay: false
          threads: 16
          nettyThreads: 32
          codec: !<org.redisson.codec.FstCodec> {}
          transportMode: "NIO"
    

    获取并使用Redisson客户端

    通过上述配置,就可以在代码中通过自动装配,直接获取并使用RedissonClientRedisTemplate/ReactiveRedisTemplate 等Bean了。

    二、使用Redisson分布式锁

    用锁的一般步骤:

    1. 获取锁实例(只是获得一把锁的引用,并不是占有锁)
    2. 通过锁实例加锁(占有了这把锁)
    3. 通过锁实例释放锁

    Redisson提供很多种类型的锁,其中最常用的就是可重入锁(Reentrant Lock)了。

    Redisson中的可重入锁

    1. 获取锁实例

    RLock lock = redissonClient.getLock(String lockName);
    

    获取的锁实例实现了RLock接口,而该接口扩展了JUC包中的Lock接口,以及异步锁接口RLockAsync

    2. 通过锁实例加锁

    同步异步特性来区分,加锁方法可分为同步加锁和异步加锁两类。异步加锁方法的名称一般是在相应的同步加锁方法后加上“Async”后缀。
    阻塞非阻塞特性来区分,加锁方法可分为阻塞加锁和非阻塞加锁两类。非阻塞加锁方法的名称一般是“try”开头。

    下面以比较常用的同步加锁方法来说明加锁的一些细节。

    阻塞加锁的方法:

    1. void lock(): (JUC中Lock接口定义的方法)如果当前锁可用,则加锁成功,并立即返回;如果当前锁不可用,则阻塞等待直至锁可用,然后返回。
    2. void lock(long leaseTime, TimeUnit unit): 加锁机制与void lock()相同,只是增加了锁的有效(租赁)时长leaseTime。加锁成功后,可以在程序中显式调用unlock()方法进行释放;如果未显式释放,则经过leaseTime时间,该锁会自动释放。如果leaseTime传入-1,则会一直持有,直至调用unlock()

    非阻塞加锁的方法:

    1. boolean tryLock(): (JUC中Lock接口定义的方法)调用该方法会立刻返回。返回值为true则表示锁可用,加锁成功;返回值为false则表示锁不可用,加锁失败。
    2. boolean tryLock(long time, TimeUnit unit): (JUC中Lock接口定义的方法)如果锁可用则立刻返回true,否则最多等待time长的时间(如果time<=0,则不会等待)。在time时间内锁可用则立刻返回true,time时间之后返回false。如果在等待期间线程被其他线程中断,则会抛出nterruptedException 异常。
    3. boolean tryLock(long waitTime, long leaseTime, TimeUnit unit): 与boolean tryLock(long time, TimeUnit unit)类似,只是增加了锁的使用(租赁)时长leaseTime。

    3. 通过锁实例释放锁

    1. void unlock(): 释放锁。如果当前线程是锁的持有者(即在该锁实例上加锁成功的线程),则会释放成功,否则会抛出异常。

    一般编程范式

    同步阻塞加锁

    String lockName = ...
    RLock lock = redissonClient.getLock(lockName);
    // 阻塞式加锁
    lock.lock();
    try {
        // 操作受锁保护的资源
    
    } finally {
        // 释放锁
        lock.unlock();
    }
    

    同步非阻塞加锁

    String lockName = ...
    RLock lock = redissonClient.getLock(lockName);
    if (lock.tryLock()) {
        try {
            // 操作受锁保护的资源
        } finally {
            lock.unlock();
        }
    } else {
        // 执行其他业务操作
    }
    

    三、分布式锁分析

    优秀的分布式锁需要具备以下特性:

    • 互斥性:在任意时刻,只有一个客户端(线程)能持有锁,这是锁的基本要求。
    • 锁的可重入:同一个客户端能多次持有同一把锁。实现上只要检查锁的持有者是否为当前客户端,若是则重入锁成功,并将锁的持有数加1。一般通过给每个客户端分配一个唯一的ID,并在加锁成功时向锁中写入该ID即可。
    • 不会因客户端异常而长久锁住:当客户端在持有锁期间崩溃而未主动解锁时,锁也会在一定时间后自动释放,即锁有超时自动释放的特性。
    • 解锁的安全性:加锁和解锁必须是同一个客户端,客户端不能把别人加的锁给释放了,即不能误解锁。实现上与锁的可重入类似,在释放锁时检查客户端ID与锁中保存的ID是否一致即可。

    Redisson的分布式锁除了实现上述几个特性外,还具有锁的自动续期功能。即当我们加锁而未指定锁的有效时长时,Redisson会按一定的周期,定时检查当前线程是否活跃,若是则自动为锁续期,这一特性称为watchdog(看门狗)机制。
    有了这个特性,我们就可以不必为设定锁的有效时间而纠结了(设得太长,则会在客户端崩溃后仍长时间占有锁;设得太短,则可能在业务逻辑执行完成前,锁自动释放),Redisson分布式锁可以在客户端崩坏时自动释放,业务逻辑未执行完时自动续期。

    用 Redis 实现分布式锁的最佳实践

    假设没有Redisson,需要我们自己用Redis实现分布式锁,以下是一些不错的编程实践:

    • set 命令要用set key value px milliseconds nx,替代 setnx + expire 需要分两次执行命令的方式,保证原子性。
    • 客户端的ID一般保存在线程本地变量(ThreadLocal)中。客户端ID要求全局唯一,可以考虑【IP+线程ID】组合,或者用UUID。
    • 阻塞式子加锁可用Object对象的wait+notify机制实现。
    • 释放锁时,需要检查当前客户端是否为锁的持有者,因此有compare and set的逻辑。为了保证两个操作的原子性,用单个Lua脚本来执行多个Redis操作(利用了eval命令执行Lua脚本的原子性,参考这里这里)。另外,如果锁有自动续期的定时任务,在解锁的时候需要停掉该任务。

    其他主题(TODO)

    • 锁的高可用(容错性):只要大多数Redis节点正常运行,客户端就能够获取和释放锁(参考这里)。
    • 死锁问题:当因客户端编程逻辑问题导致两个线程死锁时,如何检测并解决死锁问题?
    • Redisson中其他类型的锁(参考这里
    • 锁的续期机制分析(参考这里
    • Redisson 支持4种链接redis的方式:Cluster(集群)、Sentinel servers(哨兵)、Master/Slave servers(主从)、Single server(单机)(参考这里
    • 自己动手实现注解式加锁(参考这里

    参考文档

    1. Redisson的GitHub仓库
    2. redisson-spring-boot-starter 使用说明
    3. Redisson: Distributed locks and synchronizers
    4. JDK & Redisson 中的源码注释
    5. 慢谈 Redis 实现分布式锁 以及 Redisson 源码解析
    6. 使用Redisson实现分布式锁
    7. Redisson 实现分布式锁原理分析(对Redisson的源码进行分析)
    8. Redis官方文档:[用Redis构建分布式锁](http://ifeve.com/redis-lock/)
    9. Redisson分布式锁实现
    10. redisson实现分布式锁原理
  • 相关阅读:
    维护win10注册表
    win10操作技巧
    无处不网络
    事件驱动编程思想
    流程控制之if...else
    python----GIL的概念
    并发与同步异步的概念
    实现并发join的方法
    线程的调用
    三元运算符
  • 原文地址:https://www.cnblogs.com/haycheng/p/14101256.html
Copyright © 2011-2022 走看看