Redis learning route (6) - Redis distributed lock

1. Model of distributed lock

(1) Pessimistic lock: It is believed that thread safety issues will definitely occur, so acquire the lock before operating data to ensure that threads are executed serially. For example, Synchronized and Lock are all pessimistic locks.

  • Advantages: simple and rude
  • Disadvantages: slightly lower performance

(2) Optimistic lock: It is believed that thread safety issues may not necessarily occur, so no lock is added. Only when updating data can it be judged whether other threads have modified the data. If there is no modification, it is considered safe, and the data can be updated by itself; If it has been modified by other threads, it means that a security problem has occurred, and you can retry or make an exception at this time.

  • Advantages: good performance
  • Disadvantages: there is a problem of low success rate

(3) Common implementation methods:

  • Version number method: through the id-stock-version structure, judge whether it has been modified by checking whether the version is the same as this time.
  • CAS method: It is an improved version of the version number method. It uses the structure of old-query-new. Whether the stock obtained through the first query is consistent with the stock submitted for the second time, if they are consistent, old = new;

2. Distributed lock of Redis

(1) The role of distributed locks: As a common JVM lock monitor, each JVM in the cluster can obtain the threads monitored by the lock monitor, and multiple JVMs synchronize thread execution internally.

(2) Requirements for distributed locks: multi-process visibility, mutual exclusion, high availability, high performance, security...

(3) Differences of common distributed locks:

MySQL Redis Zookeeper
mutually exclusive Use the mutex lock mechanism of mysql itself Use mutex commands like setnx Use the uniqueness and order of nodes to achieve mutual exclusion
high availability good good good
high performance generally good generally
safety Disconnect, automatically release the lock Use the lock timeout to release when it expires Temporary nodes, disconnected and automatically released

(4) Redis implements distributed locks

1. Get the lock:

  • Mutual exclusion: ensures that only one thread acquires the lock.SETNX lock thread1

2. Release the lock

  • Release manually: DEL lock
  • Expired release: EXPIRE lock 5

(1) Realize atomic operation through SET operation: SET lock thread1 EX 10 NX , which means to create a lock cache, the value is thread1, and keep it for 10s, and NX is a mutual exclusion operation

(2) The method when the lock acquisition fails:

  • Blocking lock acquisition: wait until a thread releases the lock. (high consumption of CPU resources)
  • Non-blocking lock acquisition: If it fails, no more attempts to acquire the lock will be made.

3. The code realizes the distributed lock

(1) Requirements: Define a class to implement the Redis distributed lock function.

public class SimpleRedisLock implements ILockService {
    
    

    private static final String LOCK = "lock:";

    private String threadName;

    private StringRedisTemplate redisTemplate;

    public SimpleRedisLock() {
    
    
    }

