Zookeeper--集群及相关概念

Zookeeper集群及相关概念
一、前言

本文章主要讲述zk的集群使用到的概念,比如ZAB协议,如何搭建zk集群,怎么使用javaApi来操作zk集群。

首先我们要下载zookeeper,将安装包解压,安装zk就不说了。安装成功后按以下步骤进行:
1. 重命名zoo_sample.cfg文件

cp conf/zoo_sample.cfg conf/zoo-1.cfg

2.修改配置文件zoo-1.cfg,配置文件里有的,改成下面的值,没有的加上

dataDir=/tmp/zookeeper-1
# the port at which the clients will connect
clientPort=2181
server.1=192.168.199.11:2888:3888
server.2=192.168.199.11:2889:3889
server.3=192.168.199.11:2890:3890

3.再从zoo-1.cfg复制两个配置文件zoo-2.cfg和zoo-3.cfg,只需要修改dataDir和clientPort不同即可。

[root@localhost conf]# cp zoo-1.cfg zoo-2.cfg
[root@localhost conf]# cp zoo-1.cfg zoo-3.cfg
#vim zoo-2.cfg
dataDir=/tmp/zookeeper-2
# the port at which the clients will connect
clientPort=2182

#vim zoo-3.cfg
dataDir=/tmp/zookeeper-3
# the port at which the clients will connect
clientPort=2183

4.标志Server ID

创建三个文件夹/tmp/zookeeper-1,/tmp/zookeeper-2,/tmp/zookeeper-3

在每个目录中建立文myid文件,写入当前实例的server id, 即1,2,3

在这里插入图片描述
当然这个实例id你自己可以自定义,这个myid主要是用来zk主节点选举时,声明你是哪个节点的。

5. 启动三个zk实例

    [root@localhost zookeeper-3.4.14]# bin/zkServer.sh start conf/zoo-1.cfg 
    ZooKeeper JMX enabled by default
    Using config: conf/zoo-1.cfg
    Starting zookeeper ... STARTED
    
    [root@localhost zookeeper-3.4.14]# bin/zkServer.sh start conf/zoo-2.cfg  
    ZooKeeper JMX enabled by default
    Using config: conf/zoo-2.cfg
    Starting zookeeper ... STARTED
    
    [root@localhost zookeeper-3.4.14]# bin/zkServer.sh start conf/zoo-3.cfg  
    ZooKeeper JMX enabled by default
    Using config: conf/zoo-3.cfg
    Starting zookeeper ... STARTED
    

此时zk集群就启动了。

二、查看集群的状态

查看集群状态也十分简单,启动集群后可以通过下面命令查看


    [root@localhost zookeeper-3.4.14]# bin/zkServer.sh status conf/zoo-3.cfg  
    ZooKeeper JMX enabled by default
    Using config: conf/zoo-3.cfg
    Mode: leader
    
    [root@localhost zookeeper-3.4.14]# bin/zkServer.sh status conf/zoo-2.cfg      
    ZooKeeper JMX enabled by default
    Using config: conf/zoo-2.cfg
    Mode: follower
    
    [root@localhost zookeeper-3.4.14]# bin/zkServer.sh status conf/zoo-1.cfg  
    ZooKeeper JMX enabled by default
    Using config: conf/zoo-1.cfg
    Mode: standalone

注意到,zoo-3的实例时master角色,zoo-2则是zoo-3的一个从节点。

观察zoo-1发现启动模式是standalone,表示这个节点没正常启动。经过排查可以发现,这个节点的端口之前是以单机模式启动的,我们需要把这个节点停止掉,并且删除对应的tmp目录下存放该节点的数据的dataDir的文件(保留myid即可)。

然后重启该节点即可。

