zookeeper原理即代码实现

1. Zookeeper是什么?

zookeeper是一个开源的注册中心,分布式服务管理框架,用于为分布式框架提供协调服务的Apache项目,通常称为注册中心。提供相应服务:统一命名服务(比如域名对应ip命名,一个域名下面多个ip)、统一配置服务(比如一个分布式集群所有配置一样的情况,同步到集群中所有节点)、统一集群管理(客户端注册节点用于监听服务器节点)、服务器节点动态上下线,负载均衡(比如域名对应ip,里面有三个ip,对这个域名节点下的节点负载均衡)等。

1.1 zookeeper的工作机制:

服务器节点注册,客户端获取节点信息并监听。负责存储和管理节点的数据,采用观察者模式,当节点数据发生改变时候,及时监听并通知那些观察者(客户端)做出相应的通知。用通俗易懂的描绘就是,服务器启动注册信息到Zookeeper上,而客户端通过Zookeeper去监听服务器列表,当某些服务器节点改变或者修改时候,即使通知那些订阅了该服务器的客户端告知服务器有了改变。
一句话实现就是:Zookeeper=文件系统+通知机制。

1.2 Zookeeper的特点:

  1. Zookeeper集群中,只有一个leader(领导者master),多个follower(随从者)。
  2. 集群中成功机制,只有集群中只有有半数节点存活,则表示存活,否在失败。
  3. 全局数据一致性,每一份server都保存相同的数据,无论连接哪一台服务器都是一致的。
  4. 数据事务性,要么一起成功,要么失败。
  5. 数据实时性,一定时间内,能读到最新数据。

1.3 数据结构

Zookeeper的数据模型是一颗数,每一个节点一个Znode,但每个node最多只能存储1024kb数据,每个zNode都有唯一的标识。

1.4下载方式及建议

1.官网链接:https://zookeeper.apache.org/,下载好版本
(最好用于linux中更好的发挥作用,当然学习,也可以在win中安装。)

  1. 要使用zookeeper当然要安装jdk,tar指令解压到指定目录(通常会在opt下)。

  2. 修改名称,这个文件后面描述。Zookeeper下conf下的zoo_sample.cfg修改为zoo,cfg

  3. 修改zoo.cfg文件,并在该目录创建该文件夹。

  4. 启动zookeeper,如果没有关闭防火墙请关闭,service iptables stop:

  5. 查看zookeeper是否启动:
    6.1通过进程查看是否启动:

6.2通过指令查看该状态:如果报错请检查防火墙是否关闭

7.zookeeper的停止:

2.Zookeeper的集群搭建

2.1配置文件的解读(conf/zoo.cfg*):

1.tickTime=2000:表明zookeeper服务器与客户端的心跳时间,怎么理解呢,就是每隔2s发出一个心跳,表明自己这个节点还活着。
2.initLimit=10,leader与follower初始连接时候最大容忍心跳数为10.超过10次没有接收到心跳,则表示也是死了。
3.synLimit=5,leader与follower 非首次,保持通信时间,如果,超过了5次心跳时间没有心跳,则认为follower死了
4.dataDir:保存Zookeeper中的数据。
这个默认是/tem目录,会被linux扫描,不定时会清除,所以保险起见改为这个目录。
6. clientPort=2181,端口号为2181
7. 后期还有配置文件表明集群的地址

2.2集群操作:

1.根据上面操作在多台服务器安装zookeeper,内容一致。介绍一个分发shell指令xsync
1.1修改配置文件 vim /etc/hosts,将需要分发的服务器地址加入进去映射,方便之后直接用名称。

1.2 安装rsync  yum install -y rsync,xsync是对rsync进行二次封装而已
1.3 创建shell脚本xsync放入 bin文件下:vim usr/local/bin/xsync,将已下内容修改复制:

#!/bin/bash
#1 获取输入参数个数,如果没有参数,直接退出

pcount=$#
if((pcount==0)); then
echo no args;
exit;
fi
#2 获取文件名称

p1=$1
fname=basename $p1
echo fname=$fname
#3 获取上级目录到绝对路径
pdir=cd -P $(dirname $p1); pwd
echo pdir=KaTeX parse error: Expected 'EOF', got '#' at position 6: pdir #̲4 获取当前用户名称 user…host --------------
rsync -rvl p d i r / pdir/ pdir/fname u s e r @ h a d o o p user@hadoop user@hadoophost:$pdir
done

2.修改配置文件zookeeper中的zoo.cfg,新增上面数据并再新增集群服务器地址(hadoop1这种表示因为hosts文件会映射)解读一下(server.A=B:C:D)(A为myid,B为服务器地址,C为leader与follower交换信息的端口号,D为选举端口号,就是后面会说的选举机制):

#######################cluster##########################
server.1=hadoop1:2888:3888
server.2=hadoop2:2888:3888
server.3=hadoop3:2888:3888

3.在zkData目录下创建myid文件用于对应的server.1,server.2,server.3,并添加相应编号,1对应1,2对应2
vim /zkData/myid

