教学视频
源码
ZooKeeper官网
ZooKeeper 3.4 Documentation
Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的 Apache 项目。
ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. All of these kinds of services are used in some form or another by distributed applications. Each time they are implemented there is a lot of work that goes into fixing the bugs and race conditions that are inevitable. Because of the difficulty of implementing these kinds of services, applications initially usually skimp on them, which make them brittle in the presence of change and difficult to manage. Even when done correctly, different implementations of these services lead to management complexity when the applications are deployed.
ZooKeeper是一个集中服务,用于维护配置信息、命名、提供分布式同步和提供组服务。所有这些类型的服务都以某种形式被分布式应用程序使用。每次实现它们时,都要进行大量的工作来修复不可避免的bug和竞争条件。由于实现这类服务的困难,应用程序最初通常会忽略它们,这使得它们在发生变化时变得脆弱,并且难以管理。即使操作正确,在部署应用程序时,这些服务的不同实现也会导致管理复杂性。
Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应。
一言蔽之:ZooKeeper = 文件系统 + 通知机制
ZooKeeper数据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树, 每个节点称做一个ZNode。每一个ZNode默认能够存储1MB的数据, 每个ZNode都可以通过其路径唯一标识。
提供的服务包括:
在分布式环境下,经常需要对应用/服务进行统一命名,便于识别。
例如:IP不容易记住,而域名容易记住。
客户端能实时洞察到服务器上下线的变化。
在Zookeeper中记录每台服务器的访问数, 让访问数最少的服务器去处理最新的客户端请求。
在下载地址下载zookeeper-3.4.14。
略
C:\ZooKeeper\
路径下,最后得C:\ZooKeeper\zookeeper-3.4.14
C:\ZooKeeper\
路径下创建新文件夹data
C:\ZooKeeper\zookeeper-3.4.14\conf
中的zoo-sample.conf
更名为zoo.cfg
zoo.cfg
,将dataDir=/tmp/zookeeper
改成dataDir=C:\\ZooKeeper\\data
C:\ZooKeeper\zookeeper-3.4.14\bin\zkServer.cmd
C:\ZooKeeper\zookeeper-3.4.14\bin\zkCli.cmd
,若命令行窗口含有Welcome to ZooKeeper!
,表示安装成功,输入quit
退出zkCli.cmd
。Zookeeper中的配置文件zoo.cfg中参数含义解读如下:
面试重点
假设有五台服务器组成的 Zookeeper 集群,它们的 id 从 1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动,来看看会发生什么,如下图所示。
ephemeral 英 [ɪˈfemərəl] 美 [ɪˈfemərəl]
adj.
短暂的;瞬息的
说明:创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护
注意:在分布式系统中,顺序号可以被用于为所有的事件进行全局排序, 这样客户端可以通过顺序号推断事件的顺序
略
在单机上实现伪集群。
C:\ZooKeeper\zookeeper-3.4.14\conf\
新建文件夹cluster
C:\ZooKeeper\zookeeper-3.4.14\conf\cluster
创建新文件,命名为zoo1.cfg
,将下面内容复制到zoo1.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=C:\\ZooKeeper\\data\\cluster\\1
dataLogDir=C:\\ZooKeeper\\log\\cluster\\1
clientPort=2182
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
%ZOOKEEPER_HOME%\conf\cluster
创建新文件,命名为zoo2.cfg
,将下面内容复制到zoo2.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=C:\\ZooKeeper\\data\\cluster\\2
dataLogDir=C:\\ZooKeeper\\log\\cluster\\2
clientPort=2183
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
%ZOOKEEPER_HOME%\conf\cluster
创建新文件,命名为zoo3.cfg
,将下面内容复制到zoo3.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=C:\\ZooKeeper\\data\\cluster\\3
dataLogDir=C:\\ZooKeeper\\log\\cluster\\3
clientPort=2184
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
配置参数解读
server.A=B:C:D
C:\\ZooKeeper\\data\\cluster\\
分别创建名为1
、2
、3
新文件夹,然后在这三个新文件夹分别创建名为myid
的文件C:\\ZooKeeper\\data\\cluster\\1\\myid
文件写入1
C:\\ZooKeeper\\data\\cluster\\2\\myid
文件写入2
C:\\ZooKeeper\\data\\cluster\\3\\myid
文件写入2
C:\\ZooKeeper\\log\\cluster\\
分别创建名为1
、2
、3
新文件夹C:\ZooKeeper\zookeeper-3.4.14\bin\zkServer.cmd
复制成3份在同一级目录中,分别命名为zkServer1.cmd
、zkServer2.cmd
、zkServer3.cmd
zkServer1.cmd
,在set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain
下一行添加set ZOOCFG=..\conf\cluster\zoo1.cfg
zkServer2.cmd
,在set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain
下一行添加set ZOOCFG=..\conf\cluster\zoo2.cfg
zkServer3.cmd
,在set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain
下一行添加set ZOOCFG=..\conf\cluster\zoo3.cfg
C:\ZooKeeper\zookeeper-3.4.14\bin
分别运行zkServer1.cmd
、zkServer2.cmd
、zkServer3.cmd
(依次启动的时刻有错误信息,因为你启动server1 的时候 2 和 3 没找到,但是后面都启动了,就没问题了)cd C:\ZooKeeper\zookeeper-3.4.14\bin
和zkCli.cmd -server 127.0.0.1:2182
,启动客户端对其中一个服务端进行访问。jps
,可见4个Java程序在运行:C:\ZooKeeper\zookeeper-3.4.14\bin>jps
6064 QuorumPeerMain
6688 Jps
1108 QuorumPeerMain
5884 QuorumPeerMain
三个QuorumPeerMain
就是刚刚启动的三个Server。
命令基本语法 | 功能描述 |
---|---|
help | 显示所有操作命令 |
ls path [watch] | 使用 ls 命令来查看当前 znode 中所包含的内容 |
ls2 path [watch] | 查看当前节点数据并能看到更新次数等数据 |
create | 普通创建 -s 含有序列 -e 临时(重启或者超时消失) |
get path [watch] | 获得节点的值 |
set | 设置节点的具体值 |
stat | 查看节点状态 |
delete | 删除节点 |
rmr | 递归删除节点 |
1. 启动客户端
zkCli.cmd -server 127.0.0.1:2182
2.显示所有操作命令
help
3. 查看当前 znode 中所包含的内容
ls /
[zookeeper]
4. 查看当前节点详细数据
ls2 /
[zookeeper]
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1
5. 分别创建 2 个普通节点
create /sanguo "jinlian"
Created /sanguo
create /sanguo/shuguo "liubei"
Created /sanguo/shuguo
6.获得节点的值
get /sanguo
jinlian
cZxid = 0x300000004
ctime = Sat Jul 18 13:07:51 CST 2020
mZxid = 0x300000004
mtime = Sat Jul 18 13:07:51 CST 2020
pZxid = 0x300000005
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 1
get /sanguo/shuguo
liubei
cZxid = 0x300000005
ctime = Sat Jul 18 13:09:21 CST 2020
mZxid = 0x300000005
mtime = Sat Jul 18 13:09:21 CST 2020
pZxid = 0x300000005
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0
[zk: 127.0.0.1:2182(CONNECTED) 8]
7. 创建短暂节点
create -e /sanguo/wuguo "zhouyu"
Created /sanguo/wuguo
a. 在当前客户端是能查看到的
ls /sanguo
[wuguo, shuguo]
b. 退出当前客户端然后再重启客户端
quit
zkCli.cmd -server 127.0.0.1:2182
c. 再次查看根目录下短暂节点已经删除
ls /sanguo
[shuguo]
8. 创建带序号的节点
a. 先创建一个普通的根节点/sanguo/weiguo
create /sanguo/weiguo "caocao"
Created /sanguo/weiguo
b. 创建带序号的节点
[zk: 127.0.0.1:2182(CONNECTED) 2] create -s /sanguo/weiguo/xiaoqiao "meinv"
Created /sanguo/weiguo/xiaoqiao0000000000
[zk: 127.0.0.1:2182(CONNECTED) 3] create -s /sanguo/weiguo/daqiao "meinv"
Created /sanguo/weiguo/daqiao0000000001
[zk: 127.0.0.1:2182(CONNECTED) 4] create -s /sanguo/weiguo/sunshangxiang "meinv"
Created /sanguo/weiguo/sunshangxiang0000000002
如果原来没有序号节点,序号从 0 开始依次递增。 如果原节点下已有 2 个节点,则再排序时从 2 开始,以此类推。
9. 修改节点数据值
set /sanguo/weiguo "simayi"
10. 节点的值变化监听
a. 在 127.0.0.1:2182 上注册监听/sanguo 节点数据变化
[zk: 127.0.0.1:2182(CONNECTED) 7] get /sanguo watch
jinlian
cZxid = 0x300000004
ctime = Sat Jul 18 13:07:51 CST 2020
mZxid = 0x300000004
mtime = Sat Jul 18 13:07:51 CST 2020
pZxid = 0x300000009
cversion = 4
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 2
b. 在 127.0.0.1:2183 上修改/sanguo 节点的数据
[zk: 127.0.0.1:2183(CONNECTED) 1] set /sanguo "tongyi"
cZxid = 0x300000004
ctime = Sat Jul 18 13:07:51 CST 2020
mZxid = 0x30000000f
mtime = Sat Jul 18 13:46:52 CST 2020
pZxid = 0x300000009
cversion = 4
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 2
[zk: 127.0.0.1:2183(CONNECTED) 2]
c. 观察 127.0.0.1:2182 收到数据变化的监听
WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/sanguo
11. 节点的子节点变化监听(路径变化)
a. 在 127.0.0.1:2182 上注册监听/sanguo 节点的子节点变化
ls /sanguo watch
[shuguo, weiguo]
b. 在 127.0.0.1:2183 上修改/sanguo 节点上创建子节点
create /sanguo/jin "simayi"
Created /sanguo/jin
c. 观察 127.0.0.1:2182 收到子节点变化的监听
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/sanguo
12. 删除节点
delete /sanguo/jin
get /sanguo/jin
Node does not exist: /sanguo/jin
13.递归删除节点
rmr /sanguo/shuguo
get /sanguo/shuguo
Node does not exist: /sanguo/shuguo
14.查看节点状态
stat /sanguo
cZxid = 0x300000004
ctime = Sat Jul 18 13:07:51 CST 2020
mZxid = 0x30000000f
mtime = Sat Jul 18 13:46:52 CST 2020
pZxid = 0x300000012
cversion = 7
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 1
面试重点
监听原理详解:
常见的监听:
get path [watch]
ls path [watch]
<dependencies>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.8.2version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.4.10version>
dependency>
dependencies>
src/main/resources
目录下,新建一个文件,命名为log4j.properties,在文件中填入如下内容:log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
ZooKeeperTest源码
public class ZooKeeperTest {
private static String connectString = "127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184";
private static int sessionTimeout = 2000;
private ZooKeeper zkClient = null;
// @Test
@Before
public void init() throws Exception {
zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
// 收到事件通知后的回调函数(用户的业务逻辑)
public void process(WatchedEvent event) {
System.out.println(event.getType() + "--" + event.getPath());
// 再次启动监听
try {
List<String> children = zkClient.getChildren("/", true);
for (String child : children) {
System.out.println(child);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
// 创建子节点
@Test
public void create() throws Exception {
// 参数 1:要创建的节点的路径; 参数 2:节点数据 ; 参数 3:节点权限 ;参数 4:节点的类型
String nodeCreated = zkClient.create("/root", "root".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(nodeCreated);
}
// 获取子节点
@Test
public void getChildren() throws Exception {
List<String> children = zkClient.getChildren("/", true);
for (String child : children) {
System.out.println(child);
}
// 延时阻塞
Thread.sleep(Long.MAX_VALUE);
}
// 判断 znode 是否存在
@Test
public void exist() throws Exception {
Stat stat = zkClient.exists("/eclipse", false);
System.out.println(stat == null ? "not exist" : "exist");
}
某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端都能实时感知到主节点服务器的上下线。
[zk: 127.0.0.1:2182(CONNECTED) 6] create /servers "servers"
Created /servers
DistributeServer源码
public class DistributeServer {
private static String connectString = "127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184";
private static int sessionTimeout = 2000;
private ZooKeeper zkClient = null;
private String parentNode = "/servers";
// 创建到 zk 的客户端连接
public void getConnect() throws IOException {
zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
}
});
}
// 注册服务器
public void registServer(String hostname) throws Exception {
String create = zkClient.create(parentNode + "/server", hostname.getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(hostname + " is online " + create);
}
// 业务功能
public void business(String hostname) throws Exception {
System.out.println(hostname + " is working ...");
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
// 1 获取 zk 连接
DistributeServer server = new DistributeServer();
server.getConnect();
// 2 利用 zk 连接注册服务器信息
server.registServer(args[0]);
// 3 启动业务功能
server.business(args[0]);
}
}
DistributeClient源码
public class DistributeClient {
private static String connectString = "127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184";
private static int sessionTimeout = 2000;
private ZooKeeper zkClient = null;
private String parentNode = "/servers";
// 创建到 zkClient 的客户端连接
public void getConnect() throws IOException {
zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 再次启动监听
try {
getServerList();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
// 获取服务器列表信息
public void getServerList() throws Exception {
// 1 获取服务器子节点信息,并且对父节点进行监听
List<String> children = zkClient.getChildren(parentNode, true);
// 2 存储服务器信息列表
ArrayList<String> servers = new ArrayList<>();
// 3 遍历所有节点,获取节点中的主机名称信息
for (String child : children) {
byte[] data = zkClient.getData(parentNode + "/" + child, false, null);
servers.add(new String(data));
}
// 4 打印服务器列表信息
System.out.println(servers);
}
// 业务功能
public void business() throws Exception {
System.out.println("client is working ...");
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
// 1 获取 zk 连接
DistributeClient client = new DistributeClient();
client.getConnect();
// 2 获取 servers 的子节点信息,从中获取服务器信息列表
client.getServerList();
// 3 业务进程启动
client.business();
}
}
请简述 ZooKeeper 的选举机制
半数机制,myid最大的为Leader
ZooKeeper 的监听原理是什么?
ZooKeeper 的部署方式有哪几种?集群中的角色有哪些?集群最少需要几台机器?
ZooKeeper 的常用命令
CRUD: