Zookeeper
1. Zookeeper 简介
- Zookeeper 是 Apache Hadoop 项目下的一个子项目,是一个树形目录服务
- Zookeeper 是一个分布式的、开源的分布式应用程序的协调服务
- Zookeeper 提供的主要功能包括
- 配置中心:Zookeeper可以存储、管理和跟踪系统配置,从而简化了系统管理。
- 同步:Zookeeper可以实现分布式系统中的数据一致性,从而使分布式系统更加可靠。
- 命名服务:Zookeeper可以提供一致的、可靠的、分布式的命名服务,从而简化了分布式系统的管理。
- 集群管理:Zookeeper可以实现集群管理,从而简化了集群管理的复杂性。
- 共享锁定:Zookeeper可以实现共享锁定,从而简化了分布式系统中的数据同步。
2. Zookeeper 安装
- 下载后进行解压
tar zxvf apache-zookeeper-x.x.x-bin.tar.gz
- 进入安装目录中的 conf 目录,处理配置文件
cp zoo_sample.cfg zoo.cfg
vi zoo.cfg
- 启动 查看 重启 关闭 zookeeper 所在目录~/apache-zookeeper-3.7.0-bin/bin
sudo ./zkServer.sh start # 开启服务
sudo bash zkServer.sh start
sudo ./zkServer.sh status # 查看状态
sudo ./zkServer.sh restart # 重启服务
sudo ./zkServer.sh stop # 关闭服务
- 查看服务器进程
ps -ef | grep zookeeper
3. Zookeeper 操作指令
3.1 数据模型
- ZooKeeper 是一个树形目录服务,其数据模型和 Unix 的文件系统目录树很类似,拥有一个层次化结构
- 每一个节点都被称为: ZNode,每个节点上都会保存自己的数据和节点信息
- 节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下
- 节点可以分为四大类
- 持久化节点
- 持久化顺序节点
-s
- 临时节点
-e
- 临时顺序节点
-es
3.2 客户端操作指令
命令 | 说明 |
---|---|
./zkCli.sh -server localhost:2181 | 通过客户端连接服务器 |
quit | 退出客户端 |
create <节点名> [值] | 创建节点 |
get <节点名> | 获取节点中的值 |
set <节点名> <值> | 修改节点中的值 |
delete <空节点名> | 删除空节点 |
deleteall <节点名> | 删除任意节点(非空也可以) |
ls <节点名> | 查看节点中包含的所有子节点 |
help | 帮助命令 |
4 - Zookeeper JavaAPI
4.1 - Curator API
Curator 是 Apache ZooKeeper 的 Java 客户端 API
4.2 - Curator 的常用操作
-
pom.xml
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.33</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.33</version> </dependency> </dependencies>
-
log4j.properties
log4j.rootLogger=off,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
- Test.java
package com.itxw;
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.framework.recipes.cache.*;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
/**
* @Classname Test
* @Description: TODO
* @Author: User
*/
public class Test {
CuratorFramework client;
//连接ZooKeeper
@Before
public void curatorConnect() {
//定义重试的策略
ExponentialBackoffRetry retry = new ExponentialBackoffRetry(5000, 10);
//第一种连接方式:通过 newClient
// CuratorFrameworkFactory.newClient("127.0.0.1:2181",retry);
//第二种连接方式
//获取连接对象
client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181") // 连接Ip
.retryPolicy(retry) // 重试策略
.namespace("itxw") //当前连接的命名空间
.build(); //构建对象
//********所有的path都是以命名空间开始********
// 启动客户端并且建立连接
client.start();
}
@After
//断开ZooKeeper连接
public void curatorClose() {
client.close();
}
//=============================添加节点================================
@Test
//创建一个空的结点
public void createTest01() throws Exception {
// 在创建空节点的时候,里面的值实际上不是空的,Curator 会为节点填充网络的 IP 地址
String path = client.create().forPath("/lesson");
System.out.println("成功创建了:" + path);
}
@Test
//创建一个带值的结点
public void createTest02() throws Exception {
String path = client.create().forPath("/search", "ZooKeeper".getBytes());
System.out.println("成功创建了:" + path);
}
@Test
//创建一个临时的结点
public void createTest03() throws Exception {
String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/lesson", "ZooKeeper".getBytes());
System.out.println("成功创建了:" + path);
while (true) ;//加入一个循环,不会执行到Close,可以查看到创建到的内容
}
@Test
// 一次性创建多层节点
public void createTest04() throws Exception {
String path = client.create().creatingParentContainersIfNeeded().forPath("/lesson/java/thread", "ZooKeeper".getBytes());
System.out.println("成功创建了:" + path);
}
//=============================查询节点================================
@Test
//查询指定节点的值
public void getTest01() throws Exception {
byte[] info = client.getData().forPath("/search");
System.out.println("info = " + new String(info));
}
@Test
//查询子节点
public void getTest02() throws Exception {
List<String> pathList = client.getChildren().forPath("/");
for (String path : pathList) {
System.out.println(path);
}
}
@Test
//查询节点的状态
public void getTest03() throws Exception {
Stat stat = new Stat();
byte[] path = client.getData().storingStatIn(stat).forPath("/");
System.out.println("path = " + new String(path));
System.out.println("================================");
System.out.println(stat);
System.out.println(stat.getCzxid());
System.out.println(stat.getMzxid());
System.out.println(stat.getCtime());
System.out.println(stat.getMtime());
}
//=============================修改节点================================
@Test
//修改节点
public void setTest01() throws Exception {
Stat stat = client.setData().forPath("/lesson", "Curator".getBytes());
System.out.println("stat = " + stat);
System.out.println("================================");
byte[] bytes = client.getData().forPath("/lesson");
System.out.println("info = " + new String(bytes));
}
@Test
// 根据版本号 dataVersion 进行修改
public void setTest02() throws Exception {
Stat stat = new Stat();
//获取状态信息
client.getData().storingStatIn(stat).forPath("/lesson");
Stat stat1 = client.setData().withVersion(stat.getVersion()).forPath("/lesson", "CuratorAPI".getBytes());
System.out.println("stat = " + stat1);
System.out.println("================================");
byte[] bytes = client.getData().forPath("/lesson");
System.out.println("info = " + new String(bytes));
}
//=============================删除节点================================
@Test
//删除单个节点
public void delTest01() throws Exception {
client.delete().forPath("/lesson");
}
@Test
//删除带有子节点的节点
public void delTest02() throws Exception {
client.delete().deletingChildrenIfNeeded().forPath("/lesson");
}
@Test
//确保成功删除节点
public void delTest03() throws Exception {
client.delete().guaranteed().forPath("/lesson");
}
@Test
//删除动作之后,调用一个回调函数
public void delTest04() throws Exception {
client.delete().guaranteed().inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
System.out.println("删完了!");
System.out.println(curatorEvent.getPath() + "被删除了");
}
}).forPath("/search");
}
//=============================事件监听================================
@Test
// NodeCache:监听某一个指定的节点
public void nodeCacheTest() throws Exception {
// 创建 NodeCache 对象
final NodeCache nodeCache = new NodeCache(client, "/lesson");
// 注册监听器
nodeCache.getListenable().addListener(new NodeCacheListener() {
public void nodeChanged() throws Exception {
System.out.println("/lesson 中的内容发生变化了");
byte[] info = nodeCache.getCurrentData().getData();
System.out.println(new String(info));
}
});
// 开启监听
nodeCache.start(true); // true 表示开启监听的时候就加载缓存,false 则相反
// 保持运行
while (true);
}
@Test
//PathChildrenCache:监听一个 ZNode 的子节点
public void pathChildrenCacheTest() throws Exception {
// 创建 PathChildrenCache 对象
PathChildrenCache pathChildrenCache = new PathChildrenCache(client, "/lesson", true);
// 注册监听 这个监听器是PathChildrenCacheListener的一个实例
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
public void childEvent(CuratorFramework curatorFramework,
PathChildrenCacheEvent event) throws Exception {
if(event.getType() == PathChildrenCacheEvent.Type.CHILD_UPDATED){
System.out.println("子节点更新");
System.out.println("节点:"+event.getData().getPath());
System.out.println("数据" + new String(event.getData().getData()));
}else if(event.getType() == PathChildrenCacheEvent.Type.INITIALIZED ){
System.out.println("初始化操作");
}else if(event.getType() == PathChildrenCacheEvent.Type.CHILD_REMOVED ){
System.out.println("删除子节点");
System.out.println("节点:"+event.getData().getPath());
System.out.println("数据" + new String(event.getData().getData()));
}else if(event.getType() == PathChildrenCacheEvent.Type.CHILD_ADDED ){
System.out.println("添加子节点");
System.out.println("节点:"+event.getData().getPath());
System.out.println("数据" + new String(event.getData().getData()));
}else if(event.getType() == PathChildrenCacheEvent.Type.CONNECTION_SUSPENDED ){
System.out.println("连接失效");
}else if(event.getType() == PathChildrenCacheEvent.Type.CONNECTION_RECONNECTED ){
System.out.println("重新连接");
}else if(event.getType() == PathChildrenCacheEvent.Type.CONNECTION_LOST ){
System.out.println("连接失效后稍等一会儿执行");
}
}
});
// 开启监听
pathChildrenCache.start();
// 保持运行
System.in.read();
}
@Test
// TreeCache:监听整个树形结构的所有节点
public void treeCacheTest() throws Exception {
// 创建 TreeCache 对象
TreeCache treeCache = new TreeCache(client,"/");
// 注册监听
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
if(event.getType() == TreeCacheEvent.Type.NODE_ADDED){
System.out.println(event.getData().getPath() + "节点添加");
}else if (event.getType() == TreeCacheEvent.Type.NODE_REMOVED){
System.out.println(event.getData().getPath() + "节点移除");
}else if(event.getType() == TreeCacheEvent.Type.NODE_UPDATED){
System.out.println(event.getData().getPath() + "节点修改");
System.out.println(new String(event.getData().getData()));
}else if(event.getType() == TreeCacheEvent.Type.INITIALIZED){
System.out.println("初始化完成");
}else if(event.getType() ==TreeCacheEvent.Type.CONNECTION_SUSPENDED){
System.out.println("连接过时");
}else if(event.getType() ==TreeCacheEvent.Type.CONNECTION_RECONNECTED){
System.out.println("重新连接");
}else if(event.getType() ==TreeCacheEvent.Type.CONNECTION_LOST){
System.out.println("连接过时一段时间");
}
}
});
// 启动监听
treeCache.start();
// 阻塞
System.in.read();
}
}
分布式锁
跨设备的进程之间的数据同步锁
- 分布式锁的实现方式
- 基于缓存redis来实现(性能高,不可靠)
- 通过数据库实现(效率低,不推荐)
- Zookeeper 实现(相对平衡)
- Zookeeper 分布式锁原理
- 客户端获取锁时,在 lock 节点下创建 临时的顺序 节点(1.通过序号来取 2.临时的可以避免死锁的情况,比如浏览器界面卡了,如果采用持久的那么一直会不释放,出现死锁)
- 然后获取 lock 下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的子节点序号最小,就说明当前客户端获取到了锁,使用完成之后,释放锁就是删除这个节点。
- 如果发现自己创建的节点并非 lock 所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册监听器,监听删除事件。如果发现删除了。说明上一个锁已经释放了
- 当发现上一个释放了之后。再次判断自己是不是最小的。如果是我就是获取到了。如果不是重复以上步骤
- Curator 分布式锁相关 API
锁 | 说明 |
---|---|
InterProcessSemaphoreMutex | 分布式排它锁(非可重入锁) |
InterProcessMutex | 分布式可重入排它锁 |
InterProcessReadWriteLock | 分布式读写锁 |
InterProcessMultiLock | 将多个锁作为单个实体管理的容器 |
InterProcessSemaphoreV2 | 共享信号量 |
5 - Zookeeper 集群搭建
5.1 - 集群相关角色
- Leader 领导者:处理事务并同步到其他的 Follower
- Follower 跟随者:处理非事务请求,如果有事务请求将转发给 Leader,参与投票
- Observer 观察者:不参与投票
5.2 - 集群搭建步骤
-
修改配置文件
cp /home/itxw/apache-zookeeper-3.7.0-bin/conf/zoo_sample.cfg /home/itxw/apache-zookeeper-3.7.0-bin/conf/zoo.cfg
# 修改数据默认存放目录 dataDir=/home/itxw/zookeeper-cluster/zookeeper-1/data # 最底部添加集群配置 # server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口 server.1=192.168.1.88:29527:39527 server.2=192.168.1.88:29528:39528 server.3=192.168.1.88:29529:39529
-
执行统一配置脚本zookeeperCluster.sh
#!/bin/bash #create zookeeper-cluster # 创建集群配置目录 mkdir /home/itxw/zookeeper-cluster # 拷贝独立的 zookeeper 到集群中备用 cp -r /home/itxw/apache-zookeeper-3.7.0-bin /home/itxw/zookeeper-cluster/zookeeper-1 cp -r /home/itxw/apache-zookeeper-3.7.0-bin /home/itxw/zookeeper-cluster/zookeeper-2 cp -r /home/itxw/apache-zookeeper-3.7.0-bin /home/itxw/zookeeper-cluster/zookeeper-3 # 写入id echo 1 >/home/itxw/zookeeper-cluster/zookeeper-1/data/myid echo 2 >/home/itxw/zookeeper-cluster/zookeeper-2/data/myid echo 3 >/home/itxw/zookeeper-cluster/zookeeper-3/data/myid # 修改配置文件中的端口号 sed -i "s/2181/2182/g" /home/itxw/zookeeper-cluster/zookeeper-2/conf/zoo.cfg sed -i "s/2181/2183/g" /home/itxw/zookeeper-cluster/zookeeper-3/conf/zoo.cfg # 修改配置文件中的 data 目录 sed -i "s/zookeeper-1/zookeeper-2/g" /home/itxw/zookeeper-cluster/zookeeper-2/conf/zoo.cfg sed -i "s/zookeeper-1/zookeeper-3/g" /home/itxw/zookeeper-cluster/zookeeper-3/conf/zoo.cfg
#启动 zookeeper-cluster/zookeeper-1/bin/zkServer.sh start zookeeper-cluster/zookeeper-2/bin/zkServer.sh start zookeeper-cluster/zookeeper-3/bin/zkServer.sh start ###如果修改后启动不成功,记得restart #查看状态 zookeeper-cluster/zookeeper-1/bin/zkServer.sh status zookeeper-cluster/zookeeper-2/bin/zkServer.sh status zookeeper-cluster/zookeeper-3/bin/zkServer.sh status