Redisson的简单使用

  在之前的项目中分布式锁和限流是基于redis进行的,分布式锁基于setnx和expire命令实现,也可以基于lua脚本实现。限流是采用固定时间窗算法进行的。

  最近了解到redisson这个工具类,而且基于其分布式锁的实现是比较常见的,简单研究下其使用。

官网:wiki地址    https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95

1. 概述

  官网解释如下:Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

2.研究其分布式锁的用法

pom增加:

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

增加日志文件 logback.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="60000">
    <property name="LOG_HOME" value="/export/logs/cmdb/"/>
    <property name="APP_NAME" value="cmdb"/>
    <property name="LOG_FILE_EXPIRE_TIME" value="180"/>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} | ${APP_NAME} - %p | %thread | %c | line:%L - %m%n</pattern>
        </encoder>
    </appender>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}${APP_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>${LOG_FILE_EXPIRE_TIME}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} | ${APP_NAME} - %p | %thread | %c | line:%L - %m%n</pattern>
        </encoder>
    </appender>

    <root>
        <level value="ERROR"/>
        <appender-ref ref="STDOUT"/>
        <!--<appender-ref ref="FILE"/>-->
    </root>
    <!-- 不同包,设置不同的日志级别 -->
    <logger name="com.xm.ggn" level="INFO"/>
</configuration>

1. 简单的使用

package com.xm.ggn.test.redisson;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.util.concurrent.TimeUnit;

@Slf4j
public class Client {

    private static final Long TIME_LOCKED = 50 * 1000l;
    private static final String KEY_LOCKED = "myLock";
    private static RedissonClient redissonClient = null;

    public static void main(String[] args) {
        initRedissonClient();
        lock();
    }

    private static void initRedissonClient() {
        // 1. Create config object
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        // 2. Create Redisson instance
        Client.redissonClient = Redisson.create(config);
    }

    private static void lock() {
        RLock lock1 = redissonClient.getLock(KEY_LOCKED);
        log.error("lock1 clas: {}", lock1.getClass());
        lock1.lock();
        log.info("lock, ThreadName: {} id: {} locked, 重入次数: {}", Thread.currentThread().getName(), Thread.currentThread().getId(), lock1.getHoldCount());

        // 处理业务逻辑
        try {
            Thread.sleep(TIME_LOCKED);
            reLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock1.unlock();
            log.info("lock, ThreadName: {} id: {} unlock, 重入次数: {}", Thread.currentThread().getName(), Thread.currentThread().getId(), lock1.getHoldCount());
        }
    }

    /**
     * 测试锁的重入
     */
    private static void reLock() {
        RLock lock1 = redissonClient.getLock(KEY_LOCKED);
        lock1.lock();
        log.info("reLock, ThreadName: {} id: {} locked, 重入次数: {}", Thread.currentThread().getName(), Thread.currentThread().getId(), lock1.getHoldCount());

        // 处理业务逻辑
        try {
            Thread.sleep(TIME_LOCKED);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock1.unlock();
            log.info("reLock, ThreadName: {} id: {} unlock, 重入次数: {}", Thread.currentThread().getName(), Thread.currentThread().getId(), lock1.getHoldCount());
        }
    }
}

结果:

2021-02-01 16:20:23.013 | cmdb - ERROR | main | com.xm.ggn.test.redisson.Client | line:35 - lock1 clas: class org.redisson.RedissonLock
2021-02-01 16:20:23.050 | cmdb - INFO | main | com.xm.ggn.test.redisson.Client | line:37 - lock, ThreadName: main id: 1 locked, 重入次数: 1
2021-02-01 16:21:13.056 | cmdb - INFO | main | com.xm.ggn.test.redisson.Client | line:57 - reLock, ThreadName: main id: 1 locked, 重入次数: 2
2021-02-01 16:22:03.059 | cmdb - INFO | main | com.xm.ggn.test.redisson.Client | line:66 - reLock, ThreadName: main id: 1 unlock, 重入次数: 1
2021-02-01 16:22:03.061 | cmdb - INFO | main | com.xm.ggn.test.redisson.Client | line:47 - lock, ThreadName: main id: 1 unlock, 重入次数: 0

注意:

(1)redis中是通过HASH来存储锁的,key是UUID+":"+ThreadId;value 是重入的层数,例如上面过程中查看redis数据如下:

127.0.0.1:6379> hgetall myLock
1) "c20d8714-89c6-485f-ad4f-8dbb54271ebf:1"
2) "1"
127.0.0.1:6379> hgetall myLock
1) "c20d8714-89c6-485f-ad4f-8dbb54271ebf:1"
2) "2"
127.0.0.1:6379> hgetall myLock
(empty list or set)

(2)也可以使用lock(long var1, TimeUnit var3); 方法自动释放锁

    private static void lock3() {
        RLock lock1 = redissonClient.getLock(KEY_LOCKED);
        log.error("lock1 clas: {}", lock1.getClass());
        // 500s 后自动释放锁
        lock1.lock(500, TimeUnit.SECONDS);
        try {
            Thread.sleep(TIME_LOCKED);
        } catch (InterruptedException ignore) {
            // ignore
        }
    }

测试查看日志如下:

