zookeeper分布式锁
摘要:分享牛原创,zookeeper使用,zookeeper锁在实际项目开发中还是很常用的,在这里我们介绍一下zookeeper分布式锁的使用,以及我们如何zookeeper分布式锁的原理。zookeeper节点理解。
zookeeper分布式锁有什么用呢?首先要明白锁是一个什么东西?举个通俗的例子,把门锁着了,外面的人进不去,里面的人可以随时出来,出来之后,还可以继续加锁。比如我们项目中,主要有供应商系统锁库存这种情况,锁库存的时候不能让其他的人去修改库存信息。这里就需要使用的时候加锁。当然了也可以使用数据库或者redis版本锁的概念,根据版本去区分到底如何锁库。
首先我们看一下zookeeper节点类型。
zookeeper节点类型分为以下四种:
1.1.1. 节点说明
public enum CreateMode {
/**
* The znode will not be automatically deleted upon client's disconnect.
*/
PERSISTENT (0, false, false),
/**
* The znode will not be automatically deleted upon client's disconnect,
* and its name will be appended with a monotonically increasing number.
*/
PERSISTENT_SEQUENTIAL (2, false, true),
/**
* The znode will be deleted upon the client's disconnect.
*/
EPHEMERAL (1, true, false),
/**
* The znode will be deleted upon the client's disconnect, and its name
* will be appended with a monotonically increasing number.
*/
EPHEMERAL_SEQUENTIAL (3, true, true);
}
从持久化的层次划分:
1.持久化节点:不删除节点永远存在。
2.非持久节点,换言之就是临时节点,临时节点就是客户端连接的时候创建,客户端挂起的时候,临时节点自动删除。
从排序层次划分:
1.持久有序。
2.持久无序,
3.临时有序。
4.临时无序。
这里需要注意持久化节点可以创建子节点。非持久化节点不能创建子节点。这里可以自己去使用命令去测试。
非持久节点就是创建的时候存在,消失的时候,节点自动删除,所以我们利用这个特性,实现我们的需求,比如,我可以程序启动的时候在指定的持久节点,创建临时节点,当程序挂掉的时候,临时节点消失,我们可以一直监控指定父节点中的子节点集合,就可以监控程序的健康状态。
1.1.2. zookeeper 分布式锁实现
接下来,在上面理解节点的基础之上,我们可以去实现zookeeper 分布式锁。具体怎么实现呢?思路如下:
我们可以创建一个持久节点,在程序中我们每次创建临时子节点,然后我们遍历持久节点下面的子节点,因为临时节点我们设置的时候是有序的。所以我们可以加锁的时候,创建了一个临时有序节点,当我们加锁完成自己的业务之后,释放锁,然后,这个删除临时节点,所以设计的核心点就是:
1.创建父节点。持久节点
2.创建有序的临时子节点。
3.删除临时节点,当业务完成的时候。
4.每次需要判断自身的临时节点,是否是最小的。为什么要判断是最小的呢?因为不是最小的话,说明前面还有一些节点在加锁执行中,所以我们这个节点不能加锁执行。
5.怎么让自身节点监听执行呢?因为如果自身节点是最小的,可以直接执行,如果不是最小的。要监听前面的节点是否已经删除,如果其他的前面的节点都删除了。则自己就可以加锁执行业务代码了。
下面开始书写我们的代码吧?
package com.shareniu.zkTest;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
public class ShareniuDistributedLock implements Watcher {
private int threadId;
// 主要区分线程
private static String PREFIX_OF_THREAD = null;
// 子节点的前缀
private static final String EPHEMERAL_SEQUENTIAL_PATH = "/shareniuLock/sub";
// 父节点
private static final String PARENT_PATH = "/shareniuLock";
protected static final String CONNECTION_STRING = "101.201.xx.xx:2181";
protected static final int SESSION_TIMEOUT = 10000;
//开启的线程的数量
private static final int THREAD_NUM = 5;
// 创建临时节点自身
private String selfPath;
// zk连接对象
public ShareniuDistributedLock(int threadId) {
this.threadId = threadId;
PREFIX_OF_THREAD = "【第"+threadId+"个线程】";
}
private ZooKeeper zooKeeper = null;
private CountDownLatch cdl = new CountDownLatch(1);
private static final CountDownLatch threadSemaphore = new CountDownLatch(THREAD_NUM);
private String waitPath;
public static void main(String[] args) {
for(int i=0; i < THREAD_NUM; i++){
final int threadId = i+1;
new Thread(){
@Override
public void run() {
try{
ShareniuDistributedLock dc = new ShareniuDistributedLock(threadId);
dc.createConnection(CONNECTION_STRING, SESSION_TIMEOUT);
//GROUP_PATH不存在的话,由一个线程创建即可;
synchronized (threadSemaphore){
dc.createPath(PARENT_PATH, "该节点由线程" + threadId + "创建", true);
}
dc.getLock();
} catch (Exception e){
e.printStackTrace();
}
}
}.start();
}
try {
threadSemaphore.await();
System.out.println("所有线程运行结束!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 实现监听器中的方法拿到WatchedEvent对象
*/
public void process(WatchedEvent event) {
if(event == null){
return;
}
Event.KeeperState keeperState = event.getState();
Event.EventType eventType = event.getType();
if ( Event.KeeperState.SyncConnected == keeperState) {
if ( Event.EventType.None == eventType ) {
System.out.println( PREFIX_OF_THREAD + "成功连接上ZK服务器" );
cdl.countDown();
}else if (event.getType() == Event.EventType.NodeDeleted && event.getPath().equals(waitPath)) {
try {
if(checkMinPathOfChilde()){
getLockByShelf();
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}else if ( Event.KeeperState.Disconnected == keeperState ) {
System.out.println( PREFIX_OF_THREAD + "与ZK服务器断开连接" );
} else if ( Event.KeeperState.AuthFailed == keeperState ) {
System.out.println( PREFIX_OF_THREAD + "权限检查失败" );
} else if ( Event.KeeperState.Expired == keeperState ) {
System.out.println( PREFIX_OF_THREAD + "会话失效" );
}
}
/**
* 关闭ZK连接
*/
public void releaseConnection() {
if (this.zooKeeper != null) {
try {
this.zooKeeper.close();
} catch (InterruptedException e) {
}
}
System.out.println(PREFIX_OF_THREAD + "释放连接");
}
/***
* 创建连接
*
* @param connectString
* 连接的服务器字符串
* @param sessionTimeout
* 超时时间
* @throws IOException
* @throws InterruptedException
*/
public void createConnection(String connectString, int sessionTimeout)
throws IOException, InterruptedException {
zooKeeper = new ZooKeeper(connectString, sessionTimeout, this);
System.out.println("打开连接........");
// 因为打开连接只需要一次,这里为了防止并发,使用的CountDownLatch对象
// 程序一直等待,直到cdl.countDown();方法的调用
cdl.await();
}
/**
* 创建节点
*
* @param path
* 路径
* @param data
* 内容
* @param needWatch
* @return
* @throws KeeperException
* @throws InterruptedException
*/
public boolean createPath(String path, String data, boolean needWatch)
throws KeeperException, InterruptedException {
// 判断节点是否存在存在就不要创建了。
Stat exists = zooKeeper.exists(path, needWatch);
if (exists == null) {
// 节点不存在
System.out.println(PREFIX_OF_THREAD
+ "节点创建成功, Path: "
+ this.zooKeeper.create(path, data.getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
+ ", content: " + data);
}
return true;
}
/**
* 获取锁
*
* @throws KeeperException
* @throws InterruptedException
*/
private void getLock() throws KeeperException, InterruptedException {
// 创建临时节点
selfPath = zooKeeper.create(EPHEMERAL_SEQUENTIAL_PATH, null,
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
System.out.println("创建临时节点:" + selfPath);
if (checkMinPathOfChilde()) {
getLockByShelf();
}
}
/**
* 获取到锁了
*
* @throws InterruptedException
* @throws KeeperException
*/
private void getLockByShelf() throws KeeperException, InterruptedException {
// 获取到锁了 开始 执行代码 删除节点 释放连接
if (zooKeeper.exists(this.selfPath, false) == null) {
System.out.println(PREFIX_OF_THREAD + "本节点已不在了...");
return;
} else {
// 节点存在
System.out.println(PREFIX_OF_THREAD + "获取锁成功....");
// 休息一下
Thread.sleep(1000);
System.out.println(PREFIX_OF_THREAD + "删除临时节点:" + selfPath);
// 删除的时候,版本是-1就是所有的都删除
zooKeeper.delete(this.selfPath, -1);
releaseConnection();
// 释放锁 其他的程序可以 继续打开连接
cdl.countDown();
}
}
/**
* 获取锁
*
* @return
* @throws InterruptedException
* @throws KeeperException
*/
private boolean checkMinPathOfChilde() throws KeeperException,
InterruptedException {
// 获取父节点中的所有子节点
List<String> subNodes = zooKeeper.getChildren(PARENT_PATH, false);
// 临时节点是有序的,那就排序找最小的吧
Collections.sort(subNodes);
int index = subNodes
.indexOf(selfPath.substring(PARENT_PATH.length() + 1));
switch (index) {
case -1: {
System.out.println(PREFIX_OF_THREAD + "节点已不在了..." + selfPath);
return false;
}
case 0: {
System.out.println(PREFIX_OF_THREAD + "自己可以获取锁执行代码了" + selfPath);
return true;
}
default: {
this.waitPath = PARENT_PATH + "/" + subNodes.get(index - 1);
System.out.println(PREFIX_OF_THREAD + "前面的节点" + waitPath);
try {
zooKeeper.getData(waitPath, true, new Stat());
return false;
} catch (KeeperException e) {
if (zooKeeper.exists(waitPath, false) == null) {
System.out.println(PREFIX_OF_THREAD + "本节点前面的节点:"
+ waitPath + "");
// 递归找吧
return checkMinPathOfChilde();
} else {
throw e;
}
}
}
}
}
}
1.1.3. 程序的输出
程序的输出如下:
打开连接........
打开连接........
打开连接........
打开连接........
打开连接........
【第5个线程】成功连接上ZK服务器
【第5个线程】成功连接上ZK服务器
【第5个线程】成功连接上ZK服务器
【第5个线程】成功连接上ZK服务器
【第5个线程】成功连接上ZK服务器
创建临时节点:/shareniuLock/sub0000000001
【第5个线程】自己可以获取锁执行代码了/shareniuLock/sub0000000001
创建临时节点:/shareniuLock/sub0000000002
【第5个线程】获取锁成功....
【第5个线程】前面的节点/shareniuLock/sub0000000001
创建临时节点:/shareniuLock/sub0000000003
创建临时节点:/shareniuLock/sub0000000004
【第5个线程】前面的节点/shareniuLock/sub0000000002
【第5个线程】前面的节点/shareniuLock/sub0000000003
创建临时节点:/shareniuLock/sub0000000005
【第5个线程】前面的节点/shareniuLock/sub0000000004
【第5个线程】删除临时节点:/shareniuLock/sub0000000001
【第5个线程】释放连接
【第5个线程】自己可以获取锁执行代码了/shareniuLock/sub0000000002
【第5个线程】获取锁成功....
【第5个线程】删除临时节点:/shareniuLock/sub0000000002
【第5个线程】释放连接
【第5个线程】自己可以获取锁执行代码了/shareniuLock/sub0000000003
【第5个线程】获取锁成功....
【第5个线程】删除临时节点:/shareniuLock/sub0000000003
【第5个线程】释放连接
【第5个线程】自己可以获取锁执行代码了/shareniuLock/sub0000000004
【第5个线程】获取锁成功....
【第5个线程】删除临时节点:/shareniuLock/sub0000000004
【第5个线程】释放连接
【第5个线程】自己可以获取锁执行代码了/shareniuLock/sub0000000005
【第5个线程】获取锁成功....
【第5个线程】删除临时节点:/shareniuLock/sub0000000005
【第5个线程】释放连接
1.1.4. zk节点的查看
下面我们看一下zk的节点,
在此证明,节点是有序的,释放锁的时候,会删除临时节点。
分享牛原创(尊重原创 转载对的时候第一行请注明,转载出处来自分享牛http://blog.csdn.net/qq_30739519)
zookeeper分布式锁的更多相关文章
- Curator Zookeeper分布式锁
Curator Zookeeper分布式锁 pom.xml中添加如下配置 <!-- https://mvnrepository.com/artifact/org.apache.curator/c ...
- ZooKeeper 分布式锁实现
1 场景描述 在分布式应用, 往往存在多个进程提供同一服务. 这些进程有可能在相同的机器上, 也有可能分布在不同的机器上. 如果这些进程共享了一些资源, 可能就需要分布式锁来锁定对这些资源的访问. 2 ...
- ZooKeeper分布式锁浅谈(一)
一.概述 清明节的时候写了一篇分布式锁概述,里面介绍了分布式锁实现的几种方式,其实那时候我一直沉迷于使用redis的悲观锁和乐观锁来实现分布式锁,直到一个血案的引发才让我重新认识了redis分布式锁的 ...
- [转载] zookeeper 分布式锁服务
转载自http://www.cnblogs.com/shanyou/archive/2012/09/22/2697818.html 分布式锁服务在大家的项目中或许用的不多,因为大家都把排他放在数据库那 ...
- 跟着大神学zookeeper分布式锁实现-----来自Ruthless
前几天分享了@Ruthless大神的Redis锁,发现和大家都学习了很多东西.因为分布式锁里面,最好的实现是zookeeper的分布式锁.所以在这里把实现方式和大家分享一下. zookeeper分布式 ...
- 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁
首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...
- zookeeper 分布式锁原理
zookeeper 分布式锁原理: 1 大家也许都很熟悉了多个线程或者多个进程间的共享锁的实现方式了,但是在分布式场景中我们会面临多个Server之间的锁的问题,实现的复杂度比较高.利用基于googl ...
- 分布式锁(一) Zookeeper分布式锁
什么是Zookeeper? Zookeeper(业界简称zk)是一种提供配置管理.分布式协同以及命名的中心化服务,这些提供的功能都是分布式系统中非常底层且必不可少的基本功能,但是如果自己实现这些功能而 ...
- ZooKeeper 分布式锁
在Redis分布式锁一文中, 作者介绍了如何使用Redis开发分布式锁. Redis分布式锁具有轻量高吞吐量的特点,但是一致性保证较弱.我们可以使用Zookeeper开发分布式锁,来满足对高一致性的要 ...
随机推荐
- c++简单线程池实现
线程池,简单来说就是有一堆已经创建好的线程(最大数目一定),初始时他们都处于空闲状态,当有新的任务进来,从线程池中取出一个空闲的线程处理任务,然后当任务处理完成之后,该线程被重新放回到线程池中,供其他 ...
- VirtualBox 局域网独立主机设置
网络地址转换(NAT)网卡用于分配虚拟机内网地址 桥接网卡用于分配母机内网地址 母机设置-网卡指定IP地址 常见坑有两个 坑1:记得把虚拟机防火墙关闭 坑2:虚拟机不可以ping主机,但母机可以pin ...
- 最新的Windows环境搭建zeroMQ并使用java代码运行zeromq详细教程
最近项目要用zeromq,linux上很好配置使用,但是windows上配置与使用没有找到合适的解决方案,看的很头疼,这里自己总结下供大家参考 准备工作: 1.libzmq下载地址:https://g ...
- opencv2.4.9卸载安装
1.安装opencv2.4.9过程中遇到的问题. 1.使用 sudo apt-get install libopencv-dev 安装OpenCV 2.4.9 发现版本为2.4.8,故卸载重装. 查看 ...
- 简明shell入门
- java多线程的字符流与字节流
字节流: package com.wz.thread.stream;import java.io.PipedOutputStream;/** * 字节输入流 * @author Administrat ...
- [Codeforces 297E]Mystic Carvings
Description 题库链接 题面链接 Solution 这里给出主席树的版本.主席树维护直线的一个端点在前 \(i\) 个端点中,另一个端点在区间内的个数. Code //It is made ...
- [ZJOI2010]基站选址
题目描述 有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di.需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费用为Ci.如果在距离第i个村庄不超过Si的范 ...
- [HNOI2007]分裂游戏
Description 聪聪和睿睿最近迷上了一款叫做分裂的游戏. 该游戏的规则试: 共有 n 个瓶子, 标号为 0,1,2.....n-1, 第 i 个瓶子中装有 p[i]颗巧克力豆,两个人轮流取豆子 ...
- [HNOI2014]江南乐
Description 小A是一个名副其实的狂热的回合制游戏玩家.在获得了许多回合制游戏的世界级奖项之后,小A有一天突然想起了他小时候在江南玩过的一个回合制游戏. 游戏的规则是这样的,首先给定一 ...