Home  >  Article  >  Java  >  The king solution among distributed locks - Redisson

The king solution among distributed locks - Redisson

Java后端技术全栈
Java后端技术全栈forward
2023-08-24 15:31:191125browse

The king solution among distributed locks - Redisson

There are various solutions on the Internet for implementing distributed locks in Redis, but what is the king solution?

The answer is: Redisson.

Let’s first take a look at what the Redis official website says about distributed locks:

The king solution among distributed locks - Redisson

The framework of the Java version of distributed locks is Redisson.

This practical content will be based on my open source project PassJava to integrate Redisson.

I uploaded backend, frontend, 小program to the same warehouse, you can use Github or codeyun visit. The address is as follows:

Github: https://github.com/Jackson0714/PassJava-Platform

Code Cloud:https: //gitee.com/jayh2018/PassJava-Platform

Supporting Tutorial: www.passjava.cn

Before the actual combat, let’s take a look at the usage The principle of Redisson.

1. What is Redisson?

If you have been using Redis before, you will get twice the result with half the effort by using Redisson. Redisson provides the simplest and most convenient way to use Redis.

The purpose of Redisson is to promote the separation of concerns (Separation of Concern) of Redis among users, so that users can focus more on processing business logic.

Redisson is a Java in-memory data grid (In-Memory Data Grid) implemented on the basis of Redis.

The king solution among distributed locks - Redisson
  • Netty Framework: Redisson adopts the NIO-based Netty framework, which can not only serve as the Redis underlying driver client , it has the function of providing connection to various configuration forms of Redis, the function of sending Redis commands synchronously, asynchronously, asynchronously streaming or pipeline, LUA script execution processing, and the function of processing the returned results

  • Basic data structure: The native Redis Hash, List, Set, String, Geo, HyperLogLog and other data structures are encapsulated into the most familiar mapping (Map) , ## in Java #List,Set,General Object Bucket,Geospatial Bucket, Cardinality estimation algorithm (HyperLogLog) and other structures,

  • Distributed data structure: On this basis, distribution is also provided Multimap, LocalCachedMap, SortedSet, ScoredSortedSet, LexSortedSet, Queue, Blocking Queue, Bounded Blocking Queue, Deque, Blocking Deque, Blocking Fair Queue, Delayed Queue, Bloom Filter , atomic integer (AtomicLong), atomic double-precision floating point number (AtomicDouble), BitSet and other distributed data structures that Redis does not originally have.

  • Distributed lock: Redisson also implements higher-level locks like distributed lockLock mentioned in the Redis document Application scenarios. In fact, Redisson does not stop there. On the basis of distributed locks, it also provides Interlock (MultiLock) , ReadWriteLock (ReadWriteLock) , Fair Lock (Fair Lock) , Red Lock (RedLock) , Semaphore (Semaphore) , Expirable Semaphore (PermitExpirableSemaphore) and Locking (CountDownLatch) These are basic components that are crucial to multi-threaded high-concurrency applications. It is through the implementation of high-order application solutions based on Redis that Redisson becomes an important tool for building distributed systems.

  • Node: Redisson as an independent node can be used to independently execute other nodes published to Distributed Execution Service and Remote tasks in Distributed Scheduling Service.

2. Integrate Redisson

There are two options for integrating Redisson with Spring Boot:

  • Programmatic configuration.
  • File mode configuration.

This article introduces how to integrate Redisson programmatically.

2.1 Introducing Maven dependencies

Introduce redisson’s maven dependencies in the pom.xml of the passjava-question microservice.

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.15.5</version>
</dependency>

2.2 Custom configuration class

The following code is the configuration of single node Redis.

@Configuration
public class MyRedissonConfig {
    /**
     * 对 Redisson 的使用都是通过 RedissonClient 对象
     * @return
     * @throws IOException
     */
    @Bean(destroyMethod="shutdown") // 服务停止后调用 shutdown 方法。
    public RedissonClient redisson() throws IOException {
        // 1.创建配置
        Config config = new Config();
        // 集群模式
        // config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
        // 2.根据 Config 创建出 RedissonClient 示例。
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        return Redisson.create(config);
    }
}

