一、Curator介绍

zookeeper的提交人也说过,curator对于zookeeper而言就像是guava对于java差不多,更加优雅高效。

而且之前的zookeeper原生API,往往因为2个问题而让代码变的非常复杂:

  (1) session expired,当会话由于各种原因而断掉之后的客户端重连机制

  (2) watch的一次性问题,每次重连之后都要重新设置watch,而且每次的watch都是一次性的操作

Curator框架提供了一套高级的API, 解决了以上问题并且简化了ZooKeeper的操作。 它增加了很多使用ZooKeeper开发的特性,可以处理ZooKeeper集群复杂的连接管理和重试机制。 这些特性包括:

  • 自动化的连接管理: 重新建立到ZooKeeper的连接和重试机制存在一些潜在的错误case。 Curator帮助你处理这些事情,对你来说是透明的。
  • 清理API:
    • 简化了原生的ZooKeeper的方法,事件等
    • 提供了一个现代的流式接口
  • 提供了Recipes实现: 如前面的文章介绍的那样,基于这些Recipes可以创建很多复杂的分布式应用

注意官网中的版本说明:

The are currently two released versions of Curator, 2.x.x and 3.x.x:

  • Curator 2.x.x - compatible with both ZooKeeper 3.4.x and ZooKeeper 3.5.x
  • Curator 3.x.x - compatible only with ZooKeeper 3.5.x and includes support for new features such as dynamic reconfiguration, etc.

二、HelloWorld

import java.util.List;
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.curator.utils.CloseableUtils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooKeeper.States;
import org.apache.zookeeper.data.Stat; public class CuratorBase { /**
* zookeeper地址
*/
static final String CONNECT_ADDR = "192.168.1.211:2181,192.168.1.212:2181,192.168.1.213:2181";
/**
* session超时时间
*/
static final int SESSION_OUTTIME = 5000;//ms public static void main(String[] args) {
//1 重试策略:初试时间为1s 重试10次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通过工厂创建连接
CuratorFramework cf = null;
try {
cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
// .namespace("super")
.build();
//3 开启连接
cf.start();
//4 建立节点 指定节点类型(不加withMode默认为持久类型节点)、路径、数据内容
// cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/super/c1", "c1内容".getBytes());
//5 删除节点
cf.delete().guaranteed().deletingChildrenIfNeeded().forPath("/super");
// 读取、修改 /*创建节点*/
cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/super/c1", "c1内容".getBytes());
cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/super/c2", "c2内容".getBytes());
/*读取节点*/
String ret1 = new String(cf.getData().forPath("/super/c2"));
System.out.println(ret1);
/*修改节点*/
cf.setData().forPath("/super/c2", "修改c2内容".getBytes());
String ret2 = new String(cf.getData().forPath("/super/c2"));
System.out.println(ret2); // 绑定回调函数
ExecutorService pool = Executors.newCachedThreadPool();
cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
.inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework cf, CuratorEvent ce) throws Exception {
System.out.println("code:" + ce.getResultCode());
System.out.println("type:" + ce.getType());
System.out.println("线程为:" + Thread.currentThread().getName());
}
}, pool)
.forPath("/super/c3", "c3内容".getBytes());
Thread.sleep(Integer.MAX_VALUE); // 读取子节点getChildren方法 和 判断节点是否存在checkExists方法
/**
List<String> list = cf.getChildren().forPath("/super");
for(String p : list){
System.out.println(p);
} Stat stat = cf.checkExists().forPath("/super/c3");
System.out.println(stat); Thread.sleep(2000);
cf.delete().guaranteed().deletingChildrenIfNeeded().forPath("/super");
*/ //cf.delete().guaranteed().deletingChildrenIfNeeded().forPath("/super");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("关闭连接");
CloseableUtils.closeQuietly(cf);
}
}
}

总结方法如下:

