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
- Safety property: Exclusive (mutually exclusive). At any one time, only one client holds the lock.
- Liveness property A: No deadlock. Even if the client holding the lock crashes or the network gets partitioned, the lock can still be acquired.
- 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: key
Set the value value
, if it key
does not exist, it is equivalent to the SET command in this case . When it key
exists, do nothing. SETNX
It is " the SET IF N OT E X- ISTs" shorthand.
return value
1
If the key is set0
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 key
to 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 set
command is executed successfully, the expiration time set before will be invalid.
Options
Starting from version 2.6.12, redis has SET
added a series of options to the command:
EX
seconds -Set the expiration time of the key, in hours and secondsPX
milliseconds -Set the expiration time of the key, in millisecondsNX
-The value of the key will be set only when the key does not existXX
-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:
- 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
- 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
- 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
- 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:
- Client A obtains the lock from the master
- Before the master synchronizes the lock to the slave, the master goes down.
- The slave node is promoted to master node
- 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
- https://www.jianshu.com/p/bb8c6c3113dd
- http://redis.cn/topics/distlock.html
- 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:
- Redis implements distributed locks
- Golang source code bug tracking
- The realization principle of transaction atomicity, consistency and durability
- How to exercise your memory
- Detailed explanation of CDN request process
- Thoughts on the career development of programmers
- The history of blog service being crushed
- Common caching techniques
- How to efficiently connect with third-party payment
- Gin framework concise version
- Thinking about code review
- A brief analysis of InnoDB locks and transactions
- Markdown editor recommendation-typora