2.3 Test configuration class

Create a new unit test method.

@Autowired
RedissonClient redissonClient;

@Test
public void TestRedisson() {
    System.out.println(redissonClient);
}

We run this test method and print out redissonClient

org.redisson.Redisson@77f66138

3. Distributed reentrant lock

3.1 Reentrant Lock test

Redisson distributed reentrant lock based on RedisRLockThe Java object implements the java.util.concurrent.locks.Lock interface. It also provides asynchronous (Async), reflective (Reactive) and RxJava2 standard interfaces.

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

We use the passjava open source project to test two points of reentrant locks:

  • (1) Multiple threads seize the lock. Do subsequent locks need to wait? ?
  • (2) If the service where the thread that preempted the lock is stopped, will the lock be released?

3.1.1 Verification 1: Are reentrant locks blocking?

In order to verify the above two points, I wrote a demo program: the code process is to set the WuKong-lock lock, then lock it, print the thread ID, and wait for 10 seconds to release the lock. Finally, the response is returned: "test lock ok".

@ResponseBody
@GetMapping("test-lock")
public String TestLock() {
    // 1.获取锁,只要锁的名字一样,获取到的锁就是同一把锁。
    RLock lock = redisson.getLock("WuKong-lock");

    // 2.加锁
    lock.lock();
    try {
        System.out.println("加锁成功,执行后续代码。线程 ID:" + Thread.currentThread().getId());
        Thread.sleep(10000);
    } catch (Exception e) {
        //TODO
    } finally {
        lock.unlock();
        // 3.解锁
        System.out.println("Finally,释放锁成功。线程 ID:" + Thread.currentThread().getId());
    }

    return "test lock ok";
}

Verify the first point first and use two http requests to test the preemption lock.

Requested URL:

http://localhost:11000/question/v1/redisson/test/test-lock
The king solution among distributed locks - Redisson

第一个线程对应的线程 ID 为 86,10秒后,释放锁。在这期间,第二个线程需要等待锁释放。

第一个线程释放锁之后,第二个线程获取到了锁,10 秒后,释放锁。

画了一个流程图,帮助大家理解。如下图所示:

The king solution among distributed locks - Redisson
  • 第一步:线程 A 在 0 秒时,抢占到锁,0.1 秒后,开始执行等待 10 s。
  • 第二步:线程 B 在 0.1 秒尝试抢占锁,未能抢到锁(被 A 抢占了)。
  • 第三步:线程 A 在 10.1 秒后,释放锁。
  • 第四步:线程 B 在 10.1 秒后抢占到锁,然后等待 10 秒后释放锁。

由此可以得出结论,Redisson 的可重入锁(lock)是阻塞其他线程的,需要等待其他线程释放的。

3.1.2 验证二:服务停了,锁会释放吗?

如果线程 A 在等待的过程中,服务突然停了,那么锁会释放吗?如果不释放的话,就会成为死锁,阻塞了其他线程获取锁。

我们先来看下线程 A 的获取锁后的,Redis 客户端查询到的结果,如下图所示:

The king solution among distributed locks - Redisson

WuKong-lock 有值,而且大家可以看到 TTL 在不断变小,说明 WuKong-lock 是自带过期时间的。

通过观察,经过 30 秒后,WuKong-lock 过期消失了。说明 Redisson 在停机后,占用的锁会自动释放。

The king solution among distributed locks - Redisson

那这又是什么原理呢?这里就要提一个概念了,看门狗

The king solution among distributed locks - Redisson

3.2 看门狗原理

如果负责储存这个分布式锁的 Redisson 节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。

默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

如果我们未制定 lock 的超时时间,就使用 30 秒作为看门狗的默认时间。只要占锁成功,就会启动一个定时任务:每隔 10 秒重新给锁设置过期的时间,过期时间为 30 秒。

如下图所示:

The king solution among distributed locks - Redisson
看门狗原理图-1

当服务器宕机后,因为锁的有效期是 30 秒,所以会在 30 秒内自动解锁。(30秒等于宕机之前的锁占用时间+后续锁占用的时间)。

如下图所示:

The king solution among distributed locks - Redisson
看门狗原理图-2

