大数据 ZooKeeper

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Notzuonotdied/article/details/82690726

前言

ZooKeeper是一个为分布式应用所设计的开源协调服务。

介绍

ZooKeeper是一个为分布式应用所设计的开源协调服务。它主要为用户提供同步、配置管理、分组和命名等服务,减轻分布式应用程序所承担的协调任务。ZooKeeper的文件系统使用了我们所熟悉的目录树结构。ZooKeeper是使用Java编写的,但是它支持JavaC两种编程语言。

设计目标

目标 说明
简单化 ZooKeeper允许分布式的进程通过共享体系的命名空间来进行协调,ZooKeeper采用Znode搭建与标准文件系统类似的命名空间【可以参见下一章“ ZooKeeper 命名空间”】,且该命名空间是存放在内存中的,这就意味着ZooKeeper具备高吞吐量、低延迟的能力。ZooKeeper具备高性能、高可靠性以及严格的有序访问。
健壮性 使用心跳来检测服务器的状态。如果有服务器失联,就连接到其他备用服务器上。
有序性 ZooKeeper可以为每一次更新操作赋予一个版本号,并且此版本号是全局有序的,不存在重复的情况。
速度优势 读性能优于写性能。
原子性 在命名空间中,每一个Znode的数据将被原子地读写。读操作将读取与Znode相关的所有数据,写操作将替换掉所有的数据。每一个结点都有一个访问控制表,规定了用户操作的权限。
可靠性
时效性

ZooKeeper 命名空间

ZooKeeper拥有一个层次的命名空间。在命名空间中,每个结点称为Znode,每个Znode包含了它自身和它的子节点相关联的数据。指向结点的路径必须使用规范的绝对路径表示,并且以“/”来分隔。【在ZooKeeper中不允许使用相对路径来表示】

在这里插入图片描述

每个Znode维护着一个属性结构,包含了数据的版本号dataVersion,时间戳ctime、mtime等状态信息。

[zk: localhost:2181(CONNECTED) 14] get /app2
aaabbb
cZxid = 0x600000004 # 创建事务编号
ctime = Thu Sep 13 21:47:07 EDT 2018 
mZxid = 0x600000004 # 修改事务编号
mtime = Thu Sep 13 21:47:07 EDT 2018
pZxid = 0x600000004 # 持久化事务编号
cversion = 0 # 创建版本
dataVersion = 0
aclVersion = 0 # 权限版本
ephemeralOwner = 0x65d5b998e80000
dataLength = 6 # 数据长度
numChildren = 0 # 子节点个数

看不懂这些属性,可以参见【Znode 属性

Znode 主要特征

特征 说明
Watch 客户端可以在节点上设置Watch,当节点的数据发生变化(增删改等操作)将会出发Watch的对应的操作。【Watch有且仅会被触发一次并发送一个通知。】
数据访问 ZooKeeper中的每个节点上存储的数据需要被原子性的操作。
临时节点 ZooKeeper中的节点有两种,分别为临时节点和永久节点。节点的类型在创建后不能改变。ZooKeeper临时节点的生命周期依赖于创建它们的Session。一旦Session结束,临时节点将被自动删除,当然也可以手动删除。ZooKeeper的临时节点不允许拥有子节点。相反,永久节点的生命周期不依赖于Session,并且只有在客户端显示执行删除操作的时候,它们才被删除。
顺序结点 【唯一性】当创建Znode的时候,用户可以请求在ZooKeeper的路径结尾添加一个递增的计数。这个计数对于此节点的父节点来说是唯一的,它的格式为“%010d”(10位数字,没有数值的数据位用0填充,例如0000000001)。当计数值大于232-1时,计数器将会溢出。

Znode 属性

属性 说明
czxid 节点被创建的Zxid值。
mzxid 节点被修改的Zxid值。
ctime 节点被创建的时间。
mtime 节点被修改的时间。
version 节点被修改的版本号。
cversion 节点所拥有的子节点被修改的版本号。
aversion 节点的ACL被修改的版本号。
ephemeralOwner 如果此节点是临时节点,那么它的值就是这个节点拥有者的Session ID。否则,它的值为0。
numChildren 节点拥有的子节点的个数。

