14 Zookeeper的理解
一、 Zookeeper概念
1 Zookeeper
- Zookeeper是什么
- Zookeeper特点
- 哪些系统用到了Zookeeper
- HDFS
- YARN
- Storm
- HBase
- Flume
- Dubbo(阿里巴巴)
- metaq阿里巴巴)
2 Zookeeper架构
- Zookeeper 架构
- Zookeeper角色
- 3.3.0版本新增角色Observer
- Zookeeper Server数目一般为奇数
- Zookeeper 数据写流程
- Zookeeper数据模型
3 Zookeeper应用场景
- 统一命名服务
- 配置管理
- 集群管理
- 分布式通知/协调
- 分布式锁
- 分布式队列
4 zookeeper命令行操作
运行 zkCli.sh进入命令行工具
1、使用 ls 命令来查看当前 ZooKeeper 中所包含的内容:
2、创建一个新的 znode ,使用create /zk_test my_data
。这个命令创建了一个新的 znode 节点“ zk_test ”以及与它关联的字符串:
3、我们运行 get 命令来确认 znode 是否包含我们所创建的字符串:
使用
get /zk_test watch
监听这个节点的变化,当另外一个客户端改变/zk_test 时,它会打出下面的
WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/zk_test
4、下面我们通过 set 命令来对 zk 所关联的字符串进行设置set /zk_test haha
:
5、删除节点(非空不行)delete /zk_test
:
6、强制删除节点(非空也可以):rmr /zk_test
7、节点
- 7.1、Znode有两种类型:
- 短暂(ephemeral)(断开连接自己删除)
- 持久(persistent)(断开连接不删除)
- 7.2、Znode有四种形式的目录节点(默认是persistent )7.3、创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护
- PERSISTENT
- PERSISTENT_SEQUENTIAL(持久序列/test0000000019 )
- EPHEMERAL
- EPHEMERAL_SEQUENTIAL
- 7.4、在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序
- 7.5、短暂节点测试
退出重连,短暂节点不见了 - 7.6、带序号的节点测试
5 zookeeper-java-api应用
pom参考
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
功能 | 描述 |
---|---|
create | 在本地目录树中创建一个节点 |
delete | 删除一个节点 |
exists | 测试本地是否存在目标节点 |
get/set data | 从目标节点上读取 / 写数据 |
get/set ACL | 获取 / 设置目标节点访问控制列表信息 |
get children | 检索一个子节点上的列表 |
sync | 等待要被传送的数据 |
- 5.1 增删改查demo
SimpleDemo.java
import org.apache.zookeeper.*;
import java.io.IOException;
public class SimpleDemo {
// 会话超时时间,设置为与系统默认时间一致
private static final int SESSION_TIMEOUT = 30000;
// 创建 ZooKeeper 实例
ZooKeeper zk;
// 创建 Watcher 实例
Watcher wh = new Watcher() {
public void process(org.apache.zookeeper.WatchedEvent event) {
System.out.println(event.toString());
}
};
// 初始化 ZooKeeper 实例
private void createZKInstance() throws IOException {
zk = new ZooKeeper("192.168.147.10:2181", SimpleDemo.SESSION_TIMEOUT, this.wh);
}
private void ZKOperations() throws IOException, InterruptedException, KeeperException {
System.out.println("/n1. 创建 ZooKeeper 节点 (znode : zoo2, 数据: myData2 ,权限: OPEN_ACL_UNSAFE ,节点类型: Persistent");
zk.create("/zoo2", "myData2".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("/n2. 查看是否创建成功: ");
System.out.println(new String(zk.getData("/zoo2", false, null)));
System.out.println("/n3. 修改节点数据 ");
zk.setData("/zoo2", "hahahahaha".getBytes(), -1);
System.out.println("/n4. 查看是否修改成功: ");
System.out.println(new String(zk.getData("/zoo2", false, null)));
System.out.println("/n5. 删除节点 ");
zk.delete("/zoo2", -1);
System.out.println("/n6. 查看节点是否被删除: ");
System.out.println(" 节点状态: [" + zk.exists("/zoo2", false) + "]");
}
private void ZKClose() throws InterruptedException {
zk.close();
}
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
SimpleDemo dm = new SimpleDemo();
dm.createZKInstance();
dm.ZKOperations();
dm.ZKClose();
}
}
效果:
- 5.2 服务器上下线动态感知程序demo
AppServer.java
import org.apache.zookeeper.*;
public class AppServer {
private String groupNode = "sgroup";
private String subNode = "sub";
/**
* 连接zookeeper
*
* @param address server的地址
*/
public void connectZookeeper(String address) throws Exception {
ZooKeeper zk = new ZooKeeper(
"192.168.147.10:2181,192.168.147.11:2181,192.168.147.12:2181",
5000, new Watcher() {
public void process(WatchedEvent event) {
// 不做处理
}
});
// 在"/sgroup"下创建子节点
// 子节点的类型设置为EPHEMERAL_SEQUENTIAL, 表明这是一个临时节点, 且在子节点的名称后面加上一串数字后缀
// 将server的地址数据关联到新创建的子节点上
String createdPath = zk.create("/" + groupNode + "/" + subNode, address.getBytes("utf-8"),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println("create: " + createdPath);
}
/**
* server的工作逻辑写在这个方法中
* 此处不做任何处理, 只让server sleep
*/
public void handle() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
// 在参数中指定server的地址
if (args.length == 0) {
System.err.println("The first argument must be server address");
System.exit(1);
}
AppServer as = new AppServer();
as.connectZookeeper(args[0]);
as.handle();
}
}
AppClient.java
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import java.util.ArrayList;
import java.util.List;
public class AppClient {
private String groupNode = "sgroup";
private ZooKeeper zk;
private Stat stat = new Stat();
private volatile List<String> serverList;
/**
* 连接zookeeper
*/
public void connectZookeeper() throws Exception {
zk = new ZooKeeper("192.168.147.10:2181,192.168.147.11:2181,192.168.147.12:2181", 5000, new Watcher() {
public void process(WatchedEvent event) {
// 如果发生了"/sgroup"节点下的子节点变化事件, 更新server列表, 并重新注册监听
if (event.getType() == Event.EventType.NodeChildrenChanged
&& ("/" + groupNode).equals(event.getPath())) {
try {
updateServerList();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
updateServerList();
}
/**
* 更新server列表
*/
private void updateServerList() throws Exception {
List<String> newServerList = new ArrayList<String>();
// 获取并监听groupNode的子节点变化
// watch参数为true, 表示监听子节点变化事件.
// 每次都需要重新注册监听, 因为一次注册, 只能监听一次事件, 如果还想继续保持监听, 必须重新注册
List<String> subList = zk.getChildren("/" + groupNode, true);
for (String subNode : subList) {
// 获取每个子节点下关联的server地址
byte[] data = zk.getData("/" + groupNode + "/" + subNode, false, stat);
newServerList.add(new String(data, "utf-8"));
}
// 替换server列表
serverList = newServerList;
System.out.println("server list updated: " + serverList);
}
/**
* client的工作逻辑写在这个方法中
* 此处不做任何处理, 只让client sleep
*/
public void handle() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
AppClient ac = new AppClient();
ac.connectZookeeper();
ac.handle();
}
}
开启client
加入节点名称参数开启server
client端变化
再次加入节点名称参数开启server
client端变化
关闭其中一个服务client端变化
关闭另一个服务client端变化
- 5.3 分布式共享锁demo
DistributedClient.java
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class DistributedClient {
// 超时时间
private static final int SESSION_TIMEOUT = 5000;
// zookeeper server列表
private String hosts = "192.168.147.10:2181,192.168.147.11:2181,192.168.147.12:2181";
private String groupNode = "locks";
private String subNode = "sub";
private ZooKeeper zk;
// 当前client创建的子节点
private String thisPath;
// 当前client等待的子节点
private String waitPath;
private CountDownLatch latch = new CountDownLatch(1);
/**
* 连接zookeeper
*/
public void connectZookeeper() throws Exception {
zk = new ZooKeeper(hosts, SESSION_TIMEOUT, new Watcher() {
public void process(WatchedEvent event) {
try {
// 连接建立时, 打开latch, 唤醒wait在该latch上的线程
if (event.getState() == Event.KeeperState.SyncConnected) {
latch.countDown();
}
// 发生了waitPath的删除事件
if (event.getType() == Event.EventType.NodeDeleted && event.getPath().equals(waitPath)) {
doSomething();
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
// 等待连接建立
latch.await();
// 创建子节点
thisPath = zk.create("/" + groupNode + "/" + subNode, null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
// wait一小会, 让结果更清晰一些
Thread.sleep(10);
// 注意, 没有必要监听"/locks"的子节点的变化情况
List<String> childrenNodes = zk.getChildren("/" + groupNode, false);
// 列表中只有一个子节点, 那肯定就是thisPath, 说明client获得锁
if (childrenNodes.size() == 1) {
doSomething();
} else {
String thisNode = thisPath.substring(("/" + groupNode + "/").length());
// 排序
Collections.sort(childrenNodes);
int index = childrenNodes.indexOf(thisNode);
if (index == -1) {
// never happened
} else if (index == 0) {
// inddx == 0, 说明thisNode在列表中最小, 当前client获得锁
doSomething();
} else {
// 获得排名比thisPath前1位的节点
this.waitPath = "/" + groupNode + "/" + childrenNodes.get(index - 1);
// 在waitPath上注册监听器, 当waitPath被删除时, zookeeper会回调监听器的process方法
zk.getData(waitPath, true, new Stat());
}
}
}
private void doSomething() throws Exception {
try {
System.out.println("gain lock: " + thisPath);
Thread.sleep(2000);
// do something
} finally {
System.out.println("finished: " + thisPath);
// 将thisPath删除, 监听thisPath的client将获得通知
// 相当于释放锁
zk.delete(this.thisPath, -1);
}
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
new Thread() {
public void run() {
try {
DistributedClient dl = new DistributedClient();
dl.connectZookeeper();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
Thread.sleep(Long.MAX_VALUE);
}
}
测试:
二、Zookeeper环境安装
1. zookeeper源码包下载
http://mirror.bit.edu.cn/apache/zookeeper/
2. 下载软件包
- Master
wget http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz
tar zxvf zookeeper-3.4.10.tar.gz
3. 修改zookeeper配置
- Master
cd zookeeper-3.4.10
- 创建日志文件夹及数据文件夹
mkdir data
mkdir log
- 修改配置
cd conf
mv zoo_sample.cfg zoo.cfg
vim zoo.cfg
dataDir=/usr/local/src/zookeeper-3.4.10/data
dataLogDir=/usr/local/src/zookeeper-3.4.10/log
server.0=master:8880:7770
server.1=slave1:8881:7771
server.2=slave2:8882:7772
4. 配置环境变量
- Master、Slave1、Slave2
vim ~/.bashrc
#Zookeeper
ZOOKEEPER_HOME=/usr/local/src/zookeeper-3.4.10
PATH=$PATH:$ZOOKEEPER_HOME/bin
- 刷新环境变量
source ~/.bashrc
5. 拷贝安装包
- Master
scp -r /usr/local/src/zookeeper-3.4.10 root@slave1:/usr/local/src/zookeeper-3.4.10
scp -r /usr/local/src/zookeeper-3.4.10 root@slave2:/usr/local/src/zookeeper-3.4.10
6. 分别添加ID(一定要对应conf里zoo.cfg里的设置)
- Master
echo "0" > /usr/local/src/zookeeper-3.4.10/data/myid
- Slave1
echo "1" > /usr/local/src/zookeeper-3.4.10/data/myid
- Slave2
echo "2" > /usr/local/src/zookeeper-3.4.10/data/myid
7. 启动关闭Zookeeper服务
- Master、Slave1、Slave2
zkServer.sh start
zkServer.sh stop
8. 查看运行状态
zkServer.sh status
-
Master
-
Slave1
-
Slave2
9. 进程状态
jps
-
Master
-
Slave1
-
Slave2