ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
对于zookeeper的安装,大家可以先按照zookeeper官网上的介绍进行安装,因为直接在网上找到的安装步骤可能会存在问题,所以大家应该养成一个良好的习惯,尽量在官方网站获取最权威的介绍与知识,就向我们都源码是一样的。官方的价值是最高的。
重点zookeeper是一个开源的进行协调服务的中间件,高性能,分布式。它能做什么呢?数据存储、服务注册与发现、集群管理、分布式锁等。而且zookeeper的部署支持单机和集群,具有一定的灵活性。
zookeeper的底层数据结构是树形结构,每一个节点成为数据节点,注意,节点存储的是数据,而不是文件或者其他。数据节点成为znode,它是zookeeper结构的最小组成单元。
创建节点的命令是:
-s : 有序节点 无须节点
-e : 临时节点 持久节点
create -s -e path data acl
节点的特性有两种:一个是临时性节点,它的生命周期与当前该节点所属的会话相关联,在会话结束后的一段时间后,临时性节点会自动删除。这里注意的一点是为什么是会话结束一段时间之后才删除节点呢?是因为设计者在设计的时候考虑的网络抖动的问题,这可能不是人为的结束会话,而是由于网络的故障或者其他的故障,当值当前的会话被迫结束,如果立即删除调zookeeper中存储的节点数据,显然,这不是我们想要的,所以,在设计的时候,考虑到网络心跳的问题,在会话结束的一段时间后,数据节点才会通过某些算法执行自动删除。在临时行节点的数据中有一个属性字段是EphemeralOwner,它存储的是该节点所属会话的会话id,是一个唯一的值。另一个是持久化节点,它在创建之后,不会随着会话的声明周期而影响自己的生命周期。当创建节点时,不写 -s -e 参数的时候,默认创建的是持久化节点。
另外需要注意的是,只有持久化节点才可以创建子节点,临时节点是不可以创建子节点的。zookeeper的树形结构在深度上是无限制的,在广度上一般也没有限制。节点名称在同一级目录下必须唯一。
通过get命令,我们可以获取到该节点的数据信息。
下面看一下这些属性都分别表示什么:
cZxid : 表示创建事务id
ctime :表是常见时间
mzxid : 表示修改事务时间
pzxid : 只有子节点列表变更才会更新
cversion : 与乐观锁相关 任何客户端对数据库字段修改之后 对应的字段递增
下面通过程序来实现一下结构的构建与服务的调用:
目前通过java api 连接zookeeper服务端的方式有两种 : zkClient 和 curator 我们采用第一种,代码如下:
服务提供者 A 与 B 代码相同 :
package com.jd.zk;
import com.sun.xml.internal.ws.resources.ProviderApiMessages;
import org.I0Itec.zkclient.ZkClient;
import java.io.IOException;
public class ProviderA {
private String serviceName = "serviceA";
private final String ROOT = "/configcenter";
public void init(){
// 产生连接 判断根节点是否存在 不存在则创建
String zkServer = "192.168.11.142:2181";
ZkClient zkClient = new ZkClient(zkServer);
if(!zkClient.exists(ROOT)){
zkClient.createPersistent(ROOT);
}
// 启动服务 判断当前服务节点是否注册过
if(!zkClient.exists(ROOT + "/" + serviceName)){
zkClient.createPersistent(ROOT + "/" + serviceName); // 注意是全路径
}
String ip = "192.128.11.130:8080";
zkClient.createEphemeral(ROOT + "/" + serviceName + "/" + ip);
System.out.println("providerA服务启动成功");
}
public static void main(String[] args) throws IOException {
ProviderA providerA = new ProviderA();
providerA.init();
System.in.read();
}
}
消费者调用服务:
package com.jd.zk;
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.ZkClient;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 消费者 调用配置中心的地址 来访问生产者
* 从根节点开始读取
*
*/
public class Consumer {
private List<String> serverList = new ArrayList<String>();
private String serviceName = "serviceA";
public void init() throws Exception {
String zkServer = "192.168.11.142:2181";
ZkClient zkClient = new ZkClient(zkServer);
String servicePath = "/configcenter/" + serviceName;
boolean isExist = zkClient.exists(servicePath);
if(isExist){
serverList = zkClient.getChildren(servicePath);
}else{
throw new Exception("Errow");
}
// 实现服务注册监听发现
zkClient.subscribeChildChanges(servicePath, new IZkChildListener() {
public void handleChildChange(String s, List<String> list) throws Exception {
System.out.println("服务节点发生变化,节点的信息 : " + list);
serverList = list;
}
});
}
// 实现负载均衡与监听 只不过这里采取的是随机负载
public void consumer(){
Random random = new Random();
int i = random.nextInt(serverList.size());
System.out.println(i);
System.out.println("调用 :" + serverList.get(i) + "提供服务" );
}
public static void main(String[] args) throws Exception {
Consumer consumer = new Consumer();
consumer.init();
consumer.consumer();
System.in.read();
}
}
先启动providerA 和 providerB ,然后执行Consumer 效果如下: