An overview of the

I have used Redis for distributed locking in the past, and it looks like this:

  1. Setnx: creates a key with an expiration time of 1s in redis. If the creation fails, the lock cannot be obtained
  2. Expire: Adds the expiration time to the lock after obtaining the lock successfully
  3. Del: Release the lock after processing

It didn’t seem like a problem at the time. It was naive of me to suddenly realize today, well, there’s a problem.

The problem

1. If the program crashes after the first step and the lock expiration time is not set, all subsequent operations cannot obtain the lock normally. How to break?

2. After user A successfully locks the lock, the execution takes A long time due to I/O blocking and the lock has expired. In this case, User B successfully locks the lock.

3. Redis suddenly hangs up. What if Redis suddenly hangs up? Of course, you can add redis nodes. If the primary node fails, the secondary node will replace it immediately. However, synchronization of data from the master node to the slave node also takes time. Consider a scenario:

  1. A Sets A lock on the primary node
  2. The master node failed to synchronize data
  3. Succeeds from a node to a master node
  4. B also successfully sets the lock on the primary node

At this point, the distributed lock is invalidated.

To solve

So is there a way to solve the above problem? I looked it up on the almighty Google and, yes, there is.

Solve the above problems one by one.

Problem a

How can I avoid the problem of not setting an expiration time for a lock?

The expire command is not executed properly after the setnx command is executed. The expire command is not executed properly after the setnx command is executed. The expire command should be merged into a single command.

set key value NX PX 5000

NX indicates the existence and PX indicates the expiration time.

This way, you can at least guarantee that no lock will ever expire

Question 2

How to prevent A from releasing B’s lock.

How do you avoid releasing someone else’s lock? Change the question, how do you make sure you add the lock? So easy, set the value to a random number that only I know, and check if the value is mine.

There are two steps to release:

  1. Gets the value of the Redis lock
  2. If the value is mine, release the lock

Of course, to ensure atomicity of the lock release operation, it is best to combine these two steps into one. So how does Redis determine if the values are the same? The Lua script.

Just a quick introduction

eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2Key1 key2 argv1 argv2 # eval is a built-in redis command. # the first argument is the script logic to run. # The second argument indicates that there are several keys behind itCopy the code

So, the script looks like this:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end
Copy the code

That way, at least you can guarantee that A won’t release B’s lock

Question 3

How do I ensure that if the primary node fails, the secondary node will not take over the lock again?

The official website provides a method to obtain locks from multiple Instances of Redis simultaneously. Because I didn’t understand it, and I’ll talk after I understand it. pass

In fact, if it were not for the business of money, which can’t go wrong, such a small probability would be tolerable.


conclusion

Finally, the distributed lock operation under redis single machine is as follows:

Obtain distributed lock, expiration time is adjustable
set lock_key random_value NX PX 5000
# ...do something
Release the distributed lock
eval "if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end" 1 lock_key random_value 
Copy the code