127.0.0.1:6379> ttl myLock
(integer) 493
127.0.0.1:6379> hgetall myLock
1) "3cdf7b21-1e36-4f1f-b0ba-f0339286f416:1"
2) "1"

(3)tryLock(long time, TimeUnit unit)  可以尝试一定时间去获取锁,返回Boolean值

    private static void lock2() {
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    log.info(Thread.currentThread().getName() + " \t 运行");
                    RLock lock1 = redissonClient.getLock(KEY_LOCKED);
                    try {
                        // 尝试获取锁60s
                        boolean b = lock1.tryLock(7, TimeUnit.SECONDS);
                        if (!b) {
                            log.info(Thread.currentThread().getName() + " \t 获取锁失败");
                            return;
                        }
                    } catch (InterruptedException e) {
                    }

                    log.info(Thread.currentThread().getName() + " \t 获取锁");

                    try {
                        // 模拟处理逻辑用时50s
                        Thread.sleep(5 * 1000);
                    } catch (InterruptedException e) {

                    }

                    lock1.unlock();
                    log.info(Thread.currentThread().getName() + " \t 释放锁");
                }
            }).start();
        }
    }

结果:

2021-02-01 17:17:04.915 | cmdb - INFO | Thread-3 | com.xm.ggn.test.redisson.Client | line:121 - Thread-3 运行
2021-02-01 17:17:04.915 | cmdb - INFO | Thread-1 | com.xm.ggn.test.redisson.Client | line:121 - Thread-1 运行
2021-02-01 17:17:04.915 | cmdb - INFO | Thread-2 | com.xm.ggn.test.redisson.Client | line:121 - Thread-2 运行
2021-02-01 17:17:04.949 | cmdb - INFO | Thread-1 | com.xm.ggn.test.redisson.Client | line:133 - Thread-1 获取锁
2021-02-01 17:17:09.952 | cmdb - INFO | Thread-1 | com.xm.ggn.test.redisson.Client | line:143 - Thread-1 释放锁
2021-02-01 17:17:09.954 | cmdb - INFO | Thread-3 | com.xm.ggn.test.redisson.Client | line:133 - Thread-3 获取锁
2021-02-01 17:17:11.925 | cmdb - INFO | Thread-2 | com.xm.ggn.test.redisson.Client | line:127 - Thread-2 获取锁失败
2021-02-01 17:17:14.956 | cmdb - INFO | Thread-3 | com.xm.ggn.test.redisson.Client | line:143 - Thread-3 释放锁

(4)  tryLock(long var1, long var3, TimeUnit var5) 接收3个参数,第一个指定最长等待时间waitTime,第二个指定最长持有锁的时间 holdTime, 第三个是单位

    private static void lock2() {
        for (int i = 0; i < 3; i++) {
            final int index = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    log.info(Thread.currentThread().getName() + " \t 运行");
                    RLock lock1 = redissonClient.getLock(KEY_LOCKED);
                    try {
                        // 尝试获取7s
//                        boolean b = lock1.tryLock(7,  TimeUnit.SECONDS);
                        // 尝试获取锁7s, 最多占有锁2s,超过后自动释放,调用unlock可以提前释放。
                        boolean b = lock1.tryLock(7, 2, TimeUnit.SECONDS);
                        if (!b) {
                            log.info(Thread.currentThread().getName() + " \t 获取锁失败");
                            return;
                        }
                    } catch (InterruptedException e) {
                    }

                    log.info(Thread.currentThread().getName() + " \t 获取锁");

                    try {
                        // 模拟处理逻辑用时
                        Thread.sleep((index * 2) * 1000);
                    } catch (InterruptedException e) {

                    }

                    // 如果是当前线程持有锁,手动释放
                    if (lock1.isHeldByCurrentThread()) {
                        lock1.unlock();
                        log.info(Thread.currentThread().getName() + " \t 释放锁");
                    }
                }
            }).start();
        }
    }

结果:

2021-02-01 20:51:24.938 | cmdb - INFO | Thread-2 | com.xm.ggn.test.redisson.Client | line:162 - Thread-2      运行
2021-02-01 20:51:24.938 | cmdb - INFO | Thread-3 | com.xm.ggn.test.redisson.Client | line:162 - Thread-3      运行
2021-02-01 20:51:24.938 | cmdb - INFO | Thread-1 | com.xm.ggn.test.redisson.Client | line:162 - Thread-1      运行
2021-02-01 20:51:24.981 | cmdb - INFO | Thread-2 | com.xm.ggn.test.redisson.Client | line:176 - Thread-2      获取锁
2021-02-01 20:51:26.980 | cmdb - INFO | Thread-3 | com.xm.ggn.test.redisson.Client | line:176 - Thread-3      获取锁
2021-02-01 20:51:28.984 | cmdb - INFO | Thread-1 | com.xm.ggn.test.redisson.Client | line:176 - Thread-1      获取锁
2021-02-01 20:51:28.988 | cmdb - INFO | Thread-1 | com.xm.ggn.test.redisson.Client | line:188 - Thread-1      释放锁

(5)RedissonLock 的ttl也不是永久的,默认是30s。

  在加锁成功后,会注册一个定时任务监听这个锁,每隔10秒就去查看这个锁,如果还持有锁,就对过期时间进行续期。默认过期时间30秒,过10秒检查一次,一旦加锁的业务没有执行完,就会进行一次续期,把锁的过期时间再次重置成30秒。 如果在执行过程中线程死掉,不会续期。会等ttl到期后自动消失。

