On the way Redis implements distributed locks

There are many ways to implement distributed locks. This article describes the use of Redis to implement distributed locks. There are many codes on the Internet that use Redis to implement distributed locks, but these codes are more or less problematic. This article will write an implementation and also indicate some points of attention.

Scenes

For ease of explanation, here is a game scenario where user A has a mountain axe worth 500 ingots, and user B has 800 ingots and wants to buy A mountain axe. These data are stored in Redis. You need to write code to successfully implement the transaction.

problem

Redis implements distributed locks and needs to consider the following issues:

  • The process that holds the lock causes the lock to be automatically released because the operation time is too long, but the process itself does not know this, and may even release the locks held by other processes by mistake.
  • A process holding a lock and intending to perform a long operation has crashed, but other processes that want to acquire the lock do not know which process is holding the lock, nor can they detect that the process holding the lock has crashed, and can only waste time in vain Wait for the lock to be released.
  • After the lock held by one process expires, multiple other processes try to acquire the lock at the same time, and all acquire the lock.

Three characteristics

To implement a distributed lock with a minimum guarantee, three characteristics are required

  1. Safety property: Exclusive (mutually exclusive). At any one time, only one client holds the lock.
  2. Liveness property A: No deadlock. Even if the client holding the lock crashes or the network gets partitioned, the lock can still be acquired.
  3. Liveness property B: fault tolerance. As long as most of the Redis nodes are alive, the client can acquire and release the lock.

command

Use Redis to implement distributed locks, generally use SETNX or SET commands. SETNX cannot set the expiration time at the same time. If the version used is greater than or equal to 2.6.12, you can use the SET command, which can be used to achieve the functions of SETNX and EXPIRE atomically. Below is a brief introduction to the two commands

SETNX

Command format: SETNX key value

Time complexity: O(1)

Note: keySet the value value, if it keydoes not exist, it is equivalent to the SET command in this case . When it keyexists, do nothing. SETNXIt is " the SET IF N OT E X- ISTs" shorthand.

return value

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

SET

Command format: SET key value [EX seconds] [PX milliseconds] [NX|XX]

Time complexity: O(1)

Description: Set the key keyto the specified "string" value. If the key has saved a value, then this operation will directly overwrite the original value and ignore the original type. When the setcommand is executed successfully, the expiration time set before will be invalid.

Options

Starting from version 2.6.12, redis has SETadded a series of options to the command:

  • EX seconds -Set the expiration time of the key, in hours and seconds
  • PX milliseconds -Set the expiration time of the key, in milliseconds
  • NX -The value of the key will be set only when the key does not exist
  • XX -Only the key value will be set when the key exists

achieve

SETNX is used here. After all, some companies may have a lower Redis version, which can be achieved using SETNX, and SET is even more problem-free.

code show as below:

<?php

function uuid($prefix = '')
{
    
    
    $chars = md5(uniqid(mt_rand(), true));
    $uuid  = substr($chars, 0, 8) . '-';
    $uuid .= substr($chars, 8, 4) . '-';
    $uuid .= substr($chars, 12, 4) . '-';
    $uuid .= substr($chars, 16, 4) . '-';
    $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(){
    
    
    $redis      = new Redis();
    $lockName = 'market';
    //1.获取锁
    $locked = acquireLock($redis,$lockName);
    if($locked === false){
    
    
        return false;
    }
    //2.进行交易
    //判断A和B是否满足交易条件
    //使用管道,对A和B进行操作

    //3.释放锁
    $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;
}

Description:

  1. Acquire the lock:
    • Create a unique $identifier, this value is used to determine whether the lock is acquired by the current client when deleting the key, so as not to delete the locks of other clients
    • The while loop is used to keep acquiring the lock for a period of time
    • If you can get the lock, you can get it, and set a timeout to prevent the thread from crashing when it runs. The lock can never be released.
    • If the lock cannot be successfully acquired, 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. No expiration time is set
  2. Handle business
    • You need to first determine whether both parties to the transaction meet the conditions, because the entire market is locked, so once the lock is obtained, the status of both parties will not change
    • Using the pipeline can ensure that the entire transaction can be processed like a transaction, and the performance will be better than the transaction using redis
  3. Release lock
    • Use watch to monitor the lock. Once the key is changed, the transaction that deletes the key will not be executed
    • Need to determine whether the value of the key is the same as the $identifier recorded by this thread, and only if it is consistent can it be deleted
    • Use transactions to delete keys. The reason for using transactions is to prevent the lock from being acquired by other threads.
    • If it fails, remember to unwatch
  4. other problems
    • Reentrant problem: Reentrant means that the thread can acquire the lock again. The implementation method is relatively simple. You only need to pass in the identifier when acquiringLock to determine whether the identifier of the current lock is consistent with the passed in. If they are consistent, the operation can be performed
    • The thread has not been executed, the lock timeout period has passed, and other threads have acquired the lock: a solution to this problem is to check whether the lock exists or has been modified after half of the timeout period has passed, if there is no change. And the thread is running normally, extend the timeout period

Thinking

If based on Redis single instance, assuming that this single instance is always available, this method is safe enough.

But there are two special situations that everyone needs to pay attention to:

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

  1. Client A obtains the lock from the master
  2. Before the master synchronizes the lock to the slave, the master goes down.
  3. The slave node is promoted to master node
  4. Client B acquires another lock for the same resource that client A has already acquired. Safety failure!

In the distributed environment of Redis, there are N Redis masters

In this case, the Redlock algorithm can be used

to sum up

This article describes how to use Redis to implement distributed locks, and writes specific implementation and related analysis. To use Redis to implement distributed locks, there are many details to think about. You can design locks that meet your requirements according to your own business form, and make a compromise between complexity and security.

data

  1. https://www.jianshu.com/p/bb8c6c3113dd
  2. http://redis.cn/topics/distlock.html
  3. http://redis.cn/commands/set.html

At last

If you like my article, you can follow my public account (Programmer Mala Tang)

Review of previous articles:

  1. Redis implements distributed locks
  2. Golang source code bug tracking
  3. The realization principle of transaction atomicity, consistency and durability
  4. How to exercise your memory
  5. Detailed explanation of CDN request process
  6. Thoughts on the career development of programmers
  7. The history of blog service being crushed
  8. Common caching techniques
  9. How to efficiently connect with third-party payment
  10. Gin framework concise version
  11. Thinking about code review
  12. A brief analysis of InnoDB locks and transactions
  13. Markdown editor recommendation-typora

Guess you like

Origin blog.csdn.net/shida219/article/details/107137981