Redis distributed lock implementation

Business as the business is responsible for more and more, now, to support distributed and high concurrency is the basic requirement, involving high concurrent and distributed will certainly involve the distributed locking mechanism, a distributed lock is to ensure that the distributed environment, there is only one machine to be able to get a lock object, the rest were waiting for the lock is released, then apply for lock resources!

Distributed locks must comply with the following principles:

  1. There can only be one at a timeMachine (process or thread)Can get the lock object!
  2. Have expiration mechanism, prevent machine downtime did not release the lock caused deadlock!
  3. Locking and unlocking must be a machine (thread, process)!
  4. How can a live machine still do a complete add unlock operation in a cluster environment?

First, the idea diagram

Second, train of thought diagram realization

1. Implement correctly

This implementation is implemented by Jedis, which simulates the use of distributed lock in multi-threaded environment

 <! -- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>
Copy the code

Defines the interface

package com.lock.jedis;

import java.util.concurrent.TimeUnit;

/** * distributed lock *@author huangfu
 */
public interface DistributedLock {

    /** * Attempts to lock the thread *@paramLockName lockName *@paramValue * lockValue lock@paramTimeUnit timeUnit *@paramTime time *@returnCheck whether the lock is successful */
    boolean tryLock(String lockName, String lockValue, TimeUnit timeUnit, long time);

    /** * lock *@paramLockName lockName *@paramValue * lockValue lock@paramTimeUnit timeUnit *@paramTime time * /
    void lock(String lockName, String lockValue, TimeUnit timeUnit, long time);
    /** * Thread unlocks *@paramLockName Name of the lock to be unlocked */
    void unLock(String lockName);
}
Copy the code

Jedis implements distributed locking

package com.lock.jedis.impl;

import com.lock.jedis.DistributedLock;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/** * Jedis implements distributed locking *@author huangfu
 */
public class JedisDistributedLock implements DistributedLock {
    private final static String SCRIPT_UNLOCK_LUA = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                                                 "return redis.call(\"del\",KEYS[1])\n" +
                                             "else\n" +
                                                  "return 0\n" +
                                              "end";
    private final static ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<String>();
    public static final String OK = "OK";
    JedisPool pool;
    public JedisDistributedLock(a) {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(8);
        config.setMaxTotal(18);
        pool = new JedisPool(config, "192.168.1.4".6379.2000);
    }


    @Override
    public boolean tryLock(String lockName, String lockValue, TimeUnit timeUnit, 
                           											long time) {
        Jedis jedis = pool.getResource();
        try {
            long timeout = timeUnit.toSeconds(time);
            SetParams setParams = new SetParams();
            setParams.nx();
            setParams.ex((int)timeout);
            String result = jedis.set(lockName, lockValue, setParams);
            if (OK.equals(result)) {
                THREAD_LOCAL.set(lockValue);
                return true;
            }else{
                return false; }}finally{ jedis.close(); }}@Override
    public void lock(String lockName, String lockValue, TimeUnit timeUnit, long time) {
        if (tryLock(lockName,lockValue,timeUnit,time)) {
            return;
        } else {
            try {
                Thread.sleep(ThreadLocalRandom.current().nextInt(2000.3000));
            } catch(InterruptedException e) { e.printStackTrace(); } lock(lockName,lockValue,timeUnit,time); }}@Override
    public void unLock(String lockName) {
        Jedis jedis = pool.getResource();
        Object eval = jedis.eval(SCRIPT_UNLOCK_LUA, Collections.singletonList(lockName), 										Collections.singletonList(THREAD_LOCAL.get()));
        if(Integer.parseInt(eval.toString()) == 0){
            jedis.close();
            throw new RuntimeException("Unlock failed!"); } THREAD_LOCAL.remove(); }}Copy the code

2. Code comments

  1. Why set expiration time

    • Ensure that locks can still be released when the service is down!
  2. Why is a value operation performed when deleting a lock?

    • In multi-threaded environment, for locks, this thread can only delete the lock added by this thread, can not delete the lock added by other threads! This example uses UUID to achieve value uniqueness
  3. Why use SetParams to host setnx arguments in the tryLock method? Why do you need to use lua scripts to delete locks?

    • For both setnx and DEL operations, atomic code must be guaranteed
SetParams setParams = new SetParams();
setParams.nx();
setParams.ex((int)timeout);
String result = jedis.set(lockName, lockValue, setParams);
Copy the code

The code in this article is equivalent to

jedis.setnx(lockName,lockValue);
jedis.expire(lockName,(int)timeout);
Copy the code

But why not? Because redis is a single thread, only one command can be executed at the same time, and this writing method is considered to be two commands by Redis, then there may be problems when executing in a multi-machine or multi-threaded environment!

Let’s look at a picture. Normal:

However, in the abnormal case:

Why do you need to use lua scripts to delete locks?

In this paper, the code

if redis.call("get",KEYS[1]) == ARGV[1] then
	return redis.call("del",KEYS[1])
else
	return 0
end
Copy the code

This Lua script is equivalent to code

String s = jedis.get(lockName);
if(THREAD_LOCAL.get().equals(s)){
    jedis.del(lockName);
}
Copy the code

This code, like the one above, is no guarantee of atomicity! Let’s see a picture!

normal

Abnormal situation

Ok, this issue is the use of Lua script to achieve distributed lock content, this is the basic principle of distributed lock implementation, but the use of reproduction environment will have very serious problems!

  1. Modern production environments are all about high availability, so how does Redis ensure high availability of distributed locks in the cluster environment and sentinel cluster environment?
  2. The current problem does not guarantee that the service time is absolutely shorter than the lock expiration time, but in uncertain service scenarios, distributed lock failure may still occur.

How to solve the above problems? That’s all for tomorrow! I’m afraid the article is too long for you to read! Ha ha ha!


If the understanding of the article is wrong, welcome big men private chat correction! Welcome to pay attention to the author’s public number, progress together, learn together!