ZooKeeper ACL

ZooKeeper使用ACL对Znode进行访问控制。ACL拥有三种模式:

  • user:文件的拥有者
  • group:用户组
  • world:

一个ACL和一个ZooKeeper节点是对应的,并且不存在继承关系,相互独立。

访问控制权限所规定的权限

权限 权限描述
CREATE 创建子节点。
READ 从节点获取数据或者列出节点的所有子节点。
WRITE 设置节点的数据。
DELETE 删除子节点。
ADMIN 可以设置权限。

验证模式

模式 说明
world 代表某一特定的用户(Client)。
auth 代表任何已经通过验证的用户(Client)。
digest 通过用户密码进行验证。
ip 通过Client IP地址进行验证。

ZooKeeper一致性保证

一致性特点 说明
顺序一致性 Client的更新顺序与它们发送的顺序相一致的。
原子性 更新操作只有失败和成功两种结果。
单系统镜像 ZooKeeper视图在不同服务器上都一致。
可靠性 一旦一个更新操作被应用,那么在更新之前,其值都不会改变。
实时性 在特定的一段时间内,客户端看到的系统需要被保证是实时的(在十几秒的时间里)。在此时间段内,任何系统的改变将被客户端看到,或者被客户端侦测到。

ZooKeeper Leader选举

搭建环境

  • 环境:VMWare下的四个Centos7虚拟机。

首先,当然是先下载环境啦~点击跳转到下载页面

  • 修改主机名:(也可以不修改)
    • vim /etc/sysconfig/network
    • 增加:HOSTNAME=hadoop
  • 下载:wget http://mirrors.hust.edu.cn/apache/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz
  • 解压:tar -zxvf zookeeper-3.4.10.tar.gz
  • 移动到/opt目录下:
    • sudo mv zookeeper-3.4.10 /opt
    • cd /opt
    • sudo mv zookeeper-3.4.10 zookeeper
  • 修改配置文件:
    • cd conf
    • cp zoo_sample.cfg zoo.cfg
    • vim zoo.cfg
    • 修改dataDir目录:
      • mkdir /opt/zookeeper/zooData
      • 修改dataDir=/tmp/zookeeper/opt/zookeeper/zooData
    • 增加Log输出目录:
      • mkdir /opt/zookeeper/zooLog
      • dataLogDir=/opt/zookeeper/zooLog
    • 增加服务端:
      • 增加一个IP地址别名,后面配置使用。
        • vim /ect/hosts
        • 说明:IP地址 别名
        • 192.168.80.8 node0
        • 192.168.80.9 node1
        • 192.168.80.10 node2
        • 192.168.80.11 node3
      • 说明:等号后参数分别为:主机名、心跳端口、数据端口
        • server.1=node1:2888:3888
        • server.2=node2:2888:3888
        • server.3=node3:2888:3888
    • 配置myid
      • 在每个服务端都需要这样子配,但是myid不允许相同
      • myid就是上述的server.数字的数字,即server.1中的1就是myid的值。
      • echo 0 > /opt/zookeeper/zooData/myid

免密登录访问

  • 配置SSH免密登录访问。
    • 生成公钥秘钥:ssh-keygen,一路回车即可。
    • 进入~/.ssh文件夹,创建一个touch authorized_keys文件,将需要SSH免登录的服务端的公钥id_rsa.pub中的内容复制到authorized_keys中即可。

分发Zookeeper

  • 将配置好的zookeeper文件夹分发到其他的服务端上,记得修改myid,和按照上修改配置文件部分重新修改一遍。
    • scp -r /opt/zookeeper root@node1:/opt/

