zookeeper学习

zookeeper学习

Paxos算法

Paxos,它是一个基于消息传递的一致性算法,Leslie Lamport在1990年提出,近几年被广泛应用于分布式计算中,Google的Chubby,Apache的Zookeeper都是基于它的理论来实现的。Paxos还被认为是到目前为止比较有效的分布式一致性算法,其它的算法都是Paxos的改进或简化(只关注数据的一致性,不关注数据的正确性)。

   Paxos描述了这样一个场景,有一个叫做Paxos的小岛(Island)上面住了一批居民,岛上面所有的事情由一些特殊的人决定,他们叫做议员(Senator)。
议员的总数(Senator Count)是确定的,不能更改。岛上每次环境事务的变更都需要通过一个提议(Proposal),每个提议都有一个编号(PID)。这个编号是一直增长的,不能倒退。每个提议都需要超过半数((Senator Count)/2 +1)的议员同意才能生效。
   提议怎么通过呢?
    每个议员只会同意大于当前编号的提议,包括已生效的和未生效的。如果议员收到小于等于当前编号的提议,他会拒绝,并告知对方:你的提议已经有人提过了。这里的当前编号是每个议员在自己记事本上面记录的编号,他不断更新这个编号。整个议会不能保证所有议员记事本上的编号总是相同的。
    现在议会开始运作,所有议员一开始记事本上面记录的编号都是0。有一个议员发了一个提议:将电费设定为1元/度。他首先看了一下记事本,嗯,当前提议编号是0,那么我的这个提议的编号就是1,于是他给所有议员发消息:1号提议,设定电费1元/度。其他议员收到消息以后查了一下记事本,哦,当前提议编号是0,这个提议可接受,于是他记录下这个提议并回复:我接受你的1号提议,同时他在记事本上记录:当前提议编号为1。发起提议的议员收到了超过半数的回复,立即给所有人发通知:1号提议生效!收到的议员会修改他的记事本,将1好提议由记录改成正式的法令,当有人问他电费为多少时,他会查看法令并告诉对方:1元/度。
    假设总共有三个议员S1-S3,S1和S2同时发起了一个提议:1号提议,设定电费。S1想设为1元/度, S2想设为2元/度。结果S3先收到了S1的提议,于是他做了和前面同样的操作。紧接着他又收到了S2的提议,结果他一查记事本,咦,这个提议的编号小于等于我的当前编号1,于是他拒绝了这个提议:对不起,这个提议先前提过了。于是S2的提议被拒绝,S1正式发布了提议: 1号提议生效。S2向S1或者S3打听并更新了1号法令的内容,然后他可以选择继续发起2号提议。

zookeeper的介绍与安装

hadoop属于单节点,容易出现单点故障,所以我们使用zookeeper来解决这个问题。

Zookeeper是一个高效的分布式协调服务,可以提供配置信息管理、命名、分布式同步、集群管理、数据库切换等服务。它不适合用来存储大量信息,可以用来存储一些配置、发布与订阅等少量信息。

Zookeeper应用场景:Hadoop、Storm、消息中间件、RPC服务框架、分布式数据库同步系统都需要依赖Zookeeper做信息同步。

客户端发起事务请求,事务请求的结果在整个Zookeeper集群中所有机器上的应用情况是一致的。不会出现集群中部分机器应用了该事务,而存在另外一部分集群中机器没有应用该事务的情况。

在Zookeeper集群中的任何一台机器,其看到的服务器的数据模型是一致的。Zookeeper能够保证客户端请求的顺序,每个请求分配一个全局唯一的递增编号,用来反映事务操作的先后顺序。Zookeeper将全量数据保存在内存中,并直接服务于所有的非事务请求,在以读操作为主的场景中性能非常突出。

zookeeper集群的安装

Zookeeper节点个数(奇数)为3个。Zookeeper默认对外提供服务的端口号2181 。Zookeeper集群内部3个节点之间通信默认使用2888:3888。

1.下载安装包并在三台Linux节点中解压