方法名 描述
create() 开始创建操作, 可以调用额外的方法(比如方式mode 或者后台执行background) 并在最后调用forPath()指定要操作的ZNode
delete() 开始删除操作. 可以调用额外的方法(版本或者后台处理version or background)并在最后调用forPath()指定要操作的ZNode
checkExists() 开始检查ZNode是否存在的操作. 可以调用额外的方法(监控或者后台处理)并在最后调用forPath()指定要操作的ZNode
getData() 开始获得ZNode节点数据的操作. 可以调用额外的方法(监控、后台处理或者获取状态watch, background or get stat) 并在最后调用forPath()指定要操作的ZNode
setData() 开始设置ZNode节点数据的操作. 可以调用额外的方法(版本或者后台处理) 并在最后调用forPath()指定要操作的ZNode
getChildren() 开始获得ZNode的子节点列表。 以调用额外的方法(监控、后台处理或者获取状态watch, background or get stat) 并在最后调用forPath()指定要操作的ZNode
inTransaction() 开始是原子ZooKeeper事务. 可以复合create, setData, check, and/or delete 等操作然后调用commit()作为一个原子操作提交

三、Curator中Watch机制

监控当前节点的数据状况:注意不会监控delete事件

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.curator.utils.CloseableUtils; public class CuratorWatcher1 { /**
* zookeeper地址
*/
static final String CONNECT_ADDR = "192.168.1.211:2181,192.168.1.212:2181,192.168.1.213:2181";
/**
* session超时时间
*/
static final int SESSION_OUTTIME = 5000;//ms public static void main(String[] args) throws Exception { //1 重试策略:初试时间为1s 重试10次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通过工厂创建连接
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
.build(); //3 建立连接
cf.start(); //4 建立一个cache缓存
final NodeCache cache = new NodeCache(cf, "/super", false);
cache.start(true);
cache.getListenable().addListener(new NodeCacheListener() {
/**
* <B>方法名称:</B>nodeChanged<BR>
* <B>概要说明:</B>触发事件为创建节点和更新节点,在删除节点的时候并不触发此操作。<BR>
* @see org.apache.curator.framework.recipes.cache.NodeCacheListener#nodeChanged()
*/
@Override
public void nodeChanged() throws Exception {
System.out.println("路径为:" + cache.getCurrentData().getPath());
System.out.println("数据为:" + new String(cache.getCurrentData().getData()));
System.out.println("状态为:" + cache.getCurrentData().getStat());
System.out.println("---------------------------------------");
}
});
Thread.sleep(1000);
cf.create().forPath("/super", "123".getBytes());
Thread.sleep(1000);
cf.setData().forPath("/super", "456".getBytes()); Thread.sleep(1000);
cf.delete().forPath("/super");
Thread.sleep(1000);
System.out.println("按下任何键表示关闭...");
System.in.read(); CloseableUtils.closeQuietly(cache);
CloseableUtils.closeQuietly(cf);
}
}

监控子节点的数据变化

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
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.curator.utils.CloseableUtils; public class CuratorWatcher2 { /** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.1.211:2181,192.168.1.212:2181,192.168.1.213:2181";
/** session超时时间 */
static final int SESSION_OUTTIME = 5000;//ms public static void main(String[] args) throws Exception { //1 重试策略:初试时间为1s 重试10次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通过工厂创建连接
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
.build(); //3 建立连接
cf.start(); //4 建立一个PathChildrenCache缓存,第三个参数为是否接受节点数据内容 如果为false则不接受
PathChildrenCache cache = new PathChildrenCache(cf, "/super", true);
//5 在初始化的时候就进行缓存监听
cache.start(StartMode.POST_INITIALIZED_EVENT);
cache.getListenable().addListener(new PathChildrenCacheListener() {
/**
* <B>方法名称:</B>监听子节点变更<BR>
* <B>概要说明:</B>新建、修改、删除<BR>
* @see org.apache.curator.framework.recipes.cache.PathChildrenCacheListener#childEvent(org.apache.curator.framework.CuratorFramework, org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent)
*/
@Override
public void childEvent(CuratorFramework cf, PathChildrenCacheEvent event) throws Exception {
switch (event.getType()) {
case CHILD_ADDED:
System.out.println("CHILD_ADDED :" + event.getData().getPath());
break;
case CHILD_UPDATED:
System.out.println("CHILD_UPDATED :" + event.getData().getPath());
break;
case CHILD_REMOVED:
System.out.println("CHILD_REMOVED :" + event.getData().getPath());
break;
default:
break;
}
}
}); //创建本身节点不发生变化
cf.create().forPath("/super", "init".getBytes()); //添加子节点
Thread.sleep(1000);
cf.create().forPath("/super/c1", "c1内容".getBytes());
Thread.sleep(1000);
cf.create().forPath("/super/c2", "c2内容".getBytes()); //修改子节点
Thread.sleep(1000);
cf.setData().forPath("/super/c1", "c1更新内容".getBytes()); //删除子节点
Thread.sleep(1000);
cf.delete().forPath("/super/c2"); //删除本身节点
Thread.sleep(1000);
cf.delete().deletingChildrenIfNeeded().forPath("/super"); System.out.println("按下任何键表示关闭...");
System.in.read(); CloseableUtils.closeQuietly(cache);
CloseableUtils.closeQuietly(cf); }
}