(6) 查看RLock的继承关系如下

 2. 公平锁的使用

默认使用的是非公平锁,不过一般情况使用的都是公平锁,也就是先到先得。

(1)默认非公平锁

    private static void lock1() {
        for (int i = 0; i < 5; i++) {
            // 休眠一下使线程按照顺序启动
            try {
                Thread.sleep(1 * 100);
            } catch (InterruptedException e) {
            }

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    log.info(Thread.currentThread().getName() + " \t 运行");
                    // 下面方式获取到的是非公平锁
                    RLock lock1 = redissonClient.getLock(KEY_LOCKED);
//                    RLock lock1 = redissonClient.getFairLock(KEY_LOCKED);
                    log.error("lock1 clas: {}", lock1.getClass());
                    lock1.lock();

                    log.info(Thread.currentThread().getName() + " \t 获取锁");
                    try {
                        Thread.sleep(TIME_LOCKED);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    log.info(Thread.currentThread().getName() + " \t 释放锁");
                    lock1.unlock();
                }
            });
            thread.setName("MyThread: " + i);
            thread.start();
        }
    }

结果:

2021-02-01 17:34:02.226 | cmdb - INFO | MyThread: 0 | com.xm.ggn.test.redisson.Client | line:96 - MyThread: 0 运行
2021-02-01 17:34:02.233 | cmdb - ERROR | MyThread: 0 | com.xm.ggn.test.redisson.Client | line:100 - lock1 clas: class org.redisson.RedissonLock
2021-02-01 17:34:02.325 | cmdb - INFO | MyThread: 1 | com.xm.ggn.test.redisson.Client | line:96 - MyThread: 1 运行
2021-02-01 17:34:02.325 | cmdb - ERROR | MyThread: 1 | com.xm.ggn.test.redisson.Client | line:100 - lock1 clas: class org.redisson.RedissonLock
2021-02-01 17:34:02.426 | cmdb - INFO | MyThread: 2 | com.xm.ggn.test.redisson.Client | line:96 - MyThread: 2 运行
2021-02-01 17:34:02.426 | cmdb - ERROR | MyThread: 2 | com.xm.ggn.test.redisson.Client | line:100 - lock1 clas: class org.redisson.RedissonLock
2021-02-01 17:34:02.526 | cmdb - INFO | MyThread: 3 | com.xm.ggn.test.redisson.Client | line:96 - MyThread: 3 运行
2021-02-01 17:34:02.526 | cmdb - ERROR | MyThread: 3 | com.xm.ggn.test.redisson.Client | line:100 - lock1 clas: class org.redisson.RedissonLock
2021-02-01 17:34:02.627 | cmdb - INFO | MyThread: 4 | com.xm.ggn.test.redisson.Client | line:96 - MyThread: 4 运行
2021-02-01 17:34:02.627 | cmdb - ERROR | MyThread: 4 | com.xm.ggn.test.redisson.Client | line:100 - lock1 clas: class org.redisson.RedissonLock
2021-02-01 17:34:16.038 | cmdb - INFO | MyThread: 3 | com.xm.ggn.test.redisson.Client | line:103 - MyThread: 3 获取锁
2021-02-01 17:34:21.038 | cmdb - INFO | MyThread: 3 | com.xm.ggn.test.redisson.Client | line:110 - MyThread: 3 释放锁
2021-02-01 17:34:21.043 | cmdb - INFO | MyThread: 1 | com.xm.ggn.test.redisson.Client | line:103 - MyThread: 1 获取锁
2021-02-01 17:34:26.044 | cmdb - INFO | MyThread: 1 | com.xm.ggn.test.redisson.Client | line:110 - MyThread: 1 释放锁
2021-02-01 17:34:26.047 | cmdb - INFO | MyThread: 0 | com.xm.ggn.test.redisson.Client | line:103 - MyThread: 0 获取锁
2021-02-01 17:34:31.047 | cmdb - INFO | MyThread: 0 | com.xm.ggn.test.redisson.Client | line:110 - MyThread: 0 释放锁
2021-02-01 17:34:31.050 | cmdb - INFO | MyThread: 4 | com.xm.ggn.test.redisson.Client | line:103 - MyThread: 4 获取锁
2021-02-01 17:34:36.050 | cmdb - INFO | MyThread: 4 | com.xm.ggn.test.redisson.Client | line:110 - MyThread: 4 释放锁
2021-02-01 17:34:36.054 | cmdb - INFO | MyThread: 2 | com.xm.ggn.test.redisson.Client | line:103 - MyThread: 2 获取锁
2021-02-01 17:34:41.055 | cmdb - INFO | MyThread: 2 | com.xm.ggn.test.redisson.Client | line:110 - MyThread: 2 释放锁

(2)  公平锁的使用

主要代码同上,只是获取锁变为公平锁

RLock lock1 = redissonClient.getFairLock(KEY_LOCKED);

结果:

