ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁
作者:Grey
原文地址: ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁
前置知识
完成ZooKeeper集群搭建以及熟悉ZooKeeperAPI基本使用
需求
当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
在单机情况下,可以使用JUC包里面的工具来进行互斥控制。
但是在分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁的由来。
当多个进程不在同一个系统中,就需要用分布式锁控制多个进程对资源的访问。
我们可以用ZooKeeper来模拟实现一个简单的分布式锁
环境准备
一个zk集权,ip和端口分别为:
- 192.168.205.145:2181
- 192.168.205.146:2181
- 192.168.205.147:2181
- 192.168.205.148:2181
定义主方法
App.java
public class App {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
ZkLock lock = new ZkLock();
lock.lock(); // 开启锁
System.out.println(Thread.currentThread().getName() + " doing work");
lock.release(); // 释放锁
}).start();
}
while (true) {
}
}
}
如上,我们设计了一个ZkLock,其中lock方法是锁定资源,release方法是释放资源,我们并发了10个线程并发访问来模拟。
public class ZkLock implements AsyncCallback.StringCallback, Watcher, AsyncCallback.StatCallback, AsyncCallback.Children2Callback {
private CountDownLatch latch;
private ZooKeeper zk;
private String identify;
private String lockPath;
private String pathName;
public ZkLock() {
identify = Thread.currentThread().getName();
lockPath = "/lock";
latch = new CountDownLatch(1);
zk = ZookeeperConfig.create(ADDRESS + "/testLock");
}
public void lock() {
try {
zk.create(lockPath, currentThread().getName().getBytes(UTF_8), OPEN_ACL_UNSAFE, EPHEMERAL_SEQUENTIAL, this, currentThread().getName());
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void release() {
try {
zk.delete(pathName, -1);
System.out.println(identify + " over work....");
} catch (InterruptedException | KeeperException e) {
e.printStackTrace();
}
}
@Override
public void processResult(int rc, String path, Object ctx, String name) {
if (null != name) {
// 创建成功
System.out.println(identify + " created " + name);
pathName = name;
zk.getChildren("/", false, this, "dasdfas");
}
}
@Override
public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {
sort(children);
int i = children.indexOf(pathName.substring(1));
if (i == 0) {
// 是第一个,获得锁,可以执行
System.out.println(identify + " first...");
try {
zk.setData("/", identify.getBytes(UTF_8), -1);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
} else {
zk.exists("/" + children.get(i - 1), this, this, "ddsdf");
}
}
@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
zk.getChildren("/", false, this, "sdf");
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
}
}
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
}
}
关于上述代码的说明,我们规定创建的zk目录为/testLock,所以我们可以通过zk客户端在集群中先把/testLock目录建好,后续线程争抢的时候,我们只需要创建序列化的临时节点(以/lock开头),因为是序列化的,所以我们可以设置让第一个创建好节点的线程抢到锁,其他的线程排队等待。
所以lock方法实现如下:
zk.create(lockPath, currentThread().getName().getBytes(UTF_8), OPEN_ACL_UNSAFE, EPHEMERAL_SEQUENTIAL, this, currentThread().getName());
lock方法在执行的时候,会有一个回调,即:当节点创建成功后,会判断/testLock节点中有没有已经创建好的且在当前节点之前的节点,有的话,则注册一个一个对于/testLock目录的监听:
@Override
public void processResult(int rc, String path, Object ctx, String name) {
if (null != name) {
// 创建成功
System.out.println(identify + " created " + name);
pathName = name;
zk.getChildren("/", false, this, "dasdfas");
}
}
一旦发现/testLock目录下已经有节点了,那么我们拿到/testLock下的所有节点,并排序,取最小的那个节点执行即可:
@Override
public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {
sort(children);
int i = children.indexOf(pathName.substring(1));
if (i == 0) {
// 是第一个,获得锁,可以执行
System.out.println(identify + " first...");
try {
zk.setData("/", identify.getBytes(UTF_8), -1);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
} else {
zk.exists("/" + children.get(i - 1), this, this, "ddsdf");
}
}
release方法很简单,只需要把当前执行完毕的节点删除即可:
public void release() {
try {
zk.delete(pathName, -1);
System.out.println(identify + " over work....");
} catch (InterruptedException | KeeperException e) {
e.printStackTrace();
}
}
运行效果
确保zk中有/testLock这个节点,如果没有,请先创建一个:
Run App.java
可以看到控制台输出:
Thread-5 created /lock0000000000
Thread-4 created /lock0000000001
Thread-1 created /lock0000000002
Thread-9 created /lock0000000003
Thread-6 created /lock0000000004
Thread-2 created /lock0000000005
Thread-3 created /lock0000000006
Thread-0 created /lock0000000007
Thread-8 created /lock0000000008
Thread-7 created /lock0000000009
Thread-5 first...
Thread-5 doing work
Thread-5 over work....
Thread-4 first...
Thread-4 doing work
Thread-4 over work....
Thread-1 first...
Thread-1 doing work
Thread-1 over work....
Thread-9 first...
Thread-9 doing work
Thread-9 over work....
Thread-6 first...
Thread-6 doing work
Thread-6 over work....
Thread-2 first...
Thread-2 doing work
Thread-2 over work....
Thread-3 first...
Thread-3 doing work
Thread-3 over work....
Thread-0 first...
Thread-0 doing work
Thread-0 over work....
Thread-8 first...
Thread-8 doing work
Thread-8 over work....
Thread-7 first...
Thread-7 doing work
Thread-7 over work....
ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁的更多相关文章
- ensorflow学习笔记四:mnist实例--用简单的神经网络来训练和测试
http://www.cnblogs.com/denny402/p/5852983.html ensorflow学习笔记四:mnist实例--用简单的神经网络来训练和测试 刚开始学习tf时,我们从 ...
- QML学习笔记(五)— 做一个简单的待做事项列表
做一个简单的QML待做事项列表,能够动态添加和删除和编辑数据 GitHub:八至 作者:狐狸家的鱼 本文链接:QML学习笔记(五)— 做一个待做事项列表 主要用到QML:ListView 效果 全部代 ...
- 【WPF】学习笔记(一)——做一个简单的电子签名板
参加实习(WPF)已经有两个多周的时间了,踩了一些坑,也算积累了一些小东西,准备慢慢拿出来分享一下.(●'◡'●) 这次呢就讲讲一个简单的电子签名板的实现. 先上张图(PS:字写得比较丑,不要太在意哈 ...
- Directx11学习笔记【十三】 实现一个简单地形
本文由zhangbaochong原创,转载请注明出处http://www.cnblogs.com/zhangbaochong/p/5510294.html 上一个教程我们实现了渲染一个会旋转的立方体, ...
- Directx11学习笔记【十一】 画一个简单的三角形--effect框架的使用
这里不再介绍effect框架的具体使用,有关effect框架使用可参考http://www.cnblogs.com/zhangbaochong/p/5475961.html 实现的功能依然是画一个简单 ...
- 【转】Redis学习笔记(五)如何用Redis实现分布式锁(2)—— 集群版
原文地址:http://bridgeforyou.cn/2018/09/02/Redis-Dsitributed-Lock-2/ 单机版实现的局限性 在上一篇文章中,我们讨论了Redis分布式锁的实现 ...
- Redis学习笔记(三)使用Lua脚本实现分布式锁
Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行. 使用Lua脚本的好处如下: 1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放 ...
- 使用redis设计一个简单的分布式锁
最近看了有关redis的一些东西,了解了redis的一下命令,就记录一下: redis中的setnx命令: 关于redis的操作命令,我们一般会使用set,get等一系列操作,数据结构也有很多,这里我 ...
- Directx11学习笔记【十】 画一个简单的三角形
本篇笔记要实现的是在屏幕上渲染出一个三角形,重点要学习的是渲染一个几何体的流程方式. 为了渲染几何图形,需要一个顶点缓存和一个描述顶点布局的输入层,还有着色器(主要是顶点着色器和像素着色器),下面来看 ...
随机推荐
- js弹窗的3种方式:alert、confirm、prompt
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- CVE-2012-3569:VMware OVF Tool 格式化字符串漏洞调试分析
0x01 简介 VMware OVF Tool 是一个命令行实用程序,允许您从许多 VMware 产品导入和导出 OVF 包.在 2.1.0 - 2.1.3 之间的版本中存在格式化字符串漏洞,通过修改 ...
- 【easycode】使用说明 & 示例
语法 ## 是模板的注释,不会生成 # 全局的一些设置 $ 使用库函数或者调用全局定义的内容 常用方法 数据库表信息 ${tableInfo} comment 表备注 name 表名 fullColu ...
- 【Github搬砖】Python入门网络爬虫之精华版
Python学习网络爬虫主要分3个大的版块:抓取,分析,存储 另外,比较常用的爬虫框架Scrapy,这里最后也详细介绍一下. 首先列举一下本人总结的相关文章,这些覆盖了入门网络爬虫需要的基本概念和技巧 ...
- PHP中文转拼音扩展
Pinyin 基于 CC-CEDICT 词典的中文转拼音工具,更准确的支持多音字的汉字转拼音解决方案. 安装 使用 Composer 安装: $ composer require "over ...
- layui中时间插件laydate的使用
1.加载layui.js 2.html部分 <div class="layui-inline"> <label class="layui-form-la ...
- Win10安装MySQL5和MySQL8
1. 下载数据库,配置环境变量 因为是安装两个MySQL数据库,端口号要不一样,MySQL默认端口号是3306,建议先配置非默认端口号,以免出现问题 1.1 官网下载5.7和8.0的压缩包 我下载的是 ...
- 初步了解web
------------------------1.Web应用程序的main方法在哪里------------------------Tomcat:从启动到运行首先,我们是通过执行 Tomcat 的s ...
- Spring Cloud Alibaba(9)---Sentinel概述
Sentinel概述 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentinel是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流.流量整形.熔断降级.系统自适应保护.热点防 ...
- JavaWeb——反射、注解
单元测试.反射.注解 1. Junit单元测试 2. 反射 3. 注解 Junit单元测试: * 测试分类: 1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值. 2. 白盒测试:需要 ...