ZK distributed lock (to be continued)

Realize ideas

Fair lock: Create an ordered node to determine whether the node is the node with the smallest sequence number (the first node), if so, acquire the lock; if not, monitor the deletion event of the node smaller than the node.

Unfair lock: directly try to create a node under the specified path, if the creation is successful, it means that the node has grabbed the lock. If the creation fails, listen for the deletion event of the lock node, or sleep for a period of time and try again.

Reentrant: Use ThreadLocal to record the unique identifier of the locked client. When it is repeated, it is obtained from ThreadLocal first, and if it is obtained, it is considered that the locking is successful, and it returns directly.

Create reentrant fair locks with transient nodes

The advantage of using a transient node is that when the session fails, the node will be cleaned up, so as to avoid the problem that the lock cannot be released due to exceptions, downtime or server restart after the persistent node is successfully locked.

Implemented using curator

// 假设需要加锁的订单Id
private static String orderId = "157146671409578219";
// 工程名
private static String appName = "trade_";
// 此次加锁业务处理逻辑描述
private static String operatorDesc = "updateTrade_";

// 部门,每个部门可以有自己的zk空间目录
private static String department = "zfpt" ;

// 锁前缀 应该根据业务 具有唯一性
private static String lockPrefixKey = "/" + appName + operatorDesc ;

/**
 * 每个线程 创建自己的Connection ,创建自己的Session
 */
@Test
public void curator() throws Exception {

    for (int i = 0 ; i < 100 ; i++) {

        Thread currentThread = new Thread(() -> {

            // 创建Connection
            CuratorFramework client = CuratorFrameworkFactory.builder()
                    .connectString("master:2181,slave1:2181,slave2:2181")
                    .retryPolicy(new RetryOneTime(1000)) //重试策略
                    .namespace(department) // 可以设置自己部门缩写
                    .build();
            client.start();

            // 模拟对同一个订单加锁
            InterProcessMutex lock = new InterProcessMutex(client, lockPrefixKey + orderId);

            try {
                // 一直尝试加锁 直到锁可用。 有点像synchronized
                // lock.acquire();
                if(lock.acquire(1, TimeUnit.SECONDS)) {
                    System.out.println(Thread.currentThread().getName() + " 抢到锁");
                } else {
                    System.out.println(Thread.currentThread().getName() + "超时没有抢到锁");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    // 如果当前线程获得到锁,则释放锁
                    if(lock.isAcquiredInThisProcess()) {
                        System.out.println(Thread.currentThread().getName() + " 释放锁");
                        lock.release();
                    } else {
                        System.out.println(Thread.currentThread().getName() + " 没有抢到锁,故没有释放锁");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        currentThread.setName("Lock【" + i + "】");
        currentThread.start();
    }

    Thread.sleep(Integer.MAX_VALUE);
}

Implementation principle:
Because ZK does not allow the creation of child nodes under temporary nodes, the InterProcessMutextool class will create a persistent node based on the locked incoming path (that is, lockPrefixKey + orderId in the case); then create a transient node under this persistent node , after the creation is successful, sort all the child nodes under the persistent node in descending order to determine whether the current node is the first node, if so, acquire the lock, otherwise add a listening event to the previous node. Then Object.wait(), when the listening event is triggered, the method will be called notifyAllto wake up the waiting thread. Try acquiring the lock again.

shortcoming:

  1. The incoming locked node will be created as a permanent node (that is, lockPrefixKey + orderId), so the number of zk nodes will increase rapidly.
  2. Temporary node instability: After a client is successfully locked, the session may be disconnected due to network jitter and other reasons. The temporary node created by the client is cleaned up, resulting in the successful locking of another client that is listening. Simultaneous operate.

Create reentrant fair locks with persistent nodes

The temporary node mentioned above is unstable, and the parent node is a permanent node that cannot be released. My humble opinion is:

  1. Use persistent nodes instead of temporary nodes: delete the locked nodes created by yourself when releasing the lock.
  2. The parent node is a permanent node and cannot be released: you can judge when each client releases the lock. If you are the last node, delete the parent node.
  3. However, there are issues to consider: after the client is successfully locked, the locked node cannot be deleted due to downtime or restart or other extreme abnormal conditions. The last locked node is also abnormal, and the parent node cannot be deleted. At this time, you can add an expiration time to each lock, and the expiration will expire. Since zk does not provide automatic cleanup of expiration, it can be judged first when accessing the node for the second time, and if it is judged to be invalid, delete it and then create it. If there is no second-visit node, you can rely on scheduled tasks for node cleanup.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325208238&siteId=291194637