一文带你学会zookeeper(了解、安装、集群、使用)

打印 上一主题 下一主题

主题 654|帖子 654|积分 1962

概述
        1、zookeeper是一个开源的分布式的服务协调框架(Apache项目)
        2、zookeeper从设计模式的角度来理解:是一个基于观察者模式的分布式服务管理框架,他负责存储和管理大家都关心的数据,然后接收观察者的注册,一旦这些数据发生变革,zookeeper就将负责通知已经在zookeeper是哪个注册的那些观察者做出相应的反应。


特点
        1、zookeeper为一个领导者(Leader),多个跟随者(Follower)构成的集群 。
        2、集群中只要有半数以上节点存活,zookeeper集群就能正常服务,所以zookeeper适合安装奇数台服务器。
        3、全局数据一致:每个Server(服务节点)保存一份相同的数据副本,Client无论连接到哪个server,数据都是一致的。
        4、更新请求次序实行,比如来自同一个客户端的多个请求,更新实行次序按照其发送的前后次序依次实行。
        5、数据更新原子性,依次数据要么更新成功,要么失败。
        6、实时性,在一定时间范围内,Client能读到最新的数据。



数据结构
        zookeeper数据模型的结构与Unix文件体系很类似,团体上可以看作是一颗树,每个节点称为一个ZNode,每一个ZNode默认能够存储1MB(配置信息)的数据,每个ZNode都可以通过其路径唯一标识。




应用场景:
统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等等。
统一配置管理:
        ① 一般要求一个集群中,所有节点的配置信息是一致的,比如kafuka集群。
        ② 对配置文件修改后盼望能够快速同步到各个节点上。
        ③ 可将配置信息写入zookeeper上的ZNode。
        ④ 各个客户端服务器监听这个ZNode
        ⑤ 一但ZNode中的数据被修改,zookeeper将通知各个客户端服务器。
统一集群管理:
        ① 分布式环境中,实时把握每个节点的状态是须要的、可以根据节点实时状态做出调整
        ② zookeeper可将节点信息写入zookeeper上的一个ZNode。
        ③ 监听这个ZNode可获取它的实时状态变革。
服务器节点动态上下线:
        ① 如果某台服务节点下线,能随时洞察到变革,并举行通知



软负载均衡:
        ①在zookeeper中记载每台服务器的访问数,让访问数据最少的服务器去处理最新的客户端请求


Zookeeper安装

        zookeeper下载地点:Index of /dist/zookeeper
        正式生产环境比较稳固的版本:3.5.7


安装:(需要提前安装Java环境,这里自行百度,很简单)
        1.将apache-zookeeper-3.5.7-bin.tar.gz包使用 xftp工具上传到Linux中的/opt/software目次下



        2.使用命令:
  1. # 指定将zookeeper解压安装到/opt/module目录下
  2. tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz -C /opt/module/
复制代码

        3.修改文件名,一般zookeeper的文件名太长我们手动修改一下
  1. # 修改文件名称
  2. [root@node1-zookeep module]# mv apache-zookeeper-3.5.7-bin/ zookeeper-3.5.7
  3. # 查看修改后的名称
  4. [root@node1-zookeep module]# ll
  5. 总用量 0
  6. drwxr-xr-x. 6 root root 134 9月  19 23:56 zookeeper-3.5.7
复制代码

        4.修改 /opt/module/zookeeper-3.5.7/conf,也就是zookeeper安装目次下conf目次中的配置文件名称
  1. # 原文件名称
  2. -rw-r--r--. 1 502 games  922 2月   7 2020 zoo_sample.cfg
  3. # 使用mv命令修改
  4. [root@node1-zookeep conf]# mv zoo_sample.cfg  zoo.cfg
  5. [root@node1-zookeep conf]# ll
  6. 总用量 12
  7. -rw-r--r--. 1 502 games  535 5月   4 2018 configuration.xsl
  8. -rw-r--r--. 1 502 games 2712 2月   7 2020 log4j.properties
  9. # 修改后的文件名称
  10. -rw-r--r--. 1 502 games  922 2月   7 2020 zoo.cfg