#下载安装zookeper的tar包apache-zookeeper-3.5.7-bin.tar
tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz 
# 重新命名
mv apache-zookeeper-3.5.7-bin zookeeper-3.5.7

2.修改配置文件zookeeper中conf目录下的zoo_sample.cfg为zoo.cfg

#复制一份
cp zoo_sample.cfg  zoo.cfg

3.修改三个zookeeper节点中的zoo.cfg文件,修改dataDir,添加
server.0=ip:2888:3888
server.1=ip:2888:3888
server.2=ip:2888:3888

#先创建一个存放数据的文件夹
 mkdir data
#修改dataDir  数据目录
dataDir=/usr/local/soft/zookeeper-3.5.7/data
#添加
server.0=master:2888:3888
server.1=node1:2888:3888
server.2=node2:2888:3888

4.同步数据到其他两台节点
scp -r ./zookeeper-3.5.7 node1:pwd
scp -r ./zookeeper-3.5.7 node2:pwd

5.创建data目录并在3个zookeeper节点中data目录下分别创建myid文件,并分别添加内容0、1、2

# master节点上  在刚刚创建的data目录下
vim myid   #加入0
#其他节点分别是1,2

6.在节点中配置环境变量(三个节点都需要配置)
vim /etc/profile
添加内容:(具体路径为ZooKeeper安装目录)

ZOOKEEPER_HOME=/usr/local/soft/zookeeper-3.5.7
export PATH=$ZOOKEEPER_HOME/bin:$PATH

生效:
source /etc/profile

7.三个节点中分别启动并查看状态
zkServer.sh start
zkServer.sh status

master状态

image.png

node1状态

image.png

node2状态

image-20240531202806018.png

注意:各节点的Mode不是绝对的,是通过内部选举的。

Zookeeper中的进程:

Zookeeper集群中的节点,根据其身份特性分为leader、follower、observer。leader负责客户端writer类型的请求;follower负责客户端reader类型的请求,并参与leader选举;observer是特殊的follower,可以接收客户端reader请求,但是不会参与选举,可以用来扩容系统支撑能力,提高读取速度。

Zookeeper是一个基于观察者模式设计的分布式服务管理框架,负责存储和管理相关数据,接收观察者的注册。一旦这些数据的状态发生变化,zookeeper就负责通知那些已经在zookeeper集群进行注册并关心这些状态发生变化的观察者,以便观察者执行相关操作。

zookeeper命令

进入zookeeper客户端

# 使用命令
zkCli.sh

image.png

查看路径下的节点
ls  path 

创建节点
create 节点  --> 创建普通节点
create 节点 节点数据  --> 创建普通节点并携带数据
create -s 节点 --> 创建节点并在节点名称后添加编号
create -e 节点  --> 创建临时节点
获取节点中的数据

get 节点 
get -s 节点 查看节点中的数据并获取详细状态信息
get -w 节点 表示监听某个节点中的变化(只会触发一次效果)
设置节点中的数据
set 节点 数据  --> 给节点重新赋予数据

获取节点状态信息
stat  节点

删除节点
delete 节点 -- 可以删除一个不为空的节点(没有子节点)
deleteall 节点 

退出客户端
quit

image.png

对于create -e 节点 :

image.png

随后断开连接,过一段时间后,tmp节点消失:image.png

get- w 监控某个节点

此时重新打开一个master:启动zookeper :

image.png

监控到了变化:但是会触发一次。

image.png

zookeeperAPI的使用

在pom.xml中添加依赖:

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

先测试Zookeeper连接:

