Hadoop基础【Zookeeper命令行操作,API,内部原理】

本文详细介绍了Zookeeper的命令行操作,包括增删改查子节点,以及Zookeeper API的使用,如创建、查询、设置和删除节点。此外,还探讨了Zookeeper的内部原理,如节点类型、stat结构体和监听器机制,并简述了ZAB协议和选举机制。
摘要由CSDN通过智能技术生成

一、Zookeeper客户端命令行操作

        在服务器上安装好Zookeeper后,使用zkCli.sh启动Zookeeper。

        Zookeeper的命令行操作分为增删改查,以下为一些常用的命令。

        查看子节点:

ls path	    使用 ls 命令来查看当前znode的子节点
如ls -w /    -w  不仅查看根目录的子节点,还会监听子节点变化,
                如果根目录的子节点出现变化,Zookeeper会通知使用者
            -s   查看子节点,并附加次级信息

        增加子节点:

        在102执行create /test 123(在根目录下创建子节点test,值为123),当在103执行 ls -w / ,新建子节点后,103会显示更新的信息        

Create  pathname value
如creat -s /test 123	        普通创建
                                -s  普通创建下不能创建重名节点,创建含有序列的节点,
                                    也就是节点后面会带有一串数字,这串数字是全局递增的
                                -e  临时(重启或者超时消失)

        获取节点的值:        

get pathname	    获得节点的值
                    -w  并且监听节点内容的变化
                    -s   并且附加次级信息

        更改节点的值:

Set
如set /test abcd	        设置节点的具体值
stat	        查看节点状态
delete	        删除节点
deleteall	    递归删除节点

二、Zookeeper API

        创建Maven工程,添加依赖

<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>2.8.2</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
		<dependency>
			<groupId>org.apache.zookeeper</groupId>
			<artifactId>zookeeper</artifactId>
			<version>3.5.7</version>
		</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  

        环境配置完成。

        创建节点:

public class ZKClient {
    /**
     * 创建新节点
     * @throws IOException
     */
    @Test
    public void create() throws IOException, KeeperException, InterruptedException {
        String connectString = "hadoop101:2181,hadoop102:2181,hadoop103:2181";  //地址
        int sessionTimeout = 2000;          //超时时间
        //创建Zookeeper对象
        ZooKeeper zookeeper = new ZooKeeper(connectString,
                sessionTimeout,
                new Watcher() {
                    //Zookeeper监听的回调函数(进程A通过调用进程B的回调函数通知B,
                    // 当B察觉到自己的回调函数调用时,可以马上/过一会做出反应)
                    //回调函数用于异步通信,同步通信没有回调函数(不需要)
                    public void process(WatchedEvent watchedEvent) {
                        System.out.println("默认的回调函数");
                    }
                });
        //做一些工作
        zookeeper.create("/testAPI",      //设置节点的name
                "123".getBytes(),               //设置节点的value
                ZooDefs.Ids.OPEN_ACL_UNSAFE,    //设置访问权限,类似于linux中的777
                CreateMode.PERSISTENT);         //设置创建节点类型
        //关闭资源
        zookeeper.close();
    }
}

        查询子节点:

private ZooKeeper zookeeper;
    @Before
    public void before() throws IOException {
        String connectString = "hadoop101:2181,hadoop102:2181,hadoop103:2181";  //地址
        int sessionTimeout = 2000;          //超时时间
        //创建Zookeeper对象
        zookeeper = new ZooKeeper(connectString,
                sessionTimeout,
                new Watcher() {
                    //Zookeeper监听的回调函数(进程A通过调用进程B的回调函数通知B,
                    // 当B察觉到自己的回调函数调用时,可以马上/过一会做出反应)
                    //回调函数用于异步通信,同步通信没有回调函数(不需要)
                    public void process(WatchedEvent watchedEvent) {
                        System.out.println("默认的回调函数");
                    }
                });
    }

    @After
    public void after() throws InterruptedException {
        //关闭资源
        zookeeper.close();
    }

    /**
     * 查询子节点
     */
    @Test
    public void ls() throws KeeperException, InterruptedException {
        List<String> children = zookeeper.getChildren(
                "/",
                new Watcher() {
                    public void process(WatchedEvent watchedEvent) {
                        System.out.println("自定义的回调函数");
                    }
                });
        for (String child : children) {
            System.out.println(child);
        }

        Thread.sleep(Long.MAX_VALUE);   //将线程阻塞住,持续监听,如果节点发生变化,则打印“自定义的回调函数”
    }

        查询节点的值:

    /**
     * 查询节点的值
     */
    @Test
    public void get() throws KeeperException, InterruptedException, IOException {
        //节点的附加信息(stat结构体)
        Stat stat = new Stat();
        byte[] data = zookeeper.getData("/testAPI", false, stat);

        System.out.println(stat.getCversion());
        System.out.write(data); //将节点值写在控制台
        System.out.println();
    }

        查询节点的状态(stat):

    /**
     * 查询一个节点的状态
     */
    @Test
    public void stat() throws KeeperException, InterruptedException {
        Stat stat = zookeeper.exists("/testAPI", false);
        if(stat == null){
            System.out.println("节点不存在");
        }else {
            System.out.println(stat);
        }
    }

        设置节点的值:

     /**
     * 设置节点的值
     */
    @Test
    public void set() throws KeeperException, InterruptedException {
        String node = "/testAPI";
        Stat stat = zookeeper.exists(node, false);
        if(stat == null){
            System.out.println("节点不存在");
        }else{
            //第三个参数为节点的修改次数,因为在查节点和修改节点的间隔,其他人可能会修改节点
            //添加第三个参数的目的是为了防止我们改错数据,保证“我要修改的数据”和“我实际修改的
            //数据”是一致的,称为乐观锁(大家都可以看见这份数据,
            //都可以修改,只有最先改的人可以成功);悲观锁(独占这份资源,其他人不可以使用)
            //表达这是我们看到的第几个版本的数据
            zookeeper.setData("/testAPI","abcd".getBytes(),stat.getVersion());
        }

    }

        删除节点:

    /**
     * 删除节点
     */
    @Test
    public void delete() throws KeeperException, InterruptedException {
        String node = "testAPI";
        Stat stat = zookeeper.exists("/testAPI", false);
        if(stat == null){
            System.out.println("节点不存在");
        }else {
            int version = stat.getVersion();
            zookeeper.delete("/testAPI",version);
        }
    }

 三、Zookeeper内部原理

        节点类型:持久化节点,持久化有序节点,临时节点,临时有序节点

        stat结构体:

