The previous article explained how to use Redis to implement distributed locking in five ways, but we still have a better king solution, is to use Redisson.

Caching series of articles:

Cache actual combat (1) : 20 figure | 6 thousand words | cache actual combat (1)

Cache combat (2) : Redis distributed lock | from bronze to diamond five evolution scheme

Cache combat (3) : the king of distributed lock scheme – Redisson

Let’s take a look at what Redis says,

The Java version of the distributed locking framework is Redisson. This hands-on article will integrate Redisson based on my open source project PassJava.

I uploaded the back end, front end, small program to the same warehouse, you can access through Github or code cloud. The address is as follows:

Making: github.com/Jackson0714…

Yards cloud: gitee.com/jayh2018/Pa…

Accompanying tutorial: www.passjava.cn

Before going into action, let’s take a look at how Redisson works.

What is a Redisson?

Redisson is the easiest and easiest way to use Redis if you’re already using it.

The goal of Redisson is to promote the Separation of Concern for Redisso that users can focus more on business logic.

Redisson is a Java in-memory Data Grid based on Redis.

  • Netty framework: Redisson uses Netty framework based on NIO, not only can be used as Redis underlying driver client, with a variety of Redis configuration form of connection function, Redis command can be sent in synchronous, asynchronous form, asynchronous flow form or pipeline form of sending function, LUA script execution processing, And the ability to process returned results

  • Basic data structure: Encapsulate native Redis Hash, List, Set, String, Geo, HyperLogLog and other data structures into Java’s most familiar Map, List, Set, universal Object Bucket. Geospatial Bucket, HyperLogLog and other structures,

  • Distributed data structures: On this basis, distributed Multimap, local cache Map, ordered set, ScoredSortedSet, LexSortedSet, Queue are also provided. Bounded Blocking Queue, Deque, Blocking Deque, Blocking Fair Queue, Delayed Queue, Bloom Filter, AtomicLong, AtomicDouble, BitSet and other distributed data structures that Redis didn’t have originally.

  • Distributed locking: Redisson also implements higher-level application scenarios mentioned in the Redis documentation, such as distributed locking. In fact, Redisson does not stop there. On the basis of distributed locks, Redisson also provides MultiLock, ReadWriteLock, Fair Lock, RedLock, Semaphore, Permitable semaphore and CountDownLatch are the basic components that are critical to multi-threaded high-concurrency applications in practice. It is the implementation of higher-order redis-based applications that makes Redisson an important tool for building distributed systems.

  • Node: Redisson as a standalone node can be used to independently perform remote tasks that other nodes publish to distributed execution services and distributed scheduling services.

Second, integrate Redisson

Spring Boot integration Redisson has two options:

  • Programmatic configuration.
  • File mode configuration.

This article shows you how to integrate Redisson programmatically.

2.1 Introducing Maven dependencies

Redisson’s Maven dependency is introduced in the passjava-question microservice pom.xml.

<! -- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.15. 5</version>
</dependency>
Copy the code

2.2 Customizing Configuration Classes

The following code is the configuration of single-node Redis.

@Configuration
public class MyRedissonConfig {
    /** * All use of Redisson is through the RedissonClient object *@return
     * @throws IOException
     */
    @Bean(destroyMethod="shutdown") // Call shutdown when the service stops.
    public RedissonClient redisson(a) throws IOException {
        // 1. Create a configuration
        Config config = new Config();
        // Cluster mode
        / / config. UseClusterServers (.) addNodeAddress (127.0.0.1: "7004", "127.0.0.1:7001");
        // 2. Create a RedissonClient example based on Config.
        config.useSingleServer().setAddress("Redis: / / 127.0.0.1:6379");
        returnRedisson.create(config); }}Copy the code

2.3 Testing configuration classes

Create a new unit test method.

@Autowired
RedissonClient redissonClient;

@Test
public void TestRedisson(a) {
    System.out.println(redissonClient);
}
Copy the code

We run the test method and print out the redissonClient

org.redisson.Redisson@77f66138
Copy the code

Distributed reentrant lock

3.1 Reentrant lock test

Based on Redis Redisson distributed reentrant Lock RLockJava object implements the Java. Util. Concurrent. The locks. Lock interface. It also provides Async, Reactive and RxJava2 standard interfaces.