public class Demo01ZooKeeperCon {
    public static void main(String[] args) throws IOException, InterruptedException {
   /*
   *  public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
   *   connectString  连接信息     e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002"
   *   sessionTimeout 会话的超时时间   这里设置15s
   *   Watcher   一个接口
   *运行发现:
   * Zookeeper连接被获取,go!!!
        连接正在被创建
        连接正在被创建
        连接已关闭...
*   TODO
*  解释: 1。由于"连接正在被创建"是在Watcher接口的匿名内部类中创建的
*        process 方法被触发的时机是当Zookeeper连接发生变化时,才会触发process方法,当连接被创建或连接被关闭时process方法都会被创建。
*        2. System.out.println("Zookeeper连接被获取,go!!!"); 该代码会先执行的原因是:Zookeeper连接是一个异步调用的过程,相当于连接是一个线程操作的,
* 在main方法中是主线程,process放发是其他线程所调用的
   * */
        String con ="master:2181,node1:2181,node2:2181";
        ZooKeeper zooKeeper = new ZooKeeper(con, 15 * 1000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("连接正在被创建");
            }
        });
        System.out.println("Zookeeper连接被获取,go!!!");
        zooKeeper.close();
        System.out.println("连接已关闭...");



    }
}

实现功能:

①创建永久节点或临时节点

 public class Demo02CreateNode {
    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
       /*
       * 对于ZooKeeper在连接时要求本地必须有 master node1 node2的IP映射
       *   system32/drivers/etc/hosts
       * 也可以只连接其中的一个节点
       * */
        String con ="master:2181,node1:2181,node2:2181";
        ZooKeeper zooKeeper = new ZooKeeper(con, 15 * 1000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("连接正在被创建");
            }
        });
        /*
        * public String create(final String path, byte data[], List<ACL> acl,
            CreateMode createMode)
            * path = /node   路径
            * byte data[]  字节数组
            * acl   权限  可以通过ZooDefs.Ids获取
            * READ_ACL_UNSAFE 可读权限
            * CREATOR_ALL_ACL 可写权限
            * OPEN_ACL_UNSAFE 开放权限 可读可写
            *createMode  创建节点的类型   枚举类
            *PERSISTENT  持久化永久节点
            * EPHEMERAL  临时节点
            * CONTAINER  容器节点
            * PERSISTENT_WITH_TTL 创建带有生命周期的永久节点
        * */
        //  永久节点
//        zooKeeper.create("/api",
//                "当前节点用于存储api中的数据".getBytes(StandardCharsets.UTF_8),
//                ZooDefs.Ids.OPEN_ACL_UNSAFE,
//                CreateMode.PERSISTENT);

//        临时节点    会话关闭后节点消失
        zooKeeper.create("/api_tmp",
                "当前节点用于存储api中的数据".getBytes(StandardCharsets.UTF_8),
                ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.EPHEMERAL);
                Thread.sleep(5*1000);
        zooKeeper.close();

    }
}

image.png

image.png

②获取节点数据

public class Demo03GetData {
    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        String con ="master:2181,node1:2181,node2:2181";
        ZooKeeper zooKeeper = new ZooKeeper(con, 15 * 1000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("连接正在被创建");
            }
        });
        /*
        * getData:    public byte[] getData(String path, boolean watch, Stat stat)
        * path  路径
        * boolean watch  是否监听
        * stat  状态
        *
        * */
        String path = "/api";
        Stat stat = zooKeeper.exists(path, false);
//        System.out.println(stat);   节点不存在时—>null
        if (stat!=null) {
            byte[] data = zooKeeper.getData(path, false, stat);
            String dataStr = new String(data);
            System.out.println(dataStr);
        }
        zooKeeper.close();

    }
}

image.png

③获取当前节点的子节点

// 先在/api下创建几个节点
public class Demo04GetChild {
    public static void main(String[] args) throws InterruptedException, IOException, KeeperException {
        /*获取当前节点的子节点*/
        String con ="master:2181,node1:2181,node2:2181";
        ZooKeeper zooKeeper = new ZooKeeper(con, 15 * 1000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("连接正在被创建");
            }
        });
        // getChildren 方法
        List<String> children = zooKeeper.getChildren("/api", false);
        for (String child : children) {
            System.out.println(child);
        }
        zooKeeper.close();
    }
}

image.png

④监听节点变化

public class Demo05GetChange {
    public static void main(String[] args) throws InterruptedException, IOException, KeeperException {
            /*获取当前节点的子节点*/
            String con ="master:2181,node1:2181,node2:2181";
            ZooKeeper zooKeeper = new ZooKeeper(con, 15 * 1000, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    System.out.println("连接正在被创建");
                }
            });

                    watchData(zooKeeper);
                    watchChild(zooKeeper);
                    zooKeeper.close();
    }

