Home  >  Article  >  Database  >  Let's talk about the principle of distributed locks and how Redis implements distributed locks

Let's talk about the principle of distributed locks and how Redis implements distributed locks

藏色散人
藏色散人forward
2023-01-27 07:30:011163browse

This article brings you relevant knowledge about redis, which mainly introduces what distributed locks are? How does Redis implement distributed locks? What conditions need to be met? Let’s take a look below, I hope it will be helpful to friends in need.

1. Basic principles of distributed locks

Distributed lock: A lock that is visible and mutually exclusive to multiple processes in a distributed system or cluster mode.

Conditions that distributed locks should meet:

  • Visibility: Multiple threads can see the same result. Note: The visibility mentioned here does not refer to the memory visibility in concurrent programming, but only that multiple processes can perceive changes. Meaning
  • Mutual exclusion: Mutual exclusion is the most basic condition for distributed locks, making the program execute serially
  • High availability: The program is not easy to crash, and high availability is guaranteed at all times
  • High performance: Since locking itself reduces performance, all distributed locks require higher locking performance and lock release performance
  • Safety: Security is also part of the program An essential link

There are three common distributed locks:

  • Mysql: mysql itself has a lock mechanism , but due to the average performance of mysql itself, when using distributed locks, it is actually relatively rare to use mysql as a distributed lock

  • Redis: redis is very common as a distributed lock This way of usage, now enterprise-level development basically uses redis or zookeeper as a distributed lock. Using the setnx method, if the key is inserted successfully, it means that the lock is obtained. If someone inserts successfully, and others fail to insert, it means that the lock cannot be obtained. Lock, use this set of logic to implement distributed locks

  • Zookeeper: zookeeper is also a better solution for implementing distributed locks in enterprise-level development

Lets talk about the principle of distributed locks and how Redis implements distributed locks

2. Implementing distributed locks based on Redis

Two basic methods need to be implemented when implementing distributed locks:

  • Acquire lock:

    • Mutual exclusion: ensure that only one thread can acquire the lock
    • Non-blocking: try once, return true if successful, false if failed
  • Release lock:

    • Manual release
    • Timeout release: Add a timeout when acquiring the lock

Principle of distributed lock based on Redis:

SET resource_name my_random_value NX PX 30000
  • resource_name: resource name, different locks can be distinguished according to different businesses
  • my_random_value: Random value, the random value of each thread is different, used for verification when releasing the lock
  • NX: The setting is successful when the key does not exist, and the setting is unsuccessful when the key exists
  • PX: Automatic expiration time, if an abnormality occurs, the lock can expire.

Using the atomicity of NX, when multiple threads are concurrent, only one thread can be set successfully. Successful setting means obtaining the lock. Subsequent business processing can be performed; if an exception occurs and the lock validity period expires, the lock is automatically released;

Version 1

1. Define the ILock interface

public interface ILock extends AutoCloseable {
    /**
     * 尝试获取锁
     *
     * @param timeoutSec 锁持有的超时时间,过期后自动释放
     * @return true代表获取锁成功;false代表获取锁失败
     */
    boolean tryLock(long timeoutSec);

    /**
     * 释放锁
     * @return
     */
    void unLock();
}

2. Implement distributed lock based on Redis - RedisLock

public class SimpleRedisLock {
    private final StringRedisTemplate stringRedisTemplate;
    private final String name;

    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    private static final String KEY_PREFIX = "lock:";

    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        String threadId = Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unLock() {
        //通过del删除锁
        stringRedisTemplate.delete(KEY_PREFIX + name);
    }

    @Override
    public void close() {
        unLock();
    }
}

Lock accidental deletion problem

Problem description:

Thread 1 holding the lock is blocked inside the lock. At this time, the lock is automatically released after timeout. At this time, thread 2 tries to obtain the lock. Then, during the execution of thread 2 holding the lock, thread 1 reacts and continues execution. When it comes to deleting the lock logic, the lock that should belong to thread 2 will be deleted. This is the case of accidentally deleting the lock.

Solution:

When storing the lock, put the identifier of your own thread. When deleting the lock, determine whether the identifier of the current lock is the one you saved. If it is entered, it will be deleted. If it is not, it will not be deleted.

Version 2: Solve the problem of accidental lock deletion

public class SimpleRedisLock {
    private final StringRedisTemplate stringRedisTemplate;
    private final String name;

    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unLock() {
        // 获取线程标示
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        // 获取锁中的标示
        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
        // 判断标示是否一致
        if(threadId.equals(id)) {
            // 释放锁
            stringRedisTemplate.delete(KEY_PREFIX + name);
        }
    }

    @Override
    public void close() {
        unLock();
    }
}

The atomicity problem of lock release

Problem analysis:

The above release The lock code still has the problem of accidentally deleting the lock. When thread 1 obtains the thread ID in the lock and determines that it is its own lock based on the ID, the lock is automatically released when it expires. It happens that thread 2 tries to acquire the lock and gets the lock. When thread 1 still performs the operation of releasing the lock, the lock held by thread 2 is accidentally deleted.

The reason is that the lock release process implemented by java code is not an atomic operation and has thread safety issues.

Solution:

Redis provides Lua script function. Writing multiple Redis commands in one script can ensure the atomicity of execution of multiple commands.

Version 3: Call Lua script to transform distributed lock

public class SimpleRedisLock implements ILock {
    private final StringRedisTemplate stringRedisTemplate;
    private final String name;

    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unLock() {
        String script = "if redis.call("get",KEYS[1]) == ARGV[1] then\n" +
                " return redis.call("del",KEYS[1])\n" +
                "else\n" +
                " return 0\n" +
                "end";
        //通过执行lua脚本实现锁删除,可以校验随机值
        RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class);
        stringRedisTemplate.execute(redisScript,
                Collections.singletonList(KEY_PREFIX + name),
                ID_PREFIX + Thread.currentThread().getId());
    }

    @Override
    public void close() {
        unLock();
    }
}

Recommended learning: "Redis Video Tutorial"

The above is the detailed content of Let's talk about the principle of distributed locks and how Redis implements distributed locks. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.im. If there is any infringement, please contact admin@php.cn delete