RLock lock = redisson.getLock("anyLock");
// The most common way to use it
lock.lock();
Copy the code

We used passJava, an open source project, to test two points of the reentrant lock:

  • (1) If multiple threads preempt the lock, does the subsequent lock need to wait?
  • (2) Will the lock be released if the service of the thread that preempted the lock stops?

3.1.1 Verification one: Does a reentrant lock block?

In order to verify the above two points, I wrote a demo program: the code flow is to set wukong-lock, lock, print the thread ID, wait 10 seconds to release the lock, and finally return the response: “test lock OK”.

@ResponseBody
@GetMapping("test-lock")
public String TestLock(a) {
    // 1. Obtain the lock. As long as the lock name is the same, it is the same lock.
    RLock lock = redisson.getLock("WuKong-lock");

    / / 2. Lock
    lock.lock();
    try {
        System.out.println("Lock successful, execute subsequent code. Thread ID:" + Thread.currentThread().getId());
        Thread.sleep(10000);
    } catch (Exception e) {
        //TODO
    } finally {
        lock.unlock();
        / / 3. Unlock
        System.out.println("Finally, the lock was released successfully. Thread ID:" + Thread.currentThread().getId());
    }

    return "test lock ok";
}
Copy the code

Verify the first point and test the lock preemption with two HTTP requests.

Requested URL:

http://localhost:11000/question/v1/redisson/test/test-lock
Copy the code

The corresponding thread ID for the first thread is 86. After 10 seconds, the lock is released. In the meantime, the second thread needs to wait for the lock to be released.

After the first thread releases the lock, the second thread acquires it, and after 10 seconds, the lock is released.

I drew a flow chart to help you understand. As shown below:

  • Step 1: Thread A preempts the lock at 0 seconds, 0.1 seconds later, starts to wait for 10 seconds.
  • Step 2: Thread B tries to preempt the lock at 0.1 seconds and fails to do so (thread A preempts it).
  • Step 3: Thread A releases the lock after 10.1 seconds.
  • Step 4: Thread B preempts the lock after 10.1 seconds and waits 10 seconds to release the lock.

It follows that Redisson’s reentrant lock blocks other threads and waits for them to release.

3.1.2 Verification 2: When the service stops, will the lock be released?

If the service stops while thread A is waiting, will the lock be released? If not released, it becomes a deadlock, blocking other threads from acquiring the lock.

Let’s first take A look at the result queried by Redis client after thread A acquired the lock, as shown in the figure below:

Wukong-lock has a value, and you can see that the TTL is getting smaller and smaller, indicating that Wukong-lock has its own expiration time.

After 30 seconds of observation, Wukong-lock expired and disappeared. Note After Redisson is stopped, the lock occupied by Redisson is automatically released.

So how does that work? Here comes the concept of a guard dog.

3.2 Principle of watchdog

If the Redisson node that stores the distributed lock goes down and the lock happens to be in the locked state, the lock will become locked. To prevent this from happening, Redisson internally provides a lock watchdog that continuously extends the lifetime of the lock before the Redisson instance is closed.

By default, the watchdog check lock timeout time is 30 seconds, but can be by modified Config. LockWatchdogTimeout to specify separately.

If we do not specify a lock timeout, use 30 seconds as the default watchdog time. Once the lock is successfully captured, a timed task will be initiated to reset the lock expiration time to 30 seconds every 10 seconds.

As shown below:

If the server is down, the lock is automatically unlocked within 30 seconds because the lock is valid for 30 seconds. (30 seconds equals the lock occupancy time before the outage + the lock occupancy time after the outage).

As shown below:

3.3 Setting the Lock Expiration Time

We can also set an expiration time for locks to unlock automatically.

Set the lock to expire automatically after 8 seconds, as shown below.

lock.lock(8, TimeUnit.SECONDS);
Copy the code

If the service execution time exceeds 8 seconds, manually releasing the lock will prompt an error, as shown in the following figure:

Therefore, if the automatic expiration time of the lock is set, the service execution time must be shorter than the automatic expiration time of the lock; otherwise, an error will be reported.

