A distributed lock

First, a priori: the SETNX command. Its full name is Set if not exists. If the key does not exist, we can give it a set value. If yes, an error is reported.

As shown in the figure, ab is used as the key, and its value is already set to love. If you try to set it to kk, an error will be reported.

So the distributed lock uses the idea of a command: multiple threads through the simultaneous setting of this command, who can set it on the value of the first, on behalf of the lock. Otherwise, you have to wait for the lock to be released.

The first kind of

To be clear, the previous ones are too simple to repeat, but I’d like to start with an example with an expiration time.

In this example, we set the acquisition lock + set the expiration time of the lock to a transaction (because Redis supports)

The schematic diagram is as follows:

The key line of code looks like this:

\

But there is a drawback to this approach:

If there are three threads ABC executing A certain business code at the same time, line A grabs the lock (set the value to 123) and then executes the business. After 10s, the lock is automatically released, but the business code of A has not completed execution. Thread B sees that the lock is released. Immediately preempts the lock (also set to 123) and executes the business code as well. At this point, threads A and B collide. 15s later, thread A releases the lock with the value 123, but this lock belongs to lock B, and A conflict occurs again.

At this time, C sees that the lock is released and immediately preempts the lock (the value is also set to 123). At this time, B and C have another conflict in the task execution.

So here’s a reminder:

  1. The time for the thread to process the task must be less than the time for the lock to expire
  2. Why does BC open the lock of A? Because they both set value to the same, they can open each other’s locks. Therefore, value must be set to different values, which can be replaced by uUID.

The second,

Based on the shortcomings of the previous method, we improved the next method,

  • Takes the UUID as its value
  • When proactively deleting a lock, check whether the value of the lock is the same as the specified value and delete the lock only when the value is the same.

  1. Generate a UUID
  2. Lock with the generated UUID
  1. If the lock is successful, services are performed
  2. After the service is complete, the value of the current lock is obtained
  1. If the value is the same as the uUID originally generated, the lock can be safely released

You think this is perfect? But it’s not.

For example, thread A and thread B grab the lock first, and then query the value of the current value after executing the service. However, this query takes A lot of time.

When 10s is reached, the lock is released, thread B preempts the lock and starts to execute services. At this point, A finds its value, and then A releases the lock. Notice that A’s lock has expired, but the lock it releases is actually B’s lock, but B hasn’t finished executing the service yet.

That’s where the conflict starts.

The solution is to set steps 4 and 5 as atomic operations, that is, query the value of the current lock + release the lock as a transaction.

The third (Redisson)

Redis has already given us a perfect solution

Here’s how he uses it:

\

\

\

\

The lock expires by default in 30s. But it made a new optimization. However, before the redission instance is closed, the lock expiration time is extended continuously. That is, after 10s, if the Redission instance is not closed, the lock expiration time is automatically set to 30s again, and so on.

Even if the server is down, the lock will still be released 30 seconds later, resulting in no deadlock operation.