复制代码

        5.使用vim编辑zoo.cfg配置文件,这里重要先修改zookeeper快照目次,目次中存在的/tmp/**目次只是官方的示例,需要我们本身创建目次存放



        5.1创建存储目次(我放在zookeeper的安装目次下 /opt/module/zookeeper-3.5.7):
  1. # 创建‘zkData’目录
  2. [root@node1-zookeep zookeeper-3.5.7]# mkdir zkData
  3. # 进入目录后,查看当前创建目录所在位置
  4. [root@node1-zookeep zkData]# pwd
  5. /opt/module/zookeeper-3.5.7/zkData
  6. # 查看目录下所有文件
  7. [root@node1-zookeep zookeeper-3.5.7]# ll
  8. 总用量 32
  9. drwxr-xr-x. 2  502 games   232 2月  10 2020 bin
  10. drwxr-xr-x. 2  502 games    70 9月  20 00:18 conf
  11. ...
  12. drwxr-xr-x. 2 root root      6 9月  20 00:18 zkData
复制代码

        5.2将创建的目次所在位置配置到zoo.cfg文件中



启动zookeeper服务端:
  1. # 1.进入zookeeper安装目录的bin目录中
  2. [root@node1-zookeep zkData]# cd ../bin
  3. # 2.查看服务对应的可执行文件
  4. [root@node1-zookeep bin]# ls
  5. README.txt    zkCli.cmd  zkEnv.cmd  zkServer.cmd            zkServer.sh          zkTxnLogToolkit.sh
  6. zkCleanup.sh  zkCli.sh   zkEnv.sh   zkServer-initialize.sh  zkTxnLogToolkit.cmd
  7. # 3.使用./文件名 start 命令启动zookeeper服务
  8. [root@node1-zookeep bin]# ./zkServer.sh start
  9. /usr/bin/java
  10. ZooKeeper JMX enabled by default
  11. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  12. Starting zookeeper ... STARTED
  13. # 4.查看zookeeper启动服务的进程
  14. [root@node1-zookeep bin]# jps -l
  15. 8743 sun.tools.jps.Jps
  16. 8728 org.apache.zookeeper.server.quorum.QuorumPeerMain
  17. [root@node1-zookeep bin]#
  18. # 5.查看zookeeper状态
  19. [root@node1-zookeep bin]# ./zkServer.sh status
  20. /usr/bin/java
  21. ZooKeeper JMX enabled by default
  22. # 6.启动服务使用的配置文件位置
  23. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  24. Client port found: 2181. Client address: localhost.
  25. # 本地模式
  26. Mode: standalone
复制代码

启动zookeeper客户端:
  1. # 1.查看zookeeper 中的bin目录
  2. [root@node1-zookeep bin]# ls
  3. README.txt    zkCli.cmd  zkEnv.cmd  zkServer.cmd            zkServer.sh          zkTxnLogToolkit.sh
  4. zkCleanup.sh  zkCli.sh   zkEnv.sh   zkServer-initialize.sh  zkTxnLogToolkit.cmd
  5. # 2.启动zookeeper客户端,注意不需要start命令,直接启动即可
  6. [root@node1-zookeep bin]# ./zkCli.sh
  7. /usr/bin/java
  8. Connecting to localhost:2181
  9. 2023-09-20 00:31:47,347 [myid:] - INFO  [main:Environment@109] - Client environment:zookeeper.version=3.5.7-f0fdd52973d373ffd9c86b81d99842dc2c7f660e, built on 02/10/2020 11:30 GMT
  10. 2023-09-20 00:31:47,349 [myid:] - INFO  [main:Environment@109] - Client environment:host.name=node1-zookeep
  11. 2023-09-20 00:31:47,349 [myid:] - INFO  [main:Environment@109] - Client environment:java.version=1.8.0_382
  12. 2023-09-20 00:31:47,351 [myid:] - INFO  [main:Environment@109] - Client environment:java.vendor=Red Hat, Inc.
  13. 2023-09-20 00:31:47,351 [myid:] - INFO  [main:Environment@109] - Client environment:java.home=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.382.b05-1.el7_9.x86_64/jre
  14. ...
复制代码

退出zookeeper客户端:
  1. # 退出客户端 quit 命令
  2. [zk: localhost:2181(CONNECTED) 3] quit
  3. WATCHER::
  4. WatchedEvent state:Closed type:None path:null
  5. 2023-09-20 00:36:28,568 [myid:] - INFO  [main:ZooKeeper@1422] - Session: 0x1000028a4700001 closed
  6. 2023-09-20 00:36:28,568 [myid:] - INFO  [main-EventThread:ClientCnxn$EventThread@524] - EventThread shut down for session: 0x1000028a4700001
复制代码

停止zookeeper服务:
  1. # 1.停止zookeeper
  2. [root@node1-zookeep bin]# ./zkServer.sh stop
  3. /usr/bin/java
  4. ZooKeeper JMX enabled by default
  5. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  6. Stopping zookeeper ... STOPPED
  7. # 2.再次查看进程,zookeeper已经不存在了
  8. [root@node1-zookeep bin]# jps -l
  9. 8953 sun.tools.jps.Jps
复制代码

zoo.cfg文件配置参数:
  1. # The number of milliseconds of each tick
  2. # 通信心跳时间,zookeeper服务器与客户端心跳时间,单位:毫秒
  3. tickTime=2000
  4. # The number of ticks that the initial
  5. # synchronization phase can take
  6. # Leader与Follower初始通信时限(第一次两者建立通信的时候能容忍的最多的心跳次数 tickTime的数量)
  7. initLimit=10
  8. # The number of ticks that can pass between
  9. # sending a request and getting an acknowledgement
  10. # Leader与Follower同步通信时限,两者之间时间如果超过 syncLimit * tickTime
  11. # Leader 认为 Follower 已经挂掉,从服务器列表中删除Follwer
  12. syncLimit=5
  13. # the directory where the snapshot is stored.
  14. # do not use /tmp for storage, /tmp here is just
  15. # example sakes.
  16. # 保存zookeeper中的数据。
  17. # 注意:默认的tmp目录,容易被Linux系统定期删除,所以一般不使用默认的tmp目录
  18. dataDir=/opt/module/zookeeper-3.5.7/zkData
  19. # the port at which the clients will connect
  20. # 客户端连接端口,通常不做修改
  21. clientPort=2181
  22. # the maximum number of client connections.
  23. # increase this if you need to handle more clients
  24. # 客户端最大连接数
  25. #maxClientCnxns=60
复制代码

zookeeper集群


        实现集群的高可用。实际生产环境的集群搭建,需要我们使用Zookeeper的协调服务(如心跳机制)来保证集群节点的高可用,并实现故障节点自动切换、数据自动迁徙。
        当前演示的为在三台服务器(192.168.188.135(节点一),192.168.188.136(节点二),192.168.188.137(节点三))上安装摆设zookeeper(每台安装步调同上)

1.配置服务器编号
        ①在创建好的 /opt/module/zookeeper-3.5.7/zkData 目次下创建一个myid的文件
添加myid文件时,一定要在Linux里面创建,在notepad或者使用xftp用文本编辑创建很可能会导致乱码
  1. # 使用vi 命令创建myid文件,并且在文件中添加与server对应的编号
  2. # 注意上下不要有空行,左右不要有空格!
  3. [root@node1-zookeep zkData]# vi myid
  4. [root@node1-zookeep zkData]# cat myid
  5. 1
复制代码

        ②拷贝安装配置好的zookeeper目次到其他服务器上,这里有两种方式,
- 方式一:直接拷贝zookeeper的安装目次或者按照上面单点的安装方式在多太服务器上举行安装。(比较繁琐)
- 方式二:使用xsync 集群分发脚本(或者使用scp -r 目次名 目标IP地点 目标目次的方式依次分发也可以)如果知道怎么使用xsync,请看这里集群分发脚本xsync简单使用-CSDN博客
  1. # 拷贝zookeeper安装到多太服务器
  2. [root@node1-zookeep zkData]# xsync zookeeper-3.5.7
复制代码

③ 完成之后将每台服务器上的myid文件设置对应唯一标识
  1. # 节点一的myid文件改为为1
  2. [root@node1-zookeep zookeeper-3.5.7]# vim zkData/myid
  3. [root@node1-zookeep zookeeper-3.5.7]# cat  zkData/myid
  4. 1
  5. # 节点二的myid文件改为为2
  6. [root@node1-zookeep zookeeper-3.5.7]# vim zkData/myid
  7. [root@node1-zookeep zookeeper-3.5.7]# cat zkData/myid
  8. 2
  9. # 节点三的myid文件改为为3
  10. [root@node1-zookeep zookeeper-3.5.7]# vim zkData/myid
  11. [root@node1-zookeep zookeeper-3.5.7]# cat zkData/myid
  12. 3
复制代码

2.添加配置
增补:配置IP映射名(可以不配置,但是建议照旧配置好,配置 IP 映射名可以提高集群的可维护性和可扩展性。它使得节点更易于辨认和引用,而且在节点的 IP 地点发生变革时也能保持一致。)
配置IP映射的方式:


  • 打开每个zookeeper服务器节点的主机文件,一般在位置在: /etc/hosts
  • 在每个节点的主机文件最后,添加以下条目:
  1. # IP地址   主机名(自己定义)
复制代码


  • 将 IP地点 更换为节点的实际 IP 地点,将 主机名 更换为您盼望使用的节点主机名。确保在每个节点上都添加了相同的条目,并将每个节点的 IP 地点和主机名逐一对应。
  1. #例如,假设您有三个节点,节点1 的 IP 地址为 192.168.0.1,节点2 的 IP 地址为 192.168.0.2,节点3 的 IP 地址为 192.168.0.3,您可以在每个节点的主机文件中添加以下条目:
  2. 192.168.0.1   node1_zookeeper
  3. 192.168.0.2   node2_zookeeper
  4. 192.168.0.3   node3_zookeeper
  5. # 保存并关闭每个节点的主机文件。
复制代码

在 ZooKeeper 配置文件 zoo.cfg 中的 server.X 配置项中,使用相应的主机名更换 IP 地点,以便引用节点。
这样配置后,ZooKeeper 节点将使用主机名来通信和交流,而不是直接使用 IP 地点。
        ①在每一个节点服务器上的zookeeper配置文件(../conf/zoo.cfg)中增加如下配置
  1. # 如果配置IP的映射就使用hostname
  2. server.1=node1_zookeeper:2888:3888
  3. server.2=node2_zookeeper:2888:3888
  4. server.3=node3_zookeeper:2888:3888
  5. # 如果没有配置,就直接使用服务的IP即可
  6. server.1=192.168.188.135:2888:3888
  7. server.2=192.168.188.136:2888:3888
  8. server.3=192.168.188.137:2888:3888
复制代码

        ②# 配置详解 server.A=B:C
                A:是一个数字,表示这是第几号服务器,就是myid中写的唯一标识,集群模式下配置一个文件myid,这个文件在zkData目次下,这个文件里面有一个数据就是A的值,zookeeper启动时读取该文件,拿到里面的数据与zoo.cfg里面的配置信息做对你,从而判断是哪个server
                B:每个节点服务器的地点,如果修改了hostname(/etc/hostname),那么使用配置好的hostname,如果没有修改,那么就直接使用IP
                C:是这个Follower服务器与集群中的Leader服务器互换信息的端口;
                D:万一集群中的Leader服务器挂了,需要一个端口来重新选举,选出一个新的Leader,而这个端口就是用来实行选举时服务器相互通信的端口

        ③ 然后在各个服务器节点上配置环境变量(不配置也可以,对集群环境没有影响,不外这样就无法全局访问了,本身根据需求选择)
  1. # vim 命令编辑profile文件
  2. [root@node1-zookeep conf]# vim /etc/profile
  3. #在profile文件最末尾添加如下配置:
  4. # zookeeper envionment variables
  5. export ZOOKEEPER_HOME=/opt/module/zookeeper-3.5.7
  6. export PATH=$ZOOKEEPER_HOME/bin:$PATH
  7. # 激活环境变量 source /etc/profile
  8. [root@node1-zookeep /]# source /etc/profile
复制代码

④开放防火墙端口,如果关闭防火墙或者开放端口,可能会导致节点之间连接失败导致启动失败。


  • 启动Firewalld服务:sudo systemctl start firewalld
  • 停止Firewalld服务:sudo systemctl stop firewalld
  • 设置开机启动:sudo systemctl enable firewalld
  • 停止开机启动:sudo systemctl disable firewalld
  • 查看状态:sudo firewall-cmd --state
  • 查看所有开放端口:sudo firewall-cmd --list-ports
  • 开放端口:sudo firewall-cmd --add-port=端口号/协议 --permanent(永久开放)

    • 例如:sudo firewall-cmd --add-port=80/tcp --permanent(永久开放)

  • 移除开放端口:sudo firewall-cmd --remove-port=端口号/协议 --permanent(例如:sudo firewall-cmd --remove-port=80/tcp --permanent)
  • 重新加载配置:sudo firewall-cmd --reload
  1. # 开放指定端口
  2. [root@node1-zookeep conf]# sudo firewall-cmd --add-port=2888/tcp
  3. success
  4. [root@node1-zookeep conf]# sudo firewall-cmd --add-port=3888/tcp
  5. success
  6. [root@node1-zookeep conf]# sudo firewall-cmd --add-port=2181/tcp
  7. success
复制代码

  1. # 直接关闭防火墙(正式环境不建议)
  2. [root@node1-zookeep conf]# sudo systemctl stop firewalld
  3. success
复制代码

3.启动服务
1.如果是配置了环境的启动方式,三个节点全部启动zkServer
  1. # 使用zkServer.sh start 即可
  2. [root@node1-zookeep zookeeper-3.5.7]# zkServer.sh start
  3. /usr/bin/java
  4. ZooKeeper JMX enabled by default
  5. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  6. Starting zookeeper ... STARTED
  7. # 补充:
  8. # 停止命令:zkServer.sh stop
  9. # 重启命令zkServer.sh restart
复制代码

2.没有配置环境的启动方式,三个节点全部启动zkServer
  1. # 进入zookeeper的安装目录
  2. [root@node1-zookeep ~]# cd /opt/module/zookeeper-3.5.7/bin
  3. # 查看可执行文件
  4. [root@node1-zookeep bin]# ls
  5. README.txt    zkCli.cmd  zkEnv.cmd  zkServer.cmd            zkServer.sh          zkTxnLogToolkit.sh
  6. zkCleanup.sh  zkCli.sh   zkEnv.sh   zkServer-initialize.sh  zkTxnLogToolkit.cmd
  7. # 使用  ‘./要执行的文件 start’
  8. [root@node1-zookeep bin]# ./zkServer.sh start
  9. # 补充
  10. # 停止命令: ./zkServer.sh stop
  11. # 重启命令: ./zkServer.sh restart
复制代码

3.查看集群各节点状态
  1. # 查看集群节点状态
  2. [root@node1-zookeep ~]# zkServer.sh status
  3. #一个节点输出如下:
  4. /usr/bin/java
  5. ZooKeeper JMX enabled by default
  6. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  7. Client port found: 2182. Client address: localhost.
  8. Mode: leader
  9. #另外两个节点输出如下:
  10. [root@node1-zookeep ~]# zkServer.sh status
  11. /usr/bin/java
  12. ZooKeeper JMX enabled by default
  13. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  14. Client port found: 2182. Client address: localhost.
  15. Mode: follower
复制代码

有一台服务器的zk Mode为leader ,另外两台服务器的zk Mode为follower,如果配置了observer节点,则会有一台服务器的zk Mode为observer。

4.查看日志
zookeeper选举机制



投票会优先投给myid比本身大的服务器节点,直到出现leader
服务器1启动:
                投票效果:服务器1 :1票
                不够半数以上(3票),选举无法完成,服务器1状态保持为LOOKING.
服务器2 启动:
                投票效果:服务器1 :0票,服务器2 :2票
                不够半数以上(3票),选举无法完成,服务器1和2状态保持为LOOKING.
服务器3 启动:
                投票效果:服务器1 :0票,服务器2 :0票,服务器3 :3票
                此时有服务器票数凌驾半数(3票),选举服务器3为Leader,服务器1和2状态保持为Following ,服务器三为Leading.
服务器4 启动:
                投票效果:服务器1和2 :0票,服务器2 :0票,服务器3 :3票,服务器4:1票
                此时服务器1,2,3已经不是LOOKING状态,leader已经产生,即使服务器4的myid更大,也不会更改选票。
服务器5启动:
                同服务器4一样。



  • SID:服务器ID,用来标识一胎zookeeper几圈中的机器,每台机器的SID不能重复,和myid一致。
  • ZXID:事务id,zxid是一个事务id,用来标识一次服务器状态的变更,在某一时刻集群中的每台机器的zxid值不一定完全一致,这和zookeeper服务器对于客户端更新请求的处理逻辑有关。
  • Epoch:每个Leader的任期代号,如果没有Leader时同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加
1.当zookeeper集群各种的一胎服务器出现两种环境之一,就会开始进入Leader选举:


  • 服务器初始化启动。
  • 服务器运行期间无法和Leader保持连接
2.当一台机器进入Leader选举流程时,机器试图去选举Leader时,当前集群就会处于以下两种环境:

  • 集群中原来就存在一个Leader: 但是某台节点服务器由于连接不上而试图去选举Leader时,会被告知当前服务器的Leader信息,对于该机器来说,仅仅需要和Leader机器创建连接,并举行状态同步即可
  • 集群中确实不存在Leader:如果LEader不测挂掉,因此就会从新开始选举,此时选举的规则为:

    • EPOCH(任期代号)大的直接选举为Leader
    • EPOCH(任期代号)相同,事务ID大的选举为Leader
    • 如果任期代号、事务ID照旧没有选举出来的话,服务器ID大的选举为Leader

zookeeper启动停止脚本


1.创建一个文件(我放在/usr/local/bin,一般会在/home/bin目次下,自行选择)
  1. # 查看当前所在目录
  2. [root@node1-zookeep bin]# pwd
  3. /usr/local/bin
  4. # 创建zk.sh文件
  5. [root@node1-zookeep bin]# vim zk.sh
复制代码
        zookeeper实行脚本编写(zk.sh文件) 
  1. #!/bin/bash
  2. # 传入的命令
  3. case $1 in
  4. "start"){
  5.     # 循环集群中所有节点
  6.           for i in node1-zookeeper node2-zookeeper node3-zookeeper
  7.     do
  8.                      # 输出信息
  9.          echo "------------------$i start ----------------------"
  10.          # 通过ssh 执行,ssh $i "zookeeper安装目录下的执行文件"
  11.          ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh start"
  12.     done     
  13. };;
  14. "stop"){
  15.           for i in node1-zookeeper node2-zookeeper node3-zookeeper
  16.     do
  17.          echo "------------------$i stop ----------------------"
  18.          ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh stop"
  19.     done     
  20. };;
  21. "status"){
  22.           for i in node1-zookeeper node2-zookeeper node3-zookeeper
  23.     do
  24.          echo "------------------$i status ----------------------"
  25.          ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh status"
  26.     done     
  27. };;
  28. esac
复制代码

2.增加脚本的实行权限(直接给最大权限所有用户能访问,有限制不同用户需求本身研究Linux权限)
  1. # 添加前
  2. [root@node1-zookeep bin]# ll
  3. -rw-r--r--. 1 root root 822 9月  21 05:21 zookeeper.sh
  4. # 添加执行权限
  5. [root@node1-zookeep bin]# chmod 777 zookeeper.sh
  6. # 添加后
  7. [root@node1-zookeep bin]# ll
  8. -rwxrwxrwx. 1 root root 822 9月  21 05:21 zookeeper.sh
复制代码

3.实行集群脚本


  • zookeeper集群实行停止脚本
  1. [root@node1-zookeep bin]# zookeeper.sh  stop
  2. ------------------node1-zookeeper stop ----------------------
  3. root@node1-zookeeper's password:
  4. # 这里我没有配置免密,如果提示就输入自己的登录密码就好
  5. /usr/bin/java
  6. ZooKeeper JMX enabled by default
  7. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  8. Stopping zookeeper ... STOPPED
  9. ------------------node2-zookeeper stop ----------------------
  10. /usr/bin/java
  11. ZooKeeper JMX enabled by default
  12. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  13. Stopping zookeeper ... STOPPED
  14. ------------------node3-zookeeper stop ----------------------
  15. /usr/bin/java
  16. ZooKeeper JMX enabled by default
  17. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  18. Stopping zookeeper ... STOPPED
复制代码


  • zookeeper集群实行启动脚本
  1. [root@node1-zookeep bin]# zookeeper.sh  start
  2. ------------------node1-zookeeper start ----------------------
  3. root@node1-zookeeper's password:
  4. /usr/bin/java
  5. ZooKeeper JMX enabled by default
  6. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  7. Starting zookeeper ... STARTED
  8. ------------------node2-zookeeper start ----------------------
  9. /usr/bin/java
  10. ZooKeeper JMX enabled by default
  11. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  12. Starting zookeeper ... STARTED
  13. ------------------node3-zookeeper start ----------------------
  14. /usr/bin/java
  15. ZooKeeper JMX enabled by default
  16. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  17. Starting zookeeper ... STARTED
复制代码


  • 启动状态下,实行zookeeper集群状态(status)脚本 
  1. root@node1-zookeep bin]# zookeeper.sh  status
  2. ------------------node1-zookeeper status ----------------------
  3. root@node1-zookeeper's password:
  4. # 这里我没有配置免密,如果提示就输入自己的登录密码就好
  5. /usr/bin/java
  6. ZooKeeper JMX enabled by default
  7. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  8. Client port found: 2182. Client address: localhost.
  9. Mode: follower
  10. ------------------node2-zookeeper status ----------------------
  11. /usr/bin/java
  12. ZooKeeper JMX enabled by default
  13. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  14. Client port found: 2182. Client address: localhost.
  15. Error contacting service. It is probably not running.
  16. ------------------node3-zookeeper status ----------------------
  17. /usr/bin/java
  18. ZooKeeper JMX enabled by default
  19. Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
  20. Client port found: 2182. Client address: localhost.
  21. Mode: leader
复制代码

zookeeper客户端命令行
1.连接客户端
  1. # 查看可执行文件
  2. [root@node1-zookeep bin]# ls
  3. README.txt    zkCli.cmd  zkEnv.cmd  zkServer.cmd            zkServer.sh          zkTxnLogToolkit.sh
  4. zkCleanup.sh  zkCli.sh   zkEnv.sh   zkServer-initialize.sh  zkTxnLogToolkit.cmd
  5. # 启动本地客户端的命令
  6. [root@node1-zookeep bin]# zkCli.sh
  7. /usr/bin/java
  8. Connecting to localhost:2181
  9. 2023-09-21 08:12:06,571 [myid:] - INFO  [main:Environment@109] - Client environment:zookeeper.version=3.5.7-f0fdd52973d373ffd9c86b81d99842dc2c7f660e, built
  10. ...
复制代码
   启动远程客户端(其他节点)
  1. # 这里我们以启动节点2做演示
  2. [root@node1-zookeep bin]# zkCli.sh -server node2_zookeeper:2181
  3. /usr/bin/java
  4. Connecting to node2_zookeeper:2181
  5. 2023-09-21 08:18:06,417 [myid:] - INFO  [main:Environment@109] - Client environment:zookeeper.version=3.5.7-f0fdd52973d373ffd9c86b81d99842dc2c7f660e, built on 02/10/2020 11:30 GMT
  6. ...
复制代码

2.节点类型



创建长期节点:                                                                   


  • 如果是正常创建长期节点,使用create 节点名称 内容 即可,如: create /test01 "this is test node"
  • 如果是创建携带编号的长期节点,则添加[ -s ]参数即可: create -s /test01 "this is test node"
  1. # 创建一个节点名为test01,内容是this is test node
  2. [zk: node2-zookeeper:2181(CONNECTED) 5] create  /test01 "this is test node"
  3. Created /test01
  4. # 使用ls 查看所有节点
  5. [zk: node2-zookeeper:2181(CONNECTED) 6] ls /
  6. [test01, zookeeper]
  7. # 也可以创建有多个层级的节点
  8. # 如:在test01中再次创建一个demo01节点,内容是this is second level
  9. [zk: node2-zookeeper:2181(CONNECTED) 7] create /test01/demo01 "this is second level"
  10. Created /test01/demo01
  11. # 查看所有节点
  12. [zk: node2-zookeeper:2181(CONNECTED) 8] ls /
  13. [test01, zookeeper]
  14. # 查看test01中demo01
  15. [zk: node2-zookeeper:2181(CONNECTED) 9] ls /test01
  16. [demo01]
  17. # 获取到test01中的信息
  18. [zk: node2-zookeeper:2181(CONNECTED) 10] get -s /test01
  19. this is test node
  20. cZxid = 0xa00000006 #创建节点时的事务ID
  21. ctime = Thu Sep 21 09:46:24 CST 2023 #创建时间
  22. mZxid = 0xa00000006 #最后一次创建的事务ID
  23. mtime = Thu Sep 21 09:46:24 CST 2023 #后一次事务的时间
  24. pZxid = 0xa00000007 #子节点的事务ID
  25. cversion = 1 #版本号
  26. dataVersion = 0
  27. aclVersion = 0
  28. ephemeralOwner = 0x0 #零时节点
  29. dataLength = 17 # 数据长度
  30. numChildren = 1 #对应的子节点个数:一个demo01子节点
复制代码
创建零时节点:
  1. # 创建零时节点,需要携带参数[-e]
  2. [zk: node2-zookeeper:2181(CONNECTED) 12] create -e /test03 "...点"
  3. Created /test03
  4. # 创建携带编号的零时节点,需要携带参数[-e]和[-s]
  5. [zk: node2-zookeeper:2181(CONNECTED) 13] create -e -s  /test02 "临时节点"
  6. Created /test020000000002
  7. #查看创建的节点
  8. [zk: node2-zookeeper:2181(CONNECTED) 15] ls /
  9. [test01, test03, test020000000002, zookeeper]
  10. #获取带参数的零时节点和不带参数的零时节点的内容
  11. [zk: node2-zookeeper:2181(CONNECTED) 18] get /test020000000002
  12. 临时节点
  13. [zk: node2-zookeeper:2181(CONNECTED) 19] get /test03
  14. 临时节点
  15. # 或添加[-s]参数查看不带参数的零时节点的完整信息
  16. [zk: node2-zookeeper:2181(CONNECTED) 20] get -s /test03
  17. 临时节点
  18. cZxid = 0xa00000008
  19. ctime = Thu Sep 21 10:06:00 CST 2023
  20. mZxid = 0xa00000008
  21. mtime = Thu Sep 21 10:06:00 CST 2023
  22. pZxid = 0xa00000008
  23. cversion = 0
  24. dataVersion = 0
  25. aclVersion = 0
  26. ephemeralOwner = 0x20003eee5910000
  27. dataLength = 12
  28. numChildren = 0
  29. # 退出客户端
  30. [zk: node2-zookeeper:2181(CONNECTED) 21] quit
  31. WATCHER::
  32. WatchedEvent state:Closed type:None path:null
  33. 2023-09-21 10:15:57,436 [myid:] - INFO  [main...
  34. # 再次连接
  35. [root@node1-zookeeper bin]# zkCli.sh -server node2-zookeeper:2181
  36. /usr/bin/java
  37. Connecting to node2-zookeeper:2181
  38. 2023-09-21 10:16:06,267 [myid:] - INFO  [main:Environment@109] - Client environment:zookeeper.version=3.5.7-f0fdd52973d373ffd9c86b81d99842dc2c7f660e, built on 02/10/2020 11:30 GMT
  39. 2023-09-21 10:16:06,268 [myid:] - INFO  [main:Environment@109] - Client environment:host.name=node1-zookeeper
  40. 2023-09-21 10:16:06,269 [myid:] - INFO...
  41. # 查看节点
  42. [zk: node2-zookeeper:2181(CONNECTED) 0] ls /
  43. [test01, zookeeper]
  44. # 发现创建的test03和test020000000002零时节点不在了
复制代码


  • 一旦zookeeper客户端断开再次连接,零时节点就不存在了
修改节点的值
  1. # 查看所有节点
  2. [zk: node2-zookeeper:2181(CONNECTED) 0] ls /
  3. [test01, zookeeper]
  4. # 获取test01的值
  5. [zk: node2-zookeeper:2181(CONNECTED) 1] get /test01
  6. this is test node
  7. # 修改test01节点的值为 "这是一个节点",有时候输入中文是显示为“...”
  8. [zk: node2-zookeeper:2181(CONNECTED) 2] set /test01 "这是一个节点"
  9. # 再次获取
  10. [zk: node2-zookeeper:2181(CONNECTED) 3] get /test01
  11. 这是一个节点
复制代码
3.监听器


  • 首先有一个main()线程,在main线程中创建zookeeper客户端,这时就会创建两个线程,一个负责网络连接通信(connnet),一个负责监听(listener)。
  • 通过connect线程将注册的监听事件发送给zookeeper。
  • 在zookeeper的注册监听器列表中奖注册的监听事件添加到列表中。
  • zookeeper监听到有数据或路径变革,就会将这个消息发送给listener
  • listener线程内部调用了process()方法。



注意:每次设置监听时,无论是监听数据照旧监听节点变革,注册一次监听只会有一次收效,如果需要再次监听,需要再次注册
① 监听数据的变革
  1. #在node3_zookeeper中创建var_watch节点
  2. [zk: localhost:2181(CONNECTED) 0] create /var_watch "...听"
  3. Created /var_watch
  4. #查看创建好的var_watch节点
  5. [zk: localhost:2181(CONNECTED) 1] get -s /var_watch
  6. 测试监听
  7. # 查看所有节点
  8. [zk: localhost:2181(CONNECTED) 3] ls /
  9. [test01, var_watch, zookeeper]
  10. # 使用get -w [被监听的节点] 命令进行监听
  11. [zk: localhost:2181(CONNECTED) 4] get -w /var_watch
  12. 测试监听
  13. ...
  14. #在node2_zookeeper中修改该var_watch的值
  15. [zk: localhost:2181(CONNECTED) 0] set /var_watch "............了"
  16. #此时在node3_zookeeper中有消息提示:
  17. [zk: localhost:2181(CONNECTED) 5]
  18. WATCHER::
  19. WatchedEvent state:SyncConnected type:NodeDataChanged path:/var_watch
复制代码

②监听节点数量增减的变革
  1. # 在node3_zookeeper中:
  2. # 因为我们在上面刚创建了var_watch节点,里面只有一条数据,并没有子节点
  3. # 所以我们直接使用 ls -w [被监听的节点] 命令来监听节点的变化
  4. [zk: localhost:2181(CONNECTED) 5] ls -w /var_watch
  5. # 我们在node2_zookeeper中给var_watch新增一个子节点,
  6. [zk: localhost:2181(CONNECTED) 1] create /var_watch/watch01
  7. Created /var_watch/watch01
  8. #此时在node3_zookeeper中有消息提示:
  9. [zk: localhost:2181(CONNECTED) 6]
  10. WATCHER::
  11. WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/var_watch
复制代码

③节点删除与查看
  1. # 查看当前所有节点
  2. [zk: localhost:2181(CONNECTED) 3] ls /
  3. [test01, var_watch, zookeeper]
  4. # 节点一中存在一个子节点
  5. [zk: localhost:2181(CONNECTED) 4] ls /test01
  6. [demo01]
  7. # 直接删除,会提示该节点不是空的,需要先将子节点删除
  8. [zk: localhost:2181(CONNECTED) 5] delete /test01
  9. Node not empty: /test01
  10. # 删除test01的子节点
  11. [zk: localhost:2181(CONNECTED) 6] delete /test01/demo01
  12. # 删除后
  13. [zk: localhost:2181(CONNECTED) 7] ls /test01
  14. []
复制代码

④递归删除:删除当前节点及所有子节点
  1. # 查看var_watch节点,也存在一个子节点
  2. [zk: localhost:2181(CONNECTED) 10] ls /var_watch
  3. [watch01]
  4. # 使用deleteall [path] 删除该节点和其子节点  
  5. [zk: localhost:2181(CONNECTED) 11] deleteall /var_watch
  6. [zk: localhost:2181(CONNECTED) 12] ls /
  7. [zookeeper]
复制代码

zookeeper客户端API(Java)
1.创建一个java的Maven工程,在POM文件中添加与本身的zookeeper版本一致的依靠
  1. <dependency>
  2.             <groupId>org.apache.zookeeper</groupId>
  3.             <artifactId>zookeeper</artifactId>
  4.             <version>3.5.7</version>
  5. </dependency>
复制代码

2.使用
  1. /**
  2. * 客户端API测试
  3. */
  4. public class zkClient {
  5.     /**
  6.      * 1.如果配置了主机映射名则使用映射名,如果没有就使用IP地址
  7.      * 2.如果是单点,则直接写对应的映射名或IP和端口号,如果是集群,
  8.      *   那么把对应集群中需要连接节点的映射名或IP、端口写上,注意不能有任何空格
  9.      *
  10.      */
  11.     private String connectString = "node1-zookeeper:2181,node1-zookeeper:2181,node1-zookeeper:2181";
  12.     /**
  13.      * 连接超时时间
  14.      *
  15.      */
  16.     private int sessionTimeOut = 40000;
  17.     private ZooKeeper zooKeeper;
  18.     @Before
  19.     public void init() throws IOException {
  20.          zooKeeper = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
  21.             @Override
  22.             public void process(WatchedEvent watchedEvent) {
  23.             }
  24.         });
  25.     }
  26.     @Test
  27.     public void test01() throws InterruptedException, KeeperException {
  28.        zooKeeper.create("/java_Client_test",//创建的路径
  29.                "使用Java操作的".getBytes(),//数据
  30.                ZooDefs.Ids.OPEN_ACL_UNSAFE,//完全开放的ACL
  31.                CreateMode.PERSISTENT//不带编号的持久节点
  32.                );
  33.     }
  34. }
