Implementation of several locks in redis

1. redis lock classification

  1. The locking commands that redis can use are INCR, SETNX and SET

2. The first lock command INCR

The idea of locking is that if the key does not exist, the value of the key will be initialized to 0 first, and then the INCR operation will be performed to add one.  
Then, other users add one hour when executing the INCR operation. If the number returned is greater than 1, it indicates that the lock is being used.

    1, client A Request server get key A value of 1 indicates that the lock was acquired
    2, client B Also ask the server to get it key A value of 2 indicates a lock acquisition failure
    3, client A When the code is executed, delete the lock
    4, client B After waiting for a period of time, get it when you go to the request key A value of 1 indicates successful lock acquisition
    5, client B When the code is executed, delete the lock

    $redis->incr($key);
    $redis->expire($key, $ttl); //Set the generation time to 1 second

3. Second lock SETNX

The idea of locking is to set the key to value if the key does not exist
If the key already exists, SETNX does nothing

    1, client A Request server settings key If the setting is successful, it indicates that the locking is successful
    2, client B Also request server settings key If the return fails, it means that locking fails
    3, client A When the code is executed, delete the lock
    4, client B After waiting for a period of time, go to request settings key The value of is set successfully
    5, client B When the code is executed, delete the lock

    $redis->setNX($key, $value);
    $redis->expire($key, $ttl);

4. The third lock is SET

There is a problem with the above two methods. You will find that you need to set key expiration. So why set key expiration? If the request execution exits unexpectedly for some reason, causing the lock to be created but not deleted, the lock will always exist, so that the cache will not be updated in the future. So we need to add an expiration time to the lock in case of accidents.  
But setting with Expire is not atomic. Therefore, you can also ensure atomicity through transactions, but there are still some problems, so the official quoted another one. Using the SET command itself has included the function of setting the expiration time since version 2.6.12.

    1, client A Request server settings key If it is set successfully, it indicates that locking is successful
    2, client B Also request server settings key If the return fails, it means that locking fails
    3, client A When the code is executed, delete the lock
    4, client B After waiting for a period of time, go to request settings key The value of is set successfully
    5, client B When the code is executed, delete the lock

    $redis->set($key, $value, array('nx', 'ex' => $ttl));  //ex means seconds

5. Other issues

Although the above step has met our needs, we still need to consider other issues?  
1. redis found that the lock failed. What should we do? Interrupt request or loop request?  
2. In the case of circular requests, if one obtains the lock, is it easy to grab the lock when the other obtains the lock?  
3. After the lock expires in advance, client A has not completed the execution, and then client B obtains the lock. At this time, client A has completed the execution. Will the lock of B be deleted when deleting the lock?

6. Solutions

For question 1: use circular request to obtain lock
For question 2: for the second question, when the loop requests to obtain the lock, add the sleep function and wait for a few milliseconds to execute the loop
For question 3: the keys stored during locking are random. In this way, every time you delete a key, judge whether the value stored in the key is the same as that saved by yourself

        do {  //For question 1, use a loop
            $timeout = 10;
            $roomid = 10001;
            $key = 'room_lock';
            $value = 'room_'.$roomid;  //Assign a random value to question 3
            $isLock = Redis::set($key, $value, 'ex', $timeout, 'nx');//ex seconds
            if ($isLock) {
                if (Redis::get($key) == $value) {  //Prevent early expiration and delete locks created by other requests by mistake
                    //Execute internal code
                    Redis::del($key);
                    continue;//Successfully delete the key and jump out of the loop
                }
            } else {
                usleep(5000); //Sleep, reduce lock grabbing frequency, relieve redis pressure, and solve problem 2
            }
        } while(!$isLock);

7. Another lock

The above locks fully meet the requirements, but the official also provides a set of locking algorithm. Here, take PHP as an example

    $servers = [
        ['127.0.0.1', 6379, 0.01],
        ['127.0.0.1', 6389, 0.01],
        ['127.0.0.1', 6399, 0.01],
    ];

    $redLock = new RedLock($servers);

    //Lock
    $lock = $redLock->lock('my_resource_name', 1000);

    //Delete lock
    $redLock->unlock($lock)

Keywords: Database Redis Cache

Added by wardmaestro on Thu, 20 Jan 2022 01:13:36 +0200