Four, king plan

In my last article, I explained five distributed locking schemes: evolution from Bronze to Diamond. This article mainly explained how to use Redisson to implement distributed locking schemes in the Spring Boot project.

Because Redisson isso powerful, the solution for implementing distributed locking isso simple that it is called the king solution.

The schematic diagram is as follows:

The code looks like this:

// 1. Set a distributed lock
RLock lock = redisson.getLock("lock");
// 2
lock.lock();
// 3. Perform services.// release the lock
lock.unlock();
Copy the code

Compared to the previous Redis scheme, it is much simpler.

Distributed read/write lock

Based on Redis Redisson distributed re-entrant read-write lock RReadWriteLock Java object implements the Java. Util. Concurrent. The locks. ReadWriteLock interface. Read and write locks inherit the RLock interface.

A write lock is a slap lock (mutex) and a read lock is a shared lock.

  • Read lock + read lock: it is equivalent to no lock and can be read concurrently.
  • Read lock + write lock: Write lock needs to wait for the read lock to release the lock.
  • Write lock + write lock: mutually exclusive, waiting for each other’s lock to be released.
  • Write lock + Read lock: The read lock must wait for the write lock to be released.

Example code is as follows:

RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// The most common way to use it
rwlock.readLock().lock();
/ / or
rwlock.writeLock().lock();
Copy the code

In addition, Redisson provides the leaseTime parameter to specify the lock time through the lock method. After this time, the lock unlocks automatically.

// Automatically unlocks after 10 seconds
// You do not need to call the unlock method
rwlock.readLock().lock(10, TimeUnit.SECONDS);
/ / or
rwlock.writeLock().lock(10, TimeUnit.SECONDS);

// Try locking. Wait 100 seconds at most. After locking, it will be unlocked automatically within 10 seconds
boolean res = rwlock.readLock().tryLock(100.10, TimeUnit.SECONDS);
/ / or
boolean res = rwlock.writeLock().tryLock(100.10, TimeUnit.SECONDS); . lock.unlock();Copy the code

Distributed semaphores

Based on Redis Redisson distributed signal (Semaphore) Java objects RSemaphore adopted with Java. Util. Concurrent. The Semaphore similar interface and usage. It also provides Async, Reactive and RxJava2 standard interfaces.

In terms of semaphore use you can imagine a scenario where you have three parking Spaces, and when three parking Spaces are full, other cars stop. Parking space can be compared to a signal, now there are three signals, stop a car, use a signal, the car is to release a signal.

We use Redisson to illustrate the above parking space scenario.

Define a way to occupy a parking space:

/** * take up a parking space * 3 Spaces in total */
@ResponseBody
@RequestMapping("park")
public String park(a) throws InterruptedException {
  // Get semaphore (parking lot)
  RSemaphore park = redisson.getSemaphore("park");
  // Get a signal (parking space)
  park.acquire();

  return "OK";
}
Copy the code

Define another way to leave a parking space:

/** * free parking Spaces * total 3 parking Spaces */
@ResponseBody
@RequestMapping("leave")
public String leave(a) throws InterruptedException {
    // Get semaphore (parking lot)
    RSemaphore park = redisson.getSemaphore("park");
    // Release a signal (parking space)
    park.release();

    return "OK";
}
Copy the code

For simplicity, I used the Redis client to add a key: “park” with a value equal to 3, which means the semaphore is park, and there are three values in total.

Then use Postman to send park a request to occupy a parking space.

Then check the value of park in the Redis client and find that it has been changed to 2. After two more calls, park is found to be 0. When the fourth call is made, the request is always waiting, indicating that the parking space is not enough. If you want to avoid blocking, you can use tryAcquire or tryAcquireAsync.

When we call the method of leaving the parking space, the value of park changes to 1, indicating that there is one parking space left.

Note: The semaphore release operation is repeated and the remaining semaphore increases, instead of being capped at 3.

Other distributed locks:

  • Fair Lock

  • MultiLock

  • RedLock

  • ReadWriteLock

  • Permitable Semaphore

  • CountDownLatch

There are other distributed locks that are not covered in this article, but those who are interested can check out the official documentation.

References:

Github.com/redisson/re…