四、Curator Recipe用法

以上只是介绍了curator基础用法,在curator的扩展库中,还提供了非常丰富的用法,这里暂时不做详述。

或者直接去官网直接看源代码:https://git-wip-us.apache.org/repos/asf?p=curator.git;a=tree;f=curator-examples/src/main/java;hb=HEAD

leader选举

服务发现

分布式barrier

分布式队列

分布式计数器

分布式锁

五、路由和负载均衡的实现

现在很多服务治理的方案都是netty+zk,由netty提供自定义协议的rpc服务,由zk去做服务的注册和自动发现,curator自带的discovery模块可以直接被用做服务发现,这里由手动实现作为一个zk的小demo。

首先看zk服务配置中心的node图:

服务提供者在启动时,以节点的形式注册到服务配置中心,服务消费者通过服务配置中来获得需要调用的服务名称节点下的机器列表节点。通过负载均衡算法,选取其中一台服务器进行调用。
当服务器宕机或者下线时,由于znode非持久节点的特性,相应的机器可以动态地从服务配置中心中移除,并触发服务消费者的Watcher。
在这个过程中,服务消费者只有在第一次调用服务的时候查询服务配置中心,然后将查询到的服务信息缓存到本地,后面的调用直接使用本地服务地址列表信息,而不需要重新发起请求去服务配置中心去获取相应的服务地址列表,直到服务的地址列表有变更,变更行为会直接触发消费者的Watcher。这样做,对配置中心的压力也很小,只有修改操作才会触发写,大多数读都是读本地内存。

代码如下:

ServiceInfo简单模拟下服务信息,可以没有

public class ServiceInfo {
private String serviceName;
private List<String> methods;
public ServiceInfo() {
} public ServiceInfo(String serviceName, List<String> methods) {
this.serviceName = serviceName;
this.methods = methods;
}
//get and set
}

Provider模板代码,并用ProviderA和ProviderB模拟2台机器上的不同服务

public class Provider {

