安装
- 下载地址: https://zookeeper.apache.org/releases.html
本文使用3.6.0版本, 在linux系统中操作
- 解压到/usr/local目录中
tar -zxvf apache-zookeeper-3.6.0-bin.tar -C /usr/local
- 修改ZooKeeper目录名称方便管理
mv apache-zookeeper-3.6.0-bin apache-zookeeper-01
zookeeper目录.png
单机配置
- 启动ZooKeeper服务需要有配置文件, 在conf目录下新建zoo.cfg文件, 添加如下配置
# 心跳时间间隔(ms)
tickTime=2000
# 集群启动的时间限制, 10个心跳间隔时间, 10 * 2000ms
initLimit=10
# 数据同步的时间限制, 5个心跳间隔时间, 5*2000ms
syncLimit=5
# 数据存放目录
dataDir=/usr/local/apache-zookeeper-01/data
# 客户端连接端口
clientPort=2181
# 集群中的服务列表
# server后面的1 代表服务的id, 必须和myid文件(在data目录)中的内容一致, 数值在1-154之间
# 服务IP地址, 最好是内网IP, 内网传输速度快.
# 2601 是几个服务的数据同步端口
# 3601 是几个服务的选举端口, leader服务挂掉后剩余服务通过该端口通信选举新的leader
server.1=192.168.88.88:2601:3601
-
在ZooKeeper根目录下创建数据存放目录data,
cd /usr/local/apache-zookeeper-01 && mkdir data
-
在data目录下创建myid文件,并写入该服务的id为1, 必须和步骤1中集群服务列表的id一致
cd data && echo 1 > myid
-
进入bin目录使用zkServer.sh可执行文件启动ZooKeeper.
./zkServer.sh start
该命令会使用默认配置文件zoo.cfg, 如果要指定配置文件启动 需要加上--config
./zkServer.sh status
查看当前状态
./zkServer.sh stop
关闭ZooKeeper服务
./zkCli.sh -server 192.168.88.88:2181
连接ZooKeeper服务, 不写-server默认连接本机2181端口
zookeeper启动成功.png
集群配置
前面已经成功启动了一个ZooKeeper服务, 下面再添加2个ZooKeeper节点做一个集群
-
拷贝2份apache-zookeeper-01�目录分别为apache-zookeeper-02和apache-zookeeper-03
cp -R apache-zookeeper-01 apache-zookeeper-02
cp -R apache-zookeeper-01 apache-zookeeper-03
-
修改配置文件zoo.cfg
加入新添加的ZooKeeper服务的IP地址, 3个ZooKeeper都要添加
apache-zookeeper-02:server.2=192.168.88.88:2602:3602
apache-zookeeper-03:server.3=192.168.88.88:2603:3603
修改数据存放目录的路径
apache-zookeeper-02:dataDir=/usr/local/apache-zookeeper-02/data
apache-zookeeper-03:dataDir=/usr/local/apache-zookeeper-03/data
由于我这是用同一台电脑操作, IP地址一样,所以需要改一下客户端连接端口
apache-zookeeper-02:clientPort=2182
apache-zookeeper-03:clientPort=2183
由于我这是用同一台电脑操作, 所以IP地址一样 -
修改myid文件中的服务id
-
启动这3个ZooKeeper服务. 如果之前已经启动了apache-zookeeper-01, 由于修改了配置文件, 需要关闭重新启动.启动完成后通过./zkServer.sh status看到Mode是一个leader两个follower, 到此集群就已经完成
zookeeper集群.png
项目中使用ZooKeeper
项目集成ZooKeeper常用的有JDK自带zkclient框架和Apache开源的Curator框架, 这里使用zkclient.
-
首先在ZooKeeper中创建一个节点/server, 步骤2中注册服务器信息就在这个节点下面添加子节点
连接ZooKeeper服务./zkCli.sh -server 192.168.88.88:2181
创建server节点create /server
-
添加zookeeper依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
- 配置ZooKeeper添加到spring容器中, 并向ZooKeeper注册服务器信息
import org.apache.zookeeper.*;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import java.io.IOException;
@SpringBootConfiguration
public class ZookeeperConfig {
// zookeeper服务地址
private static final String ZK_SERVER_ADDR = "192.168.88.88:2181, 192.168.88.88:2182, 192.168.88.88:2183";
// 会话超时时间
private static final int SESSION_TIMEOUT = 30000;
// 存储服务地址节点的路径
private static final String PATH = "/server";
// 注册服务器信息的节点名称
private static final String SUB_PATH = "/testServer";
// 服务器信息
private static final String HOST = "192.168.88.88:8888";
private ZooKeeper zooKeeper;
@Bean
public ZooKeeper zooKeeper() throws IOException {
zooKeeper = new ZooKeeper(ZK_SERVER_ADDR, SESSION_TIMEOUT, new Watcher() {
// 监听连接事件
@Override
public void process(WatchedEvent event) {
if(event.getState() == Event.KeeperState.SyncConnected) {
System.out.println("zookeeper连接成功");
// 注册服务器信息
try {
// 创建临时节点存储服务器信息
zooKeeper.create(PATH + SUB_PATH, HOST.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
return zooKeeper;
}
}
项目启动完成后/server节点下面就会多一个子节点
![](https://img.haomeiwen.com/i3962148/9952e6582fa6b4c6.png)
- 客户端启动后从ZooKeeper获取注册的服务器信息存储到本地, 给/server节点添加永久监听事件, 如果/server节点的子节点数量有变化会自动获取最新数据
import org.apache.zookeeper.*;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import java.util.ArrayList;
import java.util.List;
@SpringBootConfiguration
public class ZKClientConfig {
// zookeeper服务地址
private static final String ZK_SERVER_ADDR = "192.168.88.88:2181, 192.168.88.88:2182, 192.168.88.88:2183";
// 会话超时时间
private static final int SESSION_TIMEOUT = 30000;
// 存储服务地址节点的路径
private static final String PATH = "/server";
private ZooKeeper zooKeeper;
private static List<String> serverList;
@Bean
public ZooKeeper zooKeeper() throws Exception {
zooKeeper = new ZooKeeper(ZK_SERVER_ADDR, SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getState() == Event.KeeperState.SyncConnected) {
System.out.println("ZooKeeper连接成功");
// 获取服务器地址数据
getServerList();
// 监听/server下的子节点变化
try {
zooKeeper.addWatch(PATH, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 获取最新的服务器地址数据
getServerList();
}
}, AddWatchMode.PERSISTENT);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
return zooKeeper;
}
private void getServerList() {
try {
// 获取/server下面的所有子节点
List<String> nodeList = zooKeeper.getChildren(PATH, null);
// 创建临时列表存放服务器地址
List<String> tempList = new ArrayList<>();
// 取出/server所有子节点的数据
for (String node : nodeList) {
byte[] addr = zooKeeper.getData(PATH + "/" + node, null, null);
tempList.add(new String(addr));
}
serverList = tempList;
} catch (Exception e) {
e.printStackTrace();
}
}
}
ZooKeeper分布式锁
ZooKeeper可以通过创建顺序临时节点来实现分布式锁, 思路如下图
![](https://img.haomeiwen.com/i3962148/e13561ad9de6cdc4.png)
特性
-
一个leader, 多个follower
-
数据全局一致
在任何节点读取到的数据都是一样的, 如果从follower节点读数据, 而该数据还没有从leader同步过来, 那么读数据的请求就会暂时阻塞,等待数据同步过来后再读取.
-
分布式读写
follower读数据, leader写数据
-
更新请求顺序执行
更新数据的请求按顺序执行
-
数据更新具有原子性
leader接收到更新数据的请求,会以广播的形式通知所有的follower, 当接收到超过一半follower的确认信息后, 再次发送广播通知follower提交数据, 不足一半确认则更新失败
-
实时性
数据同步时间是毫秒级别. 官方建议写数据每次最好不超过1M. 写的数据过大会影响同步时间
数据结构
zookeeper是以树状结构存储数据, 每个节点都可以存储一条数据. 常用的ZooKeeper节点有4种类型
PERSISTENT
持久化节点
PERSISTENT_SEQUENTIAL
持久化顺序节点
EPHEMERAL
临时节点
EPHEMERAL_SEQUENTIAL
临时顺序节点
创建的临时节点与当前会话绑定, 会话断开就会删除, 并且不允许有字节点
常用命令
-
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
-s
创建顺序节点
-e
创建临时节点
-c
创建容器节点. 容器节点在指定时间内(默认1分钟)没有子节点就会自动删除
-t
指定生存时间
设置生存时间-t 默认禁用, 需要在/bin/zkServer.sh中配置-Dzookeeper.extendedTypesEnabled=true -Dzookeeper.emulate353TTLNodes=true
开启
![](https://img.haomeiwen.com/i3962148/c37a2fa571fad972.png)
-
ls [-s] [-w] [-R] path
-s
显示节点状态信息
-w
监听子节点改变, 只监听一次.通过printwatches on|off
开启/关闭事件监听
-R
递归查看子节点 -
set [-s] [-v version] path data
-s
显示节点状态信息
-v
更新节点的版本号. 如果版本号不一致, 就不做处理
4.addWatch [-m mode] path
# optional mode is one of [PERSISTENT, PERSISTENT_RECURSIVE] - default is PERSISTENT_RECURSIVE
-m
监听的模式.
PERSISTENT
: 只监听当前节点的数据和子节点改变,
PERSISTENT_RECURSIVE
: 监听指定节点下的所有子节点的数据和子节点改变
网友评论