Dubbo教程-02-zookeeper简介,一些API,分布式锁

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_31922571/article/details/84891126

写在前面

hello 大家好
我是御风
欢迎大家来到我的
Dubbo系列教程第2课
在dubbo的使用过程中
通常我们都会选择zookeeper来作为 注册中心
本次课我将为大家介绍Zookeeper以及使用示范
阅读原文 :https://blog.bywind.cn/articles/2018/11/22/1542865223734.html
本课源码 : https://github.com/ibywind/dubbo-learn

zookeeper介绍

我们需要下载 zk

目前官网的一个稳定版本 zookeeper-3.4.12.tar.gz

http://mirrors.hust.edu.cn/apache/zookeeper/stable/

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

zookeeper 可以保证最终数据一致性

zk 实例 可以主从复制 并且 效率很高 可靠性也高

结构化存储

类似模拟文件系统的一种存储方式

因为分布式的数据最终一致性

和结构化持久存储

以及优越的读写性能

我们通常会利用 zk 做下面这几件事情

1.命名服务

在zookeeper的文件系统里创建一个目录,即有唯一的path。在我们使用tborg无法确定上游程序的部署机器时即可与下游程序约定好path,通过path即能互相探索发现。

2.配置管理

程序总是需要配置的,如果程序分散部署在多台机器上,要逐个改变配置就变得困难。现在把这些配置全部放到zookeeper上去,保存在 Zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中就好

3.集群管理

所谓集群管理无在乎两点:是否有机器退出和加入、选举master。
对于第一点,所有机器约定在父目录GroupMembers下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它上船了。
新机器加入也是类似,所有机器收到通知:新兄弟目录加入,highcount又有了,对于第二点,我们稍微改变一下,所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好。

**4.分布式锁 **

有了zookeeper的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序。
对于第一类,我们将zookeeper上的一个znode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除掉自己创建的distribute_lock 节点就释放出锁。
对于第二类, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,用完删除,依次方便。

5.队列管理

两种类型的队列:
1、同步队列,当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。
2、队列按照 FIFO 方式进行入队和出队操作。
第一类,在约定目录下创建临时目录节点,监听节点数目是否是我们要求的数目。
第二类,和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号。

zk角色介绍

本地安装

首先去我上面的地址下载zk

然后解压 放到一个 文件夹中

进入 zk文件夹 基本这个样子

我们 需要做一个 重命名 在我们 运行之前

把你看到的 那个 .cfg 文件 改成 我这样

然后 回到 bin 目录 启动

接下来我们会利用分布式锁 这个例子 演示 zk的 JAVA API

ZkDemo.java

package cn.bywind;

import org.apache.zookeeper.*;

public class ZkDemo implements Watcher {