关闭防火墙

  • 关闭防火墙(其实也可以单独开某几个端口吧,为了方便,自行选择哈~
    • Centos7
      • systemctl stop firewalld && systemctl disable firewalld
    • Centos6
      • service iptables stop

启动zookeeper

全部配置完毕之后,就是启动zookeeper了。
如果启动不成功的话,可以看看zookeeper/bin/zookeeper.out下的日志输出。

  • 启动
    • ./bin/zkServer.sh start
  • 查看状态
    • ./bin/zkServer.sh status
  • 启动输出如下:
[root@node3 bin]# ./zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
  • 启动成功如下:
    • 下面leaderMaster服务器,这个是选举出来的,如果Master挂掉,会重新随机选举出来。
    • followerSlave服务器。
[root@node3 bin]# ./zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper/bin/../conf/zoo.cfg
Mode: leader 

[root@node2 bin]# ./zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper/bin/../conf/zoo.cfg
Mode: follower

ZooKeeper JMX enabled by default
Using config: /opt/zookeeper/bin/../conf/zoo.cfg
Mode: follower
  • 如果kill -9leader,那么其他节点就会重新投票产生leader
  • 半数机制:集群中半数以上机器存活,集群可用。如果低于半数的机器存活,那么就会出现Error contacting service. It is probably not running.

zkCli Command

使用./bin/zkCli.sh启动命令行,使用help可以查看帮助文档。

  • ls path [watch]
    • ls 路径 监听
[zk: localhost:2181(CONNECTED) 6] ls /
[zookeeper]
  • create [-s] [-e] path data acl
    • create 序号 结点类型 路径 数据 权限
    • 结点类型有两种:
      • 短暂ephemeral:断开连接就会删除自身结点。
      • 永久persistent:断开连接不删除自身结点。
      • Znode有四种形式的结点(默认是persistent
        • PERSISTENT
        • PERSISTENT_SEQUENTIAL(持久序列/test0000000019 )
        • EPHEMERAL
        • EPHEMERAL_SEQUENTIAL
[zk: localhost:2181(CONNECTED) 6] ls /
[zookeeper]
[zk: localhost:2181(CONNECTED) 7] create /app1 aaa
Created /app1
[zk: localhost:2181(CONNECTED) 8] ls /
[zookeeper, app1]
[zk: localhost:2181(CONNECTED) 9] create -s /app2 aaa
Created /app20000000001
[zk: localhost:2181(CONNECTED) 10] ls /
[app20000000001, zookeeper, app1]
[zk: localhost:2181(CONNECTED) 11] create -e /app2 aaabbb
Created /app2
[zk: localhost:2181(CONNECTED) 12] ls /                  
[app20000000001, zookeeper, app2, app1]
  • get path [watch],获取数据
    • get 路径 监听
[zk: localhost:2181(CONNECTED) 14] get /app2
aaabbb
cZxid = 0x600000004 # 创建事务编号
ctime = Thu Sep 13 21:47:07 EDT 2018 
mZxid = 0x600000004 # 修改事务编号
mtime = Thu Sep 13 21:47:07 EDT 2018
pZxid = 0x600000004 # 持久化事务编号
cversion = 0 # 创建版本
dataVersion = 0
aclVersion = 0 # 权限版本
ephemeralOwner = 0x65d5b998e80000
dataLength = 6 # 数据长度
numChildren = 0 # 子节点个数
  • set path data [version],设置结点数据
    • set 路径 数据 [版本号]
# 设置数据为Hello_world
[zk: localhost:2181(CONNECTED) 15] set /app2 Hello_world 
cZxid = 0x600000004
ctime = Thu Sep 13 21:47:07 EDT 2018
mZxid = 0x600000005
mtime = Thu Sep 13 22:17:11 EDT 2018
pZxid = 0x600000004
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x65d5b998e80000
dataLength = 11
numChildren = 0
[zk: localhost:2181(CONNECTED) 16] get /app2
Hello_world # 重新设置的数据
cZxid = 0x600000004
ctime = Thu Sep 13 21:47:07 EDT 2018
mZxid = 0x600000005
mtime = Thu Sep 13 22:17:11 EDT 2018
pZxid = 0x600000004
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x65d5b998e80000
dataLength = 11
numChildren = 0
  • delete path [version]
    • delete 路径 版本号
[zk: localhost:2181(CONNECTED) 18] delete /app2
[zk: localhost:2181(CONNECTED) 19] ls /app2
Node does not exist: /app2

监听数据的变化

  • get path [watch]
    • get 路径 监听器
    • 监听效果有且仅有一次,换句话时候,就是只能监听一次,一次之后就失效了,需要重新建立监听器。

Java API使用

依赖库

  • log4j-1.2.17.jar
  • zookeeper-3.4.6.jar

代码示例

import org.apache.log4j.BasicConfigurator;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.List;

public class Test1 {
    private static ZooKeeper zk = null;

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        // 配置Log4J
        BasicConfigurator.configure();
        // 建立连接
        int sessionTimeout = 2000;
        // 注意:node1、node2、node3需要在/etc/hosts中配置以下内容
        // 192.168.80.9 node1
        // 192.168.80.10 node2
        // 192.168.80.11 node3
        String connectString = "node1:2181,node2:2181,node3:2181";
        zk = new ZooKeeper(connectString, sessionTimeout, watchedEvent -> System.out.println("时间触发……"));
    }

    /**
     * create方法参数
     * String path, byte[] data, List<ACL> acl, CreateMode createMode
     * path:路径
     * data:值bytes
     * acl:对节点的访问控制
     * createMode:节点的类型——短暂,永久,序号
     */
    @Test
    public void create() throws KeeperException, InterruptedException {
        String path = zk.create("/app3", "hello_world".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        System.out.println(path);
    }

    /**
     * 判断节点是否存在
     */
    @Test
    public void exist() throws KeeperException, InterruptedException {
        String nodeName = "/app1";
        // 设置为true会调用zk中的监听器
        Stat stat = zk.exists(nodeName, true);
        if (stat != null) {
            System.out.println("节点“" + nodeName + "”存在,长度为:" + stat.getDataLength());
        } else {
            System.out.println("节点" + nodeName + "不存在的。");
        }
    }

    /**
     * 获取子节点
     */
    @Test
    public void getChildren() throws KeeperException, InterruptedException {
        List<String> list = zk.getChildren("/", true);
        for (String str : list) {
            System.out.println(str);
        }
    }

    /**
     * 获取子节点
     * getChildren(path, watch) 监听的事件是:节点下的子节点增减变化事件。
     */
    @Test
    public void getChildren1() throws KeeperException, InterruptedException {
        List<String> list = zk.getChildren("/", watchedEvent -> System.out.println("监控节点下的子节点被改变->" + watchedEvent.getPath()));
        for (String str : list) {
            System.out.println(str);
        }
        Thread.sleep(Long.MAX_VALUE);
    }

    /**
     * 获取节点的内容
     * getData(path,watch) 监听的事件是:节点数据变化事件。
     */
    @Test
    public void getData1() throws KeeperException, InterruptedException {
        byte[] bytes = zk.getData("/app1", watchedEvent -> System.out.println("数据改变了……"), null);
        System.out.println(new String(bytes));
    }

    /**
     * 获取节点的内容
     */
    @Test
    public void getData() throws KeeperException, InterruptedException {
        byte[] bytes = zk.getData("/app1", false, null);
        System.out.println(new String(bytes));
    }

    /**
     * 修改节点的内容
     * version为-1时表示自动维护
     */
    @Test
    public void setData() throws KeeperException, InterruptedException {
        Stat stat = zk.setData("/app1", "test".getBytes(), -1);
        System.out.println(stat.toString());
    }

    /**
     * 删除节点,非空节点删除不掉。
     * version为-1时表示自动维护
     */
    @Test
    public void delete() throws KeeperException, InterruptedException {
        zk.delete("/app1", -1);
    }
}

附录

猜你喜欢

转载自blog.csdn.net/Notzuonotdied/article/details/82690726