输入对应编号即可,跟server对应

4.分发zookeeper,并修改myid
Xsync zookeeper-3.57

5.分别启动zookeeper,记得关闭防火墙,查看状态

2.3 选举机制

1.了解几个概念:
SID:服务器ID,每台集群中的机器的唯一标识,与myid一致
ZXID:事务ID,每次服务器事务更改,都会进行改变,client每次写操作都会对事务id改变。
Epoch:每一次leader运行的编号,在选票机制中,每投完一次票都会增加。

2.选票机制:只要集群中有一半以上服务器存活则可以进行选举
2.3.1首次启动选举,加入有五台服务器:
①服务器1启动,投自己一票,发起第一次选举,不够半数以上(❤️)的票则选举无法完成,仍保持观察状态。
②服务器2启动,再发生一次选举,2又投自己一票,然后与服务器1交换选票,此时发现服务器1的myid>服务器2的myid,此时,把服务器2的票交换给了服务器1,此时服务器2为0票,服务器1为2票,2<3此时还是没有完成选举。
③服务器3启动,先投自己一票,他会又去和服务器1交换选票,此时如果服务器1的myid>服务器3的myid则又把票交换到服务器1,此时服务器1为三票,3<3不成立,则服务器1选举成功为leader,后面所有服务器都为follower.
④4、5服务器启动都为follower。
2.3.2非首次分享
1.Epoch大的胜出。
2.Epoch相同,Zxid大的胜出。
3.Zxid相同,sid大的胜出。

2.4 zk集群的脚本配置启动:

1.在/home/ze/bin目录创建脚本

vim zk.sh

脚本内容为:

#!/bin/bash
case $1 in
“start”){
for i in hadoop1 hadoop2 hadoop3
do
echo ---------- zookeeper $i 启动 ------------
ssh $i “/opt/module/zookeeper-3.57/bin/zkServer.sh start”
done
};;
“stop”){
for i in hadoop1 hadoop2 hadoop3
do
echo ---------- zookeeper $i 停止 ------------
ssh $i “/opt/module/zookeeper-3.57/bin/zkServer.sh stop”
done
};;
“status”){
for i in hadoop1 hadoop2 hadoop3
do
echo ---------- zookeeper $i 状态 ------------
ssh $i “/opt/module/zookeeper-3.57/bin/zkServer.sh status”
done
};;
esac

2)增加权限

Chmod u+x zk.sh
3)启动脚本
Zk.sh start
4)分发脚本
Xsync zk.sh

2.5客户端启动与使用:

1.精确启动客户端:

2.查看节点信息:
ls /节点名
ls -s /节点名 查询该节点的详细信息

1.其中czxid为创建节点时候的事务id
Ctime:被创建的时间
Mzxid:最后更新的事务id
Pzxid:最后跟新的子节点事务id
Mtime:在最后修改的时间
Cversion:节点修改的次数
Datalength:节点的数据长度
NumChildren:子节点的数量

3.创建节点:
1)持久化节点:客户端与zookeeper断开后仍存在,但编号是没有顺序的。

2)持久化顺序节点:客户端与zookeeper断开后仍存在,但编号是有顺序的。

3)临时节点:客户端与zookeeper断开后不存在,但编号是没有顺序的

4)顺序临时节点:客户端与zookeeper断开后不存在,但编号是有顺序的

4.监听器:
4.1原理:
1.一个main()线程,当创建zookeeper客户端,会创建两个线程,一个connect线程负责网络连接,一个listerner负责监听
2.通过connect线程将注册的监听事件发送给Zookeeper.
3.zookeeper监听到有数据变化或路径变化,就会将这个消息发送给监听器,然后进行process方法进行处理。

4.2 监听指令:
①监听一个节点:

get -w /xuexi

②再另外一台服务上修改该节点
set /xuexi “python”

③监听器触发

注意点:一个监听器只会监听一次,触发后再次修改就不会再次监听。注册一次只能监听一次,若想再次监听则需要再次注册
创建create,删除同理delete节点同理
5.节点的删除与查看节点状态:
①删除节点:
delete /zeng
②删除底下不是空节点:
deleteall /sanguo
③查看节点状态

[zk: hadoop1:2181(CONNECTED) 10] stat /xuexi
cZxid = 0x600000002
ctime = Sun Sep 05 21:22:00 CST 2021
mZxid = 0x600000008
mtime = Mon Sep 06 00:32:50 CST 2021
pZxid = 0x600000006
cversion = 4
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 4

6.idea环境的操作
①导入pom文件

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

②加入日志文件,用于打印一下zookeeper日志方便查看,创建一个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

③连接Zookeeper服务并监听