复制代码

3.可能出现
  1. org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /java_Client_test
复制代码

原因及办理方式:

  • connectString错误,IP或者IP映射名、端口错误
  • sessionTimeOut的超时时间太短,可以适当延长
  • zookeeper服务没有启动,这是最low的错误
  • centOS防火墙没关闭(或者是没有开放端口)
  • 没有配置Windows的hosts文件,默认位置在 C:\Windows\System32\drivers\etc,找到hosts文件,在里面添加(根据本身的IP和映射名):
    1. # VMware_CentOS7_zookeeper
    2. 192.168.188.135 node1-zookeeper
    3. 192.168.188.136 node2-zookeeper
    4. 192.168.188.137 node3-zookeeper
    复制代码

案例:服务器动态上下线




①:客户端
  1. package com.moyuwanjia.study_zookeeper.demo01.case1;
  2. import com.sun.org.apache.xpath.internal.SourceTree;
  3. import org.apache.zookeeper.KeeperException;
  4. import org.apache.zookeeper.WatchedEvent;
  5. import org.apache.zookeeper.Watcher;
  6. import org.apache.zookeeper.ZooKeeper;
  7. import java.io.IOException;
  8. import java.util.ArrayList;
  9. import java.util.List;
  10. /**
  11. * 服务器动态上下线注册:client
  12. */
  13. public class DistributeClient {
  14.     private String connectString = "node1-zookeeper:2181,node1-zookeeper:2181,node1-zookeeper:2181";
  15.     private Integer sessionTimeOut = 4000;
  16.     private ZooKeeper zooKeeper;
  17.     public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
  18.         DistributeClient distributeClient = new DistributeClient();
  19.         // 1.建立连接
  20.         distributeClient.Build();
  21.         // 2.获取存在的服务节点名称
  22.         distributeClient.getServers();
  23.         // 3.模拟程序服务一直在运行
  24.         distributeClient.bussiness();
  25.     }
  26.     private void bussiness() throws InterruptedException {
  27.         Thread.sleep(Long.MAX_VALUE);
  28.     }
  29.     private void Build() throws IOException {
  30.         zooKeeper = new ZooKeeper(connectString, sessionTimeOut, event -> {
  31.             try {
  32.                 getServers();
  33.             } catch (InterruptedException e) {
  34.                 throw new RuntimeException(e);
  35.             } catch (KeeperException e) {
  36.                 throw new RuntimeException(e);
  37.             }
  38.         });
  39.     }
  40.     public void getServers() throws InterruptedException, KeeperException {
  41.         List<String> children = zooKeeper.getChildren("/servers", true);
  42.         ArrayList<String> servers = new ArrayList<>();
  43.         for (String child : children) {
  44.             byte[] data = zooKeeper.getData("/servers/" + child, false, null);
  45.             servers.add(new String(data));
  46.         }
  47.         System.out.println(servers);
  48.     }
  49. }