2021-02-01 17:38:04.689 | cmdb - INFO | MyThread: 0 | com.xm.ggn.test.redisson.Client | line:96 - MyThread: 0      运行
2021-02-01 17:38:04.696 | cmdb - ERROR | MyThread: 0 | com.xm.ggn.test.redisson.Client | line:98 - lock1 clas: class org.redisson.RedissonFairLock
2021-02-01 17:38:04.721 | cmdb - INFO | MyThread: 0 | com.xm.ggn.test.redisson.Client | line:101 - MyThread: 0      获取锁
2021-02-01 17:38:04.787 | cmdb - INFO | MyThread: 1 | com.xm.ggn.test.redisson.Client | line:96 - MyThread: 1      运行
2021-02-01 17:38:04.787 | cmdb - ERROR | MyThread: 1 | com.xm.ggn.test.redisson.Client | line:98 - lock1 clas: class org.redisson.RedissonFairLock
2021-02-01 17:38:04.888 | cmdb - INFO | MyThread: 2 | com.xm.ggn.test.redisson.Client | line:96 - MyThread: 2      运行
2021-02-01 17:38:04.888 | cmdb - ERROR | MyThread: 2 | com.xm.ggn.test.redisson.Client | line:98 - lock1 clas: class org.redisson.RedissonFairLock
2021-02-01 17:38:04.989 | cmdb - INFO | MyThread: 3 | com.xm.ggn.test.redisson.Client | line:96 - MyThread: 3      运行
2021-02-01 17:38:04.989 | cmdb - ERROR | MyThread: 3 | com.xm.ggn.test.redisson.Client | line:98 - lock1 clas: class org.redisson.RedissonFairLock
2021-02-01 17:38:05.089 | cmdb - INFO | MyThread: 4 | com.xm.ggn.test.redisson.Client | line:96 - MyThread: 4      运行
2021-02-01 17:38:05.089 | cmdb - ERROR | MyThread: 4 | com.xm.ggn.test.redisson.Client | line:98 - lock1 clas: class org.redisson.RedissonFairLock
2021-02-01 17:38:09.723 | cmdb - INFO | MyThread: 0 | com.xm.ggn.test.redisson.Client | line:108 - MyThread: 0      释放锁
2021-02-01 17:38:09.729 | cmdb - INFO | MyThread: 1 | com.xm.ggn.test.redisson.Client | line:101 - MyThread: 1      获取锁
2021-02-01 17:38:14.729 | cmdb - INFO | MyThread: 1 | com.xm.ggn.test.redisson.Client | line:108 - MyThread: 1      释放锁
2021-02-01 17:38:14.732 | cmdb - INFO | MyThread: 2 | com.xm.ggn.test.redisson.Client | line:101 - MyThread: 2      获取锁
2021-02-01 17:38:19.732 | cmdb - INFO | MyThread: 2 | com.xm.ggn.test.redisson.Client | line:108 - MyThread: 2      释放锁
2021-02-01 17:38:19.734 | cmdb - INFO | MyThread: 3 | com.xm.ggn.test.redisson.Client | line:101 - MyThread: 3      获取锁
2021-02-01 17:38:24.734 | cmdb - INFO | MyThread: 3 | com.xm.ggn.test.redisson.Client | line:108 - MyThread: 3      释放锁
2021-02-01 17:38:24.737 | cmdb - INFO | MyThread: 4 | com.xm.ggn.test.redisson.Client | line:101 - MyThread: 4      获取锁
2021-02-01 17:38:29.738 | cmdb - INFO | MyThread: 4 | com.xm.ggn.test.redisson.Client | line:108 - MyThread: 4      释放锁

3. 读写锁的使用 

  类似于JDK的读写锁,读锁共享,写锁排斥。

    private static void lock4() {
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(1 * 1000);
            } catch (InterruptedException e) {
            }

            new Thread(new Runnable() {
                @Override
                public void run() {
                    log.info(Thread.currentThread().getName() + " \t 运行");
                    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(KEY_LOCKED);

                    readWriteLock.readLock().lock();
                    log.info(Thread.currentThread().getName() + " \t 获取读锁");
                    try {
                        // 模拟处理逻辑用时5s
                        Thread.sleep(5 * 1000);
                    } catch (InterruptedException e) {
                    }
                    readWriteLock.readLock().unlock();
                    log.info(Thread.currentThread().getName() + " \t 释放读锁");

                    readWriteLock.writeLock().lock();
                    log.info(Thread.currentThread().getName() + " \t 获取写锁");
                    try {
                        // 模拟处理逻辑用时5s
                        Thread.sleep(5 * 1000);
                    } catch (InterruptedException e) {
                    }
                    readWriteLock.writeLock().unlock();
                    log.info(Thread.currentThread().getName() + " \t 释放写锁");
                }
            }).start();
        }
    }

结果:

