Zookeeper分布式锁原理浅析和代码示例

前言

实现分布式锁可以利用Redis、Zookeeper、MySQL等中间件,其中Zookeeper最为适合,主要是因为两点:

  • 其自带节点变更监听机制,可以在锁释放后第一时间通知其他等待的客户端,比起让客户端不断重试这种方案,它更高效,CPU占用率更低
  • 在部署多个节点避免单点故障时,也能很好的保证数据一致性

本文主要探讨的是利用Zookeeper实现的分布式排他锁,共享锁目前不在讨论范围内。

原理

定义锁

通过数据节点来表示一个锁,例如/exclusive_lock/lock节点就可以被定义为一个锁

获取锁

在需要获取排他锁时,所有客户端都会试图通过调用create接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。Zookeeper会保证在所有的客户端中,最终只有一个客户端能够创建成功,创建成功的客户端就获取了锁。同时,所有没有获取到锁的客户端就需要到/exclusive_lock节点上注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况

释放锁

因为/exclusive_lock/lock是一个临时节点,因此在以下两种情况下,都有可能释放锁。

  • 正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除
  • 当前获取锁的客户端挂掉了,临时节点在超时后会被Zookeeper移除

无论在什么情况下移除了lock节点,Zookeeper都会通知所有在/exclusive_lock节点上注册了子节点变更Watcher监听的客户端,这些客户端在接收到通知后,再次重新发起分布式锁获取。
在这里插入图片描述

实战

以下代码使用Curator框架中的分布式锁功能,Curator是Netflix公司开源的一套Zookeeper客户端框架。首先添加依赖:

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.0.1</version>
    <exclusions>
    	<exclusion>
			<groupId>org.apache.zookeeper</groupId>
			<artifactId>zookeeper</artifactId>
		</exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.6</version>
</dependency>

下面是一段没有加锁的生成ID的演示代码:

public static void generateId() {
	final CountDownLatch cdl = new CountDownLatch(1);
	for (int i = 0; i < 10; i++) {
		new Thread(() ->  {
			try {
				cdl.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss|SSS");
			String id = formatter.format(LocalDateTime.now());
			System.out.println(id);
		}).start();
	}
	cdl.countDown();
}

运行上述代码生成的ID,发现ID会重复。使用分布式锁,再次运行,ID不会重复:

/**
 * 获取客户端实例
 * @return 客户端实例
 */
public static CuratorFramework getClient() {
	RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
	return CuratorFrameworkFactory.newClient("192.168.7.81:2181", 5000, 3000, retryPolicy);
}

public static void testLock() {
	String lockPath = "/exclusive_lock";
	CuratorFramework client = getClient();
	client.start();
	final InterProcessMutex lock = new InterProcessMutex(client, lockPath);
	final CountDownLatch cdl = new CountDownLatch(1);
	for (int i = 0; i < 10; i++) {
		new Thread(() ->  {
			try {
				cdl.await();
				lock.acquire(); // 加分布式锁
				DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss|SSS");
				String id = formatter.format(LocalDateTime.now());
				System.out.println(id);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					lock.release(); // 释放分布式锁
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}
	cdl.countDown();
	try {
		TimeUnit.MINUTES.sleep(1);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	client.close();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值