    public SimpleRedisLock(String threadName, StringRedisTemplate redisTemplate) {
    
    
        this.threadName = threadName;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public boolean tryLock(long timeoutSec) {
    
    
        //1、获取锁
        Boolean absent = redisTemplate.opsForValue().setIfAbsent(LOCK + threadName, String.valueOf(Thread.currentThread().getId()), timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(absent);
    }

    @Override
    public void unlock() {
    
    
    	//2、解锁
        redisTemplate.delete(LOCK + threadName);
    }
}

4. Distributed lock optimization based on Redis - Redisson object

(1) Problems with distributed locks based on setnx

  • Non-reentrant: the same thread cannot acquire the same lock multiple times
  • Non-retryable: false is returned after only one attempt to acquire the lock, there is no retry mechanism
  • Timeout lock release: Lock timeout release, although deadlock can be avoided, but if the business takes too long, it will also cause the lock to be released, which poses a security risk.
  • Master-slave consistency: If Redis provides a master-slave cluster, there is a delay in master-slave synchronization. When the master is down, if the slave synchronizes all the data in the master, lock implementation will occur.

(2) A common object for implementing distributed locks - Redisson

1. Concept: Redisson is a Java in-memory data network (In-Memory Data Grid) implemented on the basis of Redis. Provides a series of distributed common Java objects, and also provides many distributed services, including the implementation of various distributed locks.

2. Types of distributed locks Official website address: https://redisson.org

  • Distributed lock (Lock) and synchronizer (Synchronizer)
    • Reentrant Lock
    • Fair Lock (Fair Lock)
    • Interlock (Multi Lock)
    • Red Lock
    • ReadWrite Lock (ReadWrite Lock)
    • Semaphore
    • Expirable semaphore (PermitExpirableSemaphore)
    • Latch (CountDownLatch)

3. Basic use of Redisson

<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson</artifactId>
	<version>3.13.6</version>
</dependency>
@Configuration
public class RedisConfig {
    
    
	@Bean
	public RedissonClient redissonClient() {
    
    
		//配置类
		Config config = new Config();
		//连接redis
		config.useSingleServer().setAddress("redis://192.168.92.131:6379").setPassword("123321");
		//创建客户端
		return Redisson.create(config);
	}
}
@Resource
private RedissonClient redissonClient;

@Test
void testRedisson() throws InterruptedException {
    
    
	//	获取锁(可重入),指定锁名称
	RLock lock = redissonClient.getLock("anyLock");
	//	尝试获取锁,参数分别是: 获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位
	boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
	//	判断释放获取成功
	if(isLock){
    
    
		try{
    
    
			System.out.println("执行业务");
		}finally{
    
    
			//释放锁
			lock.unlock();
		}
	}
}

4. Redisson reentrant lock principle

(1) What is a reentrant lock?

A reentrant lock refers to a lock acquisition mechanism in which a thread can acquire a lock multiple times.

(2) ReentrantLock reentrant principle

When a thread acquires a lock, if there is a thread occupying the lock, check whether the thread is itself. If it is itself, try to acquire the lock again. Every time you try to acquire the lock, there will be a reentry counter to record the number of thread reentry , after the execution of this method ends, the lock is released, and the reentry counter is correspondingly decremented by one, until the entire method is executed, the lock can be completely released.

(3) Implementation of Redis-based reentrant locks

Process: Determine whether the lock exists (if not, acquire the lock and add the thread ID, set the lock validity period, execute the business, and still need to determine the ownership of the lock and the lock count status after execution) 》Judge whether the lock ID is your own (if so, Then the lock count + 1) "If not, the acquisition of the lock failed"

lua script

local key = KEYS[1];
local threadId = ARGV[1];
local releaseTime = ARGV[2];

--1、判断当前锁是否是自己
if (redis.call("HEXISTS", key, threadId) == 0) then
    -- 不是,则直接返回
    return nil;
end
-- 如果是,则计数器-1
local count = redis.call("HINCRBY", key, threadId, -1);

--2、判断统计数是否为0
if (count > 0) then
    -- 统计数不为零,则重置计时器
    redis.call("expire", key, releaseTime);
else
    --统计数为零,则释放锁
    redis.call("del", key);
    return nil;
end

5. multiLock, master-slave consistency

(1) Cause: multiple Redis, the master node mainly stores the latest data, and the slave nodes need to synchronize data. During the data synchronization process, there will be a delay, because some abnormalities cause the master node to go down, and the slave nodes The data is inconsistent and because the redis locked by the lock object is down, the lock becomes invalid, causing all the distributed security problems mentioned before.

(2) Solution: interlocking mechanism.

(3) Solution to the master-slave consistency of the interlocking mechanism: by acquiring all the locks of the Redis cluster, only when all the locks of the Redis cluster are acquired can data update be performed.

(4) Redssion interlock: MultiLock

6. Redisson's lock retry and WatchDog mechanism

(1) The basic process of Redisson's lock retry mechanism: You can check the retry lock mechanism and release lock mechanism of the RedissonLock class by yourself

  • Convert the waiting time and release time to millisecond level uniformly
  • The first attempt to acquire the lock (if the returned TTL is null, it means that the lock has been acquired)
    • Determine whether to acquire a lock (blocking and waiting for the result to return)
      • Determine whether the lock release time is the default (the parameter -1 is passed to indicate the default), if it is the default, use WatchDog to monitor the time (30s), otherwise the parameter passed in shall prevail
      • Use an asynchronous release function, which contains a script for reentrant lock implementation (the result is returned to a Future class)
  • If the lock is not acquired, the waiting time is subtracted from the time for acquiring the lock, and it is judged whether the waiting time is insufficient (if not, give up and try again and return an error message)
  • Subscribe to the thread ID currently trying to acquire the lock (blocking and waiting for the remaining waiting time, if there is still no signal to release the lock, cancel the subscription and return an error message), when the command in the release lock script is executed, the lock acquisition will publishstart
  • Subscribe to the lock signal
    • Judging whether the waiting time is sufficient (the waiting time is first subtracted from the waiting signal time, if the calculation result is less than zero, an error message will be returned)
    • If it is sufficient, it will try to acquire the lock in a loop until the lock is successfully acquired, or an error will be returned if the waiting time is insufficient (the loop attempt does not loop all the time, it will only try to acquire when the lock signal is acquired)

insert image description here


3. Issues related to the use of distributed locks

(1) Excessive modification of data

1. Reasons: due to the participation of multi-threads, the execution order of the methods of the functional modules will be different, so when multiple threads may have inquired about the inventory due to the different operation order and there is still a surplus, they will all perform the operation of deducting the inventory , so that the original inventory number < the number of request threads, resulting in a situation where the inventory directly becomes negative.

2. Solution: Add a lock to keep the lock held by the user when accessing to modify the operation.

3. Add optimistic lock and pessimistic lock to solve the problem

(1) Optimistic locking: Since the data changes before and after the update are used to judge whether the data can be updated, the threads accessed at the same time, after the thread a accesses the update, the thread b accesses the data before and after the update is inconsistent, causing the thread update to fail. At the same time All threads accessed during the same period fail, so its efficiency will be extremely poor, but the business can still be completed.

(2) Add pessimistic lock: Since thread a has acquired the lock and entered the access update phase, but thread b has not acquired the lock and is blocked. If there is no lock retry mechanism, a large number of threads may fail. Compared with optimistic lock In this way, the success rate has obviously improved, and its security has also been improved, and it will not be updated multiple times due to multiple identical requests from the same user.

(2) In the cluster state, the lock function fails

1. Reasons: JVM maintains a lock monitor inside. Under the same userid, this thread is considered to be the same thread, but when two or more JVM clusters come out, the lock monitor does not lock the same thread. Threads, so there will be concurrency security issues.

2. Solution: use distributed locks

(3) Service blocking leads to lock timeout release

1. Reason: The thread is blocked, and the distributed lock is released after timeout, causing the thread to run chaotically.

2. Solution: After the business is completed, first check whether the lock identification is consistent, and then judge whether to release the lock.

(4) Timeout release lock

1. Cause: Due to the garbage collection mechanism of the JVM, the thread may be blocked before releasing the lock, causing the lock to be released over time

2. Solution: make the judgment representation and release lock atomic.

3. Implementation method: use Lua scripts to write multiple Redis to ensure the atomicity of Redis commands.

3. How to use Lua scripts: Redis provides a callback function that can call scripts.

EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 name Rose

4. The business process of releasing the lock

  • Get the thread ID in the lock
  • Determine whether it is consistent with the specified ID (current thread ID)
  • Release lock if consistent (delete)
  • do nothing if inconsistent

Guess you like

Origin blog.csdn.net/Zain_horse/article/details/132002653