2021-02-01 20:17:34.375 | cmdb - INFO | Thread-1 | com.xm.ggn.test.redisson.Client | line:37 - Thread-1      运行
2021-02-01 20:17:34.399 | cmdb - INFO | Thread-1 | com.xm.ggn.test.redisson.Client | line:41 - Thread-1      获取读锁
2021-02-01 20:17:35.374 | cmdb - INFO | Thread-2 | com.xm.ggn.test.redisson.Client | line:37 - Thread-2      运行
2021-02-01 20:17:35.376 | cmdb - INFO | Thread-2 | com.xm.ggn.test.redisson.Client | line:41 - Thread-2      获取读锁
2021-02-01 20:17:36.374 | cmdb - INFO | Thread-3 | com.xm.ggn.test.redisson.Client | line:37 - Thread-3      运行
2021-02-01 20:17:36.376 | cmdb - INFO | Thread-3 | com.xm.ggn.test.redisson.Client | line:41 - Thread-3      获取读锁
2021-02-01 20:17:39.404 | cmdb - INFO | Thread-1 | com.xm.ggn.test.redisson.Client | line:48 - Thread-1      释放读锁
2021-02-01 20:17:40.378 | cmdb - INFO | Thread-2 | com.xm.ggn.test.redisson.Client | line:48 - Thread-2      释放读锁
2021-02-01 20:17:41.378 | cmdb - INFO | Thread-3 | com.xm.ggn.test.redisson.Client | line:48 - Thread-3      释放读锁
2021-02-01 20:17:41.379 | cmdb - INFO | Thread-3 | com.xm.ggn.test.redisson.Client | line:51 - Thread-3      获取写锁
2021-02-01 20:17:46.381 | cmdb - INFO | Thread-3 | com.xm.ggn.test.redisson.Client | line:58 - Thread-3      释放写锁
2021-02-01 20:17:46.383 | cmdb - INFO | Thread-2 | com.xm.ggn.test.redisson.Client | line:51 - Thread-2      获取写锁
2021-02-01 20:17:51.385 | cmdb - INFO | Thread-2 | com.xm.ggn.test.redisson.Client | line:58 - Thread-2      释放写锁
2021-02-01 20:17:51.389 | cmdb - INFO | Thread-1 | com.xm.ggn.test.redisson.Client | line:51 - Thread-1      获取写锁
2021-02-01 20:17:56.390 | cmdb - INFO | Thread-1 | com.xm.ggn.test.redisson.Client | line:58 - Thread-1      释放写锁

 

  至此简单研究下redisson分布式锁使用。一般是基于AOP自定义注解实现分布式锁。

3. 基于AOP封装分布式锁

1.配置类

package com.xm.ggn.test.redisson;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author: qlq
 * @Description
 * @Date: 21:49 2021/2/1
 */
@Configuration
public class RedissonConfiguration {

    @Bean
    public RedissonClient redissonClient() {
        // 1. Create config object
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        // 2. Create Redisson instance
        return Redisson.create(config);
    }
}

2. 分布式锁注解

package com.xm.ggn.test.redisson.anno;

import java.lang.annotation.*;

/**
 * @author: 乔利强
 * @date: 2021/2/1 20:43
 * @description:
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface DistributedLock {

    /**
     * 分布式锁的key,可以理解为前缀
     */
    String value();

    /**
     * 分布式锁的最长等待时间
     */
    long waitTime() default 60;

    /**
     * 最长持有时间
     */
    long holdTime() default 60;
}

3.分布式锁参数注解,用于打在参数上,针对不同的参数生成不同的锁 (这种相当于是使锁更加细粒度)

package com.xm.ggn.test.redisson.anno;

import java.lang.annotation.*;

/**
 * key 注解,用于特殊标记分布式锁的key,用于对不同的参数生成不同的锁
 */
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface DistributedLockKey {
}

4.  LockAspect AOP处理

package com.xm.ggn.test.redisson;

import com.xm.ggn.test.redisson.anno.DistributedLock;
import com.xm.ggn.test.redisson.anno.DistributedLockKey;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;

/**
 * @author: 乔利强
 * @date: 2021/2/1 20:57
 * @description:
 */
@Component
@Aspect
@Slf4j
public class LockAspect {

    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(com.xm.ggn.test.redisson.anno.DistributedLock)")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        // 1.方法执行前的处理,相当于前置通知
        // 获取方法签名
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // 获取方法
        Method method = methodSignature.getMethod();
        // 获取方法上面的注解
        DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
        // 获取操作描述的属性值
        String lockKey = distributedLock.value();
        long waitTime = distributedLock.waitTime();
        long holdTime = distributedLock.holdTime();

        // method获取参数信息。 如果参数带有DistributedLockKey 注解,lockKey 拼接参数的value
        Parameter[] parameters = method.getParameters();
        if (ArrayUtils.isNotEmpty(parameters)) {
            for (int index = 0, length_1 = parameters.length; index < length_1; index++) {
                DistributedLockKey annotation = parameters[index].getAnnotation(DistributedLockKey.class);
                if (annotation != null) {
                    // 获取参数值
                    Object[] args = pjp.getArgs();
                    String param = String.valueOf(args[index]);
                    if (StringUtils.isNotBlank(param)) {
                        lockKey += ":" + param;
                        break;
                    }
                }
            }
        }

        log.info("lockKey: {}", lockKey);
        RLock fairLock = redissonClient.getFairLock(lockKey);
        boolean lock = fairLock.tryLock(waitTime, holdTime, TimeUnit.SECONDS);
        if (!lock) {
            throw new RuntimeException("获取锁失败");
        }

        Object result = null;
        try {
            //让代理方法执行
            result = pjp.proceed();
            // 2.相当于后置通知(方法成功执行之后走这里)
        } catch (SQLException e) {
            // 3.相当于异常通知部分
        } finally {
            // 4.相当于最终通知
            if (fairLock.isHeldByCurrentThread()) {
                fairLock.unlock();
            }
        }
        return result;
    }
}