三、停止集群节点

    [root@localhost bin]# ./zkServer.sh stop ../conf/zoo-2.cfg   
    ZooKeeper JMX enabled by default
    Using config: ../conf/zoo-2.cfg
    Stopping zookeeper ... STOPPED
    [root@localhost bin]# ./zkServer.sh stop ../conf/zoo-3.cfg   
    ZooKeeper JMX enabled by default
    Using config: ../conf/zoo-3.cfg
    Stopping zookeeper ... STOPPED
四、启动|停止|查看节点状态的脚本

提供一个日常集群常用操作脚本

    [root@localhost bin]# cat zkOperation.sh 
    #!/bin/bash
    
    for i in "$@"; do
      if [ "$i" = "start" ];then
        ./zkServer.sh start ../conf/zoo-1.cfg 
        ./zkServer.sh start ../conf/zoo-2.cfg 
        ./zkServer.sh start ../conf/zoo-3.cfg 
      fi
     if [ "$i" = "status" ];then
        ./zkServer.sh status ../conf/zoo-1.cfg
        ./zkServer.sh status ../conf/zoo-2.cfg
        ./zkServer.sh status ../conf/zoo-3.cfg
      fi
      if [ "$i" = "stop" ];then
        ./zkServer.sh stop ../conf/zoo-1.cfg
        ./zkServer.sh stop ../conf/zoo-2.cfg
        ./zkServer.sh stop ../conf/zoo-3.cfg
      fi
    done

键入命令即可执行

    [root@localhost bin]# ./zkOperation.sh start status 
    ZooKeeper JMX enabled by default
    Using config: ../conf/zoo-1.cfg
    Starting zookeeper ... STARTED
    ZooKeeper JMX enabled by default
    Using config: ../conf/zoo-2.cfg
    Starting zookeeper ... STARTED
    ZooKeeper JMX enabled by default
    Using config: ../conf/zoo-3.cfg
    Starting zookeeper ... STARTED
    ZooKeeper JMX enabled by default
    Using config: ../conf/zoo-1.cfg
    Mode: follower
    ZooKeeper JMX enabled by default
    Using config: ../conf/zoo-2.cfg
    Mode: leader
    ZooKeeper JMX enabled by default
    Using config: ../conf/zoo-3.cfg
    Mode: follower
五、连接集群

