【Zookeeper之轨迹】Zookeeper 入门使用(集群使用 Docker 模拟)


1. 安装启动与单机测试

① 下载安装包

https://archive.apache.org/dist/zookeeper/zookeeper-3.5.7/
选择 apache-zookeeper-3.5.7-bin.tar.gz

② 在 Linux 服务器中解压

得到以下目录,然后新增 data 目录存放 zookeeper 数据(mkdir data

bin  conf  docs  lib  LICENSE.txt  logs  NOTICE.txt  README.md  README_packaging.txt

③ 配置模板

进入 conf 目录,修改 zoo_sample.cfg
将模板中的 dataDir 模板路径改成上边新增的 data 目录(否则存放在 /tmp 下,数据会被定期清除)

④ 启动 zookeeper

进入 bin 目录,执行 ./zkServer.sh start 启动服务端
之后再执行 ./zkCli.sh 启动客户端

在启动完服务端后,可以通过 jps 命令查看服务端是否启动完毕
如果启动成功的话,将会看到 QuorumPeerMain 进程

[root@IceClean data]# jps
6173 QuorumPeerMain

也可以通过 ./zkServer.sh status 查看服务端状态

》》》问题排查

——(1) 服务端启动有带 start,而客户端没有
如果服务端忘记带 start,此时服务并没有真正启动,这是客户端连接就会报错

——(2) 服务端异常关闭
如果服务端异常关闭了,也会导致客户端连接报错

以上两种情况,客户端连接时,都会抛出以下错误:

[myid:localhost:2181] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@1244] - Socket error occurred: localhost/127.0.0.1:2181: Connection refused

该怎么解决呢?
可以直接使用 ./zkServer.sh stop关闭服务
也可以进入 data 目录(也就是配置文件中 dataDir 所配置的那个目录),将 zookeeper_server.pid 删除,在用正确的方法启动服务就好了

——(3) 启动步骤正确了,却没有 QuorumPeerMain 进程
这时候可能是 zookeeper 3.5 特性所致,默认使用了 8080 端口,结果该端口被 tomcat 占用了,导致服务端启动失败,可以打开 logs/ 下的日志文件进行验证:

Caused by: java.io.IOException: Failed to bind to /0.0.0.0:8080

如果看到了这个异常,那多半就是这个问题了
解决方法是修改配置文件的服务端默认端口,在 zoo.cfg 下,加入以下内容:

(端口随意设置)
admin.serverPort=8888

再次以正确的方式启动,会发现服务端正常启动,客户端也正常连接啦~

》》》成功消灭


2. Zookeeper 集群操作

注意:以下操作均在集群中操作,与服务器主机无关

这里需要用到的集群,可以使用虚拟机搭
而如果使用的是云服务器,而又不想买多台服务器的(就像我,家里没矿)
就可以使用 Docker 来搭建集群了,可以看我这篇文章(挺长的,不过真的是保姆级哦)
【Docker x Hadoop】使用 Docker 搭建 Hadoop 集群(从零开始保姆级)

终于搭好集群啦,是不是很不容易?(本人搞了几天,也是跌跌碰碰摸索出来的)
那接下拉切入另一个正题叭

① 将 Zookeeper 安装到每个容器中

这个按照实际情况来,然后再每个容器中解压
scp apache-zookeeper-3.5.7-bin.tar.gz root@hadoop001:/home/hadoop
scp apache-zookeeper-3.5.7-bin.tar.gz root@hadoop002:/home/hadoop
scp apache-zookeeper-3.5.7-bin.tar.gz root@hadoop003:/home/hadoop

② 为每台 hadoop 定一个编号
在解压目录下,创建 data 文件夹(也就是自定义的那个),在里边创建 myid 文件,写入编号
即在 hadoop001 hadoop002 hadoop003 中,该文件的内容依次为 1 2 3

③ 在 zoo.cfg 文件末尾补充配置

server.1=hadoop001:2888:3888
server.2=hadoop002:2888:3888
server.3=hadoop003:2888:3888

格式可以总结为 server.A=B:C:D

  • A 表示服务器的编号,即上边设置的 myid
  • B 表示服务器的地址
  • C 表示服务器 Follower 与集群中 Leader 服务器交换信息的端口
  • D 表示在 Leader 挂掉时,用来选举新 Leader 的端口

③ 启动 zookeeper 集群

在 3 个容器中,分别执行 ./zkServer.sh 启动 zookeeper
每次执行完毕可以使用 ./zkServer status 查看状态
然后我们就会发现,但第一行个容器启动时,状态为:
在这里插入图片描述
这是因为,我们一共配置了三台服务器,但现在只启动了一台
根据 zookeeper 的规则,必须有过半的服务器处于运行状态,集群才算正常运行
而现在只启动 1 台,没有过半,所以状态值为 ERROR