5.测试Controller:

package com.xm.ggn.test.redisson;

import com.xm.ggn.test.redisson.anno.DistributedLock;
import com.xm.ggn.test.redisson.anno.DistributedLockKey;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: qlq
 * @Description
 * @Date: 21:53 2021/2/1
 */
@RestController("redissonTestController")
@RequestMapping("/redisson")
public class TestController {

    @GetMapping("test1")
    @DistributedLock("redissonTest:test1")
    public void test1(@DistributedLockKey String value1) {
        try {
            Thread.sleep(50 * 1000);
        } catch (InterruptedException e) {
        }
    }

    @GetMapping("test2")
    @DistributedLock("redissonTest:test2")
    public void test2(String value1) {
        try {
            Thread.sleep(50 * 1000);
        } catch (InterruptedException e) {
        }
    }
}

6.访问后查看redis

127.0.0.1:6379> keys *
1) "redissonTest:test2"
2) "redissonTest:test1:123456"
127.0.0.1:6379> hgetall "redissonTest:test2"
1) "f43c6823-021c-468b-8384-86fb2384775e:403"
2) "1"
127.0.0.1:6379> hgetall "redissonTest:test1:123456"
1) "f43c6823-021c-468b-8384-86fb2384775e:402"
2) "1"

 

补充:redisson实际也是基于lua脚本执行的。Redis 脚本使用 Lua 解释器来执行脚本。 Redis 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常用命令为 EVAL。Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

(1)org.redisson.RedissonLock#tryLock() 源码如下:

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

(2)org.redisson.RedissonLock#tryLockAsync()

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

(3)org.redisson.RedissonLock#tryLockAsync(long)

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

(4)org.redisson.RedissonLock#tryAcquireOnceAsync

    private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        if (leaseTime != -1L) {
            return this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
        } else {
            RFuture<Boolean> ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
            ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
                if (e == null) {
                    if (ttlRemaining.booleanValue()) {
                        this.scheduleExpirationRenewal(threadId);
                    }

                }
            });
            return ttlRemainingFuture;
        }
    }

(5)org.redisson.RedissonLock#tryLockInnerAsync  看出来也是执行了个lua脚本

    <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        this.internalLockLeaseTime = unit.toMillis(leaseTime);
        return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hincrby', 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)});
    }

3. 封装为独立的lock组件,用于其他多个项目引入

 1. 新建项目cloud-common-lock

2. 修改pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud</artifactId>
        <groupId>cn.qz.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-common-lock</artifactId>

    <properties>
        <redisson.version>3.10.1</redisson.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- common -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!-- 引入 spring aop 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>${redisson.version}</version>
        </dependency>
    </dependencies>

</project>

3. 增加注解类

 DistributedLock 用于AOP拦截

package cn.qz.lock.anno;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface DistributedLock {

    /**
     * 分布式锁的key,可以理解为前缀
     */
    String value();

    /**
     * 分布式锁的最长等待时间
     */
    long waitTime() default 60;

    /**
     * 最长持有时间
     */
    long holdTime() default 60;
}

 DistributedLockKey 用于标记在参数上,为不同的参数生成不同的redis的锁的key

package cn.qz.lock.anno;

import java.lang.annotation.*;

/**
 * key 注解,用于特殊标记分布式锁的key,用于对不同的参数生成不同的锁
 */
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface DistributedLockKey {
}

EnableDistributedLock 用于引入分布式锁的相关配置,开启分布式锁,使AOP生效

package cn.qz.lock.anno;

import cn.qz.lock.config.LockAutoConfiguration;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * 此注解的作用是开启分布式锁开关, 加此注解实际为了将bean注入到Spring。如果包名可以被扫描到不需要打此注解也可以
 *
 * @Import 引入cn.qz.lock.config.LockAutoConfiguration,使其自动配置
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// @Import用来导入@Configuration注解的配置类、声明@Bean注解的bean方法、导入ImportSelector的实现类或导入ImportBeanDefinitionRegistrar的实现类。
@Import({LockAutoConfiguration.class})
public @interface EnableDistributedLock {
}

4. AOP处理类

package cn.qz.lock.aspect;

import cn.qz.lock.anno.DistributedLock;
import cn.qz.lock.anno.DistributedLockKey;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;

/**
 * @author: 乔利强
 * @date: 2021/2/2 11:03
 * @description:
 */
@Component
@Aspect
@Slf4j
public class LockAspect {

    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(cn.qz.lock.anno.DistributedLock)")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        // 1.方法执行前的处理,相当于前置通知
        // 获取方法签名
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // 获取方法
        Method method = methodSignature.getMethod();
        // 获取方法上面的注解
        DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
        // 获取操作描述的属性值
        String lockKey = distributedLock.value();
        long waitTime = distributedLock.waitTime();
        long holdTime = distributedLock.holdTime();

