下面要讲的是curator这个zk的开源客户端,这个客户端要比zkClient更有名且功能更加强大,他解决了很多zk客户端非常底层的细节开发工作,包括链接重连,反复注册watcher和NodeExistsException异常等,还提供了zk的各种场景例如共享锁服务,选举机制以及分布式计数器等抽象封装。下面我们将maven的依赖加入到我们的项目中。
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.4.2</version>
</dependency>
1、创建会话
使用CuratorFrameworkFactory这个工厂类的两个静态方法来创建客户端,使用CuratorFramework中的start方法启动会话。
(1)connectString zk服务器列表,英文状态逗号分开。(2)retryPolicy 重试策略 默认4种实现,之后细说 (3)sessionTimeoutMs 会话超时时间 (4)connecionTimeoutMs连接超时时间。
其中RetoryPolicy类是重试类,有3个参数:retryCount 重试次数 ;elapsedTimeMs 从第一次重试开始已经花费的时间 sleeper 用于sleep的时间,首先我们来看一下重试策略ExponentialBackoffRetry,这个类需要指定3个参数:baseSleepTimeMs 初始的sleep时间,maxRetries 最大重试次数;maxSleepMs 最大睡眠时间.下面是一个创建连接的DEMO:
package mplus.zookeeper.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class FirstCurator {
static String servers = "192.168.11.160:2181,192.168.11.161:2181/root/apps";
static String rootPath = "/root/apps";
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework cf1 = CuratorFrameworkFactory.newClient(servers,5000,3000,retryPolicy);
cf1.start();
/**
* Fluent风格
*/
CuratorFramework cf2 = CuratorFrameworkFactory.builder()
.connectString(servers)
.sessionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.build();
cf2.start();
/**
* 隔离命令空间的会话 namespace
* 即根目录
*/
CuratorFramework cf3 = CuratorFrameworkFactory.builder()
.connectString(servers)
.sessionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.namespace("root")
.build();
cf3.start();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、创建删除节点
package mplus.zookeeper.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
public class CreateNodeTest {
static String servers = "192.168.11.160:2181,192.168.11.161:2181/root/apps";
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(servers)
.sessionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.build();
client.start();
try {
/**
* 默认创建的是持久型节点 内容为空
*/
client.create().forPath("/test1");
/**
* 创建一个持久型节点并写入数据
*/
client.create().forPath("/test2","吕鹏".getBytes());
/**
* 创建一个临时节点
*/
client.create().withMode(CreateMode.EPHEMERAL).forPath("/test3");
/**
* 创建一个临时节点并自动递归创建父节点
*/
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/test4/test5");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
client.delete().forPath("/test1");
client.delete().forPath("/test2");
client.delete().deletingChildrenIfNeeded().forPath("/test4");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3、异步接口
Curator使用BackGroundCallback接口处理异步接口调用之后服务端返回的结果信息,其接口定义如下:
new BackgroundCallback() {
public void processResult(CuratorFramework client,
CuratorEvent event)
throws Exception {
}
};
其中client就是指当前的客户端,而event就是说服务端的事件。event中有两个参数比较重要一个是事件类型一个是响应码。下面我们看一个例子
package mplus.zookeeper.curator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
public class BackgroundCallTest {
static String servers = "192.168.11.160:2181,192.168.11.161:2181/root/apps";
static CountDownLatch cdl = new CountDownLatch(2);
static ExecutorService tp = Executors.newFixedThreadPool(2);
static RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
static CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(servers)
.sessionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.build();
public static void main(String[] args) {
try {
client.start();
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
public void processResult(CuratorFramework client, CuratorEvent event)
throws Exception {
System.out.println(event.getResultCode() + "," + event.getType());
System.out.println(Thread.currentThread().getName());
cdl.countDown();
}
}, tp).forPath("/test1","test1".getBytes());
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
public void processResult(CuratorFramework client, CuratorEvent event)
throws Exception {
System.out.println(event.getResultCode() + "," + event.getType());
System.out.println(Thread.currentThread().getName());
cdl.countDown();
}
}).forPath("/test1","test1".getBytes());
cdl.await();
tp.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行上面的例子会输出以下内容:‘
-110,CREATE
main-EventThread
0,CREATE
pool-1-thread-1
-110代表失败,原因是因为在第一次已经创建了/test1这个目录,所以第二次就不能创建了,第二次创建我们没有设置自定义线程所以输出的线程为主线程,第一次我们用了自定义线程所以输出的线程名称为线程池的线程。
Curator还提供了更多便利的API接口和一些典型场景的使用参考,首先将依赖加入:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.4.2</version>
</dependency>
1、事件监听
zk原生支持通过注册watcher来进行事件监听,但使用不方便,需要开发人员反复注册watcher,curator引入了cache实现对zk的服务端事件监听,cache是curator中对事件监听的包装,其对事件的监听其实可以近似看作一个本地缓存视图和远程zk视图的对比过程,同事curator能够为开发人员处理反复注册监听,从而大大简化了这个过程,cache分为两类监听类型:节点监听和子节点监听。
(1)NodeCache
nodechache用于监听指定的zk数据节点本身的变化, 其构造方法如下两个:
<1>client curator客户端实例,<2>path 是数据节点的节点路径,<3>dataIsCompressed 是否进行数据压缩
下面我们来看一个案例:
package mplus.zookeeper.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
public class NodeCacheTest {
static String servers = "192.168.11.160:2181,192.168.11.161:2181/root/apps";
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(servers)
.sessionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.build();
client.start();
try {
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/test");
final NodeCache cache = new NodeCache(client,"/test",false);
cache.start(true);
cache.getListenable().addListener(new NodeCacheListener() {
public void nodeChanged() throws Exception {
System.out.println(new String(cache.getCurrentData().getData()));
}
});
client.setData().forPath("/test","init".getBytes());
Thread.sleep(1000);
client.delete().deletingChildrenIfNeeded().forPath("/test");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
(2)PathChildrenCache 用于监听指定的zookeeper数据节点的子节点变化情况。直接看例子吧:
package mplus.zookeeper.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
public class PathChildrenCacheTest {
static String servers = "192.168.11.160:2181,192.168.11.161:2181/root/apps";
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(servers)
.sessionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.build();
client.start();
try {
PathChildrenCache cache = new PathChildrenCache(client,"/",true);
cache.start(StartMode.POST_INITIALIZED_EVENT);
cache.getListenable().addListener(new PathChildrenCacheListener() {
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
throws Exception {
switch(event.getType()){
case CHILD_ADDED:System.out.println("add");break;
case CHILD_REMOVED:System.out.println("delete");break;
case CHILD_UPDATED:System.out.println("update");break;
default:break;
}
}
});
client.create().withMode(CreateMode.PERSISTENT).forPath("/test");
Thread.sleep(1000);
client.create().withMode(CreateMode.PERSISTENT).forPath("/test/test1");
Thread.sleep(1000);
client.delete().forPath("/test/test1");
Thread.sleep(1000);
client.delete().forPath("/test");
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
这个类可以在节点创建和删除的时候触发事件,但是对二级的子节点无法进行监听。
2、Master选举
在分布式系统中经常会有类似的场景,对于一个复杂的任务仅需要从集群中选举出一台服务器进行处理即可。对于此类问题,我们需要在众多的分布式服务里选举出一个可用的服务来处理,借助zookeeper就可以完成。选取一个根节点,多台机器同事向该节点创建一个子节点,最终只有能一台机器创建成功,而这个机器就是我们选举出来的可以座位master。curator对这个过程进行了封装,看demo:
package mplus.zookeeper.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class MasterTest {
static String servers = "192.168.11.160:2181,192.168.11.161:2181";
public static void main(String[] args) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(servers)
.sessionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.build();
client.start();
try {
LeaderSelector selector = new LeaderSelector(client, "/root/apps", new LeaderSelectorListener() {
public void stateChanged(CuratorFramework client, ConnectionState state) {
}
public void takeLeadership(CuratorFramework client) throws Exception {
System.out.println("master开始执行任务");
Thread.sleep(3000);
System.out.println("master执行任务完成");
}
});
selector.autoRequeue();
selector.start();
Thread.sleep(7000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码中使用sleep简单的模拟了业务逻辑的执行,当一个线程完成master逻辑后另外一个应用程序的takeLeaderShip方法才会被调用,当一个应用实例成为master后,其他应用实例会进行等待状态,直到master挂了或者退出后才会开始选举新的master。
3、分布式锁
在分布式环境中,为了保持数据的一致性,经常在程序的某个运行点比如减少库存的时候进行同步控制,以一个流程号生成的场景为例,普通的后台应用通常使用时间差方式生成,但用户量非常大的时候可能会有并发问题。但curator实现了分布式锁的功能,看下demo
package mplus.zookeeper.curator;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class RecipesLock {
static String servers = "192.168.11.160:2181,192.168.11.161:2181";
static RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
static CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(servers)
.sessionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.build();
public static void main(String[] args) {
client.start();
try {
final InterProcessMutex lock = new InterProcessMutex(client,"/root/apps");
final CountDownLatch down = new CountDownLatch(1);
for(int i=0;i<30;i++){
new Thread(new Runnable() {
public void run() {
try {
down.await();
lock.acquire();
} catch (Exception e) {
}
SimpleDateFormat f = new SimpleDateFormat("HH:mm:ss|SSS");
String orderNo = f.format(new Date());
System.out.println(orderNo);
try {
lock.release();
} catch (Exception e) {
}
}
}).start();
}
down.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
例子还有很多这里就不再一一列举了。
最后的工具类其实挺实用的,要不等到下一节再仔细说吧。