Source:

https://www.cnblogs.com/jackyfei/p/12142840.html

How to ensure that a method, or a piece of code under the condition of high concurrency, at the same time can only be one thread execution, single application can use the API to control concurrent processing, but the evolution of monomer application architecture for distributed decay after service architecture, across processes an instance of the deployment, obviously can’t do by application layer lock mechanism to control concurrent. So what are the types of locks, why locks are used, and what are the use scenarios of locks? Today we are going to talk about the use of locks in high concurrency scenarios.

Lock the category

Different application scenarios require different locks, so let’s take a look at the types of locks and the differences between them.

  • Pessimistic locking (the synchronize)
  • The heavyweight lock Synchronize in Java
  • Database row locking
  • Optimistic locking
  • Lightweight locks volatile and CAS in Java
  • Database Version number
  • Distributed locks (Redis locks)

Optimistic locking

For example, if you are an optimistic and positive person, you always hope for the best. For example, when you go to get shared data, you will assume that others will not modify it, so you will not lock it, but when you update the data, you will judge whether there is anyone to update the data during this period.

Optimistic locking is used before judgment. Let’s take a look at the pseudocode:

1reduce() 2{3 select total_amount from table_1 4 if(total_amount < amount){5 return failed. 6} 7 update total_amount = total_amount - amount where total_amount > amount; 9}Copy the code

  • The version number of the database is optimistic lock;
  • Classes implemented by CAS algorithm are optimistic locks.

Pessimistic locking

Pessimistic lock is how to understand? Relatively optimistic locking is the opposite, always assuming the worst, assuming that every time you take data it will be modified by someone else, so every time you share data you add a lock on it, release the lock when you’re done, and then give the data to someone else.

Pessimistic locking is judged first and used later. Let’s also look at pseudocode:

1reduce() 2{3 int num = update total_amount = total_amount-amount where total_amount > amount; 5 if(num ==1){6} 8}Copy the code

  • Synchronize is a pessimistic lock in Java.
  • Database row lock belongs to pessimistic lock;

Deduction operation case

Here’s a very common example, balance deduction in a high concurrency situation, or something like merchandise inventory deduction, or balance deduction in a capital account. What happens to deductions? It’s easy to see that the problem that could happen is that the deduction gets oversold, that the deduction goes negative.

For example, let’s say I only have 100 in my inventory. In the concurrent situation, the first batch of request to sell 100, the second batch of sell 100 yuan, resulting in the current inventory quantity is negative. Encounter this kind of scene should how crack? Here are four options.

Scheme 1: synchronous exclusive lock

It is easy to think of the simplest solution: synchronize a lock. But the disadvantages of exclusive locking are obvious:

  • One drawback is the performance problem caused by serialization of threads, which is relatively high performance cost.
  • Another disadvantage is the inability to address cross-process issues in distributed deployments;

Scheme 2: Database row locks

Second, we might think that using a database row lock to hold this data solves the cross-process problem compared to exclusive locking, but it still has disadvantages.

  • One of the disadvantages is performance, blocking at the database level until the transaction commits, which is also executed sequentially;
  • Second, set the isolation level of the transaction to Read Committed. Otherwise, in the case of concurrency, another transaction cannot see the Committed data, which will still cause oversold.
  • The third disadvantage is that it is easy to fill up the database connection. If there is a third party interface interaction in the transaction (there is a possibility of timeout), it will cause the connection of the transaction to be blocked and fill up the database connection.
  • The last disadvantage is that cross-deadlocks are easy to occur. If the locking of multiple services is not well controlled, cross-deadlocks of AB and AB records can occur.

Scheme 3: Redis distributed lock

In essence, the previous solution uses a database as a distributed lock. Therefore, both Redis and ZooKeeper are equivalent to a type of lock in a database. In fact, when a lock problem occurs, the code itself, whether synchronize or any other type of lock, is complicated to use. So the idea is to hand off the problem of consistency to a specialized component that can help you deal with consistency, like a database, like Redis, like ZooKeeper, etc.

Here we analyze the advantages and disadvantages of distributed locks:

  • Advantages:
  • It can avoid a large number of database exclusive lock requisition and improve the response ability of the system.
  • Disadvantages:
  • Atomicity of setting locks and setting timeouts;
  • Disadvantages of not setting timeout;
  • Service downtime or thread blocking timeout;
  • The setting of the timeout time is unreasonable;

Atomicity of lock and expiration Settings

The redis lock setnx command is used to set the lock expiration time (expire) and unlock the lock (del). However, prior to 2.6.12, the lock and lock expiration commands are two operations and do not have atomism. If the key set by the current thread is suspended or blocked before the expiration time is set by setnx, subsequent threads cannot use setnx to obtain the lock, resulting in deadlock.