复制代码

② 服务端
  1. package com.moyuwanjia.study_zookeeper.demo01.case1;
  2. import org.apache.zookeeper.*;
  3. import java.io.IOException;
  4. /**
  5. * 服务器动态上下线注册:server
  6. *
  7. */
  8. public class DistributeServer {
  9.     private String connectString = "node1-zookeeper:2181,node1-zookeeper:2181,node1-zookeeper:2181";
  10.     private Integer sessionTimeOut = 4000;
  11.     private ZooKeeper zooKeeper;
  12.     public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
  13.         DistributeServer distributeServer = new DistributeServer();
  14.         // 1.获取连接
  15.         distributeServer.Build();
  16.         // 2.创建服务名
  17.         distributeServer.createNodeServer(args[0]);
  18.         // 模拟程序服务一直在运行
  19.         distributeServer.bussiness();
  20.     }
  21.     private void bussiness() throws InterruptedException {
  22.         Thread.sleep(Long.MAX_VALUE);
  23.     }
  24.     private void Build() throws IOException {
  25.          zooKeeper = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
  26.             @Override
  27.             public void process(WatchedEvent event) {
  28.             }
  29.         });
  30.     }
  31.     /**
  32.      * 创建一个携带编号的临时节点
  33.      * @param serverName
  34.      * @throws InterruptedException
  35.      * @throws KeeperException
  36.      */
  37.     private void createNodeServer(String serverName) throws InterruptedException, KeeperException {
  38.         String s = zooKeeper.create("/servers/" + serverName, serverName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
  39.         System.out.println(serverName + "is online");
  40.     }
  41. }
