【弄nèng - Zookeeper】Zookeeper入门教程(六)—— 分布式锁InterProcessMutex

本文介绍zk客户端curator的使用,本文主要介绍Curator recipes的使用
官方文档传送门
官方文档Curator recipes
参考:http://www.throwable.club/2018/12/16/zookeeper-curator-usage/
[https://blog.csdn.net/hosaos/article/details/88727817]
参考书籍:《从Paxos到ZooKeeper 分布式一致性原理与实践》

1. 分布式锁InterProcessMutex

在分布式系统中,经常要进行同步控制,这时候就会用到分布式锁,InterProcessMutex是ZK的分布式锁实现。

pom

		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-recipes</artifactId>
			<version>2.13.0</version>
		</dependency>

构造方法

    /**
     * @param client client:客户端
     * @param path   the path to lock:路径
     */
    public InterProcessMutex(CuratorFramework client, String path)
    {
        this(client, path, new StandardLockInternalsDriver());
    }

    /**
     * @param client client:客户端
     * @param path   the path to lock:路径
     * @param driver lock driver:可自定义lock驱动实现分布式锁
     */
    public InterProcessMutex(CuratorFramework client, String path, LockInternalsDriver driver)
    {
        this(client, path, LOCK_NAME, 1, driver);
    }

主要方法

	// 获取锁,若失败则阻塞直到成功,支持重入
    public void acquire() throws Exception

    // 获取锁,超时失败
    public boolean acquire(long time, TimeUnit unit) throws Exception
    
    // 释放锁
	public void release() throws Exception
   

2. 生成订单号事例

模拟10个线程同时抢锁,抢锁成功的线程生成订单号,睡一秒后释放锁。

事例

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 分布式锁InterProcessMutex
 */

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class InterProcessMutexTests {

    private CuratorFramework client;

    /**
     * 使用Fluent风格API创建
     */
    @Before
    public void client() {
        // 重试策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        client = CuratorFrameworkFactory.builder()
                .connectString("localhost:2181")
                .sessionTimeoutMs(5000)  // 会话超时时间,默认60000ms
                .connectionTimeoutMs(10000) // 连接超时时间,默认15000ms
                .retryPolicy(retryPolicy) // 重试策略
                // .namespace("base") // 名称空间,如果这里设置了名称空间,那么所有路径都将预先指定名称空间
                .build();
        client.start();
    }

    /**
     * InterProcessMutex事例:生成订单号
     */
    @Test
    public void InterProcessMutex() throws Exception {
        String lockPath = "/lock_path";
        InterProcessMutex lock = new InterProcessMutex(client, lockPath);
        // 模拟10个线程抢锁
        for (int i = 0; i < 10; i++) {
            new Thread(new TestThread(i, lock)).start();
        }
        Thread.sleep(Integer.MAX_VALUE);
    }

    static class TestThread implements Runnable {
        private Integer n;
        private InterProcessMutex lock;

        public TestThread(Integer n, InterProcessMutex lock) {
            this.n = n;
            this.lock = lock;
        }

        @Override
        public void run() {
            try {
                lock.acquire();
                System.out.println("第" + n + "线程获取到了锁");
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss|SSS");
                String orderNo = sdf.format(new Date());
                System.out.println("生成的订单号是 : " + orderNo);
                // 1秒后释放锁
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    lock.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

}


控制台
在这里插入图片描述

原理
多个线程同时在指定路径下创建临时顺序节点,编号最小的获取锁,没抢到锁的线程会调用wait()方法等待被唤醒,唤醒操作是通过Watch实现的,每个节点监听前一个节点的删除事件。
如果path被删除,Watch中就会触发notifyAll(),唤醒其他线程继续抢锁。

扫描二维码关注公众号,回复: 10667615 查看本文章

关键代码
获取锁,每个线程在path下面创建临时顺序节点

private boolean internalLockLoop(long startMillis, Long millisToWait, String ourPath) throws Exception
    {
        boolean     haveTheLock = false;
        boolean     doDelete = false;
        try
        {
            if ( revocable.get() != null )
            {
                client.getData().usingWatcher(revocableWatcher).forPath(ourPath);
            }
			// 自选获取锁
            while ( (client.getState() == CuratorFrameworkState.STARTED) && !haveTheLock )
            {
            	// 临时顺序节点集合,编号从小到大
                List<String>        children = getSortedChildren();
                // 当前线程节点编号
                String              sequenceNodeName = ourPath.substring(basePath.length() + 1); // +1 to include the slash
				// 根据当前节点编号得到当前节点在children中的index, 判断index是否 < maxLease,maxLease此处是1。也就是判断节点是否是最小的编号,只有最小的编号才能获取到锁。
                PredicateResults    predicateResults = driver.getsTheLock(client, children, sequenceNodeName, maxLeases);
                if ( predicateResults.getsTheLock() )
                {
                    haveTheLock = true;
                }
                else
                {
                	// 前一个节点路径
                    String  previousSequencePath = basePath + "/" + predicateResults.getPathToWatch();

                    synchronized(this)
                    {
                        try 
                        {
                            // use getData() instead of exists() to avoid leaving unneeded watchers which is a type of resource leak
							// 如果没抢到锁就监听前一个节点删除事件                  client.getData().usingWatcher(watcher).forPath(previousSequencePath);
                            if ( millisToWait != null )
                            {
                                millisToWait -= (System.currentTimeMillis() - startMillis);
                                startMillis = System.currentTimeMillis();
                                // 判断超时
                                if ( millisToWait <= 0 )
                                {
                                	// 如果超时,直接退出并且标记删除该节点
                                    doDelete = true;    // timed out - delete our node
                                    break;
                                }

                                wait(millisToWait);
                            }
                            else
                            {
                            	// 等待
                                wait();
                            }
                        }
                        catch ( KeeperException.NoNodeException e ) 
                        {
                            // it has been deleted (i.e. lock released). Try to acquire again
                        }
                    }
                }
            }
        }
        catch ( Exception e )
        {
            ThreadUtils.checkInterrupted(e);
            doDelete = true;
            throw e;
        }
        finally
        {
            if ( doDelete )
            {
            	//如果标记删除,删除节点
                deleteOurPath(ourPath);
            }
        }
        return haveTheLock;
    }

判断是否获取到锁
driver.getsTheLock(client, children, sequenceNodeName, maxLeases);

    @Override
    public PredicateResults getsTheLock(CuratorFramework client, List<String> children, String sequenceNodeName, int maxLeases) throws Exception
    {
        int             ourIndex = children.indexOf(sequenceNodeName);
        validateOurIndex(sequenceNodeName, ourIndex);

        boolean         getsTheLock = ourIndex < maxLeases;
        String          pathToWatch = getsTheLock ? null : children.get(ourIndex - maxLeases);

        return new PredicateResults(pathToWatch, getsTheLock);
    }

释放锁
就是删除节点,触发Watch,调用notifyAll()唤醒其他线程。

private void deleteOurPath(String ourPath) throws Exception {
        try {
            this.client.delete().guaranteed().forPath(ourPath);
        } catch (NoNodeException var3) {
        }

    }

    private synchronized void notifyFromWatcher() {
        this.notifyAll();
    }

源码地址

IT-CLOUD-ZOOKEEPER :zookeeper客户端Curator事例


项目推荐

IT-CLOUD :IT服务管理平台,集成基础服务,中间件服务,监控告警服务等。
IT-CLOUD-ACTIVITI6 :Activiti教程源码。博文在本CSDN Activiti系列中。
IT-CLOUD-ELASTICSEARCH :elasticsearch教程源码。博文在本CSDN elasticsearch系列中。
IT-CLOUD-KAFKA :spring整合kafka教程源码。博文在本CSDN kafka系列中。
IT-CLOUD-KAFKA-CLIENT :kafka client教程源码。博文在本CSDN kafka系列中。
IT-CLOUD-ZOOKEEPER :zookeeper客户端Curator事例。博文在本CSDN zookeeper系列中。

开源项目,持续更新中,喜欢请 Star~

发布了178 篇原创文章 · 获赞 48 · 访问量 22万+

猜你喜欢

转载自blog.csdn.net/yy756127197/article/details/105297124
今日推荐