// 监听数据
    public static void watchData(ZooKeeper zooKeeper) throws InterruptedException, KeeperException {
        String path = "/api";
        Stat exists = zooKeeper.exists(path, false);
        byte[] data = zooKeeper.getData(path, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("发生变化,触发了监听");
            }
        }, exists);
        System.out.println(new String(data));
//                  Watcher创建了一个新线程,但是主线程结束,从线程也会结束达不到监听效果所以:
        Thread.sleep(10*1000);

    }
// 监听节点
    public static void watchChild(ZooKeeper zooKeeper) throws InterruptedException, KeeperException {
        String path = "/api";
        List<String> children = zooKeeper.getChildren(path, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("子节点发生变化...");
            }
        });
        for (String child : children) {
            System.out.println(child);
        }
        Thread.sleep(10*1000);
        zooKeeper.close();
    }
}

在虚拟机中对 /api 节点赋予数据:

image.png

image.png

在/api下新创建一个节点:

image-20240602200334548.png

image.png

⑤删除节点

public class Demo06DeleteNode {
    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        String con ="master:2181,node1:2181,node2:2181";
        ZooKeeper zooKeeper = new ZooKeeper(con, 15 * 1000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("连接正在被创建");
            }
        });
        // 如果要删除最新版本  就是-1
        // 不能删除不为空的目录
        zooKeeper.delete("/node0000000002",-1);
        zooKeeper.close();
    }
    // 如何完成一个deleteAll操作? 循环遍历节点下的节点,数据。。。

}

删除node0000000002节点:

image.png

image.png

基于ZooKeeper构建HA

什么是HA?

​ HDFS的HA,指的是在一个集群中存在两个NameNode,分别运行在独立的物理节点上。在任何时间点,只有一个NameNodes是处于Active状态,另一种是在Standby状态。 Active NameNode负责所有的客户端的操作,而Standby NameNode用来同步Active NameNode的状态信息,以提供快速的故障恢复能力。

​ 为了保证Active NN与Standby NN节点状态同步,即元数据保持一致。除了DataNode需要向两个NN发送block位置信息外,还构建了一组独立的守护进程”JournalNodes”,用来同步Edits信息。当Active NN执行任何有关命名空间的修改,它需要持久化到一半以上的JournalNodes上。而Standby NN负责观察JNs的变化,读取从Active NN发送过来的Edits信息,并更新自己内部的命名空间。一旦ActiveNN遇到错误,Standby NN需要保证从JNs中读出了全部的Edits,然后切换成Active状态。使用HA的时候,不能启动SecondaryNameNode,会出错

HA步骤

配置之前先拍摄快照(重要)