集群节点都启动成功了,就可以使用下面的命令用客户端连接到集群。
./zkCli.sh -server 192.168.199.11:2181

    [root@localhost bin]# ./zkCli.sh -server 192.168.199.11:2181 
    Connecting to 192.168.199.11:2181
    2020-04-29 10:43:43,781 [myid:] - INFO  [main:Environment@100] - Client environment:zookeeper.version=3.4.14-4c25d480e66aadd371de8bd2fd8da255ac140bcf, built on 03/06/2019 16:18 GMT
    2020-04-29 10:43:43,789 [myid:] - INFO  [main:Environment@100] - Client environment:host.name=localhost
    2020-04-29 10:43:43,789 [myid:] - INFO  [main:Environment@100] - Client environment:java.version=1.8.0_131
    2020-04-29 10:43:43,794 [myid:] - INFO  [main:Environment@100] - Client environment:java.vendor=Oracle Corporation
    2020-04-29 10:43:43,794 [myid:] - INFO  [main:Environment@100] - Client environment:java.home=/usr/java/jdk1.8.0_131/jre
    2020-04-29 10:43:43,794 [myid:] - INFO  [main:Environment@100] - Client environment:java.class.path=/usr/local/zookeeper/zookeeper-3.4.14/bin/../zookeeper-server/target/classes:/usr/local/zookeeper/zookeeper-3.4.14/bin/../build/classes:/usr/local/zookeeper/zookeeper-3.4.14/bin/../zookeeper-server/target/lib/*.jar:/usr/local/zookeeper/zookeeper-3.4.14/bin/../build/lib/*.jar:/usr/local/zookeeper/zookeeper-3.4.14/bin/../lib/slf4j-log4j12-1.7.25.jar:/usr/local/zookeeper/zookeeper-3.4.14/bin/../lib/slf4j-api-1.7.25.jar:/usr/local/zookeeper/zookeeper-3.4.14/bin/../lib/netty-3.10.6.Final.jar:/usr/local/zookeeper/zookeeper-3.4.14/bin/../lib/log4j-1.2.17.jar:/usr/local/zookeeper/zookeeper-3.4.14/bin/../lib/jline-0.9.94.jar:/usr/local/zookeeper/zookeeper-3.4.14/bin/../lib/audience-annotations-0.5.0.jar:/usr/local/zookeeper/zookeeper-3.4.14/bin/../zookeeper-3.4.14.jar:/usr/local/zookeeper/zookeeper-3.4.14/bin/../zookeeper-server/src/main/resources/lib/*.jar:/usr/local/zookeeper/zookeeper-3.4.14/bin/../conf:/usr/java/jdk1.8.0_131/jre/lib/ext:/usr/java/jdk1.8.0_131/lib/tools.jar
    2020-04-29 10:43:43,795 [myid:] - INFO  [main:Environment@100] - Client environment:java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
    2020-04-29 10:43:43,795 [myid:] - INFO  [main:Environment@100] - Client environment:java.io.tmpdir=/tmp
    2020-04-29 10:43:43,795 [myid:] - INFO  [main:Environment@100] - Client environment:java.compiler=<NA>
    2020-04-29 10:43:43,795 [myid:] - INFO  [main:Environment@100] - Client environment:os.name=Linux
    2020-04-29 10:43:43,795 [myid:] - INFO  [main:Environment@100] - Client environment:os.arch=amd64
    2020-04-29 10:43:43,795 [myid:] - INFO  [main:Environment@100] - Client environment:os.version=3.10.0-1062.1.2.el7.x86_64
    2020-04-29 10:43:43,795 [myid:] - INFO  [main:Environment@100] - Client environment:user.name=root
    2020-04-29 10:43:43,796 [myid:] - INFO  [main:Environment@100] - Client environment:user.home=/root
    2020-04-29 10:43:43,796 [myid:] - INFO  [main:Environment@100] - Client environment:user.dir=/usr/local/zookeeper/zookeeper-3.4.14/bin
    2020-04-29 10:43:43,798 [myid:] - INFO  [main:ZooKeeper@442] - Initiating client connection, connectString=192.168.199.11:2181 sessionTimeout=30000 watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@67424e82
    Welcome to ZooKeeper!
    JLine support is enabled
    2020-04-29 10:43:44,158 [myid:] - INFO  [main-SendThread(192.168.199.11:2181):ClientCnxn$SendThread@1025] - Opening socket connection to server 192.168.199.11/192.168.199.11:2181. Will not attempt to authenticate using SASL (unknown error)
    2020-04-29 10:43:44,331 [myid:] - INFO  [main-SendThread(192.168.199.11:2181):ClientCnxn$SendThread@879] - Socket connection established to 192.168.199.11/192.168.199.11:2181, initiating session
    2020-04-29 10:43:44,535 [myid:] - INFO  [main-SendThread(192.168.199.11:2181):ClientCnxn$SendThread@1299] - Session establishment complete on server 192.168.199.11/192.168.199.11:2181, sessionid = 0x1000406ed350000, negotiated timeout = 30000
    
    WATCHER::
    
    WatchedEvent state:SyncConnected type:None path:null
    [zk: 192.168.199.11:2181(CONNECTED) 0] 

此时表示连接到集群节点为 192.168.199.11:2181的节点。我们可以设置下值

    [zk: 192.168.199.11:2181(CONNECTED) 1] create /username haizhang
    Created /username
    [zk: 192.168.199.11:2181(CONNECTED) 2] get /username
    haizhang
    cZxid = 0x100000002
    ctime = Wed Apr 29 10:45:25 CST 2020
    mZxid = 0x100000002
    mtime = Wed Apr 29 10:45:25 CST 2020
    pZxid = 0x100000002
    cversion = 0
    dataVersion = 0
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 8
    numChildren = 0
    
    ##2182的机器也可以查到
    [zk: 192.168.199.11:2182(CONNECTED) 0] get /username 
    haizhang
    cZxid = 0x100000002
    ctime = Wed Apr 29 10:45:25 CST 2020
    mZxid = 0x100000002
    mtime = Wed Apr 29 10:45:25 CST 2020
    pZxid = 0x100000002
    cversion = 0
    dataVersion = 0
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 8
    numChildren = 0
    
    ##2183的机器也可以查到
    [zk: 192.168.199.11:2183(CONNECTED) 0] get /username 
    haizhang
    cZxid = 0x100000002
    ctime = Wed Apr 29 10:45:25 CST 2020
    mZxid = 0x100000002
    mtime = Wed Apr 29 10:45:25 CST 2020
    pZxid = 0x100000002
    cversion = 0
    dataVersion = 0
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 8
    numChildren = 0

可以看到在2181机器设置的username目录及节点数据,会同步到2182、2183这两个子节点。并且2181是从节点,从节点上只能够读,但是我们发现在连接从节点操作写命令也可以成功!这里就要涉及到ZAB原子广播协议了。

六、ZAB原子广播协议

ZAB原子广播协议包括两个方面:
在这里插入图片描述
下面我们将对ZAB原子广播协议进行分开讲解,首先讲解下集群消息广播(二阶段提交)保证数据一致性。

6.1 二阶段提交

为什么从节点只负责读但可以执行写命令?
集群处理请求示意图:
在这里插入图片描述
实际上写请求是发送到了从节点上,但是实际上从节点并不是真正的去执行写操作,而是将这个写命令转发到Leader节点中。

这里就是设计到了集群消息广播(二阶段提交)的一个概念

写请求就会有个zxid的东西,从服务器收到客户端的写请求之后就会将写请求转发到leader服务器上,leader接收到这个写命令的时候,就会生成一个按顺序的全局唯一的zxid(按请求到达leader服务器的先后顺序的)。实际上就相当于leader节点为所有写操作的请求按先后顺序(换句话讲按zxid大小)放入队列,谁先到就先执行谁。那么如果有并发的写请求到来,实际上leader底层也是会加锁并生成zxidzxid可称为唯一的、全局的、顺序的 id。

所有对zk数据做修改新增操作的动作,leader都会为它生成一个zxid,也就是事务的id(写操作才叫说事务,会对数据做修改。读操作不能叫事务,不会对数据做修改)

那么当leader节点要处理某个写请求的时候,首先会将这个写请求的zxid通知到集群中的follower,并等待follwer的ack确认收到写事务id并准备好接收数据的回应。只要集群中超过半数以上的follower响应ack到leader节点中,leader节点就进行对数据的提交操作,将这个zxid的写请求数据Commit到响应ack命令的节点上。这就完成了数据同步的一个操作。

实际上follwer也是要执行写命令,才能让follwer拥有leader发送的节点数据的。

那为什么不一开始就让follwer执行写操作,而是要兜一个大圈子呢? 答案就是保证数据一致性

如果你多个写请求发送到zk的多个节点,这多个节点都并发的去写数据,那就可能出现数据不一致的情况,难以控制和管理。如果将这几个写请求统一发送给某台服务器做中转处理,那么就能够保证follower之间存在半数以上的机器拥有这个数据,数据就不容易丢失。而且更重要的一点是,保证性能。

6.2 集群节点中的leader宕机了,怎么办?
当zk的leader宕机了,就会被其他的follower发现,此时就会在剩下可用的follower中发起选票,选举一个follower成为为zk的leader。
那么选举的过程又是怎么样的呢?什么样的follower才可以成为leader呢?
假设我们存在三台zk集群的节点。
在这里插入图片描述
其中ZKNode2为leader节点,当ZKNode2接收到一个写请求的时候,假设leader为这个写请求生成的事务zxid=102。当把这个事务通知完ZKNode3做同步时,而还没来得及给ZKNode1同步这个事务,ZKNode2突然宕机了。此时ZKNode1就相当于没有接收到这个写事务。
在这里插入图片描述
上图说明的很明白,ZKnode1中存放的zxid最大值还是101 ,而ZKnode3变成了102。 当leader宕机了,follwer1和follwer2就会通知集群的其他节点开始选举leader。
这个时候,follower2就会通过选举投票端口发送自己的信息给其他follwer进行投票,这条信息的格式为(myid,节点最大zxid),其中myid则为你zk配置文件中保存在dataDir目录下的myid文件的内容,标志当前节点。

上图中follower2会发给follower1的消息即为(3,102) ,follower1回发给follower2的消息为(1,101)
在发送消息之前,各自都给自己投上一票。

在这里插入图片描述

在这里插入图片描述
如果follower节点收到其他其他follower节点转发它自己的消息,那就会在票数的基础上为自己再加一票,否则就会忽略这条消息。就像上面,zkNode1发现自己的zxid比zkNode3要小,那就会给把zkNode3的消息转发给候选节点,同样的也会发回给zkNode3,当zkNode3发现是别的集群节点发送自己的消息的时候,就会为自己再加上一票。

主节点计算公式只要某个节点的选票大于集群中总机器数的一半,就可以成为集群的leader了,这个节点就会把自己的节点设置成主节点。

七、java客户端连接集群

很简单,做如下操作:

    public class ZookeeperProSync  implements Watcher {
    
        private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
        private static ZooKeeper zk = null;
        private  static Stat stat = new Stat();
    
        public static void main(String[] args) {
            //zookeeper配置数据存放路径,相当于在zk下新建一个存放数据节点的目录
            String path = "/username";
            try {
                //连接zk并注册一个默认的监听器,其中192.168.199.11:2181是zk提供客户端连接的主机端口地址,集群机器使用,隔开。
                //sessionTimeout的值表示客户端和服务端保持连接,心跳超时的时间。因为zk客户端和服务端是基于长连接的。所以服务器会和客户端使用心跳机制保持连接状态,一旦超过指定心态时间没回复,就把客户端剔除。
                zk =  new ZooKeeper("192.168.199.11:2183,192.168.199.11:2182,192.168.199.11:2181",5000,new ZookeeperProSync() );
                //等待zk连接成功的通知
                connectedSemaphore.await();
                //获取path目录节点的配置数据,并注册默认的监听器。 其中zk.getData相当于zk的get命令,去指定的目录下获取数据。
                //而该方法的第二个参数watch=true表明当前客户端设置了一个监听器在zk的path目录下,只要path目录有变动,就立刻发送一个监听事件回来通知客户端。
                System.out.println(new String(zk.getData(path,true,stat)));
                //模拟程序一直在运行
                Thread.sleep(Integer.MAX_VALUE);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (KeeperException e) {
                e.printStackTrace();
            }
        }
    
        //zk监听事件处理逻辑。
        public void process(WatchedEvent watchedEvent) {
            //zk成功连接的通知事件
            if(Event.KeeperState.SyncConnected  == watchedEvent.getState()){
                if(Event.EventType.None == watchedEvent.getType() && null == watchedEvent.getPath()){
                    connectedSemaphore.countDown();
                }else if (watchedEvent.getType() == Event.EventType.NodeDataChanged){
                   //zk目录节点数据发生变化的通知事件
                   try {
                       System.out.println("配置已被修改,新值为:"+new String(zk.getData(watchedEvent.getPath(),true,stat)));
                   }catch (Exception e){
                       e.printStackTrace();
                   }
                }
            }
        }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值