Java based on jedislock redis distributed lock implementation example code

What is a distributed lock?

The concept of stand-alone lock: it is easy to lock a normal stand-alone project (i.e. running a project under Tomcat without configuring a cluster). Java provides many mechanisms, such as synchronized, volatile, reentrantlock and so on.

Why do we need distributed locks: when our projects are relatively large, the single version projects can no longer meet the throughput requirements, so we need to do load balancing for the projects, and it may also need to decouple the projects into different services, so it must be made into distributed projects. Distributed projects are controlled by different programs, Therefore, using the lock provided by java can not fully ensure the concurrency requirements. We need to use the third-party framework to realize the concurrency blocking control to meet the needs of the actual business.

1、 Several conditions for using distributed locks:

1. The system is a distributed system (the key is distributed, which can be implemented by reentrantlock or synchronized code block for single machine) 2. Shared resources (each system accesses the same resource, and the carrier of the resource may be traditional relational database or NoSQL) 3. Synchronous access (that is, many process colleagues access the same shared resource. Without synchronous access, who cares if your resources compete or not)

2、 Application scenario examples

The deployment architecture of the management background (multiple Tomcat servers + redis [multiple Tomcat servers access one redis] + MySQL [multiple Tomcat servers access MySQL on one server]) meets the conditions for using distributed locks. If multiple servers want to access the resources of redis global cache, there will be problems if they do not use distributed locks. See the following pseudo code:

The above code mainly realizes the following functions:

Obtain the value n from redis, check the boundary of the value n, add 1, and then write n back to redis. This kind of application scenario is very common, such as second kill, global increment ID, IP access restriction, etc. In terms of IP access restrictions, malicious attackers may initiate unlimited accesses with a large amount of concurrency. The boundary check of N in a distributed environment is unreliable, because the N read from redis may already be dirty data. The traditional locking methods (such as Java synchronized and lock) are also useless, because this is a distributed environment, and the firefighters of this synchronization problem are helpless. In this critical autumn, distributed locks have finally come into use.

Distributed locks can be implemented in many ways, such as zookeeper, redis, etc. Either way, its basic principle remains the same: a state value is used to represent the lock, and the occupation and release of the lock are identified by the state value.

Here we mainly talk about how to implement distributed locks with redis.

3、 Use the setnx command of redis to implement distributed locks

1. Principle of implementation

Redis is a single process and single thread mode. The queue mode is used to change concurrent access into serial access, and there is no competition between multiple clients for redis connection. The setnx command of redis can easily implement distributed locks.

2. Basic command parsing

1)setNX(SET if Not eXists)

Syntax:

SETNX key value

Set the value of the key to value if and only if the key does not exist.

If the given key already exists, setnx will not do anything.

Setnx is short for set if not exists

Return value:

Set successfully, return 1.

Setting failed, return 0.

example:

So we use to execute the following command

If 1 is returned, the client obtains the lock and sends the lock The key value of foo is set to the time value, which indicates that the key has been locked. Finally, the client can use del lock Foo to release the lock.

If 0 is returned, it indicates that the lock has been obtained by other clients. At this time, we can return or retry first, wait for the other party to complete or wait for the lock to timeout.

2)getSET

Syntax:

GETSET key value

Set the value of the given key to value and return the old value of the key.

When the key exists but is not of string type, an error is returned.

Return value:

Returns the old value of the given key.

When the key has no old value, that is, when the key does not exist, nil is returned.

3)get

Syntax:

GET key

Return value:

When the key does not exist, nil is returned; otherwise, the value of the key is returned.

If the key is not of string type, an error is returned

4、 Resolve deadlock

The above locking logic has a problem: if a client holding a lock fails or crashes and cannot release the lock, how to solve it?

We can judge whether this happens by the timestamp corresponding to the key of the lock. If the current time is greater than lock The value of foo indicates that the lock has expired and can be reused.

When this happens, you can't simply delete the lock through del and then setnx again (logically, the operation of deleting the lock should be performed by the lock owner. Here, you just need to wait until it times out). When multiple clients detect that the lock times out, they will try to release it. Here may be a race condition. Let's simulate this scenario:

C0 operation timed out, but it still holds the lock. C1 and C2 read the lock Foo checked the timestamp and found that it timed out. C1 send del lock Foo C1 sends setnx lock Foo and succeeded. C2 sends del lock Foo C2 sends setnx lock Foo and succeeded.

In this way, C1 and C2 have got the lock! Big problem!

Fortunately, this problem can be avoided. Let's take a look at the C3 client:

C3 send setnx lock Foo wants to obtain the lock. Since C0 still holds the lock, redis returns a 0 to C3 and C3 sends a get lock Foo to check whether the lock has timed out. If not, wait or try again. Conversely, if it has timed out, C3 attempts to obtain the lock through the following operation: GetSet lock Foo < current UNIX time + lock timeout + 1 > if the timestamp obtained by C3 through GetSet still times out, it means that C3 has obtained the lock as desired.

If a client named C4 performs the above operations one step faster than C3 before C3, the timestamp obtained by C3 is an untimed value. At this time, C3 does not obtain the lock as scheduled and needs to wait or retry again. Note that although C3 did not get the lock, it rewrites the timeout value of the lock set by C4, but the impact of this very small error can be ignored.

Note: in order to make the distributed lock algorithm more stable, the client holding the lock should check whether its lock has timed out before unlocking, and then do del operation, because the client may hang up because of a time-consuming operation. After the operation, the lock has been obtained by others because of the timeout, so it is not necessary to unlock.

5、 Code implementation

Expiremsecs lock holding timeout prevents the thread from executing indefinitely after entering the lock, so that the lock cannot be released. Timeoutmsecs lock waiting timeout prevents the thread from starving and never has the opportunity to enter the lock to execute code

Note: the redis configuration needs to be built in the project

Call:

6、 Some problems

1. Why not directly use expire to set the timeout, and put the milliseconds of the time in redis as a value?

Give the timeout to redis for processing as follows:

There seems to be no problem with this method, but if redis crashes after setnx, expire will not be executed, and the result will be deadlock. The lock never times out.

2. Why use GetSet to set the time of the new timestamp, get the old value, and then compare it with the external timestamp to determine the timeout time when the previous lock has timed out?

Because it is a distributed environment, two processes can enter the judgment of lock timeout when the previous lock fails. For example:

C0 times out and still holds the lock. C1 / C2 requests to enter the method at the same time

C1 / C2 got the timeout of C0

C1 using the GetSet method

C2 also executes the GetSet method

If we don't add oldvaluestr According to the judgment of equals (currentvaluestr), both C1 and C2 will obtain locks. After adding, it can ensure that only one of C1 and C2 can obtain locks and the other can only wait.

Note: the timeout may not be the original timeout. The timeout of C1 may be overwritten by C2, but the difference in milliseconds is extremely small, which is ignored here.

The above is the whole content of this article. I hope it will be helpful to your study, and I hope you can support programming tips.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>