快照名称设置为 Zookeeper

  • 配置免密登录

    # 在node1节点执行
    ssh-keygen -t rsa
    # 三次回车之后
    ssh-copy-id node1
    ssh-copy-id node2
    ssh-copy-id master
    
  • 关闭所有Hadoop

    # 关闭Hadoop
    stop-all.sh
    
  • 在core-site.xml中添加如下配置:

    -- 注意需要将原先的9000修改成cluster
    <property>
            <name>fs.defaultFS</name>
           <!-- <value>hdfs://master:9000</value> -->
            <value>hdfs://cluster</value>
    </property>
    
    <property>
                  <name>ha.zookeeper.quorum</name>
                  <value>master:2181,node1:2181,node2:2181</value>
    </property>
    
  • 在hdfs-site.xml中添加如下配置:

    <!-- //以下为HDFS HA的配置// -->
    <!-- 指定hdfs的nameservices名称为mycluster -->
    <property>
    <name>dfs.nameservices</name>
    <value>cluster</value>
    </property>
    
    <!-- 指定cluster的两个namenode的名称分别为nn1,nn2 -->
    <property>
    <name>dfs.ha.namenodes.cluster</name>
    <value>master,node1</value>
    </property>
    
    <!-- 配置nn1,nn2的rpc通信端口 -->
    <property>
    <name>dfs.namenode.rpc-address.cluster.master</name>
    <value>master:8020</value>
    </property>
    <property>
    <name>dfs.namenode.rpc-address.cluster.node1</name>
    <value>node1:8020</value>
    </property>
    
    <!-- 配置nn1,nn2的http通信端口 -->
    <property>
    <name>dfs.namenode.http-address.cluster.master</name>
    <value>master:9870</value>
    </property>
    <property>
    <name>dfs.namenode.http-address.cluster.node1</name>
    <value>node1:9870</value>
    </property>
    
    <!-- 指定namenode元数据存储在journalnode中的路径 -->
    <property>
    <name>dfs.namenode.shared.edits.dir</name>
    <value>qjournal://master:8485;node1:8485;node2:8485/cluster</value>
    </property>
    
    <!-- 指定journalnode日志文件存储的路径 -->
    <property>
    <name>dfs.journalnode.edits.dir</name>
    <value>/usr/local/soft/hadoop-2.7.6/data/journal</value>
    </property>
    
    <!-- 指定HDFS客户端连接active namenode的java类 -->
    <property>
    <name>dfs.client.failover.proxy.provider.cluster</name>
    <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
    </property>
    
    <!-- 配置隔离机制为ssh -->
    <property>
    <name>dfs.ha.fencing.methods</name>
    <value>sshfence</value>
    </property>
    
    <!-- 指定秘钥的位置 -->
    <property>
    <name>dfs.ha.fencing.ssh.private-key-files</name>
    <value>/root/.ssh/id_rsa</value>
    </property>
    
    <!-- 开启自动故障转移 -->
    <property>
    <name>dfs.ha.automatic-failover.enabled</name>
    <value>true</value>
    </property>
    
    <!-- 关闭权限验证 -->
    <property>
    <name>dfs.permissions.enabled</name>
    <value>false</value>
    </property>
    
  • 在hadoop-env.sh添加环境变量配置

    export HDFS_ZKFC_USER=root
    export HDFS_JOURNALNODE_USER=root
    
  • 上传配置文件至master的 /usr/local/soft/hadoop-3.1.3/etc/hadoop 并分发其他节点

    cd /usr/local/soft/hadoop-3.1.3/etc/hadoop
    scp hdfs-site.xml root@node1:`pwd`
    scp hdfs-site.xml root@node2:`pwd`
    scp core-site.xml root@node1:`pwd`
    scp core-site.xml root@node2:`pwd`
    scp hadoop-env.sh root@node1:`pwd`
    scp hadoop-env.sh root@node2:`pwd`
    
  • 删除所有节点中的tmp目录

    # 在master node1 node2 节点删除
    rm -rf /usr/local/soft/hadoop-3.1.3/data
    
  • 启动Zookeeper三台都需要启动

    zkServer.sh start
    zkServer.sh status
    
  • 配置node1、node2 环境变量添加 HADOOP_HOME

    vim /etc/profile
    # 添加:
    # HADOOP_HOME
    export HADOOP_HOME=/usr/local/soft/hadoop-3.1.3
    export PATH=$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$PATH
    
    source /etc/profile
    
  • 启动JN 存储hdfs元数据

    三台JN上执行 启动命令: 
     	hadoop-daemon.sh start journalnode
    
  • 格式化 在一台NN上执行(在master上执行)

    hdfs namenode -format
    # 并启动NameNode
    hadoop-daemon.sh start namenode
    
  • 执行同步 没有格式化的NN上执行 在另外一个namenode上面执行(在node1上执行)

    hdfs namenode -bootstrapStandby
    
  • 格式化ZK 在已经启动的namenode上面执行(在master上执行)

    # 保证Zookeeper启动状态
    hdfs zkfc -formatZK
    
  • 启动整个集群

    start-all.sh
    
image.png

master成为备用节点

image.png

如果想要使用master节点,可以直接杀死node1下的NameNode进程。这样master就会变成active状态。此时就可以做上传数据等操作呢。再想启动node1可以使用:

hadoop-daemon.sh start namenode

注意:学习之后,回退到快照。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值