3.3 设置锁过期时间

我们也可以通过给锁设置过期时间,让其自动解锁。

如下所示,设置锁 8 秒后自动过期。

lock.lock(8, TimeUnit.SECONDS);

如果业务执行时间超过 8 秒,手动释放锁将会报错,如下图所示:

The king solution among distributed locks - Redisson

所以我们如果设置了锁的自动过期时间,则执行业务的时间一定要小于锁的自动过期时间,否则就会报错。

四、王者方案

上一篇我讲解了分布式锁的五种方案:Redis 分布式锁|从青铜到钻石的五种演进方案,这一篇主要是讲解如何用 Redisson 在 Spring Boot 项目中实现分布式锁的方案。

因为 Redisson 非常强大,实现分布式锁的方案非常简洁,所以称作王者方案

原理图如下:

The king solution among distributed locks - Redisson

代码如下所示:

// 1.设置分布式锁
RLock lock = redisson.getLock("lock");
// 2.占用锁
lock.lock();
// 3.执行业务
...
// 4.释放锁
lock.unlock();

和之前 Redis 的方案相比,简洁很多。

下面讲解下 Redisson 的其他几种分布式锁,相信大家在以后的项目中也会用到。

五、分布式读写锁

基于 Redis 的 Redisson 分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。其中读锁和写锁都继承了 RLock接口。

写锁是一个排他锁(互斥锁),读锁是一个共享锁。

  • 读锁 + 读锁:相当于没加锁,可以并发读。
  • 读锁 + 写锁:写锁需要等待读锁释放锁。
  • 写锁 + 写锁:互斥,需要等待对方的锁释放。
  • 写锁 + 读锁:读锁需要等待写锁释放。
The king solution among distributed locks - Redisson

示例代码如下:

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

另外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();

六、分布式信号量

基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。

关于信号量的使用大家可以想象一下这个场景,有三个停车位,当三个停车位满了后,其他车就不停了。可以把车位比作信号,现在有三个信号,停一次车,用掉一个信号,车离开就是释放一个信号。

The king solution among distributed locks - Redisson

我们用 Redisson 来演示上述停车位的场景。

先定义一个占用停车位的方法:

/**
* 停车,占用停车位
* 总共 3 个车位
*/
@ResponseBody
@RequestMapping("park")
public String park() throws InterruptedException {
  // 获取信号量(停车场)
  RSemaphore park = redisson.getSemaphore("park");
  // 获取一个信号(停车位)
  park.acquire();

  return "OK";
}

再定义一个离开车位的方法:

/**
 * 释放车位
 * 总共 3 个车位
 */
@ResponseBody
@RequestMapping("leave")
public String leave() throws InterruptedException {
    // 获取信号量(停车场)
    RSemaphore park = redisson.getSemaphore("park");
    // 释放一个信号(停车位)
    park.release();

    return "OK";
}

为了简便,我用 Redis 客户端添加了一个 key:“park”,值等于 3,代表信号量为 park,总共有三个值。

The king solution among distributed locks - Redisson

然后用 postman 发送 park 请求占用一个停车位。

The king solution among distributed locks - Redisson

Then check the value of park on the redis client and find that it has been changed to 2. Continue to call twice and find that park is equal to 0. When the fourth call is made, you will find that the request has been waiting, indicating that there are not enough parking spaces. If you want to avoid blocking, you can use tryAcquire or tryAcquireAsync.

We call the method of leaving the parking space again, and the value of park changes to 1, which means there is 1 parking space left.

Note: If the semaphore release operation is performed multiple times, the remaining semaphore will continue to increase instead of being capped after 3.

Other distributed locks:

  • Fair Lock

  • Interlock (MultiLock)

  • Red Lock (RedLock)

  • ##Read-write lock ( ReadWriteLock)

  • Expirable Semaphore (PermitExpirableSemaphore)

  • Latch (CountDownLatch)

The king solution among distributed locks - Redisson
There are other distributed locks that will not be discussed in this article. Interested students can check the official documentation.

The above is the detailed content of The king solution among distributed locks - Redisson. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:Java后端技术全栈. If there is any infringement, please contact admin@php.cn delete