        // method获取参数信息。 如果参数带有DistributedLockKey 注解,lockKey 拼接参数的value
        Parameter[] parameters = method.getParameters();
        if (ArrayUtils.isNotEmpty(parameters)) {
            for (int index = 0, length_1 = parameters.length; index < length_1; index++) {
                DistributedLockKey annotation = parameters[index].getAnnotation(DistributedLockKey.class);
                if (annotation != null) {
                    // 获取参数值
                    Object[] args = pjp.getArgs();
                    String param = String.valueOf(args[index]);
                    if (StringUtils.isNotBlank(param)) {
                        lockKey += ":" + param;
                        break;
                    }
                }
            }
        }

        log.info("lockKey: {}", lockKey);
        RLock fairLock = redissonClient.getFairLock(lockKey);
        boolean lock = fairLock.tryLock(waitTime, holdTime, TimeUnit.SECONDS);
        if (!lock) {
            throw new RuntimeException("获取锁失败");
        }

        Object result = null;
        try {
            //让代理方法执行
            result = pjp.proceed();
            // 2.相当于后置通知(方法成功执行之后走这里)
        } catch (SQLException e) {
            // 3.相当于异常通知部分
        } finally {
            // 4.相当于最终通知
            if (fairLock.isHeldByCurrentThread()) {
                fairLock.unlock();
            }
        }
        return result;
    }
}

5. 自动配置类

package cn.qz.lock.config;

import cn.qz.lock.aspect.LockAspect;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;

/**
 * Lock 自动配置类。 该类的Configuration 注解没有被扫描到是因为和Boot的主启动类不在同一个子包下,所以扫描不到。通过cn.qz.lock.anno.EnableDistributedLock中的@Import可以引入
 * 该类的主要作用是引入LockAspect,注册到Spring中,使其AOP生效;也可以做一些其他的自动配置。
 */
@Configuration
@AutoConfigureAfter({RedissonAutoConfiguration.class})
@EnableAspectJAutoProxy(
        exposeProxy = true
)
// @Import用来导入@Configuration注解的配置类、声明@Bean注解的bean方法、导入ImportSelector的实现类或导入ImportBeanDefinitionRegistrar的实现类。
@Import({RedissonAutoConfiguration.class, LockAspect.class})
public class LockAutoConfiguration {
    public LockAutoConfiguration() {
    }
}

6. 测试

(1) 其他项目引入依赖

        <!--引入分布式锁-->
        <dependency>
            <groupId>cn.qz.cloud</groupId>
            <artifactId>cloud-common-lock</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

(2) 主启动类开启分布式锁

package cn.qz.cloud;

import cn.qz.lock.anno.EnableDistributedLock;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;

/**
 * @Author: qlq
 * @Description
 * @Date: 22:08 2020/10/17
 */
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
@EnableHystrix
// 开启分布式锁注解(此注解实际为了将bean注入到Spring,如果包名可以被扫描到不需要打此注解也可以)
@EnableDistributedLock
public class PaymentHystrixMain8081 {

    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8081.class, args);
    }

    /**
     * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
     * ServletRegistrationBean因为SpringBoot的默认路径不是 “/hystrix.stream"
     * 只要在自己的项目里配置上下的servlet就可以了
     */
    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }
}

(3) 建立 Controller进行测试

    /********S 测试分布式锁*****/
    @GetMapping("test1")
    @DistributedLock("redissonTest:test1")
    public void test1(@DistributedLockKey String value1) {
        try {
            Thread.sleep(50 * 1000);
        } catch (InterruptedException e) {
        }
    }

    @GetMapping("test2")
    @DistributedLock("redissonTest:test2")
    public void test2(String value1) {
        try {
            Thread.sleep(50 * 1000);
        } catch (InterruptedException e) {
        }
    }
    /********E 测试分布式锁*****/

(4) 访问Controller后,查看redis生成的数据即可验证分布式锁是否生效

 

  需要注意AOP的自调用问题。 

 

补充:关于锁的删除问题 

  删除的时候肯定不是任何一个线程都可以删除,只有加锁的线程可以删除,这也就明白了上面用hash存储并且key设计为UUID:ThreadId的原因,是为了在删除的时候便于判断删除的线程是否是加锁时候的线程。

 比如:key里面包含了当前lock的id信息和加锁时候的线程ID,value 是重入的次数。如下是lock的信息

 查看其删除方法:org.redisson.RedissonFairLock#unlockInnerAsync

    protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "while true do local firstThreadId2 = redis.call('lindex', KEYS[2], 0);if firstThreadId2 == false then break;end; local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));if timeout <= tonumber(ARGV[4]) then redis.call('zrem', KEYS[3], firstThreadId2); redis.call('lpop', KEYS[2]); else break;end; end;if (redis.call('exists', KEYS[1]) == 0) then local nextThreadId = redis.call('lindex', KEYS[2], 0); if nextThreadId ~= false then redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); end; return 1; end;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; end; redis.call('del', KEYS[1]); local nextThreadId = redis.call('lindex', KEYS[2], 0); if nextThreadId ~= false then redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); end; return 1; ", Arrays.asList(this.getName(), this.threadsQueueName, this.timeoutSetName, this.getChannelName()), new Object[]{LockPubSub.unlockMessage, this.internalLockLeaseTime, this.getLockName(threadId), System.currentTimeMillis()});
    }

单独拿出来其脚本如下:

