zookeeper快速上手
zookeeper的基本功能和应用场景
zookeeper的整体运行机制
zookeeper的数据存储机制
数据存储形式
zookeeper中存储数据的基本形式为: key , value
只是zk有点特别:
key:是以路径的形式表示的,那就意味着,各key之间有父子关系,比如
/ 是顶层key
用户建的key只能在/ 下作为子节点,比如建一个key: /aa 这个key可以带value数据
也可以建一个key: /bb
也可以建key: /aa/xx
zookeeper中,每一个key-value称为一个znode(zookeeper数据节点)
综上所述,zk中的数据存储形式如下:
znode类型
zookeeper中的数据节点有4种类型:
- 持久节点:客户端一旦建立,zk会持久保存,除非有客户端手动删除
- 短暂节点:创建这个节点的客户端一旦断开与zookeeper集群的联系,zookeeper集群就会自动将该节点删除
- 带序号的节点:在同一个父节点下,建带序号的子节点,zk会自动给客户端指定的子节点名后拼接一个自增的序号
- 不带序号的节点:
上述4中类型,可以有以下组合类型:
持久-带序号
持久-不带序号
短暂-带序号
短暂-不带序号
zookeeper的集群部署
- 上传安装包到集群服务器
- 解压
- 修改配置文件
进入zookeeper的安装目录的conf目录
cp zoo_sample.cfg zoo.cfg
vi zoo.cfg
# The number of milliseconds of each tick
dataDir=/home/ws/apps/zookeeper/zkdata
#autopurge.purgeInterval=1
server.1=node1:2888:3888
server.2=node2:2888:3888
server.3=node3:2888:3888
对3台节点,都创建目录 mkdir /home/ws/apps/zookeeper/zkdata
对3台节点,在工作目录中生成myid文件,但内容要分别为各自的id: 1,2,3
node1上: echo 1 > /home/ws/apps/zookeeper/zkdata/myid
node2上: echo 2 > /home/ws/apps/zookeeper/zkdata/myid
node3上: echo 3 >/home/ws/apps/zookeeper/zkdata/myid
从node1上scp安装目录到其他两个节点
scp -r zookeeper-3.4.6/ node1:$PWD
scp -r zookeeper-3.4.6/ node2:$PWD
启动zookeeper集群
zookeeper没有提供自动批量启动脚本,需要手动一台一台地起zookeeper进程
在每一台节点上,运行命令:
bin/zkServer.sh start
启动后,用jps应该能看到一个进程:QuorumPeerMain
但是,光有进程不代表zk已经正常服务,需要用命令检查状态:
bin/zkServer.sh status
能看到角色模式:为leader或follower,即正常了。
补充:编写简单批量启动脚本
第一步:vim zkmanage.sh
第二步:编写脚本,代码如下:
#!/bin/bash
for host in node1 node2 node3
do
echo "${host}:${1}ing......."
ssh $host "source /etc/profile;/home/ws/apps/zookeeper/zookeeper-3.4.6/bin/zkServer.sh $1"
done
第三步:为了方便,将zkmanage.sh更改为可执行文件
chmod +x zkmanage.sh
第四步:执行
# 启动所有节点上zk
./zkmanage.sh start
# 关闭所有节点上zk
./zkmanage.sh stop
# 查看所有节点上的状态
./zkmanage.sh status
zookeeper的命令行客户端操作
数据管理功能:
##创建节点
create /aaa 'ppppp'
##查看节点下的子节点
ls /aaa
##获取节点的v
alue: get /aaa
##修改节点的value
set /aaa 'mmmmm'
##删除节点
rmr /aaa
数据监听功能:
## 查看/aaa的子节点的同时,注册了一个监听“节点的子节点变化事件”的监听器
ls /aaa watch
## 获取/aaa的value的同时,注册了一个监听“节点value变化事件”的监听器
get /aaa watch
注意:注册的监听器在正常收到一次所监听的事件后,就失效
zookeeper简单demo-分布式系统服务器上下线感知
demo背景假设
假设现在我在node1上有一个服务,提供时间查询。此时,由于node1提供服务时压力太大,于是我在node2上也布置了同样的服务,并打算将node2也面向消费者提供服务,但是目前消费者只知道node1上有服务,并不知道node2上也提供同样的服务。更坏的情况是如果node1 死掉了(dead),消费者就得不到服务,怎么让消费者知道node2也可以提供服务呢?如果将来还要加入node3、node4.....,消费者怎么获取信息。
demo示意图
代码实现
TimeQueryServer.java
public class TimeQueryServer {
ZooKeeper zk = null;
// 构造zk客户端连接
public void connectZK() throws Exception{
zk = new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, null);
}
// 注册服务器信息
public void registerServerInfo(String hostname,String port) throws Exception{
/**
* 先判断注册节点的父节点是否存在,如果不存在,则创建
*/
Stat stat = zk.exists("/servers", false);
if(stat==null){
zk.create("/servers", null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
// 注册服务器数据到zk的约定注册节点下
String create = zk.create("/servers/server", (hostname+":"+port).getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(hostname+" 服务器向zk注册信息成功,注册的节点为:" + create);
}
public static void main(String[] args) throws Exception {
TimeQueryServer timeQueryServer = new TimeQueryServer();
// 构造zk客户端连接
timeQueryServer.connectZK();
// 注册服务器信息
timeQueryServer.registerServerInfo(args[0], args[1]);
// 启动业务线程开始处理业务
new TimeQueryService(Integer.parseInt(args[1])).start();
}
}
TimeQueryService.java
public class TimeQueryService extends Thread{
int port = 0;
public TimeQueryService(int port){
this.port = port;
}
@Override
public void run() {
try {
ServerSocket ss = new ServerSocket(port);
System.out.println("业务线程已绑定端口"+port+"准备接受消费端请求了.....");
while(true){
Socket sc = ss.accept();
InputStream inputStream = sc.getInputStream();
OutputStream outputStream = sc.getOutputStream();
outputStream.write(new Date().toString().getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Consumer.java
public class Consumer {
// 定义一个list用于存放最新的在线服务器列表
private volatile ArrayList<String> onlineServers = new ArrayList<>();
// 构造zk连接对象
ZooKeeper zk = null;
// 构造zk客户端连接
public void connectZK() throws Exception {
zk = new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getState() == KeeperState.SyncConnected && event.getType() == EventType.NodeChildrenChanged) {
try {
// 事件回调逻辑中,再次查询zk上的在线服务器节点即可,查询逻辑中又再次注册了子节点变化事件监听
getOnlineServers();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
// 查询在线服务器列表
public void getOnlineServers() throws Exception {
List<String> children = zk.getChildren("/servers", true);
ArrayList<String> servers = new ArrayList<>();
for (String child : children) {
byte[] data = zk.getData("/servers/" + child, false, null);
String serverInfo = new String(data);
servers.add(serverInfo);
}
onlineServers = servers;
System.out.println("查询了一次zk,当前在线的服务器有:" + servers);
}
public void sendRequest() throws Exception {
Random random = new Random();
while (true) {
try {
// 挑选一台当前在线的服务器
int nextInt = random.nextInt(onlineServers.size());
String server = onlineServers.get(nextInt);
String hostname = server.split(":")[0];
int port = Integer.parseInt(server.split(":")[1]);
System.out.println("本次请求挑选的服务器为:" + server);
Socket socket = new Socket(hostname, port);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
out.write("haha".getBytes());
out.flush();
byte[] buf = new byte[256];
int read = in.read(buf);
System.out.println("服务器响应的时间为:" + new String(buf, 0, read));
out.close();
in.close();
socket.close();
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
Consumer consumer = new Consumer();
// 构造zk连接对象
consumer.connectZK();
// 查询在线服务器列表
consumer.getOnlineServers();
// 处理业务(向一台服务器发送时间查询请求)
consumer.sendRequest();
}
}