There are many ways to implement distributed locking. This article describes how to implement distributed locking using Redis. There is a lot of code on the web that uses Redis to implement distributed locking, but it is more or less problematic. This article will write an implementation, along with some caveats.

scenario

For the sake of illustration, A game scenario is assumed here. User A has an axe worth 500 yuan, and user B has 800 yuan and wants to buy A’s axe. All these data are stored in Redis. Code needs to be written to implement the transaction successfully.

The problem

Redis implements distributed locking, the following issues need to be considered:

  • The process holding the lock takes too long and the lock is automatically released, but the process itself is unaware of this and may even release a lock held by another process by mistake.
  • One process that holds the lock and intends to operate on it for a long time crashes, but other processes that want to acquire the lock don’t know which process holds the lock, can’t detect that the process holding the lock has crashed, and waste their time waiting for the lock to be released.
  • After a lock held by one process expires, multiple other processes simultaneously attempt to acquire the lock and all acquire it.

Three characteristics

Three features are required to implement a minimum guaranteed distributed lock

  1. Safety property: exclusive (mutually exclusive). At any given time, only one client is holding the lock.
  2. Lip Property A: there are no deadlocks. Locks can be acquired even if the client holding them crashes or the network is partitioned.
  3. Liveness property B: Fault tolerance. Clients can acquire and release locks as long as most Redis nodes are alive.

The command

If the Redis version is greater than or equal to 2.6.12, you can use the SET command. You can use this command to implement the SETNX and EXPIRE functions atomatically. The following is a description of the two commands

SETNX

Command format: SETNX key value

Time complexity: O(1)

Note: SET key to value. If key does not exist, this is the same as the SET command. When the key is present, nothing is done. SETNX is short for “SET if Not eXists.”

The return value

  • 1If the key is set
  • 0If the key is not set

SET

Format: SET the key value [EX seconds] [PX milliseconds] [NX | XX]

Time complexity: O(1)

Note: Set key to the specified “string” value. If the key already holds a value, the operation overwrites the original value and ignores the original type. After the set command is executed successfully, the set expiration time will become invalid.

options

Starting with 2.6.12, Redis has added a number of options for the SET command:

  • EX seconds– Set the expiration time of the key, in seconds
  • PX milliseconds– Set the expiration time of the key, in milliseconds
  • NX– The key value is set only when the key does not exist
  • XX– The key value is set only when the key exists

implementation

Here use SETNX implementation, after all, some companies Redis version may be lower, use SETNX can be implemented, SET is more no problem.

The code is as follows:


      

function uuid($prefix = ' ')
{
    $chars = md5(uniqid(mt_rand(), true));
    $uuid  = substr($chars, 0.8).The '-';
    $uuid .= substr($chars, 8.4).The '-';
    $uuid .= substr($chars, 12.4).The '-';
    $uuid .= substr($chars, 16.4).The '-';
    $uuid .= substr($chars, 20.12);
    $ret = $prefix . $uuid;
    return strtoupper($ret);
}

function acquireLock($redis,$lockName, $acquireTime = 10, $lockTime = 10)
{
    $lockKey    = 'lock:' + $lockName;
    $identifier = uuid('identify');
    $end        = time() + $acquireTime;
    while (time() < $end) {
        if ($redis->setnx($lockKey, $identifier)) {
            $redis->expire($lockKey, $lockTime);
            return $identifier;
        } elseif ($redis->ttl($lockKey) == - 1) {
            $redis->expire($lockKey, $lockTime);
        }
        usleep(1000);
    }
    return false;
}


function process(a){
    $redis      = new Redis();
    $lockName = 'market';
    / / 1. Acquiring a lock
    $locked = acquireLock($redis,$lockName);
    if($locked === false) {return false;
    }
    //2. Trade
    // Determine whether A and B satisfy the transaction conditions
    // Use the pipe to operate on A and B

    / / 3. Release the lock
    $releaseRes = releaseLock($redis,$lockName,$locked);
    if($releaseRes === false) {return false; }}function releaseLock($redis,$lockName,$identifier){
    $lockKey    = 'lock:' + $lockName;
    $redis->watch($lockKey);
    if($redis->get($lockKey) === $identifier){
        $redis->multi();
        $redis->del($lockKey);
        $redis->exec();
        return true;
    }
    $redis->unwatch();
    return false;
}
Copy the code

Description:

  1. Acquiring a lock:
    • Create a unique $identifier. This value is used to determine whether the lock is obtained by the current client when deleting the key, so that locks on other clients are not deleted
    • The while loop is used to acquire locks continuously over a period of time
    • Acquire the lock if you can, and set a timeout to prevent the thread from crashing while running, and the lock is never released
    • If the lock fails to be obtained, check the expiration time of the current lock. If the expiration time is not set, set it to prevent other threads from crashing immediately after acquiring the lock
  2. Deal with business
    • It is necessary to determine whether both sides of the transaction meet the conditions, because the entire market is locked, so once the lock is obtained, the state of both sides of the transaction will not change
    • Using a pipeline ensures that the entire transaction will be processed as a transaction and will perform better than transactions using Redis
  3. Release the lock
    • With watch, the lock is monitored. Once the key is changed, the transaction that deletes the key will not be executed
    • We need to determine whether the value of key is the same as the $identifier recorded by this thread. Only if the value is consistent can we delete it
    • Use a transaction to delete the key. The reason for using a transaction is to prevent the lock from being acquired by another thread
    • If you fail, remember unwatch
  4. Other problems
    • Reentrant problem: Reentrant means that the thread can acquire the lock again. The implementation method is relatively simple, just need to pass in the identifier when acquireLock, judge whether the identifier of the current lock and the passed in is consistent, if consistent then can operate
    • One solution to this problem is to check whether the lock exists or has been modified halfway through the timeout period after the lock is acquired. If there is no change and the thread is running normally, then extend the timeout period

thinking

This approach is secure enough if based on a Redis single instance, assuming that the single instance is always available.

But there are two special cases that you should pay attention to:

There are obvious race states in the master-slave structure:

  1. Client A obtains the lock from the master
  2. The master failed before synchronizing the lock to the slave.
  3. The slave node is promoted to the master node
  4. Client B acquires another lock already acquired by client A on the same resource. Safety failure!

In the distributed Redis environment, there are N Redis masters

In this case, you can use the Redlock algorithm

conclusion

This article describes how to use Redis to implement distributed locking, and write the implementation and related analysis. To use Redis to implement distributed locking, there are many details to think about, you can according to their own business shape design to meet their requirements of lock, in the complexity and security do a good compromise.

data

  1. www.jianshu.com/p/bb8c6c311…
  2. Redis. Cn/switchable viewer/dist…
  3. Redis. Cn/commands/se…

The last

If you like my article, you can follow my public number (programmer Malatang)

Review of previous articles:

  1. Redis implements distributed locking
  2. Golang source BUG tracking
  3. The implementation principle of atomicity, consistency and persistence of transactions
  4. How to exercise your memory
  5. This section describes the CDN request process
  6. Thinking about programmer career development
  7. Keep track of how your blogging service has been overwhelmed
  8. Common Cache Techniques
  9. How to connect third-party payment efficiently
  10. Gin framework compact version
  11. Thoughts on code Review
  12. InnoDB lock and transaction analysis
  13. Markdown editor recommended – Typora