前一讲中我们知道,Zookeeper通过维护一个分布式目录数据结构,实现分布式协调服务。本文主要介绍利用Zookeeper有序目录的创建和删除,实现分布式共享锁。

举个例子,性能管理系统中,告警规则只允许最多创建450条,我们如何保证这个约束呢?

如果只有一个web节点,我们只需要简单的把规则数量查询服务,入库服务加一个锁即可以解决,代码如下

synchronized(this)
{
if(450 > queryRuleCount())
{
insertRule(rule);
}
}

实际上,性能管理系统至少有两个以上的web节点,一方面保障服务性能,一方面用于容灾备份。这种场景两个规则创建请求可能在两个web节点上执行,synchronized就无用武之地了。这种冲突在规则导入场景下更容易发生。所以,使用分布式共享锁就势在必行了。

我们知道,zookeeper维护的分布式目录数据结构视图,对于各个zookeeper节点都是相同。zookeeper允许客户端创建一个有序的目录——在CreateMode.EPHEMERAL_SEQUENTIAL创建模式下,zookeeper会自动在客户端创建的目录名称后面添加一个自增长的id。关键代码

            // 关键方法,创建包含自增长id名称的目录,这个方法支持了分布式锁的实现
// 四个参数:
// 1、目录名称 2、目录文本信息
// 3、文件夹权限,Ids.OPEN_ACL_UNSAFE表示所有权限
// 4、目录类型,CreateMode.EPHEMERAL_SEQUENTIAL表示会在目录名称后面加一个自增加数字
String lockPath = getZkClient().create(
ROOT_LOCK_PATH + '/' + PRE_LOCK_NAME,
Thread.currentThread().getName().getBytes(),
Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);

利用zookeeper允许客户端创建一个有序的目录的特性,可以实现一个可靠的分布式共享锁。

分布式进程在读写一个共享数据时,可以先在某个公共目录下创建一个有序子目录,然后判断该目录id是否最小。
目录id最小则获得锁并消费共享数据,然后删除该目录。否则则等待,直到自己的目录id成为最小后,才获得锁。

zookeeper所有目录操作事件都可以注册监听器,所以分布式进程不必循环查询子目录判断自己的目录id是否最小,可以注册一个监听器在前一个目录上,监听前一个目录是否被删除。

下面是一个分布式进程消费共享消息的例子

1、 zookeeper共享锁

package com.coshaho.learn.zookeeper;