(1)czxid-创建节点的事务zxid

每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。

事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。如:cZxid = 0x100000003 前3个字符一部分,表示目前的leader是第一个选举出来的leader,后面为一部分,表示第一个leader做的第三件事。

(2)ctime - znode被创建的毫秒数(从1970年开始)

(3)mzxid - znode最后更新的事务zxid

(4)mtime - znode最后修改的毫秒数(从1970年开始)

(5)pZxid-znode最后更新的子节点zxid

(6)cversion - znode子节点变化号,znode子节点修改次数

(7)dataversion - znode数据变化号

(8)aclVersion - znode访问控制列表(是一个权限管理系统,管理那些人能够访问这个节点,那些人不能访问这个节点)的变化号,建立节点的时候默认所有人都可以访问

(9)ephemeralOwner- 如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0。

(10)dataLength- znode的数据长度

(11)numChildren - znode子节点数量

        监听器原理:

          简而言之,若监听A节点,主线程(main)会将请求发送给sent thread,sent thread将请求发送给服务器,执行完,主线程去做自己的事情,直到节点出现变化,服务器将变化信息发送给event thread,event thread再调用回调函数,来通知主线程。

        ZAB协议:

        Zookeeper为了实现全局数据一致,使用了ZAB协议。协议分为两个部分,第一部分:集群中的所有节点都是平等的,但是ZAB想要达到全局一致的目的,如果没有leader,需要在原来互相平等的节点中选取一个leader;第二部分:如果有leader,让它正常运行即可。

        对于第一部分,Zookeeper有一套自己的选举机制。

        选举机制:        

(1)半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器。

(2)Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的。

(3)以一个简单的例子来说明整个选举的过程。

        假设有五台服务器组成的Zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动。

(1)服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为LOOKING;

(2)服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的ID比自己目前投票推举的(服务器1)大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING

(3)服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数已经超过半数,服务器3当选Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING;

(4)服务器4启动,发起一次选举。此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING;

(5)服务器5启动,同4一样当小弟。

        以上有一个问题,每台服务器怎么认为其他服务器选举的leader比自己选举的leader厉害?比较规则:两台服务器先比较自己选举leader的zxid(Zookeeper的数据经过哪些改变)谁大,谁的数据就比较新,适合作为leader,相同时,再比较myid,谁大,谁适当作为leader,myid不可能相等。只要leader选举成功,之后的服务器都会服从。

        根据以上规则,如果5台同时启动且zxid相同,5号就是leader。正常工作的集群,zxid都相同。

        第二部分leader正常工作,即数据的读写。

        数据的读写:

         leader发起一次写请求,如果server1同意,server2不同意,leader自己同意,共计两票同意,leader发起写请求,server1,leader完成写操作,写成功之后向client发布广播,但是server不容易写,它就会原地自杀,然后重启,重启之后再向leader同步数据。因为Zookeeper集群要尽最大的可能保持数据的一致性。

        那么它会在什么情况下不同意呢?每个节点都会落实写请求,也就是每个节点都会有一个zxid(leader发送请求时已经编好了),但是每个server又会有自己最新的zxid,当leader发送来的zxid比它自己的大,就同意写请求,反之,不同意,因为follower的数据比leader更新。

        为什么会出现这种情况呢?因为集群中的网络状况十分复杂且Zookeeper使用UDP协议,如当发生网络延迟时,某一请求丢失的情况。因为网络问题,可能会出现leader发送的请求其他server都不同意,那么leader就会原地自杀,重启,然后发起新一轮的选举(这种情况十分少见)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

OneTenTwo76

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

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

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

打赏作者

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

抵扣说明:

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

余额充值