当前位置:网站首页>Distributed lock implementation scheme (glory collection version)
Distributed lock implementation scheme (glory collection version)
2022-07-24 10:38:00 【Dragon back ride Shi】

Catalog
Two 、 be based on Zookeeper To implement distributed locking
Preface
At present, more and more Internet projects adopt cluster deployment , That's the distributed situation , These two kinds of locks are not enough .
Let's take two pictures to illustrate , In the case of local locks :
In the case of distributed locks :
In terms of its ideas , It's a kind of “ All I want ” Thought , All services come to a unified place to get locks , Only those who get the lock can continue to execute .

Finish thinking , Let's talk about the specific implementation .
One 、Redis Realization
To implement distributed locks , stay Redis in SETNX key value command , Meaning for set if not exists( If it doesn't exist key, Just to go set value ), For example, Zhang San went to the toilet , Look, the toilet is locked , He won't go in , He didn't go until the toilet door was open .

You can see , for the first time set Back to 1, It means success , But the second time back 0, Express set Failure , Because there is already this key 了 .
Of course, it's just setnx Is this order OK ? Of course not , Imagine a situation , Zhang San is in the toilet , But he hasn't been released in it , Squatting in it all the time , No one outside wants to go to the toilet , Trying to hammer him to death .
Redis Empathy , Suppose locking has been done , But the lock is not released because of downtime or exception , That's what it's called “ Deadlock ”.

Smart, you must have thought of it , Just set the expiration time for it , Sure SETEX key seconds value command , For a given key Set expiration time , The unit is in seconds .
But there's another problem , I just locked it , Expiration time has not been set ,Redis When it goes down, it's deadlocked again , So we should ensure the atomicity , Or make it together , Or fail together .
Of course we can think of Redis It must have been done for you , stay Redis 2.8 After the version of the ,Redis It gives us a combined command SET key value ex seconds nx, Lock at the same time set the expiration time .

It's like the company rules that everyone can only stay in the toilet at most 2 minute , Whether it's released or not, it has to be released , That's it “ Deadlock ” problem .
But then there's no problem ? How is that possible? .
Imagine another situation , The toilet door can only be opened from the inside , After Zhang San went to the toilet, Zhang Si went in and locked the door , But people outside think it's still Zhang San inside , And it's been 3 Minutes. , I just pried the door open , It's a four inside , It's embarrassing .
Switch to Redis That is to say, a business takes a long time to execute , The lock has expired by itself , Someone else has set up a new lock , But when the business is finished, release the lock directly , It's possible that someone else's lock has been deleted , Isn't that a mess .
So when locking , To set a random value , Compare when deleting locks , If it's your own lock , To delete .
It's no use saying more , annoying , Go straight to the code :
// be based on jedis and lua Script to achieve
privatestaticfinal String LOCK_SUCCESS = "OK";
privatestaticfinal Long RELEASE_SUCCESS = 1L;
privatestaticfinal String SET_IF_NOT_EXIST = "NX";
privatestaticfinal String SET_WITH_EXPIRE_TIME = "PX";
@Override
public String acquire() {
try {
// The timeout for obtaining the lock , After this time, the lock is abandoned
long end = System.currentTimeMillis() + acquireTimeout;
// Randomly generate one value
String requireToken = UUID.randomUUID().toString();
while (System.currentTimeMillis() < end) {
String result = jedis
.set(lockKey, requireToken, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return requireToken;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} catch (Exception e) {
log.error("acquire lock due to error", e);
}
returnnull;
}
@Override
public boolean release(String identify) {
if (identify == null) {
returnfalse;
}
// adopt lua Script to compare and delete , Guaranteed atomicity
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = new Object();
try {
result = jedis.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(identify));
if (RELEASE_SUCCESS.equals(result)) {
log.info("release lock success, requestToken:{}", identify);
returntrue;
}
} catch (Exception e) {
log.error("release lock due to error", e);
} finally {
if (jedis != null) {
jedis.close();
}
}
log.info("release lock failed, requestToken:{}, result:{}", identify, result);
returnfalse;
}reflection : The atomicity of lock and release lock can be used lua Script to ensure , How can the automatic renewal of the lock be realized ?
1.1.Redisson Realization
Redisson seeing the name of a thing one thinks of its function ,Redis Son , In essence Redis Lock , Just right Redis A lot of encapsulation , It not only provides a series of distributed Java Common objects , There are also many distributed services .
In the introduction of Redisson After our dependence , You can call it directly :
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.4</version>
</dependency>Let's start with Redisson Lock code for :
private void test() {
// Distributed lock name The finer the granularity of the lock , The better the performance
RLock lock = redissonClient.getLock("test_lock");
lock.lock();
try {
// Specific business ......
} finally {
lock.unlock();
}
}It's that simple , Usage method jdk Of ReentrantLock almost , And it supports ReadWriteLock( Read-write lock )、Reentrant Lock( Reentrant lock )、Fair Lock( Fair lock )、RedLock( Red lock ) All kinds of locks , For details, please refer to redisson Official documents to see .

that Redisson What are the advantages ? Automatic renewal of locks ( The default is 30 second ), If the business is too long , The lock will be automatically renewed during operation 30s, Don't worry that the lock will be automatically deleted when the business execution time is too long .
Lock the business as long as the operation is completed , There will be no renewal of the current , Even without manual unlocking , By default, the lock is in 30s Delete after , No deadlock problem .
I also mentioned the automatic renewal of locks , Let's see Redisson How to achieve .
Let's start with , Here's the main thing Redisson Medium RLock, It's a reentrant lock , There are two ways to do it :
// The most common use method
lock.lock();
// After the lock 10 Seconds auto unlock
// Don't need to call unlock Method to manually unlock
lock.lock(10, TimeUnit.SECONDS);The only method without parameters is to provide automatic renewal of lock , Internal use is “ watchdog ” Mechanism , Let's take a look at the source code .


Whether it's a null parameter or a method with parameters , They all call the same lock Method , If you don't pass the parameters, you've passed one -1, And the method with parameters passes the actual time .

Keep going scheduleExpirationRenewal Method :
Click in renewExpiration Method :
To sum up , When we specify the lock expiration time , Then the lock will be released automatically when it comes to time . If no lock expiration time is specified , Just use the default time of the watchdog 30s, As long as the lock is successful , Will start a timed task , every other 10s Set a new expiration time for the lock , Time is the default time of the watchdog , Until the lock is released .
Summary : although lock() There is an automatic renewal mechanism , But it's still recommended in development
lock(time,timeUnit), Because it saves the performance loss of the whole renewal , You can set the expiration time a little longer , collocationunlock().
If business execution is completed , Will release the lock manually , If the business execution times out , In general, our service will also set the business timeout , It's just a mistake , After the error is reported, the lock will be released through the set expiration time .
public void test() {
RLock lock = redissonClient.getLock("test_lock");
lock.lock(30, TimeUnit.SECONDS);
try {
//....... Specific business
} finally {
// Release the lock manually
lock.unlock();
}
}
Two 、 be based on Zookeeper To implement distributed locking
Many kids know that in a distributed system , It can be used ZK To be a registry , But in fact, in addition to being an ancestral Center , use ZK Distributed lock is also a common solution .
Let's take a look first ZK How to create a node in ?ZK in create [-s] [-e] path [data] command ,-s To create ordered nodes ,-e Create temporary nodes .

This creates a parent node and a child node for the parent node , The combined command means to create a temporary ordered node .
and ZK Distributed lock is mainly realized by creating temporary sequential nodes . Why use sequential nodes and why use temporary nodes instead of persistent nodes ? Think about it first , This is explained below .
As well as ZK How to view nodes in ?ZK in ls [-w] path To view node commands ,-w Add a watch( The monitor ),/ To view all nodes of the root node , You can see the node we just created , At the same time, if it is followed by the name of the specified node, it is to view the child nodes under the specified node .

hinder 00000000 by ZK The order added to the order node . Registered listeners are also ZK One of the most important things in implementing distributed locks .

So let's see ZK The main process of implementing distributed lock :
When the first thread comes in, it will go to the parent node to create a temporary sequential node .
The second thread comes in and finds that the lock has been held , Will register a... For the node that currently holds the lock watcher Monitor .
The third thread comes in and finds that the lock has been held , Because it's a sequential node , It will create one for the previous node watcher Monitor .
When the first thread releases the lock , Delete node , The lock is held by its next node .
See here , Smart guys have seen the benefits of sequential nodes . Non sequential nodes , Every thread that comes in will register a listener on the node that holds the lock , Easy to trigger “ Herd behaviour ”.

Such a large flock of sheep are coming to you , Whether you can stand it or not , Anyway ZK Servers increase the risk of downtime .
And sequential nodes don't , When the sequence node finds that a thread already holds a lock , It registers a listener with its previous node , So when the node holding the lock is released , Only the next node holding the lock can seize the lock , It's equivalent to queuing up for execution , Reduce the risk of server downtime .
As for why temporary nodes are used , and Redis There's a reason for the expiration time of , Even if the ZK Server down , Temporary nodes will disappear with the server down , The deadlock situation is avoided .
Here is the implementation of the previous code :
public class ZooKeeperDistributedLock implements Watcher {
private ZooKeeper zk;
private String locksRoot = "/locks";
private String productId;
private String waitNode;
private String lockNode;
private CountDownLatch latch;
private CountDownLatch connectedLatch = new CountDownLatch(1);
private int sessionTimeout = 30000;
public ZooKeeperDistributedLock(String productId) {
this.productId = productId;
try {
String address = "192.168.189.131:2181,192.168.189.132:2181";
zk = new ZooKeeper(address, sessionTimeout, this);
connectedLatch.await();
} catch (IOException e) {
throw new LockException(e);
} catch (KeeperException e) {
throw new LockException(e);
} catch (InterruptedException e) {
throw new LockException(e);
}
}
public void process(WatchedEvent event) {
if (event.getState() == KeeperState.SyncConnected) {
connectedLatch.countDown();
return;
}
if (this.latch != null) {
this.latch.countDown();
}
}
public void acquireDistributedLock() {
try {
if (this.tryLock()) {
return;
} else {
waitForLock(waitNode, sessionTimeout);
}
} catch (KeeperException e) {
throw new LockException(e);
} catch (InterruptedException e) {
throw new LockException(e);
}
}
// Get the lock
public boolean tryLock() {
try {
// It's coming in locksRoot + “/” + productId
// hypothesis productId Represents a commodity id, for instance 1
// locksRoot = locks
// /locks/10000000000,/locks/10000000001,/locks/10000000002
lockNode = zk.create(locksRoot + "/" + productId, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// See if the newly created node is the smallest one
// locks:10000000000,10000000001,10000000002
List<String> locks = zk.getChildren(locksRoot, false);
Collections.sort(locks);
if(lockNode.equals(locksRoot+"/"+ locks.get(0))){
// If it's the smallest node , It means to obtain the lock
return true;
}
// If it's not the smallest node , Find something smaller than yourself 1 The node of
int previousLockIndex = -1;
for(int i = 0; i < locks.size(); i++) {
if(lockNode.equals(locksRoot + “/” + locks.get(i))) {
previousLockIndex = i - 1;
break;
}
}
this.waitNode = locks.get(previousLockIndex);
} catch (KeeperException e) {
throw new LockException(e);
} catch (InterruptedException e) {
throw new LockException(e);
}
return false;
}
private boolean waitForLock(String waitNode, long waitTime) throws InterruptedException, KeeperException {
Stat stat = zk.exists(locksRoot + "/" + waitNode, true);
if (stat != null) {
this.latch = new CountDownLatch(1);
this.latch.await(waitTime, TimeUnit.MILLISECONDS);
this.latch = null;
}
return true;
}
// Release the lock
public void unlock() {
try {
System.out.println("unlock " + lockNode);
zk.delete(lockNode, -1);
lockNode = null;
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
// abnormal
public class LockException extends RuntimeException {
private static final long serialVersionUID = 1L;
public LockException(String e) {
super(e);
}
public LockException(Exception e) {
super(e);
}
}
}summary
Now that I understand Redis and ZK The implementation of distributed lock respectively , So it should be different . you 're right , I've sorted it out for you :
Different ways of implementation ,Redis The implementation is to insert a piece of occupied data , and ZK The implementation is to register a temporary node .
In case of downtime ,Redis Need to wait until the expiration time to automatically release the lock , and ZK Because it's a temporary node , At the time of downtime, the node has been deleted to release the lock .
Redis In the case of not seizing the lock, we usually spin to get the lock , It's a waste of performance , and ZK The way to get the listener is to register , In terms of performance, it is better than Redis.
But what kind of implementation should be adopted , We still need to analyze the specific situation , Combined with the technology stack referenced by the project to realize the landing .

边栏推荐
- PC Museum (1) 1970 datapoint 2000
- MySQL - normal index
- 二叉树基础知识概览
- 用 Signal Processing Toolbox 软件对数据进行滤波
- Differential restraint system -- 1 and 2 -- May 27, 2022
- ZOJ 2770 differential restraint system -- 2 -- May 20, 2022
- Set up mail server with dynamic ip+mdaemon
- Kotlin Advanced Grammar
- [correcting Hongming] what? I forgot to take the "math required course"!
- Analysis of distributed lock redistribution principle
猜你喜欢
![When to use obj['attribute name'] for the attribute name of an object](/img/ec/cd265444b60d2d2da646ae64aab267.png)
When to use obj['attribute name'] for the attribute name of an object

Sentinel flow control quick start

MySQL - 索引的隐藏和删除

《nlp入门+实战:第二章:pytorch的入门使用 》

N-tree, page_ Size, database strict mode modification, and the difference between delete and drop in the database

Image processing: rgb565 to rgb888

分布式事务处理方案大 PK!
![[correcting Hongming] what? I forgot to take the](/img/41/c8fa6380ab63949ae6d904fdbb005c.png)
[correcting Hongming] what? I forgot to take the "math required course"!

Qt应用程序防止多开,即单例运行

MySQL - lock
随机推荐
Erlang学习番外
zoj-Swordfish-2022-5-6
[dish of learning notes dog learning C] detailed operator
PC博物馆(1) 1970年 Datapoint 2000
OSPF含特殊区域实验,MGRE构建和重发布
《nlp入门+实战:第二章:pytorch的入门使用 》
Common Unicode encoding range
Erlang studies abroad
MySQL - 删除数据库表中的数据
What does resource pooling and resource pooling mean?
Sentinel implements the persistence of pull pattern rules
I admire a Google boss very much, and he left..
[electronic device note 4] inductance parameters and type selection
The paper of gaojingjian center was selected into the ACL 2022 of the international summit to further expand the privacy computing capacity of Chang'an chain
MySQL - 更新表中的数据记录
Zoj1137+ operation 1 -- May 28, 2022
Volcanic engine: open ByteDance, the same AI infrastructure, a system to solve multiple training tasks
NiO knowledge points
JS function call download file link
Adobe Substance 3D Designer 2021软件安装包下载及安装教程