我们再以相同的方式,启动 hadoop002 和 hadoop003
再回过头看状态,就会发现 hadoop002 是 leader,而其他两台是 follower
在这里插入图片描述

④ Leader 选举机制

选举存在两个阶段:服务器启动时的选举、运行过程中 leader 服务器宕机重新选取
规则如下:

  • 服务器 ID (myid):编号越大在选举算法中权重越大
  • 事务 ID (zxid):值越大说明数据越新,权重越大
  • 逻辑时钟 (epoch-logicalclock):同一轮投票过程中的逻辑时钟值是相同的,每投完一次值会增加

⑤ 集群启动与停止脚本

⑥ 写入流程

只有 Leader 有处理写请求的能力
所以有两种情况
① Leader 收到了写入请求:他将自己先写入一份,然后通知其他 follower 执行写入操作,follower 写入完毕后,将放回消息给 leader,一旦 leader 发现写入数量已经达到集群半数,便会放回给客户端写入成功的消息,然后一般继续通知其他服务器尽心感谢如操作
② 如果是 follower 收到了写入请求,由于没有执行写入操作的权限,它将会把请求交给 leader,leader 依旧先将数据写入自己的,再通知其他服务器写入,等到写入数量过半时,leader 会通知被请求的 follower 已经写入成功,再由该 follower 放回给客户端写入成功的消息

3. 命令行操作

通过 ./zkCli.sh 进入 zookeeper 后,可以进行以下操作

1)	查看指定路径的子节点__例:ls -R /
	ls [选项] <结点路径>
		-s	显示节点详情
		-R	递归显示所有子节点

2)	create <节点路径> <节点值>__例:create /xxx/node1 "node data"
		-s	创建带序号的节点
			不带序号的节点不允许重复创建,会报错
			而带序号的节点被多次创建时,由于节点带序号可重复创建,只不过序号 +1
			注意,该序号是相对与整个系统的,系统中有一个默认的计数器,决定所有带序号节点的序号
		-e	创建临时节点
			临时节点在客户端关闭后,将被清除

3)	查看节点数据
	get -s <节点路径>
	
4)	修改节点值
	set <节点路径> <新值>

5)	监控节点变化,但该节点值发生变化时,便会被监听到,但一次监听只能监听一次变化
	get -w <节点路径>

6)	监听子节点变化,铜钴盐也是注册一次监听只能生效一次
	ls -w <节点路径>

7)	删除节点
	delete <节点路径>
		这种只能删除最小节点(及空节点)
	deleteall <节点路径>
		这种能删除非空节点,即将所有子节点删除然后删除本身

get 命令包含的属性
在这里插入图片描述

第一行			节点的数据
cZxid			创建节点时的事务 ID
ctime			创建节点时的时间
mZxid			最后修改节点时的事务 ID
mtime			最后修改节点时的时间
pZxid			表示该节点的子节点列表最后一次修改的事务 ID,添加子节点或删除子节点就会影响子节点列表,但是修改子节点的数据内容则不影响该ID(注意,只有子节点列表变更了才会变更pzxid,子节点内容变更不会影响pzxid)
cversion		子节点版本号,子节点每次修改版本号 +1
dataversion		数据版本号,数据每次修改该版本号 +1
aclversion		权限版本号,权限每次修改该版本号 +1
ephemeralOwner	创建该临时节点的会话的 sessionID(如果该节点是持久节点,那么这个属性值为0)
dataLength		该节点的数据长度
numChildren		该节点拥有子节点的数量(只统计直接子节点的数量)

4. IDEA 操作

① 原生 API 操作

首先是导包:

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.7.0</version>
</dependency>

接下来是基本的使用
以下所有测试代码可以放在一个类中验证,前后代码有关联

(1)创建 zookeeper 连接
监听器注意事项:每一次设置设置只能生效一次,若想要监听一直生效,则需要在监听被触发时,再次设置监听
这里实现的逻辑为,每一次根路径(’/’)下的节点发生变化时,就打印一次节点列表(getChildren 中查看的是根路径)
然后每一次调用又相当于重新注册了一次监听,所以可以一直监听下去

/** 需要连接的服务器地址+端口" */
private static final String CONNECT_STRING = "hadoop001:2181,hadoop002:2182,hadoop003:2183";
/** 连接超时的时间,单位为毫秒,如 2000 表示 2 秒没有连接上便抛超时错误 */
private static final int SESSION_TIMEOUT = 2000;
/** zookeeper 连接 */
private ZooKeeper zooKeeper;

@Before
public void init() throws IOException {
    zooKeeper = new ZooKeeper(CONNECT_STRING, SESSION_TIMEOUT, watchedEvent -> {
        try {
            getChildren();
        } catch (KeeperException | InterruptedException e) {
            e.printStackTrace();
        }
    });
}

