Using the Redis SETNX command to implement distributed locks

Reprinted from: https://blog.csdn.net/lihao21/article/details/49104695

Distributed locks can be implemented using the SETNX command of Redis. The implementation method is described below.

Introduction to the SETNX command

command format

SETNX key value

Set the value of key to value if and only if key does not exist. 
If the given key already exists, SETNX does nothing. 
SETNX is shorthand for SET if Not eXists.

return value

Returns an integer, specifically 
- 1, when the value of key is set 
- 0, when the value of key is not set

example

redis> SETNX mykey “hello” 
(integer) 1 
redis> SETNX mykey “hello” 
(integer) 0 
redis> GET mykey 
“hello” 
redis>

Using SETNX to implement distributed locks

Multiple processes execute the following Redis commands:

SETNX lock.foo <current Unix time + lock timeout + 1>

If SETNX returns 1, indicating that the process has acquired the lock, SETNX sets the value of the key lock.foo to the lock timeout (current time + lock valid time). 
If SETNX returns 0, it means that other processes have acquired the lock and the process cannot enter the critical section. A process can keep trying SETNX operations in a loop to acquire a lock.

resolve deadlock

Consider a situation, if the process is disconnected from Redis after acquiring the lock (maybe the process hangs, or the network is interrupted), if there is no effective mechanism to release the lock, then other processes will be in a state of waiting, that is A "deadlock" occurs.

When using SETNX to obtain the lock above, we set the value of the key lock.foo to the effective time of the lock. After the process obtains the lock, other processes will continue to check whether the lock has timed out. If it times out, the waiting process will also have Chance to acquire lock.

However, when the lock times out, we cannot simply use the DEL command to delete the key lock.foo to release the lock. Consider the following situation, process P1 has first acquired the lock lock.foo, and then process P1 dies. Processes P2 and P3 are constantly checking whether the lock has been released or has timed out. The execution flow is as follows:

  • The P2 and P3 processes read the value of the key lock.foo and check whether the lock has timed out (by comparing the current time with the value of the key lock.foo to determine whether the lock has timed out)
  • P2 and P3 processes find that lock lock.foo has timed out
  • P2 executes DEL lock.foo command
  • P2 executes the SETNX lock.foo command and returns 1, that is, P2 acquires the lock
  • P3 executes the DEL lock.foo command to delete the key lock.foo just set by P2 (this step is because P3 has just detected that the lock has timed out)
  • P3 executes the SETNX lock.foo command and returns 1, that is, P3 acquires the lock
  • P2 and P3 acquired the lock at the same time

As can be seen from the above situation, after detecting the lock timeout, the process cannot simply perform the operation of DEL delete key to obtain the lock.

In order to solve the problem that multiple processes may acquire locks at the same time in the above algorithm, let's look at the following algorithm. 
We also assume that process P1 has first acquired the lock lock.foo, and then process P1 dies. What happens next:

  • Process P4 executes SETNX lock.foo to try to acquire the lock
  • Since the process P1 has acquired the lock, P4 executes SETNX lock.foo and returns 0, that is, the acquisition of the lock fails
  • P4 executes GET lock.foo to check whether the lock has timed out, if not, wait for a while and check again
  • If P4 detects that the lock has timed out, i.e. the current time is greater than the value of the key lock.foo, P4 will do the following 
    GETSET lock.foo <current Unix timestamp + lock timeout + 1>
  • Since the GETSET operation returns the old value of the key while setting the value of the key, by comparing whether the old value of the key lock.foo is less than the current time, it can be determined whether the process has acquired the lock
  • If another process P5 also detects that the lock has timed out and performs a GETSET operation before P4, then P4's GETSET operation returns a timestamp greater than the current time, so that P4 will not acquire the lock and continue to wait. Note that it doesn't matter if P4 then sets the key lock.foo to a larger value than P5.

In addition, it is worth noting that before the process releases the lock, that is, executes the DEL lock.foo operation, it needs to determine whether the lock has timed out. If the lock has timed out, the lock may have been acquired by another process, and the direct execution of the DEL lock.foo operation will result in the release of the lock already acquired by the other process.

code

Use the following python code to implement the above algorithm of using the SETNX command as a distributed lock.

LOCK_TIMEOUT = 3
lock = 0
lock_timeout = 0
lock_key = 'lock.foo'

# 获取锁
while lock != 1:
    now = int(time.time())
    lock_timeout = now + LOCK_TIMEOUT + 1
    lock = redis_client.setnx(lock_key, lock_timeout)
    if lock == 1 or (now > int(redis_client.get(lock_key))) and now > int(redis_client.getset(lock_key, lock_timeout)):
        break
    else:
        time.sleep(0.001)

# 已获得锁
do_job()

# 释放锁
now = int(time.time())
if now < lock_timeout:
    redis_client.delete(lock_key)

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325508795&siteId=291194637