Zookeeper系列二:Java API介绍
之前曾经介绍了Zookeeper的一些基础概念以及一些shell操作,这次笔者将介绍一下如何通过Java API操纵Zookeeper,并且通过Zookeeper实现一个简单的分布式锁服务。
Zookeeper的API使用
这里主要介绍Curator框架,它是在Zookeeper的原生API接口上进行了包装,提供了各种应用场景的抽象封装,是目前很流行的zookeeper客户端。
引入JAR包
<dependencies>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>com.google.collections</groupId>
<artifactId>google-collections</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- java编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
节点操作
这里主要介绍创建节点、删除节点、获取节点信息、查询节点是否存在以及Watch机制。
public static void main(String[] args) throws Exception {
//连接字符串
String connectionString = "192.168.52.110:2181,192.168.52.120:2181,192.168.52.130:2181";
//指定重试策略
ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3,Integer.MAX_VALUE);
// ExponentialBackoffRetry包含三个参数:初始Sleep时间,最大重试次数,最大Sleep时间
// 当前Sleep时间=初始Sleep时间*Math.max(1, random.nextInt(1<<(重试次数+1)))
//创建客户端对象
CuratorFramework client = CuratorFrameworkFactory.newClient(connectionString, retryPolicy);
//启动客户端
client.start();
/**
* 创建一个 允许所有人访问的 持久节点
*/
client.create()
.creatingParentsIfNeeded()//递归创建,如果没有父节点,自动创建父节点
.withMode(CreateMode.PERSISTENT)//节点类型,持久节点
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//设置ACL,和原生API相同
.forPath("/test02/node01","123456".getBytes());
// OPEN_ACL_UNSAFE: 创建任何人都可以操作的节点
// READ_ACL_UNSAFE: 创建任何人都可以读的节点
/**
* 创建一个 允许所有人访问的 持久节点
*/
client.create()
.creatingParentsIfNeeded()//递归创建,如果没有父节点,自动创建父节点
.withMode(CreateMode.EPHEMERAL)//节点类型,临时节点
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//设置ACL,和原生API相同
.forPath("/test02/node02","haha".getBytes());
/**
* 节点下面添加数据与修改是类似的,一个节点下面会有一个数据,新的数据会覆盖旧的数据
*/
client.setData().forPath("/test02/node01", "hello".getBytes());
//删除节点
Void aVoid = client.delete()
.forPath("/test02/node02");
System.out.println("=====>" + aVoid);
//递归删除节点
client.delete()
.deletingChildrenIfNeeded()
.forPath("/test02");
//获取/test02/node03节点数据 和 Stat信息
//创建一个Stat对象
Stat statinfo = new Stat();
byte[] node10 = client.getData()
.storingStatIn(statinfo)//获取stat信息存储到stat对象
.forPath("/test02/node03");
System.out.println("=====>该节点信息为:" + new String(node10));
System.out.println("=====>该节点的数据版本号为:" + statinfo.getVersion());
// 查询节点是否存在
Stat existsNodeStat = client.checkExists().forPath("/test02/node04");
if(existsNodeStat == null){
System.out.println("=====>节点不存在");
}
if(existsNodeStat.getEphemeralOwner() > 0){
System.out.println("=====>临时节点");
}else{
System.out.println("=====>持久节点");
}
// Watch机制
CuratorCache curatorCache = CuratorCache.build(curatorFramework, "/test02");
curatorCache.listenable().addListener(new CuratorCacheListener() {
@Override
public void event(Type type, ChildData oldData, ChildData data) {
switch (type){
case NODE_CHANGED:
String node_name = data.getPath().replace("/test02", "");
System.out.println("变更节点名称为" + node_name);
System.out.println("变更节点数据为" + new String(data.getData()));
break;
case NODE_CREATED:
String node_name2 = data.getPath().replace("/test02", "");
System.out.println("变更节点名称为" + node_name2);
System.out.println("变更节点数据为" + new String(data.getData()));
break;
case NODE_DELETED:
String node_name3 = data.getPath().replace("/test02", "");
System.out.println("变更节点名称为" + node_name3);
System.out.println("变更节点数据为" + new String(data.getData()));
break;
default:
break;
}
}
});
curatorCache.start();
Thread.sleep(Integer.MAX_VALUE);
client.close();
client.close();
}
分布式锁的实现
分布式锁类
分布式锁的实现思路之前也有提到过,主要是基于临时顺序节点来实现的。创建一个持久节点,每个想要获取锁服务的请求都会在这个持久节点下创建一个临时顺序节点,每次只有最小编号的节点能获取锁服务,而对应的最小编号+1的节点则使用Watch机制监视最小编号节点是否被删除,即锁是否被释放。
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class DistributeLocks implements Lock, Watcher {
private ZooKeeper zooKeeper = null;
private String ROOT_LOCK ="/locks";
private String CURRENT_LOCK ;
private String WAIT_LOCK;
private CountDownLatch countDownLatch;
//连接才开始操作的
private CountDownLatch sysConectDownLatch;
public DistributeLocks() {
try {
//建立zookeeper的连接
sysConectDownLatch = new CountDownLatch(1);
zooKeeper = new ZooKeeper("192.168.52.100:2181",40000,this);
sysConectDownLatch.await();
//是否存在根节点,如果不存在,则去创建持久化节点
Stat stat = zooKeeper.exists(ROOT_LOCK,false);
if(stat == null){
zooKeeper.create(ROOT_LOCK,"0".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
@Override
public void lock() {
if(tryLock()){
System.out.println(Thread.currentThread().getName()+"->"+CURRENT_LOCK+"->获得锁成功");
return ; }
//添加监控
waitForLocks(WAIT_LOCK);
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
//尝试获取锁
@Override
public boolean tryLock() {
try {
//创建当前的节点
CURRENT_LOCK = zooKeeper.create(ROOT_LOCK+"/","0".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
//获取当前的所有的子节点
System.out.println(Thread.currentThread().getName()+"->"+ CURRENT_LOCK+",尝试竞争锁");
List<String> childrens = zooKeeper.getChildren(ROOT_LOCK,false);
SortedSet<String> treeSet =new TreeSet<String>();
for(String children : childrens){
treeSet.add(ROOT_LOCK+"/"+children);
}
//获取最小的节点
String minNode = treeSet.first();
if(CURRENT_LOCK.equals(minNode)){
return true;
}
//获取当前节点的上一个节点 返回比当前节点更小的所有节点
SortedSet<String> lessCurrentSets= treeSet.headSet(CURRENT_LOCK);
if(lessCurrentSets != null){
WAIT_LOCK = lessCurrentSets.last();
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
//对上一节点记性监控
private boolean waitForLocks(String pre){
try {
//进行监控
Stat stat= zooKeeper.exists(pre,true);
if(stat!=null){
System.out.println("当前线程"+Thread.currentThread().getName()+"等待"+WAIT_LOCK+"释放");
countDownLatch = new CountDownLatch(1);
countDownLatch.await();
System.out.println("当前线程"+Thread.currentThread().getName()+"获得锁");
return true; }
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public void unlock() {
System.out.println(Thread.currentThread().getName()+ "->释放锁"+ CURRENT_LOCK);
try {
zooKeeper.delete(CURRENT_LOCK,-1);
CURRENT_LOCK=null;
zooKeeper.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
@Override
public Condition newCondition() {
return null;
}
@Override
public void process(WatchedEvent watchedEvent) {
if( watchedEvent.getState().equals(Event.KeeperState.SyncConnected)){
sysConectDownLatch.countDown();//说明已经建立了连接
}
//当节点的信息发送删除的时候就会触发该监控
if(this.countDownLatch != null){
countDownLatch.countDown();
}
}
}
测试类
作为测试类,这里生成10个线程同时获取分布式锁服务。
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
public class LockOpt {
public static void main(String[] args) throws IOException {
final CountDownLatch countDownLatch = new CountDownLatch(10);
for(int i=0;i<10;i++){
new Thread(new Runnable() {
@Override
public void run() {
DistributeLocks lock = null;
try {
countDownLatch.await();
lock = new DistributeLocks();
lock.lock(); //获得锁
//todo 做一些事情
System.out.println("获得了锁");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
},"Thread-"+i).start();
countDownLatch.countDown();
}
}
}