    private ServiceInfo serviceInfo;
private CuratorFramework cf = null;
private String ip; public Provider(ServiceInfo serviceInfo, CuratorFramework cf, String ip) {
this.serviceInfo = serviceInfo;
this.cf = cf;
this.ip = ip;
} public void start() throws Exception {
//根节点路径
String path = "/configcenter/" + serviceInfo.getServiceName();
Stat stat = cf.checkExists().forPath(path);
if (stat == null) {
//不存在
cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path, FastJsonUtils.convertObjectToJSON(serviceInfo).getBytes());
}
InetAddress addr = InetAddress.getLocalHost();
String ip = addr.getHostAddress().toString();
System.out.println("获取本机ip:" + ip);
//但是为了测试,这里不使用本机ip,因为只有一台机器...
cf.create().withMode(CreateMode.EPHEMERAL).forPath(path + "/" + ip);
}
}
public class ProviderA {
public static final String CONNECT_ADDR = "192.168.1.211:2181,192.168.1.212:2181,192.168.1.213:2181";
public static final int SESSION_OUTTIME = 5000;//ms public static void main(String[] args) throws Exception {
//简单演示启动服务
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通过工厂创建连接
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
.build();
try {
cf.start();
//启动2个服务A和B
new Provider(
new ServiceInfo(
"service-A",
Lists.newArrayList("m1", "m2")
),
cf,
"192.168.1.101"
).start(); new Provider(
new ServiceInfo(
"service-B",
Lists.newArrayList("m3", "m4")
),
cf,
"192.168.1.101"
).start();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("输入任何字符串关闭服务器");
System.in.read();
CloseableUtils.closeQuietly(cf);
}
}
}
public class ProviderB {
public static final String CONNECT_ADDR = "192.168.1.211:2181,192.168.1.212:2181,192.168.1.213:2181";
public static final int SESSION_OUTTIME = 5000;//ms public static void main(String[] args) throws Exception {
//简单演示启动服务
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
//2 通过工厂创建连接
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
.build();
try {
cf.start();
//启动2个服务A和B
new Provider(
new ServiceInfo(
"service-B",
Lists.newArrayList("m1", "m2")
),
cf,
"192.168.1.102"
).start();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("输入任何字符串关闭服务器");
System.in.read();
CloseableUtils.closeQuietly(cf);
}
}
}

消费端代码:

public class Client {
private CuratorFramework cf = null;
//本地保存了数据信息
private Map<String, List<String>> data = Maps.newHashMap();
public static final String CONNECT_ADDR = "192.168.1.211:2181,192.168.1.212:2181,192.168.1.213:2181";
public static final int SESSION_OUTTIME = 5000;//ms public Client() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
this.cf = CuratorFrameworkFactory.builder()
.connectString(CONNECT_ADDR)
.sessionTimeoutMs(SESSION_OUTTIME)
.retryPolicy(retryPolicy)
.build();
this.cf.start();
} public static void main(String[] args) throws Exception {
Client client = new Client();
client.subscribe(new ServiceInfo( "service-A",
Lists.newArrayList("m1", "m2")
));
client.subscribe(new ServiceInfo( "service-B",
Lists.newArrayList("m3", "m4")
));
System.out.println("输入任何键关闭");
System.in.read();
CloseableUtils.closeQuietly(client.cf);
} public void subscribe(final ServiceInfo serviceInfo) throws Exception {
String serviceName = serviceInfo.getServiceName();
//订阅某一个服务
final String path = "/configcenter/" + serviceName;
System.out.println("path:" + path);
Stat stat = cf.checkExists().forPath(path);
if (stat == null) {
System.out.println(serviceName + "没有服务");
cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path, FastJsonUtils.convertObjectToJSON(serviceInfo).getBytes());
}
PathChildrenCache cache = new PathChildrenCache(cf, path, true);
//5 在初始化的时候就进行缓存监听
cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
cache.getListenable().addListener((CuratorFramework client, PathChildrenCacheEvent event) -> {
List<String> children = client.getChildren().forPath(path);
data.put(serviceName, children);
System.out.println(String.format("%s发生了改变:%s", serviceName, children.toString()));
});
} }

先启动client,再启动ProviderA和ProviderB,可以发现Client自动发现服务的行为。