复制代码

④ 启动客户端,再启动服务端,在启动服务端的时间,我们需要传递一个参数模拟服务节点名称:点击编辑配置 -> 选择运行服务端的主程序->填入参数(Program arguments)-> 点击Apply -> ok





⑤演示效果:
1.客户端启动



2.服务端node02启动



此时的客户端就监听到node02已经上线



3.把服务端传递的参数node2改为node3,然后再重启服务端



服务端:



此时的客户端已经监听到node2已下线,node3已上线



案例:zookeeper分布式锁


  1. package com.moyuwanjia.study_zookeeper.demo01.case2;
  2. import org.apache.zookeeper.*;
  3. import org.apache.zookeeper.data.Stat;
  4. import java.io.IOException;
  5. import java.util.Collections;
  6. import java.util.List;
  7. import java.util.concurrent.CountDownLatch;
  8. /**
  9. * 测试案列: zk分布式锁
  10. */
  11. public class DistributedLock {
  12.     private String connectString = "node1-zookeeper:2181,node1-zookeeper:2181,node1-zookeeper:2181";
  13.     private Integer sessionTimeOut = 4000;
  14.     private ZooKeeper zooKeeper;
  15.     private CountDownLatch connectLatch = new CountDownLatch(1);
  16.     private CountDownLatch waitLatch = new CountDownLatch(1);
  17.     // 记录前一个节点的路径
  18.     private String waitPath;
  19.     // 记录当前节点
  20.     private String currentMod;
  21.     public DistributedLock() throws IOException, InterruptedException, KeeperException {
  22.         // 获取连接
  23.         zooKeeper = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
  24.             @Override
  25.             public void process(WatchedEvent watchedEvent) {
  26.                 // connectLatch 如果连接上zookeeper就释放
  27.                 if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
  28.                     connectLatch.countDown();
  29.                 }
  30.                 // waitLatch 需要释放
  31.                 if (Event.EventType.NodeDeleted == watchedEvent.getType() && watchedEvent.getPath().equals(waitPath)) {
  32.                     waitLatch.countDown();
  33.                 }
  34.             }
  35.         });
  36.         // 等待zk获取到连接,再往下执行(阻塞等待)
  37.         connectLatch.await();
  38.         // 判断根节点是否存咋,不存在则创建
  39.         Stat exists = zooKeeper.exists("/locks", false);
  40.         if (exists == null) {
  41.             // 创建根节点
  42.             zooKeeper.create("/locks", "locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  43.         }
  44.     }
  45.     /**
  46.      * 添加锁的方法
  47.      */
  48.     public void Lock() throws InterruptedException, KeeperException {
  49.         //创建带序号的零时节点
  50.         currentMod = zooKeeper.create("/locks/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
  51.         // 判断创建的节点是否是最小的序号节点,如果是则获取到锁,如果不是,监听他的前一个序号比他小的节点
  52.         List<String> children = zooKeeper.getChildren("/locks", false);
  53.         //判断是否只存在一个节点
  54.         if (children.size() == 1) {
  55.             return;
  56.         } else {
  57.             //存在多个节点,先将其进行排序
  58.             Collections.sort(children);
  59.             //查找当前创建的节点的所在位置
  60.             String thisNodeName = currentMod.substring("/locks/".length());
  61.             //通过节点名获取节点位置
  62.             int index = children.indexOf(thisNodeName);
  63.             if (-1 == index) {
  64.                 System.out.println("数据错误");
  65.             } else if (index == 0) {
  66.                 // 如果当前创建的节点位置在第一个,则直接获取锁即可
  67.                 return;
  68.             } else {
  69.                 // 监听前一个节点
  70.                 waitPath = "/locks/" + children.get(index - 1);
  71.                 zooKeeper.getData(waitPath, true, null);
  72.                 // 等待监听
  73.                 waitLatch.await();
  74.                 return;
  75.             }
  76.         }
  77.     }
  78.     /**
  79.      * 释放锁的方法
  80.      */
  81.     public void unLock() throws InterruptedException, KeeperException {
  82.         zooKeeper.delete(currentMod, -1);
  83.         System.out.println("释放锁");
  84.     }
  85. }
  86. /**
  87. * 测试锁:
  88. *  同时有三个线程获取锁,看看是否能同时获取到?
  89. *  通过结果我们看到,三个线程同时获取锁,一旦被某个线程获取到锁,就会被抢占,其他线程无法获取,只有等待其他线程将其释放掉,才能再次被获取!!
  90. *
  91. */
  92. class DistributedLockTest {
  93.     public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
  94.         final DistributedLock lock1 = new DistributedLock();
  95.         final DistributedLock lock2 = new DistributedLock();
  96.         final DistributedLock lock3 = new DistributedLock();
  97.         new Thread(() -> {
  98.             try {
  99.             lock1.Lock();
  100.             System.out.println("线程1启动,获取到锁");
  101.             // 等待之后释放锁
  102.             Thread.sleep(5* 1000);
  103.             lock1.unLock();
  104.             } catch (InterruptedException | KeeperException e) {
  105.                 throw new RuntimeException(e);
  106.             }
  107.         }).start();
  108.         new Thread(() -> {
  109.             try {
  110.                 lock2.Lock();
  111.                 System.out.println("线程2启动,获取到锁");
  112.                 // 等待之后释放锁
  113.                 Thread.sleep(5* 1000);
  114.                 lock2.unLock();
  115.             } catch (InterruptedException | KeeperException e) {
  116.                 throw new RuntimeException(e);
  117.             }
  118.         }).start();
  119.         new Thread(() -> {
  120.             try {
  121.                 lock3.Lock();
  122.                 System.out.println("线程3启动,获取到锁");
  123.                 // 等待之后释放锁
  124.                 Thread.sleep(5*1000);
  125.                 lock3.unLock();
  126.             } catch (InterruptedException | KeeperException e) {
  127.                 throw new RuntimeException(e);
  128.             }
  129.         }).start();
  130.     }
  131. }
