1. Watch事件监听
1.1 一次性监听方式:Watcher
利用 Watcher 来对节点进行监听操作,可以典型业务场景需要使用可考虑,但一般情况不推荐使用。
public class CuratorWatchTest {
private CuratorFramework client;
/**
* 建立连接
*/
@Before
public void testConnect(){
/**
* String connectString, 连接字符串 zk地址 端口: "192.168.58.100:2181,,,,"
* int sessionTimeoutMs, 会话超时时间
* int connectionTimeoutMs, 连接超时时间
* RetryPolicy retryPolicy 重试策略
*/
//1. 第一种方式
RetryPolicy retryPolicy =new ExponentialBackoffRetry(3000,10);
//2. 第二种方式
client = CuratorFrameworkFactory.builder()
.connectString("192.168.58.100:2181")
.sessionTimeoutMs(60*1000)
.connectionTimeoutMs(15*1000)
.retryPolicy(retryPolicy)
.namespace("mashibing") //当前程序创建目录的根目录
.build();
client.start();
}
/**
* 演示一次性监听
*/
@Test
public void testOneListener() throws Exception {
byte[] data = client.getData().usingWatcher(new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("监听器 watchedEvent: " + watchedEvent);
}
}).forPath("/test");
System.out.println("监听节点内容:" + new String(data));
while(true){
}
}
@After
public void close(){
client.close();
}
}
上面这段代码对 /test 节点注册了一个 Watcher 监听事件,并且返回当前节点的内容。后面进行两次数据变更,实际上第二次变更时,监听已经失效,无法再次获得节点变动事件了。测试中控制台输出的信息如下:
1.2 Curator事件监听机制
ZooKeeper 原生支持通过注册Watcher来进行事件监听,但是其使用并不是特别方便需要开发人员自己反复注册Watcher,比较繁琐。
Curator引入了 Cache 来实现对 ZooKeeper 服务端事件的监听。
ZooKeeper提供了三种Watcher:
- NodeCache : 只是监听某一个特定的节点
- PathChildrenCache : 监控一个ZNode的子节点.
- TreeCache : 可以监控整个树上的所有节点,类似于PathChildrenCache和NodeCache的组合
1)watch监听 NodeCache
监听数据节点本身的变化。NodeCacheListener 来完成后续处理。
public class CuratorWatchTest {
/**
* 演示 NodeCache : 给指定一个节点注册监听
*/
@Test
public void testNodeCache() throws Exception {
//1. 创建NodeCache对象
NodeCache nodeCache = new NodeCache(client, "/app1"); //监听的是 /mashibing和其子目录app1
//2. 注册监听
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
System.out.println("节点变化了。。。。。。");
//获取修改节点后的数据
byte[] data = nodeCache.getCurrentData().getData();
System.out.println(new String(data));
}
});
//3. 设置为true,开启监听
nodeCache.start(true);
while(true){
}
}
}
NodeCache不仅可以监听节点内容变化,还可以监听指定节点是否存在。如果原本节点不存在,那么Cache就会在节点被创建时触发监听事件,如果该节点被删除,就无法再触发监听事件。
2)watch监听 PathChildrenCache
/**
* 演示 PathChildrenCache: 监听某个节点的所有子节点
*/
@Test
public void testPathChildrenCache() throws Exception {
//1.创建监听器对象 (第三个参数表示缓存每次节点更新后的数据)
PathChildrenCache pathChildrenCache = new PathChildrenCache(client, "/app2", true);
//2.绑定监听器
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
System.out.println("子节点发生变化了。。。。。。");
System.out.println(pathChildrenCacheEvent);
if(PathChildrenCacheEvent.Type.CHILD_UPDATED == pathChildrenCacheEvent.getType()){
//更新子节点
System.out.println("子节点更新了!");
//在一个getData中有很多数据,我们只拿data部分
byte[] data = pathChildrenCacheEvent.getData().getData();
System.out.println("更新后的值为:" + new String(data));
}else if(PathChildrenCacheEvent.Type.CHILD_ADDED == pathChildrenCacheEvent.getType()){
//添加子节点
System.out.println("添加子节点!");
String path = pathChildrenCacheEvent.getData().getPath();
System.out.println("子节点路径为: " + path);
}else if(PathChildrenCacheEvent.Type.CHILD_REMOVED == pathChildrenCacheEvent.getType()){
//删除子节点
System.out.println("删除了子节点");
String path = pathChildrenCacheEvent.getData().getPath();
System.out.println("子节点路径为: " + path);
}
}
});
//3. 开启
pathChildrenCache.start();
while(true){
}
}
- 事件对象信息分析
PathChildrenCacheEvent{
type=CHILD_UPDATED,
data=ChildData
{
path='/app2/m1',
stat=164,166,1670114647087,1670114698259,1,0,0,0,3,0,164,
data=[49, 50, 51]
}
}
3)watch监听 TreeCache
TreeCache相当于NodeCache(只监听当前结点)+ PathChildrenCache(只监听子结点)的结合版,即监听当前和子结点。
/**
* 演示 TreeCache: 监听某个节点的所有子节点
*/
@Test
public void testCache() throws Exception {
//1.创建监听器对象
TreeCache treeCache = new TreeCache(client, "/app2");
//2.绑定监听器
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
System.out.println("节点变化了");
System.out.println(treeCacheEvent);
if(TreeCacheEvent.Type.NODE_UPDATED == treeCacheEvent.getType()){
//更新节点
System.out.println("节点更新了!");
//在一个getData中有很多数据,我们只拿data部分
byte[] data = treeCacheEvent.getData().getData();
System.out.println("更新后的值为:" + new String(data));
}else if(TreeCacheEvent.Type.NODE_ADDED == treeCacheEvent.getType()){
//添加子节点
System.out.println("添加节点!");
String path = treeCacheEvent.getData().getPath();
System.out.println("子节点路径为: " + path);
}else if(TreeCacheEvent.Type.NODE_REMOVED == treeCacheEvent.getType()){
//删除子节点
System.out.println("删除节点");
String path = treeCacheEvent.getData().getPath();
System.out.println("删除节点路径为: " + path);
}
}
});
//3. 开启
treeCache.start();
while(true){
}
}
2. 事务&异步操作演示
2.1 事务演示
CuratorFramework 的实例包含 inTransaction( ) 接口方法,调用此方法开启一个 ZooKeeper 事务。
可以复合create、 setData、 check、and/or delete 等操作然后调用 commit() 作为一个原子操作提交。
/**
* 事务操作
*/
@Test
public void TestTransaction() throws Exception {
//1. 创建Curator对象,用于定义事务操作
CuratorOp createOp = client.transactionOp().create().forPath("/app3", "app1-data".getBytes());
CuratorOp setDataOp = client.transactionOp().setData().forPath("/app2", "app2-data".getBytes());
CuratorOp deleteOp = client.transactionOp().delete().forPath("/app2");
//2. 添加事务操
Collection<CuratorTransactionResult> results = client.transaction().forOperations(createOp, setDataOp, deleteOp);
//3. 遍历事务操作结果
for (CuratorTransactionResult result : results) {
System.out.println(result.getForPath() + " - " + result.getType());
}
}
2.2 异步操作
前面提到的增删改查都是同步的,但是 Curator 也提供了异步接口,引入了 BackgroundCallback 接口用于处理异步接口调用之后服务端返回的结果信息。
BackgroundCallback 接口中一个重要的回调值为 CuratorEvent,里面包含事件类型、响应码和节点的详细信息。
// 异步操作
@Test
public void TestAsync() throws Exception {
while(true){
// 异步获取子节点列表
GetChildrenBuilder builder = client.getChildren();
builder.inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
System.out.println("子节点列表:" + curatorEvent.getChildren());
}
}).forPath("/");
TimeUnit.SECONDS.sleep(5);
}
}
3. Zookeeper权限控制
3.1 zk权限控制介绍
Zookeeper作为一个分布式协调框架,内部存储了一些分布式系统运行时的状态的数据,比如master选举、比如分布式锁。对这些数据的操作会直接影响到分布式系统的运行状态。因此,为了保证zookeeper中的数据的安全性,避免误操作带来的影响。Zookeeper提供了一套ACL权限控制机制来保证数据的安全。
ACL权限控制,使用:
scheme:id:perm
来标识。
- Scheme(权限模式),标识授权策略
- ID(授权对象)
- Permission:授予的权限
ZooKeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限,每个znode支持设置多种权限控制方案和多个权限,子节点不会继承父节点的权限,客户端无权访问某节点,但可能可以访问它的子节点。
3.2 Scheme 权限模式
Zookeeper提供以下权限模式,所谓权限模式,就是使用什么样的方式来进行授权。
- world: 默认方式,相当于全部都能访问。
- auth:代表已经认证通过的用户> cli中可以通过 >
> addauth digest user:pwd>
> 来添加当前上下文中的授权用户 - digest:即用户名:密码这种方式认证,这也是业务系统中最常用的。> 用 username:password 字符串来产生一个MD5串,然后该串被用来作为ACL ID。认证是通过明文发送username:password 来进行的,当用在ACL时,表达式为username:base64 ,base64是password的SHA1摘要的编码。
- ip:通过ip地址来做权限控制> 比如 ip:192.168.1.1 表示权限控制都是针对这个ip地址的。也可以针对网段 ip:192.168.1.1/24,此时addr中的有效位与客户端addr中的有效位进行比对。
3.3 ID 授权对象
指权限赋予的用户或一个指定的实体,不同的权限模式下,授权对象不同。
Id ipId = new Id("ip", "192.168.58.100");
Id ANYONE_ID_UNSAFE = new Id("world", "anyone");
3.4 Permission权限类型
指通过权限检查后可以被允许的操作,create /delete/write/read/admin
- Create 允许对子节点Create 操作
- Delete 允许对子节点Delete 操作
- Write 允许对本节点SetData 操作
- Read 允许对本节点GetChildren 和GetData 操作
- Admin 允许对本节点setAcl 操作
权限模式(Schema)和授权对象主要用来确认权限验证过程中使用的验证策略:
比如ip地址、digest:username:password,匹配到验证策略并验证成功后,再根据权限操作类型来决定当前客户端的访问权限。
3.5 在控制台实现操作
在Zookeeper中提供了ACL相关的命令
getAcl getAcl <path> 读取ACL权限
setAcl setAcl <path> <acl> 设置ACL权限
addauth addauth <scheme> <auth> 添加认证用户
1)word方式
创建一个节点后默认就是world模式
[zk: localhost:2181(CONNECTED) 6] create /auth
Created /auth
[zk: localhost:2181(CONNECTED) 7] getAcl /auth
'world,'anyone
: cdrwa
[zk: localhost:2181(CONNECTED) 8] create /auth2
Created /auth2
[zk: localhost:2181(CONNECTED) 9] getAcl /auth2
'world,'anyone
: cdrwa
[zk: localhost:2181(CONNECTED) 10]
其中, cdrwa,分别对应 create . delete read write admin
2)IP方式
在ip模式中,首先连接到zkServer的命令需要使用如下方式
zkCli.sh -server 127.0.0.1:2181
接着按照IP的方式操作如下
[zk: 127.0.0.1:2181(CONNECTED) 0] create /ip-model
Created /ip-model
[zk: 127.0.0.1:2181(CONNECTED) 1] setAcl /ip-model ip:127.0.0.1:cdrwa
[zk: 127.0.0.1:2181(CONNECTED) 3] getAcl /ip-model
'ip,'127.0.0.1
: cdrwa
3) Auth模式
auth模式的操作如下。
[zk: 127.0.0.1:2181(CONNECTED) 5] create /spike
Created /spike
[zk: 127.0.0.1:2181(CONNECTED) 6] addauth digest spike:123456
[zk: 127.0.0.1:2181(CONNECTED) 9] setAcl /spike auth:spike:cdrwa
[zk: 127.0.0.1:2181(CONNECTED) 10] getAcl /spike
'digest,'spike:pPeKgz2N9Xc8Um6wwnzFUMteLxk=
: cdrwa
当我们退出当前的会话后,再次连接,执行如下操作,会提示没有权限
[zk: localhost:2181(CONNECTED) 0] get /spike
Insufficient permission : /spike
这时候,我们需要重新授权。
[zk: localhost:2181(CONNECTED) 1] addauth digest spike:123456
[zk: localhost:2181(CONNECTED) 2] get /spike
null
**4) Digest模式 **
使用语法,会发现使用方式和Auth模式相同
setAcl /digest digest:用户名:密码:权限
但是有一个不一样的点,密码需要用加密后的,否则无法被识别。
密码: 用户名和密码加密后的字符串。
使用下面程序生成密码
public class TestAcl {
@Test
public void createPw() throws NoSuchAlgorithmException {
String up = "msb:msb";
byte[] digest = MessageDigest.getInstance("SHA1").digest(up.getBytes());
String encodeStr = Base64.getEncoder().encodeToString(digest);
System.out.println(encodeStr);
}
}
得到: 5FAC7McRhLdx0QUWsfEbK8pqwxc=
再回到client上进行如下操作
[zk: localhost:2181(CONNECTED) 14] create /digest
Created /digest
[zk: localhost:2181(CONNECTED) 15] setAcl /digest digest:msb:5FAC7McRhLdx0QUWsfEbK8pqwxc=:cdrwa
[zk: localhost:2181(CONNECTED) 16] getAcl /digest
'digest,'msb:5FAC7McRhLdx0QUWsfEbK8pqwxc=
: cdrwa
当退出当前会话后,需要再次授权才能访问**/digest**节点
[zk: localhost:2181(CONNECTED) 0] get /digest
Insufficient permission : /digest
[zk: localhost:2181(CONNECTED) 1] addauth digest msb:msb
[zk: localhost:2181(CONNECTED) 2] get /digest
null
3.6 Curator演示ACL的使用
接下来我们使用Curator简单演示一下ACL权限的访问操作。
public class TestAcl {
private CuratorFramework client;
@Test
public void createPw() throws NoSuchAlgorithmException {
String up = "msb:msb";
byte[] digest = MessageDigest.getInstance("SHA1").digest(up.getBytes());
String encodeStr = Base64.getEncoder().encodeToString(digest);
System.out.println(encodeStr);
}
//1.创建连接
@Before
public void createConnect(){
client = CuratorFrameworkFactory.builder()
.connectString("192.168.58.100:2181")
.sessionTimeoutMs(5000).connectionTimeoutMs(20000)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.namespace("msbAcl").build();
client.start();
}
@Test
public void testCuratorAcl() throws Exception {
//创建ID,以Digest方式认证,用户名和密码为 msb:msb
Id id = new Id("digest", DigestAuthenticationProvider.generateDigest("msb:msb"));
//为ID对象指定权限
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(ZooDefs.Perms.ALL,id));
//创建节点 "auth",设置节点数据,并设置ACL权限
String node = client.create().creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT) // 设置节点类型是持久节点
.withACL(acls,false) //设置节点的ACL权限
.forPath("/auth","hello".getBytes()); //设置节点的路径和数据
System.out.println("成功创建带权限的节点: " + node);
//获取刚刚创建的节点的数据
byte[] bytes = client.getData().forPath(node);
System.out.println("获取数据结果: " + new String(bytes));
}
}
上述代码执行后会报错
先删除节点
[zk: localhost:2181(CONNECTED) 6] deleteall /msbAcl
修改代码, 连接时增加授权
4. Zookeeper集群搭建
4.1 搭建要求
真实的集群是需要部署在不同的服务器上的,但是在我们测试时同时启动很多个虚拟机内存会吃不消,所以我们通常会搭建伪集群,也就是把所有的服务都搭建在一台虚拟机上,用端口进行区分。
我们这里要求搭建一个三个节点的Zookeeper集群(伪集群)。
4.2 Zookeeper集群角色
zookeeper集群中的节点有三种角色
- Leader:处理集群的所有事务请求(增删改),集群中只有一个Leader。
- Follower:只能处理读请求,参与Leader选举。
- Observer:只能处理读请求,提升集群读的性能,但不能参与Leader选举。
4.2 准备工作
重新部署一台虚拟机作为我们搭建集群的测试服务器。
(1)安装JDK 【此步骤省略】。
(2)Zookeeper压缩包上传到服务器 (3)将Zookeeper解压 ,建立/usr/local/zookeeper-cluster目录,将解压后的Zookeeper复制到以下三个目录。
[root@localhost ~]# mkdir /usr/local/zookeeper-cluster
[root@localhost software]# cp -r apache-zookeeper-3.7.1-bin /usr/local/zookeeper-cluster/zookeeper-1
[root@localhost software]# cp -r apache-zookeeper-3.7.1-bin /usr/local/zookeeper-cluster/zookeeper-2
[root@localhost software]# cp -r apache-zookeeper-3.7.1-bin /usr/local/zookeeper-cluster/zookeeper-3
(4)创建data目录 ,并且将 conf下zoo_sample.cfg 文件改名为 zoo.cfg
mkdir /usr/local/zookeeper-cluster/zookeeper-1/data
mkdir /usr/local/zookeeper-cluster/zookeeper-2/data
mkdir /usr/local/zookeeper-cluster/zookeeper-3/data
mv /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo_sample.cfg /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg
mv /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo_sample.cfg /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfg
mv /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo_sample.cfg /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfg
(5) 配置每一个Zookeeper 的dataDir 和 clientPort 分别为:2181 2182 2183
修改/usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg
vim /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg
clientPort=2181
dataDir=/usr/local/zookeeper-cluster/zookeeper-1/data
修改/usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfg
vim /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfg
clientPort=2182
dataDir=/usr/local/zookeeper-cluster/zookeeper-2/data
修改/usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfg
vim /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfg
clientPort=2183
dataDir=/usr/local/zookeeper-cluster/zookeeper-3/data
4.3 配置集群
(1)在每个zookeeper的 data 目录下创建一个 myid 文件,内容分别是1、2、3 。这个文件就是记录每个服务器的ID
[root@localhost software]# echo 1 > /usr/local/zookeeper-cluster/zookeeper-1/data/myid
[root@localhost software]# echo 2 > /usr/local/zookeeper-cluster/zookeeper-2/data/myid
[root@localhost software]# echo 3 > /usr/local/zookeeper-cluster/zookeeper-3/data/myid
(2)在每一个zookeeper 的 zoo.cfg配置客户端访问端口(clientPort)和集群服务器IP列表。
集群服务器IP列表如下
vim /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg
vim /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfg
vim /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfg
server.1=192.168.58.200:2881:3881
server.2=192.168.58.200:2882:3882
server.3=192.168.58.200:2883:3883
解释:server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口
4.4 启动集群
启动集群就是分别启动每个实例。
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh start
启动后我们查询一下每个实例的运行状态
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status
查询第一个服务,Mode为follower表示是跟随者(从)
再查询第二个服务Mode 为leader表示是领导者(主)
查询第三个为跟随者(从)
5.Zookeeper集群操作
5.1 客户端操作zk集群
1) 第一步启动集群,启动后查看Zookeeper进程。
jps命令 作用是显示当前所有java 进程的pid 的命令,QuorumPeerMain是zookeeper集群的启动入口类
2) 客户端连接
- 连接集群所有客户端
[root@localhost zookeeper-1]# ./bin/zkCli.sh -server 192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183
- 连接集群单个客户端
# 连接2181
[root@localhost zookeeper-1]# ./bin/zkCli.sh -server 192.168.58.200:2181
# 连接2182
[root@localhost zookeeper-1]# ./bin/zkCli.sh -server 192.168.58.200:2182
# 在2181中创建节点
[zk: 192.168.58.200:2181(CONNECTED) 0] create /test2
# 在2182中查询,发现数据已同步
[zk: 192.168.58.200:2182(CONNECTED) 0] ls /
[test1, test2, zookeeper]
以上两种方式的区别在于:
- 如果只连接单个客户端,如果当前连接的服务器挂掉,当前客户端连接也会挂掉,连接失败。
- 如果是连接所有客户端的形式,则允许集群中半数以下的服务挂掉!当半数以上服务挂掉才会停止服务,可用性更高一点!
3)集群节点信息查看
集群中的节点信息被存放在每一个节点/zookeeper/config/目录下
5.2 模拟集群异常操作
(1)首先我们先测试如果是从服务器挂掉,会怎么样
把3号服务器停掉,观察1号和2号,发现状态并没有变化
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh stop
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
由此得出结论,3个节点的集群,从服务器挂掉,集群正常
(2)我们再把1号服务器(从服务器)也停掉,查看2号(主服务器)的状态,发现已经停止运行了。
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh stop
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
由此得出结论,3个节点的集群,2个从服务器都挂掉,主服务器也无法运行。因为可运行的机器没有超过集群总数量的半数。
(3)我们再次把1号服务器启动起来,发现2号服务器又开始正常工作了。而且依然是领导者。
(4)我们把3号服务器也启动起来,把2号服务器停掉,停掉后观察1号和3号的状态。
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh stop
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status
发现新的leader产生了~
由此我们得出结论,当集群中的主服务器挂了,集群中的其他服务器会自动进行选举状态,然后产生新得leader 。
(5)我们再次测试,当我们把2号服务器重新启动起来启动后,会发生什么?2号服务器会再次成为新的领导吗?我们看结果
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status
我们会发现,2号服务器启动后依然是跟随者(从服务器),3号服务器依然是领导者(主服务器),没有撼动3号服务器的领导地位。
由此我们得出结论,当领导者产生后,再次有新服务器加入集群,不会影响到现任领导者。
5.3 curate客户端连接zookeeper集群
public class CuratorCluster {
//zookeeper连接
private final static String CLUSTER_CONNECT = "192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183";
//session超时时间
private static final int sessionTimeoutMs = 60 * 1000;
//连接超时时间
private static final int connectionTimeoutMs = 5000;
private static CuratorFramework client;
public static String getClusterConnect() {
return CLUSTER_CONNECT;
}
@Before
public void init(){
// 重试策略
RetryPolicy retryPolicy =new ExponentialBackoffRetry(3000,10);
// zookeeper连接
client = CuratorFrameworkFactory.builder()
.connectString(getClusterConnect())
.sessionTimeoutMs(60*1000)
.connectionTimeoutMs(15*1000)
.retryPolicy(retryPolicy)
.namespace("mashibing") //当前程序创建目录的根目录
.build();
// 添加监听器
client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
@Override
public void stateChanged(CuratorFramework curatorFramework, ConnectionState connectionState) {
System.out.println("连接成功!");
}
});
client.start();
}
//创建节点
public void createIfNeed(String path) throws Exception {
Stat stat = client.checkExists().forPath(path);
if(stat == null){
String s = client.create().forPath(path);
System.out.println("创建节点: " + s);
}
}
//从集群中获取数据
@Test
public void testCluster() throws Exception {
createIfNeed("/test");
//每隔一段时间 获取一次数据
while(true){
byte[] data = client.getData().forPath("/test");
System.out.println(new String(data));
TimeUnit.SECONDS.sleep(5);
}
}
}
在集群中的任意服务器节点,为test设置数据
[zk: 192.168.58.200:2181,192.168.58.200:2182,192.168.58.200:2183(CONNECTED) 2] set /mashibing/test 12345
版权归原作者 做个专注的工程师 所有, 如有侵权,请联系我们删除。