private static String connectString = "192.168.119.129:2181,192.168.119.131:2181,192.168.119.132:2181";//服务器ip
private static int sessionTimeOut = 200000;//超时事件
private ZooKeeper zkClient = null;//客户端
@Before
public void init() throws IOException {
    zkClient = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
        public void process(WatchedEvent watchedEvent) {

            System.out.println("---------------------------");
            // 收到事件通知后的回调函数(用户的业务逻辑)
            System.out.println(watchedEvent.getType() + "--" + watchedEvent.getPath());
            // 再次启动监听
            try {
                System.out.println("-----------------------------");
                List<String> children = zkClient.getChildren("/", true);
                for (String child : children) {
                    System.out.println(child);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });

}

④客户端创建节点

@Test
public void create() throws InterruptedException, KeeperException {
    String s = zkClient.create("/zeng", "zengjinxin".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}

// OPEN_ACL_UNSAFE : 完全开放的ACL,任何连接的客户端都可以操作该属性znode
//CREATOR_ALL_ACL : 只有创建者才有ACL权限
//READ_ACL_UNSAFE:只能读取ACL
1、PERSISTENT 持久化目录节点,存储的数据不会丢失。
2、PERSISTENT_SEQUENTIAL 顺序自动编号的持久化目录节点,存储的数据不会丢失,并且根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名。
3、EPHEMERAL 临时目录节点,一旦创建这个节点的客户端与服务器端口也就是session 超时,这种节点会被自动删除。
4、EPHEMERAL_SEQUENTIAL 临时自动编号节点,一旦创建这个节点的客户端与服务器端口也就是session 超时,这种节点会被自动删除,并且根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名。

⑤获取节点

@Test
public void getChildNode() throws InterruptedException, KeeperException {
    List<String> children = zkClient.getChildren("/", true);
    for (String child : children) {
        System.out.println(child);
    }
    // 延时阻塞
    Thread.sleep(Long.MAX_VALUE);
}

7.写流程写入请求发送给leader和follower流程:
①客户端发送写请求,如果发送的是给leader,发送给follower写请求,但达到一半以上服务后,给个消息ack,表示通知了一半以上就返回给客户端ack,后台再异步发送写请求到其他服务器,同步完成后再发一个ack消息给leader同步完成。
②客户端发送写请求,如果发送的是给follower写请求,会给leader一个写请求,leader会发布写请求到各个服务器,单达到半数以上时候,发送ack到leader,leader发送ack给接收请求的follower,该follower发送ack给客户端,之后leader再异步发送写请求给各个剩余服务器,同步完成后发送ack给leader.

  1. idea实例,通过代码实现服务器上线下线通知案例
    思路:把该节点服务器进行注册到zookeeper中,通过不断监听该节点,并处理。
    代码实现:
    ①创建一个节点
public class DistributeServer {
    private static String connectString="192.168.119.129:2181,192.168.119.131:2181,192.168.119.132:2181";
    private static int sessionTimeOut=200000;
    private ZooKeeper zk=null;
    private String parentNode="/zengjinxin";

    //获取zk连接
    public void getConnect() throws IOException {
        zk = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
            public void process(WatchedEvent watchedEvent) {

            }
        });
    }
    //注册服务器
    public void registServer(String hostName) throws InterruptedException, KeeperException {
        String create = zk.create(parentNode + "/guoqin", hostName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        System.out.println(hostName +" is online "+ create);
    }
    //业务代码
    public void business(String hostName) throws InterruptedException {
        System.out.println(hostName + " is working ...");
        Thread.sleep(Long.MAX_VALUE);
    }

    public static void main(String[] args) throws Exception {
        DistributeServer distributeServer = new DistributeServer();
        //获取连接
        distributeServer.getConnect();
        //创建server节点
        distributeServer.registServer("guoqin");
        //业务代码
        distributeServer.business("guoqin");
    }
}

②监听该节点:

public class DistributeClient {
    private static String connectString="192.168.119.129:2181,192.168.119.131:2181,192.168.119.132:2181";
    private static int sessionTimeOut=200000;
    private static ZooKeeper zk=null;
    private static String parentNode="/zengjinxin";
    //获取zk连接
    public static void getConnect() throws IOException {
        zk = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
            public void process(WatchedEvent watchedEvent) {
                // 再次启动监听
                try {
                    getServerList();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
// 获取服务器列表信息
  private static void getServerList() throws InterruptedException, KeeperException {

        // 1 获取服务器子节点信息,并且对父节点进行监听
        List<String> children = zk.getChildren(parentNode, true);
        // 2 存储服务器信息列表
        ArrayList<String> servers = new ArrayList<String>();
        //遍历子节点
        for (String child : children) {
            byte[] data = zk.getData(parentNode + "/" + child, true, null);
            servers.add(new String(data));
        }
        // 4 打印服务器列表信息
        System.out.println(servers);
    }

    // 业务功能
    public static void business() throws Exception{
        System.out.println("client is working ...");
        Thread.sleep(Long.MAX_VALUE);
    }

    public static void main(String[] args) throws Exception {
        //获取连接
        getConnect();
        //获取服务器列表
        getServerList();
        //业务功能
        business();

    }
}

测试的话:服务器修改或者查看该节点试试会不会触发监听器即可。

以上就是zookeeper的所有总结学习,再见!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Z J X

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

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

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

打赏作者

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

抵扣说明:

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

余额充值