复制代码
Curator 框架实现分布式锁
使用原生的java API 开发存在的问题


  • 会话连接时异步的,需要本身去处理。比如使用CountDownLatch
  • Watch 需要重复的注册,否则不会收效
  • 开发的复杂性比较高
  • 不支持多借点的删除和创建。需要本身递归等等
Curator是一个专门办理分布式锁的框架,办理了原生Java API开发分布式遇到的问题。


  • 使用
    1. <dependency>
    2.     <groupId>org.apache.curator</groupId>
    3.     <artifactId>curator-framework</artifactId>
    4.     <version>4.3.0</version>
    5. </dependency>
    6.     <dependency>
    7.     <groupId>org.apache.curator</groupId>
    8.     <artifactId>curator-recipes</artifactId>
    9.     <version>4.3.0</version>
    10. </dependency>
    11. <dependency>
    12.     <groupId>org.apache.curator</groupId>
    13.     <artifactId>curator-client</artifactId>
    14.     <version>4.3.0</version>
    15. </dependency>
    复制代码
演示
  1. package com.moyuwanjia.study_zookeeper.demo01.case3;
  2. import org.apache.curator.framework.CuratorFramework;
  3. import org.apache.curator.framework.CuratorFrameworkFactory;
  4. import org.apache.curator.framework.recipes.locks.InterProcessMutex;
  5. import org.apache.curator.retry.ExponentialBackoffRetry;
  6. /**
  7. * 使用Curator 实现分布式锁
  8. */
  9. public class CuratorLockTest {
  10.     public static void main(String[] args) {
  11.     // 创建分布式锁1
  12.         InterProcessMutex locks1 = new InterProcessMutex(getCuratorFramework(), "/locks");
  13.         InterProcessMutex locks2 = new InterProcessMutex(getCuratorFramework(), "/locks");
  14.         InterProcessMutex locks3 = new InterProcessMutex(getCuratorFramework(), "/locks");
  15.         new Thread(() ->{
  16.             try {
  17.                 locks1.acquire();
  18.                 System.out.println(Thread.currentThread() + "获取到锁");
  19.                 locks1.acquire();
  20.                 System.out.println(Thread.currentThread() + "再次获取到锁");
  21.                 Thread.sleep(5*1000);
  22.                 // 可重入锁
  23.                 locks1.release();
  24.                 System.out.println(Thread.currentThread() + "释放锁");
  25.                 locks1.release();
  26.                 System.out.println(Thread.currentThread() + "再次获释放锁");
  27.             } catch (Exception e) {
  28.                 throw new RuntimeException(e);
  29.             }
  30.         }).start();
  31.         new Thread(() ->{
  32.             try {
  33.                 locks2.acquire();
  34.                 System.out.println(Thread.currentThread() + "获取到锁");
  35.                 locks2.acquire();
  36.                 System.out.println(Thread.currentThread() + "再次获取到锁");
  37.                 Thread.sleep(5*1000);
  38.                 locks2.release();
  39.                 System.out.println(Thread.currentThread() + "释放锁");
  40.                 locks2.release();
  41.                 System.out.println(Thread.currentThread() + "再次获释放锁");
  42.             } catch (Exception e) {
  43.                 throw new RuntimeException(e);
  44.             }
  45.         }).start();
  46.         new Thread(() ->{
  47.             try {
  48.                 locks3.acquire();
  49.                 System.out.println(Thread.currentThread() + "获取到锁");
  50.                 locks3.acquire();
  51.                 System.out.println(Thread.currentThread() + "再次获取到锁");
  52.                 Thread.sleep(5*1000);
  53.                 locks3.release();
  54.                 System.out.println(Thread.currentThread() + "释放锁");
  55.                 locks3.release();
  56.                 System.out.println(Thread.currentThread() + "再次获释放锁");
  57.             } catch (Exception e) {
  58.                 throw new RuntimeException(e);
  59.             }
  60.         }).start();
  61.     }
  62.     public static CuratorFramework getCuratorFramework(){
  63.         // 重试策略(重试时长、重试次数)
  64.         ExponentialBackoffRetry exponentialBackoffRetry = new ExponentialBackoffRetry(3000, 3);
  65.         CuratorFramework client = CuratorFrameworkFactory.builder().connectString("node1-zookeeper:2181,node1-zookeeper:2181,node1-zookeeper:2181")
  66.                 .connectionTimeoutMs(2000)
  67.                 .sessionTimeoutMs(2000)
  68.                 .retryPolicy(exponentialBackoffRetry).build();
  69.         client.start();
  70.         System.out.println("---------------------------zookeeper 启动成功-------------------------");
  71.         return client;
  72.     }
  73. }
复制代码
常见问题:





如有劳绩,就点个赞吧

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

河曲智叟

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表