zookeeper(2)-curator的更多相关文章

  1. Zookeeper客户端Curator使用详解

    Zookeeper客户端Curator使用详解 前提 最近刚好用到了zookeeper,做了一个基于SpringBoot.Curator.Bootstrap写了一个可视化的Web应用: zookeep ...

  2. zookeeper(六):Zookeeper客户端Curator的API使用详解

    简介 Curator是Netflix公司开源的一套zookeeper客户端框架,解决了很多Zookeeper客户端非常底层的细节开发工作,包括连接重连.反复注册Watcher和NodeExistsEx ...

  3. 转:Zookeeper客户端Curator使用详解

    原文:https://www.jianshu.com/p/70151fc0ef5d Zookeeper客户端Curator使用详解 前提 最近刚好用到了zookeeper,做了一个基于SpringBo ...

  4. ZooKeeper与Curator注册和监控

    Curator提供了对zookeeper客户端的封装,并监控连接状态和会话session,特别是会话session过期后,curator能够重新连接zookeeper,并且创建一个新的session. ...

  5. Zookeeper与Curator二三事【坑爹】

    起因:我的Dubbo服务起不来:我本地Zookeeper3.4.11,Curator4.1 Caused by: org.apache.zookeeper.KeeperException$Unimpl ...

  6. Zookeeper客户端Curator基本API

    在使用zookeper的时候一般不使用原生的API,Curator,解决了很多Zookeeper客户端非常底层的细节开发工作,包括连接重连.反复注册Watcher和NodeExistsExceptio ...

  7. Zookeeper客户端Curator的使用,简单高效

    Curator是Netflix公司开源的一个Zookeeper客户端,与Zookeeper提供的原生客户端相比,Curator的抽象层次更高,简化了Zookeeper客户端的开发量. 1.引入依赖: ...

  8. ZooKeeper和Curator相关经验总结

    一.关于ZooKeeper的watch用法,需要注意 详细说明如下: ZooKeeper Watches All of the read operations in ZooKeeper - getDa ...

  9. 7.5 zookeeper客户端curator的基本使用 + zkui

    使用zookeeper原生API实现一些复杂的东西比较麻烦.所以,出现了两款比较好的开源客户端,对zookeeper的原生API进行了包装:zkClient和curator.后者是Netflix出版的 ...

随机推荐

  1. c语言,字符串原地翻转

    实现字符串的原地翻转: #include<stdlib.h> #include<stdio.h> #include<assert.h> #define SWAP(a ...

  2. 读取xml并将节点保存到Excal

    using NPOI.HPSF; using NPOI.HSSF.UserModel; using NPOI.SS.UserModel; using System; using System.Coll ...

  3. Java反射——引言

    Java反射——引言 原文地址:http://tutorials.jenkov.com/java-reflection/index.html *By Jakob Jenkov Java的反射机制使得它 ...

  4. GitHub上搭建个人网站

    大致如下步骤: 1.注册Git账号 2.创建SSH keys 3.新建repository --- 4.设置网站 5.clone库到本地 6.提交.上传 7.预览 本教程默认你了解GitHub的基础之 ...

  5. 依赖注入(DI)和Ninject

    [ASP.NET MVC 小牛之路]04 - 依赖注入(DI)和Ninject 本文目录: 1.为什么需要依赖注入 2.什么是依赖注入 3.使用NuGet安装库 4.使用Ninject的一般步骤 5. ...

  6. enode框架step by step之消息队列的设计思路

    enode框架step by step之消息队列的设计思路 enode框架系列step by step文章系列索引: enode框架step by step之开篇 enode框架step by ste ...

  7. 模块化与MVC

    [javascript激增的思考02]模块化与MVC 前言 之前我们遇到了这么一个项目,也就是我们昨天提到的,有很多的小窗口的,昨天说的太抽象了,今天我们再来理一理什么是小窗口(后面点说下),当时由于 ...

  8. 史上最“脑残”的“抢火车票”程序(node.js版)

    [背景] 快过年了,我妈一个电话打过来叫我给他买火车票,我到12306一查,硬座和硬卧基本没有了,高铁又太贵. 最后只抢了3张无座票,但是我妈说能不能买有座位的啊,我说没有了啊,我妈:你过两天再帮我看 ...

  9. CSS3特性修改(自定义)浏览器默认滚动条

    前言:我们做前端时,会遇到一些需求,要求把默认浏览器的滚动条样式给改写了,诶.好好的改它干啥了,也带不来用户体验,就是好看点嘛!实现原理其实是用了伪元素,webkit的伪元素实现很强,可以把滚动条当成 ...

  10. for循环和while循环

    for循环和while循环 --道心 for循环 name1_list=['daoxin','wuxin','zhixin']for ele in name1_list: #找到"wuxin ...