1.概述:
zookeeper提供了中心化的服务:统一配置信息,命名,提供分布式锁,提供组服务。
如下图所示,hive和flume都是基于Hadoop的,这些框架都到zookeeper中进行注册,当有其中一个框架的信息发生改变,就可以使用zookeeper来进行通知,告诉其他框架。
2.zookeeper安装的三种模式:
- 单机模式:只在一台服务器上安装,只能启动这个框架的部分功能
- 伪分布式:只在一台服务器上安装,能提供这个框架的大部分甚至全部功能
- 完全分布式:在集群中安装,能提供这个框架的所有功能
Linux单机安装方法:
1.进入Linux中,把目录切换到/home/software
cd /home/software
2.下载Zookeeper的安装包,直接再software目录下复制下载地址:
wget http://bj-yzjd.ufile.cn-north-02.ucloud.cn/zookeeper-3.4.8.tar.gz
3.解压安装包
tar -xvf zookeeper-3.4.8.tar.gz
4.进入解压好的zookeeper文件夹中的conf文件
cd zookeeper-3.4.8/conf
5.复制conf中的zoo_sample.cfg,启动zookeeper的时候,会来加载这个配置文件,而zoo_sample.cfg是zookeeper给我们准备好的案例配置模板,我们需要做的就是复制出来一份
cp zoo_sample.cfg zoo.cfg
6.编辑zoo.cfg并且把dataDir修改成我们指定的路径,dataDir中的路径放的是快照,默认/tmp,如果不自己指定,tmp中的文件会自动清除。
vim zoo.cfg
dataDir=/home/software/zookeeper-3.4.8/tmp
7.进入Zookeeper安装目录的子目录bin下
cd ../bin
8.启动Zookeeper ,在bin下产生启动日志zookeeper.out,如果启动失败,那么请查看zookeeper.out 通过jps查看,如果出现QuorumPeerMain表示Zookeeper启动了 或者通过sh zkServer.sh status来查看,在单机模式下如果出现standalone表示成功
sh zkServer.sh start
9.启动客户端
sh zkCli.sh
3.zookeeper的结构:
zookeeper中信息的储存是以节点的形式。
比如下图中,zookeeper中挂载了一个Hadoop服务器,一个Log日志服务器,一个video视频服务器。而这些挂载的服务器下面又可以挂载其他的服务器。
关于zookeeper节点你需要知道的一些特点:
- Zookeeper本身是一个树状结构,根节点是/
- 每一个节点都称之为是一个Znode节点 - Znode树
- 每一个节点必须携带数据,这个数据一般是对节点的描述或者是配置信息
- 在Zookeeper中,不存在相对路径,所有的节点必须从根路径开始计算
- Zookeeper会将Znode树维系在内存以及磁盘中
a.维系在内存中的目的是为了查询快
b.维系在磁盘中的目的是为了可靠 - 数据在磁盘上的存储位置dataDir来决定
- 在Zookeeper中,会将每一个写操作(connect,create,set,delete,rmr等)看作是一个事务。Zookeepe会给事务分配一个全局递增的编号,这个编号称之为事务id,简写为Zxid,事务id在计算的时候,只需要关心是整个过程中的第几个写操作即可
- 临时节点不能挂载子节点,持久节点可以挂载子节点
- 在Zookeeper中不允许存在同名节点
zookeeper中的命令:
节点种类:
零时节点: zookeeper关闭后节点消失,创建的时候加-e
持久节点: zookeeper关闭后节点不消失,创建的时候什么都不加
顺序节点: 节点会根据当前已存在的节点数自动加 1
非顺序节点: 没有顺序值
根据上面四种可以组合为:零时顺序,零时非顺序,持久顺序,持久分顺序
zookeeper中的节点信息:
解释事务id变化规律:
每一个写操作(创建,修改等),都会被分配一个全局递增的事务IP,在创建了一个znode节点的时候,cz,mz,pz的值都是一样的。
首先是根节点/,在创建的时候,都是0。
其次是创建一个节点/a,根目录的事务id变化很容易理解就不解释,我们看/a的事务id变化,首先是节点创建的事务id为1,又因为在创建了一个znode节点的时候,cz,mz,pz的值都是一样的。所以另外两个也是1.
我们再来看创建节点/b,/中的pzxid变为了2,因为这时/下的第二个创建节点,子节点发生了变化,所以是2。/a没有和上面一样。/b是第二个创建的节点,所以czxid为2,又因为在创建了一个znode节点的时候,cz,mz,pz的值都是一样的。所以另外两个也是2.
最后来看再a节点下创建子节点a1,先看/的pzxid,因为/下的子节点个数没有变化所以还是2。/a的pzxid,a下创建了a1,是全局的第三个事务,所以是3,a下子节点第一次变,所以是1
java中使用zookeeper:
1.注入依赖:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.8</version>
</dependency>
</dependencies>
2.连接zookeeper:
private ZooKeeper zk;
// 连接Zookeeper
@Before
public void connect() throws IOException, InterruptedException {
// @Test和main起的作用是一致的
// main所在的类是一个线程类 - 主线程
// Zookeeper底层连接过程使用的是Netty连接
// Netty:异步非阻塞连接
// 非阻塞:无论连上连不上都会继续向下执行
// 异步:当前连接和监控线程与主线程之间是抢占的
// 连接可能还没连完或者还没有来得及监控,主线程可以提前抢占执行
// 需要等连接连接且监控到了,主线程才能继续执行
CountDownLatch cdl = new CountDownLatch(1);
zk = new ZooKeeper(
"10.42.130.141:2181", // 连接地址+端口号,请写自己的IP
5000, // 会话超时时间,现阶段这个值必须在4000~4000之间,单位是毫秒
new Watcher() {
// 监控连接事件
@Override
public void process(WatchedEvent event) {
if (event.getState() == Event.KeeperState.SyncConnected)
System.out.println("连接已经建立~~~");
cdl.countDown();
}
}
);
cdl.await();
System.out.println("finish");
}
3.创建节点:
@Test
public void createNode() throws KeeperException, InterruptedException {
// 返回值表示节点名
String path = zk.create("/log", "log servers".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(path);
}
4.其他操作:
// 修改数据
@Test
public void setData() throws KeeperException, InterruptedException {
// 第三个参数version对应的dataVersion
// 在修改数据的时候,先比较version是否一致,如果不一致则直接报错
// 如果version为-1,那么表示忽略版本校验强制修改
// 返回值表示节点信息
Stat stat = zk.setData("/log", "log server".getBytes(), -1);
System.out.println(stat);
}
// 获取数据
@Test
public void getData() throws KeeperException, InterruptedException {
// 如果需要节点信息,那么可以传入一个Stat对象
// Stat s = new Stat();
// byte[] data = zk.getData("/log", null, s);
// System.out.println(s);
// 如果不需要节点信息,那么可以传入null
byte[] data = zk.getData("/log", null, null);
System.out.println(new String(data));
}
// 删除节点
@Test
public void deleteNode() throws KeeperException, InterruptedException {
// 在删除的时候也会进行版本校验
//要求节点下没有子节点
zk.delete("/log", -1);
}
// 获取子节点
@Test
public void getChildren() throws KeeperException, InterruptedException {
// 将子节点的名称放到一个List中返回
List<String> cs = zk.getChildren("/", null);
for (String c : cs) {
System.out.println(c);
}
}
// 判断节点是否存在
@Test
public void exist() throws KeeperException, InterruptedException {
// 如果节点存在,则返回一个Stat对象
// 如果节点不存在,则返回null
Stat s = zk.exists("/log", null);
System.out.println(s == null);
}
// 监控节点数据是否发生改变
@Test
public void dataChanged() throws KeeperException, InterruptedException {
CountDownLatch cdl = new CountDownLatch(1);
zk.getData("/log", new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeDataChanged)
// 实际开发中,这儿应该产生日志:IP,时间,数据等
System.out.println("节点数据被改变");
cdl.countDown();
}
}, null);
cdl.await();
}
// 监控子节点个数是否发生变化
@Test
public void childChanged() throws KeeperException, InterruptedException {
CountDownLatch cdl = new CountDownLatch(1);
zk.getChildren("/log", new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeChildrenChanged)
System.out.println("子节点个数发生变化");
cdl.countDown();
}
});
cdl.await();
}
// 监控节点的创建或者删除
@Test
public void nodeChanged() throws KeeperException, InterruptedException {
CountDownLatch cdl = new CountDownLatch(1);
zk.exists("/music", new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeCreated)
System.out.println("节点被创建");
else if (event.getType() == Event.EventType.NodeDeleted)
System.out.println("节点被删除");
cdl.countDown();
}
});
cdl.await();
}