import java.io.IOException;
import java.util.Collections;
import java.util.List; import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.Stat; /**
* zookeeper分布式共享锁
* @author coshaho
*
*/
public class ZookeeperLock
{
private String ROOT_LOCK_PATH = "/Locks";
private String PRE_LOCK_NAME = "mylock_";
private static ZookeeperLock lock;
public static ZookeeperLock getInstance()
{
if(null == lock)
{
lock = new ZookeeperLock();
}
return lock;
} /**
* 获取锁:实际上是创建线程目录,并判断线程目录序号是否最小
* @return
*/
public String getLock()
{
try
{
// 关键方法,创建包含自增长id名称的目录,这个方法支持了分布式锁的实现
// 四个参数:
// 1、目录名称 2、目录文本信息
// 3、文件夹权限,Ids.OPEN_ACL_UNSAFE表示所有权限
// 4、目录类型,CreateMode.EPHEMERAL_SEQUENTIAL表示会在目录名称后面加一个自增加数字
String lockPath = getZkClient().create(
ROOT_LOCK_PATH + '/' + PRE_LOCK_NAME,
Thread.currentThread().getName().getBytes(),
Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(Thread.currentThread().getName() + " create lock path : " + lockPath);
tryLock(lockPath);
return lockPath;
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
} private boolean tryLock(String lockPath) throws KeeperException, InterruptedException
{
// 获取ROOT_LOCK_PATH下所有的子节点,并按照节点序号排序
List<String> lockPaths = getZkClient().getChildren(ROOT_LOCK_PATH, false);
Collections.sort(lockPaths);
int index = lockPaths.indexOf(lockPath.substring(ROOT_LOCK_PATH.length() + 1));
if (index == 0)
{
System.out.println(Thread.currentThread().getName() + " get lock, lock path: " + lockPath);
return true;
}
else
{
// 创建Watcher,监控lockPath的前一个节点
Watcher watcher = new Watcher()
{
@Override
public void process(WatchedEvent event)
{
// 创建的锁目录只有删除事件
System.out.println("Received delete event, node path is " + event.getPath());
synchronized (this)
{
notifyAll();
}
}
}; String preLockPath = lockPaths.get(index - 1);
// 查询前一个目录是否存在,并且注册目录事件监听器,监听一次事件后即删除
Stat state = getZkClient().exists(ROOT_LOCK_PATH + "/" + preLockPath, watcher);
// 返回值为目录详细信息
if (state == null)
{
return tryLock(lockPath);
}
else
{
System.out.println(Thread.currentThread().getName() + " wait for " + preLockPath);
synchronized (watcher)
{
// 等待目录删除事件唤醒
watcher.wait();
}
return tryLock(lockPath);
}
}
} /**
* 释放锁:实际上是删除当前线程目录
* @param lockPath
*/
public void releaseLock(String lockPath)
{
try
{
getZkClient().delete(lockPath, -1);
System.out.println("Release lock, lock path is" + lockPath);
}
catch (InterruptedException | KeeperException e)
{
e.printStackTrace();
}
} private String zookeeperIp = "192.168.1.104:12181";
private static ZooKeeper zkClient = null;
public ZooKeeper getZkClient()
{
if(null == zkClient)
{
try
{
zkClient = new ZooKeeper(zookeeperIp, 3000, null);
}
catch (IOException e)
{
e.printStackTrace();
}
}
return zkClient;
}
}

2、 模拟分布式进程消费共享消息

package com.coshaho.learn.zookeeper;

import java.util.ArrayList;
import java.util.List; import org.springframework.util.CollectionUtils; /**
* 分布式进程消费共享消息
* @author coshaho
*
*/
public class DistributeCache
{
private static List<String> msgCache = new ArrayList<String>(); static class MsgConsumer extends Thread
{
@Override
public void run()
{
while(!CollectionUtils.isEmpty(msgCache))
{
String lock = ZookeeperLock.getInstance().getLock();
if(CollectionUtils.isEmpty(msgCache))
{
return;
}
String msg = msgCache.get(0);
System.out.println(Thread.currentThread().getName() + " consume msg: " + msg);
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
msgCache.remove(msg);
ZookeeperLock.getInstance().releaseLock(lock);
}
}
} public static void main(String[] args)
{
for(int i = 0; i < 10; i++)
{
msgCache.add("msg" + i);
}
MsgConsumer consumer1 = new MsgConsumer();
MsgConsumer consumer2 = new MsgConsumer();
consumer1.start();
consumer2.start();
}
}

3、 测试结果

log4j:WARN No appenders could be found for logger (org.apache.zookeeper.ZooKeeper).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
Thread-1 create lock path : /Locks/mylock_0000000217
Thread-0 create lock path : /Locks/mylock_0000000216
Thread-0 get lock, lock path: /Locks/mylock_0000000216
Thread-0 consume msg: msg0
Thread-1 wait for mylock_0000000216
Received delete event, node path is /Locks/mylock_0000000216
Release lock, lock path is/Locks/mylock_0000000216
Thread-1 get lock, lock path: /Locks/mylock_0000000217
Thread-1 consume msg: msg1
Thread-0 create lock path : /Locks/mylock_0000000218
Thread-0 wait for mylock_0000000217
Received delete event, node path is /Locks/mylock_0000000217
Release lock, lock path is/Locks/mylock_0000000217
Thread-0 get lock, lock path: /Locks/mylock_0000000218
Thread-0 consume msg: msg2
Thread-1 create lock path : /Locks/mylock_0000000219
Thread-1 wait for mylock_0000000218
Received delete event, node path is /Locks/mylock_0000000218
Release lock, lock path is/Locks/mylock_0000000218
Thread-1 get lock, lock path: /Locks/mylock_0000000219
Thread-1 consume msg: msg3
Thread-0 create lock path : /Locks/mylock_0000000220
Thread-0 wait for mylock_0000000219

Zookeeper使用实例——分布式共享锁的更多相关文章

  1. Zookeeper使用实例——服务节点管理

    分布式处理中,总会存在多个服务节点同时工作,并且节点数量会随着网络规模的变化而动态增减,服务节点也有可能发生宕机与恢复.面对着动态增减的服务节点,我们如何保证客户请求被服务器正确处理呢.我们可以通过z ...

  2. ZooKeeper 分布式共享锁的实现

    原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/8352919.html ------------------------------------ ...

  3. zookeeper编程入门系列之zookeeper实现分布式进程监控和分布式共享锁(图文详解)

    本博文的主要内容有 一.zookeeper编程入门系列之利用zookeeper的临时节点的特性来监控程序是否还在运行   二.zookeeper编程入门系列之zookeeper实现分布式进程监控 三. ...

  4. Zookeeper概念学习系列之zookeeper实现分布式共享锁

    首先假设有两个线程, 两个线程要同时到mysql中更新一条数据, 对数据库中的数据进行累加更新.由于在分布式环境下, 这两个线程可能存在于不同的机器上的不同jvm进程中, 所以这两个线程的关系就是垮主 ...

  5. 8.6.zookeeper应用案例_分布式共享锁的简单实现

    1.分布式共享锁的简单实现 在分布式系统中如何对进程进行调度,假设在第一台机器上挂载了一个资源,然后这三个物理分布的进程都要竞争这个资源,但我们又不希望他们同时 进行访问,这时候我们就需要一个协调器, ...

  6. Mesos+Zookeeper+Marathon+Docker分布式集群管理最佳实践

    参考赵班长的unixhot以及马亮blog 笔者QQ:572891887 Linux架构交流群:471443208 1.1Mesos简介 Mesos是Apache下的开源分布式资源管理框架,它被称为分 ...

  7. 基于Zookeeper实现的分布式互斥锁 - InterProcessMutex

    Curator是ZooKeeper的一个客户端框架,其中封装了分布式互斥锁的实现,最为常用的是InterProcessMutex,本文将对其进行代码剖析 简介 InterProcessMutex基于Z ...

  8. zookeeper的安装及共享锁的应用

         Zookeeper的安装及共享锁的应用 1.zookeeper的安装 1.1  下载安装包 Wget http://mirror.bit.edu.cn/apache/zookeeper/zo ...

  9. java使用zookeeper实现的分布式锁示例

    java使用zookeeper实现的分布式锁示例 作者: 字体:[增加 减小] 类型:转载 时间:2014-05-07我要评论 这篇文章主要介绍了java使用zookeeper实现的分布式锁示例,需要 ...

随机推荐

  1. TX大手笔做业务必然失败的原因

    首先说一个伪命题: 物体会向下落这是一个基本的定律,一个小小的物理规则会覆盖所有物体的行为准则. 那么,当地球上的所有东西都下落的时候,你指望整个地球,月球,太阳也会下落么? 事实上大家都知道星球在宇 ...

  2. [VS]VS2010如何使用Visual Studio Online在线服务管理团队资源(在线TFS)

    前言 Visual Studio Online,也就是以前的Team Foundation Service,从名字可以看出这是一个团队资源管理服务.在微软的云基础架构中运行,无需安装或配置任何服务器, ...

  3. was cached in the local repository, resolution will not be reattempted until the update interval of localhost-repository has elapsed or updates are forced

    ailed to collect dependencies at com.eshore:common:jar:0.0.1-SNAPSHOT: Failed to read artifact descr ...

  4. Druid的Segment Balance及其代价计算函数分析

    Balance $Cost(X, Y) $ $$ J_\alpha(x) = \sum_{m=0}^\infty \frac{(-1)^m}{m! \Gamma (m + \alpha + 1)} { ...

  5. Yii2 使用json 和设置component 中'format' => yii\web\Response::FORMAT_JSON 的区别

    在Yii2中如果设置了 'response' => [  'format' => yii\web\Response::FORMAT_JSON,  'charset' => 'UTF- ...

  6. Mysql----数据备份、pymysql模块

    一 IDE工具介绍 生产环境还是推荐使用mysql命令行,但为了方便我们测试,可以使用IDE工具 下载链接:https://pan.baidu.com/s/1bpo5mqj 掌握: #1. 测试+链接 ...

  7. CodeForces - 847B Preparing for Merge Sort 二分

    http://codeforces.com/problemset/problem/847/B 题意:给你n个数(n<2e5)把它们分成若干组升序的子序列,一行输出一组.分的方法相当于不断找最长递 ...

  8. hdu-4283 You Are the One 区间dp,

    题意:n个人排队上台,每个人有一屌丝值D,他的不满意值=D*(k-1)(k为他前面的总人数). 求整个队列不满意值之和的最小值.你只有一个操作,就是把队首的人塞进小黑屋,也就是压入栈中,后面的人就被提 ...

  9. JMeter(十)-正则表达式关联

    jmeter中,接口自动化的关键在于参数关联.比如需要登录的接口,如何调用登录口令?一个增删改查的闭环,如何将接口参数上下传递?下面就以实际的例子来仔细说一说 1:登录接口 这里有一个实际的登录接口, ...

  10. Github常用命令【转】

    本地仓库(local repository) 创建一个本地仓库的流程: 为本地仓库创建一个目录 在目录中执行 git init 对本地仓库所做的改变(例如添加.删除文件等)首先加入到本地仓库的 Ind ...