while true do local firstThreadId2 = redis.call('lindex', KEYS[2], 0);if firstThreadId2 == false then break;end; local timeout = tonumber(redis.call('zscore', KEYS[3], firstThreadId2));if timeout <= tonumber(ARGV[4]) then redis.call('zrem', KEYS[3], firstThreadId2); redis.call('lpop', KEYS[2]); else break;end; end;if (redis.call('exists', KEYS[1]) == 0) then local nextThreadId = redis.call('lindex', KEYS[2], 0); if nextThreadId ~= false then redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); end; return 1; end;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; end; redis.call('del', KEYS[1]); local nextThreadId = redis.call('lindex', KEYS[2], 0); if nextThreadId ~= false then redis.call('publish', KEYS[4] .. ':' .. nextThreadId, ARGV[1]); end; return 1; 

keys如下:

 params如下:

补充:关于原理 

1. 逻辑:

1. 分布式情况下区分机器

  每个服务启动的时候会有一个单例,连接管理器,然后会生成唯一的ID,后续相关的key 操作会携带这个ID,标识当前机器。

  org.redisson.config.ConfigSupport#createConnectionManager 生成唯一ID, 并且创建链接管理器(用于分布式环境判断持有锁是否是当前机器的当前线程)。

2. 续期问题

有的场景下,我们不能正确评估锁的时间。 比如锁上了30 s,但是确实存在超过 30s 的情况,redission 的设计思路是后台有个线程检测。当剩余时间低于20s,且线程存活的情况下就续期到30s。

boolean tryLock(long time, TimeUnit unit) throws InterruptedException; # 这种会自动续期, time 是等待时间,持有时间默认30s, 低于20 s 自动续期为30 s

boolean tryLock(long var1, long var3, TimeUnit var5) throws InterruptedException; # 这种不会自动续期

------ 查看源码:
org.redisson.RedissonLock#tryAcquireAsync 这里有判断,如果传的持有时间为-1, 会将持有时间设为30 s。 并且为该key会添加监听事件

2. 非公平锁:

1.测试以及查看数据结构

代码:

package com.bj58.hy.algorith.platform.abtest.utils.spring;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 分布式锁
 */
@Component
public class DistributedLockUtil {

    @Autowired
    private RedissonClient redissonClient;

    public boolean tryLock(String lockKey, long waitTime, long holdTime, TimeUnit timeUnit) {
        RLock fairLock = redissonClient.getLock(lockKey);
        boolean lockResult = false;
        try {
            lockResult = fairLock.tryLock(waitTime, holdTime, timeUnit);
        } catch (InterruptedException ignore) {
            // ignore
        }
        return lockResult;
    }

    public boolean unlock(String lockKey) {
        RLock fairLock = redissonClient.getLock(lockKey);
        if (fairLock.isHeldByCurrentThread()) {
            fairLock.unlock();
            return true;
        }

        return false;
    }

}

--- 测试代码
    @Test
    public void test() {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                boolean b = distributedLockUtil.tryLock(lockKey, 20, 10, TimeUnit.MINUTES);
                if (b) {
                    PrintTool.printTimeAndThread("lock success");
                    // 模拟处理2min
                    PrintTool.sleep(15000);
                    boolean unlock = distributedLockUtil.unlock(lockKey);
                    PrintTool.printTimeAndThread("unlock " + unlock);
                } else {
                    PrintTool.printTimeAndThread("lock fail");
                }
            }).start();
        }

        PrintTool.sleep(1000000);
    }

2.查看其生成的redis 结构

xxx> ttl custom_lockKey
(integer) 594
xxx> type custom_lockKey
hash
# 查看hash 里面存的key 以及值,key 是UUID(标识当前机器):持有锁的线程ID
xxx> hgetall custom_lockKey
1) "62721d17-69ab-4d97-b378-304fa1fbb6db:87"
2) "1"

3. 公平锁

换成公平锁查看其数据结构:

xxx> keys cus*
1) "custom_lockKey"
xxx> keys redis*
1) "redisson_lock_timeout:{custom_lockKey}"
2) "redisson_lock_queue:{custom_lockKey}"
xxx> type custom_lockKey
hash
xxx> ttl custom_lockKey
(integer) 586
xxx> hgetall custom_lockKey
1) "241dac44-4b81-434c-a3fe-6ad50bef0d22:88"
2) "1"
xxx> type redisson_lock_timeout:{custom_lockKey}
zset
xxx> zrange redisson_lock_timeout:{custom_lockKey} 0 -1 withscores
1) "241dac44-4b81-434c-a3fe-6ad50bef0d22:86"
2) "1700644977559"
3) "241dac44-4b81-434c-a3fe-6ad50bef0d22:87"
4) "1700644977559"
xxx> type redisson_lock_queue:{custom_lockKey}
list
xxx> lrange redisson_lock_queue:{custom_lockKey} 0 -1
1) "241dac44-4b81-434c-a3fe-6ad50bef0d22:86"
xxx> ttl redisson_lock_queue:{custom_lockKey}
(integer) -1

一个为key 的hash 结构同上面普通锁。 另外有两个数据结构:

一个list有序队列 代表等待的顺序队列,里面存的也是UUID+线程ID;

一个zset队列 代表队列超时时间,里面存的也是UUID+线程ID,分数是超时时间。

 

posted @ 2021-02-01 18:45  QiaoZhi  阅读(23558)  评论(1编辑  收藏  举报