    public static void main(String[] args) throws Exception {
        ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181",3000*10,new ZkDemo());
        ZooKeeper.States state = zooKeeper.getState();
        System.out.println(state);
        zooKeeper.exists("/data",new ZkDemo());
        zooKeeper.create("/data","bywind".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        Thread.sleep(2000);

    }

    @Override
    public void process(WatchedEvent event) {
        System.out.println("watcher:"+event.getState());
        if (event.getType().equals(Event.EventType.NodeCreated)){
            System.out.println("watcher:node created");
        }
    }
}

HADemo.java 看ZK如何实现HA

package cn.bywind;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class HADemo implements Watcher {

    private String role = "master";
    private static final String HA_PATH = "/HA";

    private ZooKeeper zooKeeper;



    public static void main(String[] args) throws Exception {
        HADemo haDemo = new HADemo();
        ZooKeeper zk = haDemo.getZk();
        Stat exists = zk.exists(HA_PATH, haDemo);
        if (exists == null) {
            zk.create(HA_PATH, "1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        } else {
            haDemo.role = "slave";
            System.out.println("i am the :" + haDemo.role);
        }
        while (true) {
            if ("q".equalsIgnoreCase(readFromConsole())) {
                System.exit(0);
            } else {
                System.out.println("输入q停止当前程序");
            }
        }
    }

    private ZooKeeper getZk () {
        try {
            zooKeeper = new ZooKeeper("127.0.0.1:2181", 3000, null);
            return zooKeeper;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }


    @Override
    public void process(WatchedEvent event) {

        if (event.getType().equals(Event.EventType.NodeCreated)) {
            System.out.println("I am the :" + role);
        }

        if (event.getType().equals(Event.EventType.NodeDeleted)) {
            System.out.println("master is down");
            this.role = "master";
            try {
                zooKeeper.create(HA_PATH, "1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
                System.out.println("I am the :" + role);
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }

    public static String readFromConsole() {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        try {
            return reader.readLine();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }
}



客户端通过事件回调(观察者模式)
实现对 zk 服务端的 心跳监控
事件同步

分布式锁

我会按照两种方式 给大家创建分布式锁d

  1. zk 原生模式

  2. 第三方框架 Curator

首先来看 原生 硬编码 模式吧

我们和 zk 打交道 肯定是需要 引入对应的依赖的

接下来就是 编程模型了

首先你的类 需要实现 Watcher

我来看他的 具体源码

然后就是 具体的编码过程了

为了给大更好的演示
我们首先来回顾一下传统JAVA中的锁机制

LocalLockDemo.java

package cn.bywind;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class LocalLockDemo {
    static int n = 500;

    public static void secskill() {
        --n;
    }

    public static void main(String[] args) {
        final ReentrantLock lock = new ReentrantLock(true);
        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                try {
                    boolean b = lock.tryLock(2, TimeUnit.SECONDS);
                    if (b){
                        String name = Thread.currentThread().getName();
                        secskill();
                        System.out.println("name:"+name+"--"+n);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }


            }
        };

        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                secskill();
                System.out.println("name:"+name+"--"+n);
            }
        };

        for (int i = 0; i < 20; i++) {
            new Thread(runnable).start();
        }
    }
}

根据上面的 锁 机制
我们尝试使用zookeeper 来实现
他的 tryLock lock 等方法

DistributedLock.java

package cn.bywind;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class DistributedLock implements Lock, Watcher {
    private ZooKeeper zk = null;
    // 根节点
    private String ROOT_LOCK = "/locks";
    // 竞争的资源
    private String lockName;
    // 等待的前一个锁
    private String WAIT_LOCK;
    // 当前锁
    private String CURRENT_LOCK;
    // 计数器
    private CountDownLatch countDownLatch;
    private int sessionTimeout = 30000;
    private List<Exception> exceptionList = new ArrayList<Exception>();

    /**
     * 配置分布式锁
     * @param config 连接的url
     * @param lockName 竞争资源
     */
    public DistributedLock(String config, String lockName) {
        this.lockName = lockName;
        try {
            // 连接zookeeper
            zk = new ZooKeeper(config, sessionTimeout, this);
            Stat stat = zk.exists(ROOT_LOCK, false);
            if (stat == null) {
                // 如果根节点不存在,则创建根节点
                zk.create(ROOT_LOCK, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    // 节点监视器
    public void process(WatchedEvent event) {
        if (this.countDownLatch != null && event.getType()== Event.EventType.NodeDeleted) {
            this.countDownLatch.countDown();
        }
    }

    public void lock() {
        if (exceptionList.size() > 0) {
            throw new LockException(exceptionList.get(0));
        }
        try {
            if (this.tryLock()) {
                System.out.println(Thread.currentThread().getName() + " " + lockName + "获得了锁");
                return;
            } else {
                // 等待锁
                waitForLock(WAIT_LOCK, sessionTimeout);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    public boolean tryLock() {
        try {
            String splitStr = "_lock_";
            if (lockName.contains(splitStr)) {
                throw new LockException("锁名有误");
            }
            // 创建临时有序节点
            CURRENT_LOCK = zk.create(ROOT_LOCK + "/" + lockName + splitStr, new byte[0],
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(CURRENT_LOCK + " 已经创建");
            // 取所有子节点
            List<String> subNodes = zk.getChildren(ROOT_LOCK, false);
            // 取出所有lockName的锁
            List<String> lockObjects = new ArrayList<String>();
            for (String node : subNodes) {
                String _node = node.split(splitStr)[0];
                if (_node.equals(lockName)) {
                    lockObjects.add(node);
                }
            }
            Collections.sort(lockObjects);
            System.out.println(Thread.currentThread().getName() + " 的锁是 " + CURRENT_LOCK);
            // 若当前节点为最小节点,则获取锁成功
            if (CURRENT_LOCK.equals(ROOT_LOCK + "/" + lockObjects.get(0))) {
                return true;
            }

            // 若不是最小节点,则找到自己的前一个节点
            String prevNode = CURRENT_LOCK.substring(CURRENT_LOCK.lastIndexOf("/") + 1);
            WAIT_LOCK = lockObjects.get(Collections.binarySearch(lockObjects, prevNode) - 1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
        return false;
    }

    public boolean tryLock(long timeout, TimeUnit unit) {
        try {
            if (this.tryLock()) {
                return true;
            }
            return waitForLock(WAIT_LOCK, timeout);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    // 等待锁
    private boolean waitForLock(String prev, long waitTime) throws KeeperException, InterruptedException {
        Stat stat = zk.exists(ROOT_LOCK + "/" + prev, true);

        if (stat != null) {
            System.out.println(Thread.currentThread().getName() + "等待锁 " + ROOT_LOCK + "/" + prev);
            this.countDownLatch = new CountDownLatch(1);
            // 计数等待,若等到前一个节点消失,则precess中进行countDown,停止等待,获取锁
            this.countDownLatch.await(waitTime, TimeUnit.MILLISECONDS);
            this.countDownLatch = null;
            System.out.println(Thread.currentThread().getName() + " 等到了锁");
        }
        return true;
    }

    public void unlock() {
        try {
            System.out.println("释放锁 " + CURRENT_LOCK);
            zk.delete(CURRENT_LOCK, -1);
            CURRENT_LOCK = null;
            zk.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    public Condition newCondition() {
        return null;
    }

    public void lockInterruptibly() throws InterruptedException {
        this.lock();
    }


    public class LockException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        public LockException(String e){
            super(e);
        }
        public LockException(Exception e){
            super(e);
        }
    }
}

zk 的分布式锁实现
实现 lock 接口,把本地的实现 换成 zk znode 的读写实现
依靠 zk的优异读写性能 及 分布式协调能力

App.java

package cn.bywind;

/**
 * Hello world!
 *
 */
public class App 
{
    static int n = 500;

    public static void secskill() {
        System.out.println(--n);
    }

    public static void main(String[] args) {


        Runnable runnable = new Runnable() {
            public void run() {
                DistributedLock lock = null;
                try {
                    lock = new DistributedLock("127.0.0.1:2181", "testZk");
                    lock.lock();
                    secskill();
                    System.out.println(Thread.currentThread().getName() + "正在运行");
                } finally {
                    if (lock != null) {
                        lock.unlock();
                    }
                }
            }
        };

        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(runnable);
            t.start();
        }
    }
}

代码跑起来看看
imagepng

线程 获取锁 释放锁
运行有序

imagepng

大家可以看我的 视频教程

总体的一个 思路就是

我们现在把 一个公共的 争夺的 资源 放到了第三方 zk 哪里

这个资源 就是一个 类似文件目录的东西 (你不可能在文件目录项建立两个一样名字的资源)

所以保证了唯一性

然后 你 来了 我给你一个 零时资源 让你 排队 。 (利用 零时 递增 存储方式)

然后 前一个人 释放了 锁

我就来看谁排在 他后面咯

然后 就顺序给你就好了 。

我画了个路程图

接下来 我们开下 如何使用 第三方框架 实现

Curator是Netflix公司开源的一个Zookeeper客户端,与Zookeeper提供的原生客户端相比,Curator的抽象层次更高,简化了Zookeeper客户端的开发量。

具体代码实现
首先我们来看一下 curator 操作 zk 的一些API
这里演示了 增删改查等操作
感觉确实比zk 的原生API简单 不少
并且设计很优秀的
大家可以看下代码

CuratorTest.java

package cn.bywind;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.junit.Test;

public class CuratorTest {

    private static final String ZK_ADDRESS = "127.0.0.1:2181";
    private static final String ZK_PATH = "/zktest";
    @Test
    public void testZk() throws Exception {
        CuratorFramework client = CuratorFrameworkFactory.newClient(
                ZK_ADDRESS,
                new RetryNTimes(10, 5000)
        );
        client.start();
        System.out.println("zk client start successfully!");

        client.delete().deletingChildrenIfNeeded().withVersion(-1).forPath(ZK_PATH);

        /** 2.Client API test*/
        // 2.1 Create node
        String data1 = "hello";
        print("create", ZK_PATH, data1);
        client.create().
                creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).
                forPath(ZK_PATH, data1.getBytes());

        // 2.2 Get node and data
        print("ls", "/");
        print(client.getChildren().forPath("/"));
        print("get", ZK_PATH);
        print(client.getData().forPath(ZK_PATH));

        // 2.3 Modify data
        String data2 = "world";
        print("set", ZK_PATH, data2);
        client.setData().forPath(ZK_PATH, data2.getBytes());
        print("get", ZK_PATH);
        print(client.getData().forPath(ZK_PATH));

        // 2.4 Remove node
        print("delete", ZK_PATH);
        client.delete().forPath(ZK_PATH);
        print("ls", "/");
        print(client.getChildren().forPath("/"));


    }

    private static void print(String... cmds) {
        StringBuilder text = new StringBuilder("$ ");
        for (String cmd : cmds) {
            text.append(cmd).append(" ");
        }
        System.out.println(text.toString());
    }

    private static void print(Object result) {
        System.out.println(
                result instanceof byte[]
                        ? new String((byte[]) result)
                        : result);
    }


}


演示效果如下
imagepng

接下来就是 使用curator 实现 zk的分布式锁
代码在下方
大家可以查看下
为了方便测试
curator 可以自己模拟 zk 服务端
很方便
如果大家想要尝试 可以注释打开

CuratorDistributedLockDemo.java

package cn.bywind;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.*;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.test.TestingCluster;
import org.apache.curator.test.TestingServer;
import org.apache.curator.utils.CloseableUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁演示说明
 */
public class CuratorDistributedLockDemo {

	// zookeeper 锁节点路径,分布式锁的相关操作都是在这个节点上进行
    private final String lockPath = "/distributed-lock";

	// zookeeper 服务地址,单机格式为:(127.0.0.1:2181),集群格式为:(127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183)
	private String connectString;
	// Curator 客户端重试策略
	private RetryPolicy retry;
	// Curator 客户端对象
	private CuratorFramework client;
	// client2 用户模拟其他客户端
	private CuratorFramework client2;

	private TestingServer testingServer;
	private TestingCluster testingCluster;

	// 内存 zookeeper 服务器(单个),用于测试,也可以选择使用外部服务器,如 127.0.0.1:2181
	// 同时提供 TestingCluster,用于测试集群

	// 初始化资源
	@Before
	public void init() throws Exception {

		// 创建一个测试 zookeeper 服务器
		//testingServer = new TestingServer();
		// 获取该服务器的链接地址
		//connectString = testingServer.getConnectString();

		// 创建一个集群测试 zookeeper 服务器
		//testingCluster = new TestingCluster(3);
		// 获取该服务器的链接地址
		//connectString = testingCluster.getConnectString();

		connectString = "127.0.0.1:2181";
		// 重试策略
		// 初始休眠时间为 1000ms,最大重试次数为3
		retry = new ExponentialBackoffRetry(1000, 3);
		// 创建一个客户端 60000(ms)为 session 超时时间,15000(ms)为链接超时时间
		client = CuratorFrameworkFactory.newClient(connectString, 60000, 15000, retry);
		client2 = CuratorFrameworkFactory.newClient(connectString, 60000, 15000, retry);
		// 创建会话
		client.start();
		client2.start();
	}

	// 释放资源
	@After
	public void close() {
		CloseableUtils.closeQuietly(client);
		CloseableUtils.closeQuietly(testingServer);
		CloseableUtils.closeQuietly(testingCluster);
	}

	// 共享锁
	@Test
	public void sharedLock() throws Exception {
		// 创建共享锁
		InterProcessLock lock = new InterProcessSemaphoreMutex(client, lockPath);
		// lock2 用于模拟其他客户端
		InterProcessLock lock2 = new InterProcessSemaphoreMutex(client2, lockPath);

		// 获取锁对象
		lock.acquire();

		// 测试是否可以重入
		// 超时获取锁对象(第一个参数为时间,第二个参数为时间单位),因为锁已经被获取,所以返回 false
		Assert.assertFalse(lock.acquire(2, TimeUnit.SECONDS));
		// 释放锁
		lock.release();

		// lock2 尝试获取锁成功,因为锁已经被释放
		Assert.assertTrue(lock2.acquire(2, TimeUnit.SECONDS));
		lock2.release();
	}

	// 重入锁
	@Test
	public void sharedReentrantLock() throws Exception {
		// 创建可重入锁
		InterProcessLock lock = new InterProcessMutex(client, lockPath);
		// lock2 用于模拟其他客户端
		InterProcessLock lock2 = new InterProcessMutex(client2, lockPath);
		// lock 获取锁
		lock.acquire();
		try {
			// lock 第二次获取锁
			lock.acquire();
			try {
				// lock2 超时获取锁,因为锁已经被 lock 客户端占用,所以获取失败,需要等 lock 释放
				Assert.assertFalse(lock2.acquire(2, TimeUnit.SECONDS));
			} finally {
				lock.release();
			}
		} finally {
			// 重入锁获取与释放需要一一对应,如果获取2次,释放1次,那么该锁依然是被占用,如果将下面这行代码注释,那么会发现下面的 lock2 获取锁失败
			lock.release();
		}
		// 在 lock 释放后,lock2 能够获取锁
		Assert.assertTrue(lock2.acquire(2, TimeUnit.SECONDS));
		lock2.release();
	}

	// 读写锁
	@Test
	public void sharedReentrantReadWriteLock() throws Exception {
		// 创建读写锁对象,因 curator 的实现原理,该锁是公平的
		InterProcessReadWriteLock lock = new InterProcessReadWriteLock(client, lockPath);
		// lock2 用于模拟其他客户端
		InterProcessReadWriteLock lock2 = new InterProcessReadWriteLock(client2, lockPath);
		// 使用 lock 模拟读操作
		// 使用 lock2 模拟写操作
		// 获取读锁(使用InterProcessMutex实现,所以是可以重入的)
		final InterProcessLock readLock = lock.readLock();
		// 获取写锁(使用InterProcessMutex实现,所以是可以重入的)
		final InterProcessLock writeLock = lock2.writeLock();

		/**
		 * 读写锁测试对象
		 */
		class ReadWriteLockTest {
			// 测试数据变更字段
			private Integer testData = 0;
			private Set<Thread> threadSet = new HashSet<Thread>();

			// 写入数据
			private void write() throws Exception {
				writeLock.acquire();
				try {
					Thread.sleep(10);
					testData++;
					System.out.println("写入数据\t" + testData);
				} finally {
					writeLock.release();
				}
			}

			// 读取数据
			private void read() throws Exception {
				readLock.acquire();
				try {
					Thread.sleep(10);
					System.out.println("读取数据\t" + testData);
				} finally {
					readLock.release();
				}
			}

			// 等待线程结束,防止test方法调用完成后,当前线程直接退出,导致控制台无法输出信息
			public void waitThread() throws InterruptedException {
				for (Thread thread : threadSet) {
					thread.join();
				}
			}

			// 创建线程方法
			private void createThread(final int type) {
				Thread thread = new Thread(new Runnable() {
					@Override
					public void run() {
						try {
							if (type == 1) {
								write();
							} else {
								read();
							}
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				});
				threadSet.add(thread);
				thread.start();
			}

			// 测试方法
			public void test() {
				for (int i = 0; i < 5; i++) {
					createThread(1);
				}
				for (int i = 0; i < 5; i++) {
					createThread(2);
				}
			}
		}

		ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
		readWriteLockTest.test();
		readWriteLockTest.waitThread();
	}

	// 信号量
	@Test
	public void semaphore() throws Exception {
		// 创建一个信号量
		InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, lockPath, 6);
		// semaphore2 用于模拟其他客户端
		InterProcessSemaphoreV2 semaphore2 = new InterProcessSemaphoreV2(client2, lockPath, 6);

		// 获取一个许可
		Lease lease = semaphore.acquire();
		Assert.assertNotNull(lease);
		// semaphore.getParticipantNodes() 会返回当前参与信号量的节点列表,俩个客户端所获取的信息相同
		Assert.assertEquals(semaphore.getParticipantNodes(), semaphore2.getParticipantNodes());

		// 超时获取一个许可
		Lease lease2 = semaphore2.acquire(2, TimeUnit.SECONDS);
		Assert.assertNotNull(lease2);
		Assert.assertEquals(semaphore.getParticipantNodes(), semaphore2.getParticipantNodes());

		// 获取多个许可,参数为许可数量
		Collection<Lease> leases = semaphore.acquire(2);
		Assert.assertTrue(leases.size() == 2);
		Assert.assertEquals(semaphore.getParticipantNodes(), semaphore2.getParticipantNodes());

		// 超时获取多个许可,第一个参数为许可数量
		Collection<Lease> leases2 = semaphore2.acquire(2, 2, TimeUnit.SECONDS);
		Assert.assertTrue(leases2.size() == 2);
		Assert.assertEquals(semaphore.getParticipantNodes(), semaphore2.getParticipantNodes());

		// 目前 semaphore 已经获取 3 个许可,semaphore2 也获取 3 个许可,加起来为 6 个,所以他们无法在进行许可获取
		// 无法获取许可
		Assert.assertNull(semaphore.acquire(2, TimeUnit.SECONDS));
		Assert.assertNull(semaphore2.acquire(2, TimeUnit.SECONDS));

		semaphore.returnLease(lease);
		semaphore2.returnLease(lease2);
		semaphore.returnAll(leases);
		semaphore2.returnAll(leases2);
	}

	// 多重锁
	@Test
	public void multiLock() throws Exception {
		// 可重入锁
		InterProcessLock interProcessLock1 = new InterProcessMutex(client, lockPath);
		// 不可重入锁
		InterProcessLock interProcessLock2 = new InterProcessSemaphoreMutex(client2, lockPath);
		// 创建多重锁对象
		InterProcessLock lock = new InterProcessMultiLock(Arrays.asList(interProcessLock1, interProcessLock2));
		// 获取参数集合中的所有锁
		lock.acquire();

		// 因为存在一个不可重入锁,所以整个 InterProcessMultiLock 不可重入
		Assert.assertFalse(lock.acquire(2, TimeUnit.SECONDS));
		// interProcessLock1 是可重入锁,所以可以继续获取锁
		Assert.assertTrue(interProcessLock1.acquire(2, TimeUnit.SECONDS));
		// interProcessLock2 是不可重入锁,所以获取锁失败
		Assert.assertFalse(interProcessLock2.acquire(2, TimeUnit.SECONDS));

		// 释放参数集合中的所有锁
		lock.release();

		// interProcessLock2 中的所已经释放,所以可以获取
		Assert.assertTrue(interProcessLock2.acquire(2, TimeUnit.SECONDS));

	}

}

另外如果大家想更直观了解ZK分布式锁的话
可以参看我的视频

总结

zookeeper在实际的开发过程中

用的地方非常多

比如 Hadoop hbase spark 等大数据库框架

显然 zk 已经是 分布式 大数据 时代

绕不过去的一道坎了

本次课讲到的 分布式锁 只是 他众多功能的一个

其实 分布式锁的 实现方案 有很多的

比如 redis 也是可以的 而且 速度 性能要比 zk 快

只是借这次课的一个机会 和大家一起重新温习下 zk 的原理和使用

本课源码 : https://github.com/ibywind/dubbo-learn

猜你喜欢

转载自blog.csdn.net/qq_31922571/article/details/84891126