Redis2.6.12 and later add an optional parameter that allows you to set the expiration time of the key at the same time as the lock, ensuring atomization of the lock and expiration operations.

However, even if the atomicity problem is solved, some extreme problems will also be encountered in the business. For example, in the distributed environment, after A obtains the lock, the business code of thread A takes too long, leading to the timeout time of the lock and automatic lock failure. Then thread B accidentally held the lock, and thread A resumed execution and directly released the lock with the del command. Thus, thread B mistakenly deleted the lock with the same Key. It’s still a common scenario for code to take too long, and it can easily happen if you have external communication interface calls in your code.

Set a reasonable duration

What if you don’t set the duration of the thread timeout block, and of course you don’t, if the thread is holding the lock and the service goes down, then the lock will never expire. If the lock timeout period is set too long, performance will be affected. If the lock timeout period is set too short, services may be blocked and processing may not be completed. Can you set the lock timeout period properly?

Life lock

This is a very not easy to solve the problem, but there is a way to solve the problem, that is life lock, we can lock first to set a timeout, then start a daemon thread, let daemon thread after a period of time again to set the timeout of the lock, life-saving lock implementation process is to write a daemon thread, and then to judge object lock, When it is about to fail, re-lock it again, but be sure to judge that the lock object is the same, not disorderly continuation.

Similarly, when the main thread service is finished, the daemon thread also needs to be destroyed to avoid resource waste. The solution of using the continuous lock is more complicated. Therefore, if the service is simple, you can set the lock timeout time reasonably according to experience and analogy.

Solution 4: Optimistic database locking

One of the principles of optimistic database locking is to minimize the scope of locking. The larger the lock range, the worse the performance, the database lock is to reduce the lock range to the minimum. Let’s look at the pseudocode below

1reduce() 2{3 select total_amount from table_1 4 if(total_amount < amount){5 return failed. 6} 7 update total_amount = total_amount - amount; 9}Copy the code

We can see that the code before the modification does not have a WHERE condition. After modification, add where condition judgment: the total inventory is greater than the inventory to be deducted.

1update total_amount = total_amount - amount where total_amount > amountCopy the code

If the number of updates returns 0, the deduction was preempted by another thread during execution and the negative deduction was avoided.

However, this solution also involves the problem of how to roll back the data if there are other database writes in the previous update code and other business logic.

Here’s what I suggest. You can write it one of two ways:

  • Using transaction rollback writing:

We first add a transaction to the business method, which throws an exception when the inventory impact count reaches zero and rolls back its previous business code.

1reduce() 2{3 select total_amount from table_1 4 if(total_amount < amount){5 return failed. 6} 7 int num = update total_amount = total_amount - amount where total_amount > amount; 9 the if (num = = 0) throw Exception; 10}Copy the code

  • The second way to write it
1reduce() 2{3 int num = update total_amount = total_amount-amount where total_amount > amount; 5 if(num ==1){6} else{8throw Exception; 10 9}}Copy the code

Executing the update business logic first and then executing the logic if it succeeds is my preferred option. This approach can be used for shared resource deductions in concurrent situations, but it raises the question, for example, what if the business in the other business logic fails for some particular reason? For example, in the process of deduction service OOM how to do?

All I can say is that there are very extreme cases, such as sudden outage and loss of intermediate data, and in those rare cases you have to intervene manually, and if you take all the extreme cases into account, it’s not realistic. The focus of our discussion is how operations on shared resources can be locked in concurrent cases.

conclusion

Let me summarize for you. If you can solve this kind of problem skillfully, the first thing that comes to mind is: database version number solution or distributed lock solution; But if you’re a beginner, you’ll probably be the first to consider synchronous locking or database row locking in Java.

The purpose of today’s discussion is to put the locks in these scenarios into a specific scenario, step by step to compare and analyze, so that you can understand the ins and outs of the use of locks in a more comprehensive system. My name is Zhang Feihong. I hope my sharing can help you.

● Thread synchronization notes

● Rambling about when to move from monolithic architecture to microservices?

● What happened to the time and cost of microservices

● Micro-service learning navigation

● Why do you need DDD when designing microservices?

● If you were an architect, what would you do

● Micro service division posture

● NIO model of Java IO model

● MongoDB cluster construction: sharding + copy + election

When the Fork – Join framework

● Spring Boot implements dynamic adding, deleting, starting, and stopping scheduled tasks

● MongoDB – Users and permissions

● SpringForAll Community, 10 selected articles from 2019

This article is published by OpenWrite!