安装
- 下载地址: 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服务需要有配置文件, 在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服务, 下面再添加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
项目集成ZooKeeper常用的有JDK自带zkclient框架和Apache开源的Curator框架, 这里使用zkclient.
首先在ZooKeeper中创建一个节点/server, 步骤2中注册服务器信息就在这个节点下面添加子节点
连接ZooKeeper服务./zkCli.sh -server 192.168.88.88:2181
创建server节点create /server
添加zookeeper依赖
org.apache.zookeeper
zookeeper
3.6.0
- 配置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节点下面就会多一个子节点
- 客户端启动后从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 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 nodeList = zooKeeper.getChildren(PATH, null);
// 创建临时列表存放服务器地址
List 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可以通过创建顺序临时节点来实现分布式锁, 思路如下图
特性
一个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
开启
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
: 监听指定节点下的所有子节点的数据和子节点改变