preface
Pick up the computer, pick up the keyboard, think back to the last time I wrote an article, as if I can’t remember when.
It wasn’t because I was lazy, because I was preparing for the interview, and then I went on a job hunting tour. I met with 6 companies and received 5 offers, and then I secured the job. However, I haven’t gone to all kinds of big factories, because I feel that the current technology is not up to the requirements of big factories, and the algorithm is also relatively bad. However, I found a good company this time. Entering a big factory is a small goal for the next stage.
Back to the topic, look at distributed lock, in the last article has also introduced what is distributed lock, also said distributed lock based on Redis, this article will introduce distributed lock based on ZooKeeper.
Zookeeper implementation
Many partners know that in distributed systems, you can use ZK to do the registry, but in fact, in addition to do the ancestor registry, using ZK to do the distributed lock is also a very common solution.
How do you create a node in ZK
The create [-s] [-e] path [data] command exists in zk. -s creates an ordered node and -e creates a temporary node.
This creates a parent node and a child node for the parent node. The combined command means to create a temporary ordered node. In ZK, distributed locking is mainly achieved by creating temporary sequential nodes. Why sequential nodes and why temporary nodes rather than persistent nodes? Think about it for a moment and it will be explained below.
How do I view nodes in ZK
In zk, ls [-w] path is the command to view the node,-w is the command to add a watch, / is the command to view all nodes of the root node, you can see the node we just created, and also the child nodes of the specified node if the specified node name is followed.
The following 00000000 is the order that ZK adds to the sequential nodes. Registering listeners is also an important thing in ZK’s distributed lock implementation. Here is a look at the main process of ZK’s distributed lock implementation.
When the first thread comes in, it creates a temporary sequence node on the parent node. 2. When the second thread comes in and finds that the lock is already held, it registers a Watcher listener for the node that currently holds the lock. 4. When the first thread releases the lock, the node is removed and its next node takes possession of the lock
Now, the smart guys already see the benefits of sequential nodes. For non-sequential nodes, every time a thread comes in, it registers a listener on the node that holds the lock, leading to “herd behavior”.
With so many sheep racing towards you, zK servers increase the risk of downtime, whether you can handle it or not. The sequential node will register a listener to its previous node when it finds that there is already a thread holding the lock. In this way, when the node holding the lock is released, only the next node holding the lock can grab the lock, which is equivalent to queuing up to reduce the risk of server breakdown.
As for why temporary nodes are used, redis expiration time is the same reason, even if the ZK server is down, temporary nodes will disappear with the server down, avoiding the deadlock situation.
Let’s implement the last piece of 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:2181192168 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 newLockException(e); }}public void process(WatchedEvent event) {
if (event.getState() == KeeperState.SyncConnected) {
connectedLatch.countDown();
return;
}
if (this.latch ! =null) {
this.latch.countDown(); }}public void acquireDistributedLock(a) {
try {
if (this.tryLock()) {
return;
} else{ waitForLock(waitNode, sessionTimeout); }}catch (KeeperException e) {
throw new LockException(e);
} catch (InterruptedException e) {
throw newLockException(e); }}/ / acquiring a lock
public boolean tryLock(a) {
try {
LocksRoot + "/" + productId passed in
// Suppose productId represents a commodity ID, such as 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
Locks: 10000000000,10000000001,10000000002
List<String> locks = zk.getChildren(locksRoot, false);
Collections.sort(locks);
if(lockNode.equals(locksRoot+"/"+ locks.get(0))) {// If it is the smallest node, the lock is acquired
return true;
}
// If it is not the smallest node, find the node 1 less than you
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;
}
/ / releases the lock
public void unlock(a) {
try {
System.out.println("unlock " + lockNode);
zk.delete(lockNode, -1);
lockNode = null;
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch(KeeperException e) { e.printStackTrace(); }}/ / exception
public class LockException extends RuntimeException {
private static final long serialVersionUID = 1L;
public LockException(String e) {
super(e);
}
public LockException(Exception e) {
super(e); }}}Copy the code
summary
Now that you understand redis and ZK’s respective implementation of distributed locks, it must be different. Yeah, I got it all sorted out for everyone.
1. Different implementation methods: Redis is implemented to insert a placeholder data, while ZK is implemented to register a temporary node. 2. In case of outage, Redis needs to wait until the expiration time to automatically release the lock, while ZK has deleted the node to release the lock during outage because it is a temporary node. 3. Redis usually spins to acquire the lock without preempting the lock, which wastes performance. However, ZK obtains the lock by registering listeners, which has better performance than REDis.
However, the specific way to use which implementation, or need to be specific to the case, combined with the technology stack referenced by the project to implement.
Finished!
I have seen it, click a like point a concern and then go ~