(2)创建一个节点
第一个参数为节点路径,必须写全路径
第二个为节点的值,需要转化为 byte[]
第三个为节点的访问权限,OPEN_ACL_UNSAFE 表示任何人可以访问
最后一个为节点类型,PERSISTENT 表示永久节点,其他的如下

PERSISTENT_SEQUENTIAL:持久化并且带序列号的节点
EPHEMERAL:临时节点
EPHEMERAL_SEQUENTIAL:临时并且带序列号的节点

@Test
public void create() throws KeeperException, InterruptedException {
    zooKeeper.create("/idea5", "data in idea".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}

(3) 获取目标节点的直接子节点
第一个参数为目标节点的全路径(这里获取的是 根目录)
第二个参数为是否监听,设置为 true 表示使用默认监听器(即在 init 中设置的监听器),也可以手动设置局部监听器
相当于使用 init 中的监视器,监视所指定的根目录下的变化情况
这部分和 init 配合使用哦

@Test
public void getChildren() throws KeeperException, InterruptedException {
    List<String> children = zooKeeper.getChildren("/", true);
    System.out.println("当前子节点:" + children);
}

@After
public void waitListen() throws InterruptedException {
    // 延时,这里是为了看到监听器的效果
    // 可以在这段时间内在服务器中添加节点,便可以在 idea 控制台中看到监听器输出信息
    Thread.sleep(50000L);
}

(3) 判断子节点是否存在

@Test
public void exist() throws KeeperException, InterruptedException {
    Stat exists = zooKeeper.exists("/idea3", false);
    System.out.println("存在吗:" + exists);
}

② 使用 Curator 操作

首先是导包:

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.7.0</version>
</dependency>

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>5.1.0</version>
</dependency>

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>5.1.0</version>
</dependency>

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-client</artifactId>
    <version>5.1.0</version>
</dependency>

接下来是基本的使用,这部分代码有上边原生的衍生而来,同时结合了 Curator 的特性
同样,以下所有测试代码可以放在一个类中验证,前后代码有关联

(1)创建 zookeeper 连接
retryPolicy 为重试模式,表示连接失败时需要怎么做,必须有,缺失时报错
namespace 为命名空间,表示接下来的一系列操作全都是基于 /idea 这个目录的
(注意啦,最前边默认就加了 / 所以不是写 /idea 而是直接 idea)

@Before
public void init() {
    // 创建客户端
    zooKeeper = CuratorFrameworkFactory.builder()
            .connectString(CONNECT_STRING)
            .sessionTimeoutMs(SESSION_TIMEOUT)
            .namespace("idea")
            .retryPolicy(new ExponentialBackoffRetry(3000, 3))
            .build();

    // 接着可以启动
    zooKeeper.start();
}
    

(2)创建一个节点
与之前不同的是,该节点是基于 /idea 节点的(前边 init 设置的)
creatingParentContainersIfNeeded 可以在父节点不存在时,自动创建父节点
(这里创建后的节点路径为 /idea/father/idea-msg1)
withMode 为设置节点的类型
withACL 为设置所需的访问权限

@Test
public void create() throws Exception {
    System.out.println(zooKeeper);
    String s = zooKeeper.create()
            .creatingParentContainersIfNeeded()
            .withMode(CreateMode.PERSISTENT)
            .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
            .forPath("/father/idea-msg1", "data in /idea/idea-msg1".getBytes());
    System.out.println("创建结果:" + s);
}

(3)获取节点数据

@Test
public void getData() throws Exception {
    byte[] dataByte = zooKeeper.getData().forPath("/idea-msg1");
    String data = new String(dataByte);
    System.out.println("/idea-msg1 数据:" + data);
}

(4)删除节点
默认只能删除空节点
加了 deletingChildrenIfNeeded 后为递归删除,可删除非空节点
guaranteed 为保证想要删除的节点被强制删除

@Test
public void delete() throws Exception {
    zooKeeper.delete()
            .guaranteed()
            .deletingChildrenIfNeeded()
            .forPath("/idea-msg1");
}

(5) 获取目标节点的直接子节点
这里写的根路径 /,是相对于 /idea 的根路径,所以实际获得的是 /idea 的子节点

    @Test
    public void getChildren() throws Exception {
        List<String> children = zooKeeper.getChildren().forPath("/");
        System.out.println("当前子节点:" + children);
    }

(6) 检测节点是否存在

@Test
public void exist() throws Exception {
    Stat exists = zooKeeper.checkExists().forPath("/idea-msg1");
    System.out.println("存在吗:" + exists);
}


星铭传说(IceClean)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寒冰小澈IceClean

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值