Zookeeper入门

一、zookeeper简介

ZooKeeper 是 Apache 软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。
ZooKeeper 的架构通过冗余服务实现高可用性。
Zookeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
在这里插入图片描述
zookeeper的文件系统:
它的文件系统用来保存少量,服务器相关的配置文件信息。
zookkeeper 提供的名称空间非常类似于标准文件系统,key-value 的形式存储。名称 key 由斜线 / 分割的一系列路径元素,zookeeper 名称空间中的每个节点都是由一个路径标识。
在这里插入图片描述
解释说明:

1.zookeeper的存储结构是一个树形结构
2.zookeeper的树形结构是由znode组成的
znode特点:
1.znode是由name-value组成
	例:create /a aaa    语法:create name value
2.znode既可以看作是一个文件夹(目录)也可以看作是一个文件(文件名)
	2.1当看作是一个文件时
		znode的name就是文件名,value就是文件中的内容
	2.2当看作是一个目录时
		每个znode下面还可以存在子znode
			例:create /a/b bbb
注意name必须是以根目录开始即:/...

二.zookeeper用法

ZK就是一个管理多个服务(集群分布式环境下)的通知机制 Watcher+文件系统`

ZNode 文件系统:保存少量,服务器相关的配置文件信息。

Watcher 监听通知机制:注册监听服务器的上下线。

特点:
1. zk集群中的数据内容会自动同步,所以每个节点上的数据完全一致。
2. zk作为集群管理者,不存在单点故障等问题。
3. zk的主机是动态选举出来的,不是规定死的。
	当集群启动的时候,会选举出来一个leader。如果主机挂了,就会重新选举新的leader
4.半数机制
	正常运行的集群必须在集群的半数以上。即例如有三台机器,半数是1.5,半数以上就要求最少有2台可以正常工作
										如有四台或5台,半数是22.5.则要求都是最少有3台可以正常工作

zookeeper解决的问题:

1.HDFS系统中NameNode中存在的单点故障问题

2.Yarn中ReduceManager单点故障问题

在解决单点故障过程中实现原理

以HDFS中的NameNode为例。

引入:
 1. 在HDFS系统中,只能存在一个NameNode节点。那么如果这个NameNode进程挂掉了用户在发过来请求就不能在进行后续操作了,这个 现象称为NameNode的单点故障问题
解决方案:
准备一个NameNode备用机器,在主NameNode挂机的时候自动转换到备用机器运行。
流程 :
1.NameNode在启动时候向zookeeper中注册一个临时节点
2. NamoNode备用机启动时也向zookeeper中去注册临时节点,但是在注册的时候如果发现已经有人注册过了,就不再注册,自己老老实实当个备胎,然后监听着该节点什么时候挂掉
3. 如果主机挂机了,或者其他原因一段时间不再向zookeeper发送心跳,那么zookeeper就认为他挂了,并删除它当时注册的临时节点信息
4. 这时候zookeeper就会向正在监听着的备用机发送一个信息说主机挂了
5. 备用机器NameNode就向zookeeper中去注册临时节点,成为新的主机

 
 

三、Zookeeper Leader 选举原理

zookeeper 的 leader 选举存在两个阶段,一个是服务器启动时 leader 选举,另一个是运行过程中 leader 服务器宕机。

在分析选举原理前,先介绍几个重要的参数。
    1.服务器 ID(myid):编号越大在选举算法中权重越大
    2.事务 ID(zxid):值越大说明数据越新,权重越大
    3.逻辑时钟(epoch-logicalclock):同一轮投票过程中的逻辑时钟值是相同的,每投完一次值会增加 
选举状态:
    1.LOOKING: 竞选状态
    2.FOLLOWING: 随从状态,同步 leader 状态,参与投票
    3.OBSERVING: 观察状态,同步 leader 状态,不参与投票
    4.LEADING: 领导者状态 

1、服务器启动时的 leader 选举
每个节点启动的时候都会进入 LOOKING 观望状态,接下来就开始进行选举主流程。这里选取三台机器组成的集群为例。第一台服务器 server1启动时,无法进行 leader 选举,当第二台服务器 server2 启动时,两台机器可以相互通信,进入 leader 选举过程。

选举流程:

1)每台 server 发出一个投票,由于是初始情况,server1 和 server2 都将自己作为 leader 服务器进行投票,每次投票包含所推举的服务器myid、zxid、epoch,使用(myid,zxid)表示,此时 server1 投票为(1,0),server2 投票为(2,0),然后将各自投票发送给集群中其他机器。
    
    (2)接收来自各个服务器的投票。集群中的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票(epoch)、是否来自 LOOKING 状态的服务器。
    
    (3)分别处理投票。针对每一次投票,服务器都需要将其他服务器的投票和自己的投票进行对比,对比规则如下:
        a. 优先比较 epoch
        b. 检查 zxid,zxid 比较大的服务器优先作为 leader
        c. 如果 zxid 相同,那么就比较 myid,myid 较大的服务器作为 leader 服务器
        
    (4)统计投票。每次投票后,服务器统计投票信息,判断是都有过半机器接收到相同的投票信息。server1、server2 都统计出集群中有两台机器接受了(2,0)的投票信息,此时已经选出了 server2 为 leader 节点。
    
    (5)改变服务器状态。一旦确定了 leader,每个服务器响应更新自己的状态,如果是 follower,那么就变更为 FOLLOWING,如果是 Leader,变更为 LEADING。此时 server3继续启动,直接加入变更自己为 FOLLOWING。


在这里插入图片描述
2、运行过程中的 leader 选举
当集群中 leader 服务器出现宕机或者不可用情况时,整个集群无法对外提供服务,进入新一轮的 leader 选举。
流程:

1)变更状态。leader 挂后,其他非 Oberver服务器将自身服务器状态变更为 LOOKING。
    (2)每个 server 发出一个投票。在运行期间,每个服务器上 zxid 可能不同。
    (3)处理投票。规则同启动过程。
    (4)统计投票。与启动过程相同。
    (5)改变服务器状态。与启动过程相同。

四、Zookeeper 分布式锁实现原理

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。

排他锁
排他锁(Exclusive Locks),又被称为写锁或独占锁,如果事务T1对数据对象O1加上排他锁,那么整个加锁期间,只允许事务T1对O1进行读取和更新操作,其他任何事务都不能进行读或写。

定义锁:
/exclusive_lock/lock

实现:

利用 zookeeper 的同级节点的唯一性特性,在需要获取排他锁时,
所有的客户端试图通过调用 create() 接口,
在 /exclusive_lock 节点下创建临时子节点 /exclusive_lock/lock,
最终只有一个客户端能创建成功,那么此客户端就获得了分布式锁。
同时,所有没有获取到锁的客户端可以在 /exclusive_lock 节点上注册一个子节点变更的 watcher 监听事件,以便重新争取获得锁。

共享锁
共享锁(Shared Locks),又称读锁。如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁,直到该数据对象上的所有共享锁都释放。

定义锁:
/shared_lock/[hostname]-请求类型W/R-序号

实现:

  1. 客户端调用 create 方法创建类似定义锁方式的临时顺序节点
    在这里插入图片描述

  2. 客户端调用 getChildren 接口来获取所有已创建的子节点列表。

  3. 判断是否获得锁:
    如果得到锁
    对于读请求:如果所有比自己小的子节点都是读请求或者自己后面没有节点了,表明已经成功获取共享锁,同时开始执行自己要进行的操作。
    对于写请求,如果自己不是序号最小的子节点,那么就进入等待。一直到所有的读操作执行完,写操作前面的都用完了轮到自己的 时候,自己再执行
    如果没有获取到共享锁,读请求向比自己序号小的最后一个写请求节点注册 watcher 监听,写请求向比自己序号小的最后一个节点注册watcher 监听。

实际开发过程中,可以 curator 工具包封装的API帮助我们实现分布式锁。

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

五、集群环境下部署zookeeper

1.准备三台服务器 (hadoop11/hadoop12/hadoop13)

0. 设置ip
1. 安装jdk
2. 配置java环境变量
3. 关闭防火墙
4. 设置hostname
5. 设置hosts(3台彼此之间集群互通)

2.安装

上传下载包到Linux服务器上
1. 解压: tar zxvf zookeeper-3.4.6.tar.gz -C /opt/installs/
2. 修改文件名:mv zookeeper-3.4.6/ zookeeper3.4.6
3. 编辑环境变量配置文件:vim /etc/profile
		# ------------------下面是添加的内容------
	export PATH=$PATH:/opt/installs/zookeeper3.4.6/bin/
4. 重新加载profile配置:source /etc/profile 

3.标识自己的身份

1. zookeeper目录下新建一个data目录
  mkdir data
2. 在data下,新建一个myid文件。
	cd data
3. 里面内容填写当前zk节点的编号
   echo 11 > myid 

4.初始化配置文件

1. 拷贝zoo.cfg文件:cp zoo_sample.cfg zoo.cfg
2. 配置zoo.cfg
		配置以下内容:
		#Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。
		tickTime=2000 #心跳时间周期(单位: 毫秒)
		#集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。
		initLimit=10 #启动zk时候的时间延迟最大值(10倍心跳)
		#Leader发送心跳包给集群中所有Follower,若Follower在syncLimit时间内没有响应,那么Leader就认为该follower已经挂掉了,单位:tickTime
		syncLimit=5 # zk的主机和从机之间的通信访问的最大延迟(5倍心跳)
		#设置zookeeper的数据存储位置(这个要改)
		dataDir=/opt/installs/zookeeper3.4.6/data/ 
		clientPort=2181 # zk的客户端访问zk的端口号

		# server.myid=zookeeper的ip:2888:3888
		server.11=hadoop11:2888:3888
		server.12=hadoop12:2888:3888
		server.13=hadoop13:2888:3888
		# 2888(内部数据通信的端口) 
		#3888(选举投票使用的端口)

5.同步配置文件到另两台机器上

1. 同步zookeeper的软件及其内部配置文件信息
  scp -r zookeeper3.4.6 root@hadoop12:/opt/installs/
  scp -r zookeeper3.4.6 root@hadoop13:/opt/installs/
2. 同步zookeeper的环境变量文件/etc/profile
  scp -r /etc/profile root@hadoop12:/etc
  scp -r /etc/profile root@hadoop13:/etc
3. 重新加载其他节点上的zk的环境变量
  source /etc/profile
4. 修改其他节点上的myid的zk编号。
	在12上的zookeeper安装目录的data 目录下:echo 12 > myid 
	在13上的zookeeper安装目录的data 目录下:echo 13 > myid 

6.zookeeper服务器管理命令

1. 启动:zkServer.sh start
   查看进程: jps
    2610 QuorumPeerMain # zk节点的进程。
    3500 Jps
2. 查看状态:    zkServer.sh status
3. 停止: 	 zkServer.sh stop
4. 客户端
  	zkCli.sh 登录本机的zk
  	zkCli.sh -server ip:2181 登录指定ip的zk主机

7.查看日志

如果zookeeper启动异常,查看日志文件:zookeeper.out   
默认位置: 启动zkServer的命令所在的目录,
可以通过bin/zkEnv.sh修改 ZOO_LOG_DIR = "/opt/installs/zookeeper3.4.6/logs"

六、zookeeper客户端命令

1.操作客户端命令

客户端使用基本命令
  1. 进入客户端
    zkCli.sh
  2. 查看帮助命令
    [zk: localhost:2181(CONNECTED) 1] help
  3. 退出客户端
    [zk: localhost:2181(CONNECTED) 1] quit

2. znode管理命令
在这里插入图片描述
3.节点类型

节点类型
zookeeper可以将节点设置不同的类型
1. 持久化节点
   节点只要不删除,会一致存在。
2. 顺序编号节点
   每次对相同节点,重复创建,会自动对znode名称进行编号
3. 临时节点
   客户端断开,则节点消失。


顺序节点的应用场景:分布式锁
线程 synchronized : 两个线程同时访问一个资源(临界资源),出现线程不安全问题
分布式项目( 说白了可以理解为两个项目 ) 需要同时使用一个资源,传统的synchronized无法使用
使用zookeeper的顺序节点可以解决分布式项目的锁问题

在这里插入图片描述
在这里插入图片描述
4.Watcher监听器命令

1. 监听节点值得修改(set)和删除(delete)变化
2. 监听某个节点及其子节点的增加、删除。

在这里插入图片描述

七、使用Java代码操作zookeeper

1、首先导入依赖和日志文件

<!--zk的客户端( curator )-->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.7.1</version>
</dependency>
<!--junit测试-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

2、编写代码测试

public class TestCurator {
	//定义一个全局变量,每个类中到要用到
    CuratorFramework curator;
    @Before
    public void test1(){
        // 1. 创建一个连接(自动重连)
        RetryNTimes retry = new RetryNTimes(10,1000);// 重连10次,每次间隔1000秒
        // 2. 创建一个客户端对象。
        curator = CuratorFrameworkFactory.newClient("hadoop11:2181,hadoop12:2181,hadoop13:2181", retry);
        // 3. 启动客户端
        curator.start();
    }
    @Test
    public void test2()throws Exception{
        //create /a1  测试创建一个节点
        String s = curator.create().withMode(CreateMode.PERSISTENT).forPath("/a1", "测试".getBytes());
        System.out.println(s);
        curator.close();//记得关闭连接
    }

    @Test
    public void test3()throws Exception{
        //get /a1    测试查看节点的值
        byte[] bytes = curator.getData().forPath("/a1");
        System.out.println(new String(bytes));
        curator.close();
    }

    @Test
    public void test4()throws Exception{
        //set /a1 测试修改节点的值
        curator.setData().forPath("/a1","测试2".getBytes());
        curator.close();
    }

    @Test
    public void test5()throws Exception{
        //stat /a1  
        Stat stat = curator.checkExists().forPath("/a1");
        System.out.println(stat);
        curator.close();
    }

    @Test
    public void test6()throws Exception{
        //delete /a1  
        //.deletingChildrenIfNeeded()  rmr /a1
        curator.delete().deletingChildrenIfNeeded().forPath("/a1");
        curator.close();
    }

    @Test
    public void test7()throws Exception{
        //ls /a1  查看某个节点下的所有节点信息,并监听下节点的变化(添加删除子节点)
        List<String> strings = curator.getChildren().forPath("/a1");
        for (String node : strings) {
            //System.out.println(node);
            // get   /a1/b1 
            byte[] bytes = curator.getData().forPath("/a1/" + node);
            System.out.println("/a1/" + node  + "   " + new String(bytes));
        }
        curator.close();
    }


    @Test
    public void test8()throws Exception{
        //get /a1 watch
        // 1 创建节点监听客户端
        final NodeCache nodeCache = new NodeCache(curator, "/a1");
        // 2 启动客户端监听器
        nodeCache.start();
        // 3 为客户端监听器,绑定监听事件函数
        nodeCache.getListenable().addListener(new NodeCacheListener() {
            /**
             * 一旦节点值变化,调用函数
             * @throws Exception
             */
            public void nodeChanged() throws Exception {
                byte[] data = nodeCache.getCurrentData().getData();
                System.out.println(new String(data));
            }
        });
        // 5 程序停止,客户端消失,监听也就失效
        Thread.sleep(Long.MAX_VALUE);
    }

    @Test
    public void test9()throws Exception{
        //ls /a1 watch
        // 1 创建子节点监听器客户端
        PathChildrenCache childCache = new PathChildrenCache(curator, "/a1", true);
        // 2 启动监听器
        childCache.start();
        // 3 为监听器绑定注册事件函数
        childCache.getListenable().addListener(new PathChildrenCacheListener() {
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
                    throws Exception {
                System.out.println(event.getType());
                switch(event.getType()){
                    case CHILD_ADDED:
                        System.out.println("节点添加");
                        System.out.println(event.getData().getPath()+":"+new String(event.getData().getData()));
                        break;
                    case CHILD_UPDATED:
                        System.out.println("节点更新");
                        System.out.println(event.getData().getPath()+":"+new String(event.getData().getData()));
                        break;
                    case CHILD_REMOVED:
                        System.out.println("节点删除");
                        System.out.println(event.getData().getPath()+":"+new String(event.getData().getData()));
                        break;
                    default:
                        System.out.println("其他");
                }
            }
        });
        // 5 客户端程序永不停止。
        Thread.sleep(Long.MAX_VALUE);
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值