preface

When we learn multi-threaded development, we need to lock when the thread is operating on the same resource at the same time; ReentrantLock and synchronized are commonly used, and the difference between reentrantLock and synchronized is often discussed in interviews. Here is another type of lock commonly used by enterprises, distributed locks. I’m sure you’ve heard of it, but it’s not always the right one. Today we will introduce the evolution of distributed lock scheme in depth.

Common use

Let’s also take the cliche example of simultaneous inventory deduction

Let’s look at the code

// Deduct goods inventory
// productId: productId
// Number of deductions: count
public void reduce(int productId,int count){
     // Step 1 Obtain the product entity from the database
        Product product = getProduct(productId);
     // Step 2 Obtain the current inventory quantity
    int stockCount = product.getStock();
    if(stockCount >= count){
          // Step 3 Deduct inventory
          product.setStock(stockCount - count);
          // Step 4 Update the product entity to the database
          productService.update(product);
          log.info("Purchase successful!")}else{
          log.info("Not enough stock to buy!")}}Copy the code

Purchasing scenario

The current number of products in stock is 10

Request A bought 2 products, so 2 should be deducted

Request B bought 3 products, so it should deduct 3 more

So the final inventory surplus is 5

The code above is in a distributed environment, and as long as the traffic is a little bit high, there will be a situation where the inventory deduction is not expected. The reason is that

When both requests arrive at the same time, step 1 is executed at the same time and the same product inventory is obtained at the same time. The current inventory is 10. However, the value of 10 minus count is used in step 3, so whether request A or request B performs step 4 first, the inventory surplus is either 8 or 7. It’s not going to be the final 5.

We know the reason. How do we solve it? The friend thought is to get a lock, but also distributed lock.

Debut of distributed lock

The above problem many partners should know to use distributed lock, that use what technical solution? I’m sure many of you would say that using redis, setNx is pretty easy.

The setnx command is a native command of Redis. The set if not exists command is executed successfully if the specified key does not exist. If the key does exist, the command is executed successfully.

This is a solution that a lot of companies use, so let’s tweak the code

There are business exceptions that need to be taken into account, locks that need to be released, and try/finally

While there are still some problems, that is, if the lock is successful, the business is not completed. Sudden power failure or operation and maintenance personnel use the kill -9 command to delete the thread; This will result in the lock never being released because the code in finally will not be executed.

So what to do? Experienced friends should know the solution

Optimized distributed lock

The scheme is relatively simple, add an expiration time on the line

Even if the power, after 10 seconds after the lock will automatically expire, that is, failure; Other requests can be requested normally

At this point, it is common practice for many companies to apply distributed locks. Is there no problem with that, guys

Problem analysis

Let’s see what the problem is, okay? Let’s adjust the business code

Because we deduct inventory business, it is impossible to write very simple business; In a formal scenario, there is a lot of business and it cannot be that simple; If the business code executes beyond the lock expiration time, then the lock expires but the business code has not finished executing. This scenario leads to data confusion.

So how can we solve this problem?

solution

The essence of this problem is that the lock expires when the business is not completed. Then we ** can not let him ineffective not on the line? ** Then why not disable it?

The solution is simple

Start a background thread, which can be executed every 3 or 5 seconds to find the lock key and extend the lock key expiration time; This will allow you to renew the lock expiration time. Isn’t that easy?

We write our own code to implement is no problem, but now there are wheels on the market, we do not need to write this code, directly use someone else’s wheel; This is the famous Redisson.

Distributed lock Redisson

Redisson is a powerful toolkit for Redis distributed locks, which provides automatic renewal, as well as reentrant locks, just need to be introduced

<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.13.2</version>
</dependency> 
Copy the code

And instantiate it

@Bean
publicRedisson redisson(a){
    Config config = new Config();
config.useSingleServer().setAddress("redis://xxx:6379").setPassword("*").setDatabase(0);
    return (Redisson) Redisson.create(config);
}
Copy the code

Modify the code

The usage is very simple. Let’s take a look at his rationale and the source code and see how he can renew and implement reentrant. Okay

Source code analysis

Let’s take a look at his flowchart for locking

Locking mechanism

1, thread to obtain lock, obtain success: execute lua script, save data to redis database.

2. The thread tries to acquire the lock, but fails to acquire it: It tries to acquire the lock through the while loop. After obtaining the lock successfully, lua script is executed to save the data to the Redis database. That is, it blocks the thread

Redisson is executed by lua script core code tryLockInnerAsync as follows

Redis executes lua scripts atomically and either they succeed or they fail

The following code is to acquire lock failure, on the spin; Keep trying to get the lock

Renewal mechanism

The renewed business is done by the watchdog in the above flow chart. It should be noted here thatIf you want a watchdog to be effective, don’t set an expiration dateIf you set the expiration time, the watchdog will not work; Look at the source code

Renewal of the code is the enclosing scheduleExpirationRenewal (threadId);

If (leaseTime == -1l), the task thread will be started

Master-slave problem

If you look at this, do you think there should be no problem in implementing distributed locks? Is that true?

Let’s look at a problem

In the figure above, we find that if the master node of Redis suddenly hangs up and the lock key is set before it is synchronized to the Slave node,

If the master node fails, the slave node becomes the master node. However, the new master node does not have the lock key.

Then there’s the problem, the other requests request the lock again, they get the lock; This creates business chaos.

Of course, this situation is quite special, quite rare. So how to solve this problem?

The solution

Let’s analyze the above problem, mainly because there is a delay in master and slave synchronization data.

Let’s take a look at CAP theory in distributed systems

C represents data consistency, A represents high availability, and P represents partition fault tolerance

The CAP principle is that, at best, these three elements can achieve two, not all at once.

Then our REDis architecture is AP principle, which is to ensure high availability at the expense of data consistency; That is, master/slave data has a certain delay.

Let’s take a look at another distributed lock solution on the market, zooKeeper;

The architectural principle of ZooKeeper is CP, which ensures data consistency at the expense of high availability. The principle of ZooKeeper is to ensure that data is written successfully to the master node and to most of the follow and slave nodes. Only when data is written successfully, the client will be returned successfully. This ensures that the data is not delayed.

However, you may notice that the write performance of ZooKeeper is slightly lower, because both the master and slave must be successfully written.

conclusion

In the whole mainstream scheme, if it is necessary to ensure data consistency and lock will not have problems, zK scheme can be selected to achieve distributed lock; However, it is acceptable to use the Redis Redisson scheme (recommended) for special locking errors. You can choose according to different services, that is, different solutions can be implemented on the same platform.

Three things to watch ❤️

If you find this article helpful, I’d like to invite you to do three small favors for me:

  1. Like, forward, have your “like and comment”, is the motivation of my creation.
  2. Follow the public account “ARCHITECTURE Notes of A Wind” and share original knowledge from time to time.
  3. Also look forward to the follow-up article ing🚀
  4. [666] Scan code to obtain the architecture advanced learning materials package