Zookeeper使用场景
Zookeeper Java 客户端
项目构建
Zookeeper官方客户端没有和服务端代码分离,他们为同一个Jar文件,我们直接引入Zookeeper的maven依赖就可以了,这里版本要保持与服务端一直,不然会有很多很多兼容性的问题
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.8</version>
</dependency>
创建客户端实例
@Slf4j
public class ConfigCenter {
private static ZooKeeper zooKeeper=null;
private final static String SERVER_ADD="127.0.0.1:2181";
private final static int SESSION_TIMEOUT=10*60*60;
private final static CountDownLatch countDownLatch=new CountDownLatch(1);
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
zooKeeper=new ZooKeeper(SERVER_ADD, SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getType().equals(Event.EventType.None)&&
watchedEvent.getState().equals(Event.KeeperState.SyncConnected)){
log.info("connected");
countDownLatch.countDown();
}
}
});
countDownLatch.await();
}
zookeeper构造方法
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher) throws IOException {
this(connectString, sessionTimeout, watcher, false);
}
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, ZKClientConfig conf) throws IOException {
this(connectString, sessionTimeout, watcher, false, conf);
}
**加粗样式** public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, HostProvider aHostProvider) throws IOException {
this(connectString, sessionTimeout, watcher, canBeReadOnly, aHostProvider, (ZKClientConfig)null);
}
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, HostProvider aHostProvider, ZKClientConfig clientConfig) throws IOException {
//...省略无关代码
this.cnxn.start();
}
- connectString: Zookeeper服务端的地址,由IP:port组成;当有多台Zookeeper机器,则可以使用host1:port1,host2:port2,host3,port3来指定。
- 另外,也可以在connectString中设置客户端连接上ZooKeeper 后的根目录,方法是在host:port字符串之后添加上这个根目录,例如,host1:port1,host2:port2,host3:port3/zk-base,**这样就指定了该客户端连 接上ZooKeeper服务器之后,所有对ZooKeeper 的操作,都会基于这个根目录。**例如,客户端对/sub-node 的操作,最终创建 /zk-node/sub-node, 这个目录也叫Chroot,即客户端隔离命名空间。
- sessionTimeout:会话超时时间,超过一定时间没有发送心跳给服务端,服务端认为客户端失活,会话失效会;
- watcher:ZooKeeper允许 客户端在构造方法中传入一个接口 watcher (org.apache. zookeeper. Watcher)的实现类对象来作为默认的 Watcher事件通知处理器。当然,该参 数可以设置为null 以表明不需要设置默认的 Watcher处理器。
- canBeReadOnly:用于表示当前会员是否支持只读模式。默认情况下,zk集群中,一个机器如果和集群中过半以上机器失去网络连接,那么这个机器将不再处理客户端请求(包括读写请求)。但是在某些场景下,当Zookeeper服务器发生此类故障时,还是希望服务器能够提供读服务(写无法提供)
创建结点
public void createPZonde() throws KeeperException, InterruptedException {
String path = zooKeeper.create(ZK_NODE, "pdata".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, PERSISTENT);
log.info(path);
}
其他的类型结点:指定最后一个参数即可:
public enum CreateMode {
PERSISTENT(0, false, false, false, false),
PERSISTENT_SEQUENTIAL(2, false, true, false, false),
EPHEMERAL(1, true, false, false, false),
EPHEMERAL_SEQUENTIAL(3, true, true, false, false),
CONTAINER(4, false, false, true, false),
PERSISTENT_WITH_TTL(5, false, false, false, true),
PERSISTENT_SEQUENTIAL_WITH_TTL(6, false, true, false, true);
}
修改节点
public static void setPZnode() throws KeeperException, InterruptedException {
Stat stat = new Stat();
byte[] data = zooKeeper.getData(ZK_NODE, false, stat);
log.info("修改前: {}",new String(data));
zooKeeper.setData(ZK_NODE,"changed".getBytes(),stat.getVersion());
byte[] newData=zooKeeper.getData(ZK_NODE,false,stat);
log.info("修改前: {}",new String(newData));
}
删除结点
private static void deleteNode() throws KeeperException, InterruptedException {
Stat stat = new Stat();
byte[] data = zooKeeper.getData(ZK_NODE, false, stat);
log.info("删除前: {}",new String(data));
zooKeeper.delete(ZK_NODE,stat.getVersion());
}
确实不存在zknode2结点。
Curator
Curator 是 ZooKeeper 客户端框架,Curator项目是现在ZooKeeper 客户端中使用最多,对ZooKeeper 版本支持最好的第三方客户端,并推荐使用,Curator 把我们平时常用的很多 ZooKeeper 服务开发功能做了封装,例如 Leader 选举、 分布式计数器、分布式锁。这就减少了技术人员在使用 ZooKeeper 时的大部分底层细节开发工 作。在会话重新连接、Watch 反复注册、多种异常处理等使用场景中,用原生的 ZooKeeper 处理比较复杂。而在使用 Curator 时,由于其对这些功能都做了高度的封装,使用起来更加简单,不但减少了开发时间,而且增强了程序的可靠性。
Curator实战
引入Curator框架依赖,一个是curator-framework,封装了一些底层API,另一个是curator-recipes包,该包封装了一些 ZooKeeper 服务的高级特性,如: Cache 事件监听、选举、分布式锁、分布式 Barrier。
<!-- -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.0.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>5.0.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.8</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.3</version>
</dependency>
</dependencies>
会话创建
要进行客户端服务器交互,第一步就要创建会话 Curator 提供了多种方式创建会话,比如用静态工厂方式创建:
private static void createClient1() {
ExponentialBackoffRetry retry = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectAdd, retry);
client.start();
}
或者流式风格创建:
private static void createClient2() {
ExponentialBackoffRetry retry = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder().connectString(connectAdd).canBeReadOnly(false)
.retryPolicy(retry).sessionTimeoutMs(5000).namespace("base").connectionTimeoutMs(5000).build();
client.start();
}
参数如下:
-
connectString
服务器地址列表,在指定服务器地址列表的时候可以是一个地址,也可以 是多个地址。如果是多个地址,那么每个服务器地址列表用逗号分隔, 如 host1:port1,host2:port2,host3;port3 。 -
canBeReadOnly:只支持只读模式
-
sessionTimeoutMs:会话超时时间,作用在客户端
-
connectionTimeoutMs:客户端创建会话的超时时间,用来限制客户端发起一个会话连接到接收 ZooKeeper 服务端应答的时间。
-
namespace:创建客户端时创建一个只属于自己的目录,这里是base;
-
retryPolicy:重试策略,当客户端异常退出或者与服务端失去连接的时候,可以通过设置客户端重新连接Zookeeper服务端,而Curator提供了一次重试,多次重试等不同种类的实现方式。在Curator内部,可以通过服务器返回的KeeperException的状态代码判断是否进行重试处理,如果返回的是OK,表示一切操作都没有问题,而SYSTEMERROR表示系统或者服务端错误;
策略名称 | 描述 |
---|---|
ExponentialBackoffRetry | 重试一组次数,重试之间的睡眠时间增加 |
RetryNTimes | 重试最大次数 |
RetryOneTime | 重试一次 |
RetryUntilElapsed | 在给定的时间结束之前重试 |
创建节点
private static void createNode() throws Exception {
String p = client.create().withMode(CreateMode.EPHEMERAL).forPath("/curator-node","curator-data".getBytes());
log.info("curator create node :{} successfully.",p);
}
使用 create 函数创建数据节点,并通过 withMode 函数指定节点类型 (持久化节点,临时节点,顺序节点,临时顺序节点,持久化顺序节点等),默认是持久化节 点,之后调用 forPath 函数来指定节点的路径和数据信息。
一次性创建带层级结构的节点
private static void createWithParent() throws Exception {
String pathWithP="/node-parent/sub1";
String path = client.create().creatingParentsIfNeeded().forPath(pathWithP);
log.info("curator create node :{} successfully.",path);
}
获取数据
public static Object getData() throws Exception {
byte[] bytes = client.getData().forPath("/curator-node");
System.out.println("------------------------------------");
System.out.println(new String(bytes));
return bytes;
}
更新节点
private static void udpateData() throws Exception {
Stat path = client.setData().forPath("/curator-node", "changed".getBytes());
byte[] bytes = client.getData().forPath("/curator-node");
log.info("get data from node /curator‐node :{} successfully.",new String(bytes));
}
删除节点
private static void deleteNode() throws Exception {
client.delete().guaranteed().deletingChildrenIfNeeded().forPath("/curator-node");
System.out.println("delete ok");
}
- guaranteed:该函数的功能如字面意思一样,主要起到一个保障删除成功的作用,其底层工作 方式是:只要该客户端的会话有效,就会在后台持续发起删除请求,直到该数据节点在 ZooKeeper 服务端被删除。
- deletingChildrenIfNeeded:指定了该函数后,系统在删除该数据节点的时候会以递归的方式 直接删除其子节点,以及子节点的子节点
异步处理
public interface BackgroundCallback {
void processResult(CuratorFramework var1, CuratorEvent var2) throws Exception;
}
- 用来处理服务器端返回来的信息,这个处理过程是 在异步线程中调用,默认在 EventThread 中调用,也可以自定义线程池
- 主要参数为 client 客户端, 和 服务端事件 event
- inBackground 异步处理默认在EventThread中执行
private static void callback() throws Exception {
byte[] bytes = client.getData().inBackground((item1, item2) -> {
log.info(" *********************************************background: {}", item2);
}).forPath("/curator-node");
}
指定线程池
private static void callbackWithExecutor() throws Exception {
ExecutorService service = Executors.newFixedThreadPool(2);
client.getData().inBackground((item1,item2)->{
log.info(" *********************************************background: {}", item2);
},service).forPath("/curator-node");
}
Curator监听器
public interface CuratorListener {
void eventReceived(CuratorFramework var1, CuratorEvent var2) throws Exception;
}
针对 background 通知和错误通知。使用此监听器之后,调用inBackground 方法会异步获得 监听
Curator Caches
Curator 引入了 Cache 来实现对 Zookeeper 服务端事件监听,Cache 事件监听可以理解为一 个本地缓存视图与远程 Zookeeper 视图的对比过程。Cache 提供了反复注册的功能。Cache 分 为两类注册类型:节点监听和子节点监听。
private static void addListener() throws Exception {
NodeCache cache = new NodeCache(client, CuratorTest.nodeCache);
cache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
log.info("{} path nodeChanged: ",nodeCache);
printNodeData();
}
});
cache.start();
}
path cache
PathChildrenCache 会对子节点进行监听,但是不会对二级子节点进行监听.
可以通过注册监听器来实现,对当前节点的子节点数据变化的处理
private static void addPathListener() throws Exception {
PathChildrenCache pathChildrenCache=new PathChildrenCache(client,PATH,true);
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
log.info("event: {}",pathChildrenCacheEvent);
}
});
pathChildrenCache.start();
}
Zookeeper集群模式安装
Linux下的集群搭建
本例搭建的是伪集群模式,即一台机器上启动四个zookeeper实例组成集群,真正的集群模式无 非就是实例IP地址不同,搭建方法没有区别
- 配置JAVA环境,检验环境:保证是jdk7 及以上即可
java ‐version
- 下载并解压zookeeper
wget https://mirror.bit.edu.cn/apache/zookeeper/zookeeper‐3.5.8/apache‐zookeep er‐3.5.8‐bin.tar.gz
tar ‐zxvf apache‐zookeeper‐3.5.8‐bin.tar.gz
cd apache‐zookeeper‐3.5.8‐bin
- 重命名zoo_sample.cfg文件
cp conf/zoo_sample.cfg conf/zoo‐1.cfg
- 修改配置文件zoo-1.cfg,原配置文件里有的,修改成下面的值,没有的则加上
# The number of milliseconds of each tick
# 用于配置Zookeeper中最小时间单位的长度,很多运行时的时间间隔都是 使用#tickTime的倍数来表示的。
tickTime=2000
# 该参数用于配置Leader服务器等待Follower启动,并完成数据同步的时间。
#Follower服务器再启动过程中,会与Leader建立连接并完成数据的同步,从而确定自己对外提供服务的起始状态。
#Leader服务器允许Follower再initLimit 时间内完成这个工 作
initLimit=10
# The number of ticks thatcan pass between
# sending a request and getting an acknowledgement
#Leader 与Follower心跳检测的最大延时时间
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=E:/java_springboot/Java_Zookeeper/data-1
# the port at which the clients will connect
clientPort=2181
server.1=127.0.0.1:2001:3001:participant
server.2=127.0.0.1:2002:3002:participant
server.3=127.0.0.1:2003:3003:participant
server.4=127.0.0.1:2004:3004:observer
- server.A=B:C:D:E
A是一个数字,表示第几号服务器,B是IP地址,C是服务器与leader交换信息的端口,D是leader挂了,需要一个端口来重新进行选举,选出一个新的leader,这个端口就是用来执行选举时服务器相互通信的端口。
如果是伪集群的配 置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给 它们分配不同的端口号。如果需要通过添加不参与集群选举以及事务请求的过半机制的 Observer节点,可以在E的位置,添加observer标识
- 再从zoo-1.cfg复制三个配置文件zoo-2.cfg,zoo-3.cfg和zoo-4.cfg,只需修改 dataDir和clientPort不同即可
cp conf/zoo1.cfg conf/zoo2.cfg
cp conf/zoo1.cfg conf/zoo3.cfg
cp conf/zoo1.cfg conf/zoo4.cfg
vim conf/zoo2.cfg
dataDir=/usr/local/data/zookeeper2
clientPort=2182
vim conf/zoo3.cfg
dataDir=/usr/local/data/zookeeper3
clientPort=2183
vim conf/zoo4.cfg
dataDir=/usr/local/data/zookeeper4
clientPort=2184
- 标识Server ID 创建四个文件夹/usr/local/data/zookeeper-1,/usr/local/data/zookeeper- 2,/usr/local/data/zookeeper-3,/usr/local/data/zookeeper-4,在每个目录中创建文件 myid 文件,写入当前实例的server id,即1,2,3,4
cd /usr/local/data/zookeeper‐1
vim myid
1
cd /usr/local/data/zookeeper‐2
vim myid
2
cd /usr/local/data/zookeeper‐3
vim myid
3
cd /usr/local/data/zookeeper‐4
vim myid
4
- 启动三个zookeeper实例
bin/zkServer.sh start conf/zoo1.cfg
bin/zkServer.sh start conf/zoo2.cfg
bin/zkServer.sh start conf/zoo3.cfg
- 检测集群状态,也可以直接用命令 zkServer.sh status conf/zoo1.cfg 进行每台服务 的状态查询
Window环境下的搭建
请看这篇文章,传递门,我亲手搭建成功了。
运行客户端:
连接得上,通过config查看配置。