zookeeper完整详细版

Zookeeper

写在之前

集群中的全部机器应该都使用root用户登录

准备jdk,版本不得低于1.6,推荐使用1.8(使用人数最多,稳定)

集群机器数量应该尽量准备奇数台

什么是zookeeper?

Zookeeper是Apache的一个项目是一个分布式的协调服务框架,Zookeeper可以解决分布式环境常见的问题:

如:统一命名服务,信息配置管理,数据一致性,集群管理,分布式锁等等。

了解分布式服务

将不同的系统甚至功能部署到不同的机器上,再利用多台机器并行执行同一个任务,每台机器上都有独立的程序模块及要处理的数据,即人多干活快的思想。但是,由于由原来的单一节点运算模式转变为分布式运算,所以,代码的编写、数据资源的分配策略都发生了改变,并且也引发了很多新的问题

分布式服务带来的问题

1.容易出现死锁

2.容易活锁,处于活锁的线程都是非阻塞的,而且每个线程都抢到资源,会造成cpu的耗费。线程在执行过程中产生了碰撞——再执行——再碰撞,如此循环往复,形成活锁。

3.集群的管理问题,比如某台服务器宕机需要能够检测到

4.集群配置文件的统一管理问题

5.集群中信息状态的更新通知问题

6.管理集群的选举问题

7.分布式锁的实现,这需要用新的机制和技术来实现

zookeeper要解决的问题

zookeeper旨在在分布式应用中,提供可靠的、可扩展的、分布式的、可配置的协调机制来管理整个集群的状态

zookeeper安装

注意:在安装zookeeper之前应该先安装jdk

配置java环境,这个教程应该能帮你:https://www.runoob.com/java/java-environment-setup.html

单机版安装

将下载好的zookeeper压缩包,上传到linux

解压zookeeper

tar -xvf 压缩包

进入zookeeper目录下的conf目录,有一个zoosample.cfg的文件

复制一份,并重命名为zoo.cfg文件,这个名字固定写死,因为zookeeper启动会检查这个文件,根据这个配置文件里的信息来启动服务

默认我们已经在zookeeper目录,如果不知道你所在的目录可以使用pwd命令查看

cd conf

mv zoosample.cfg zoo.cfg

使用vim打开zoo.cfg文件

vim zoo.cfg

将datadir的值修改,因为默认etc目录是存放临时文件的

dataDir=/home/software/zookeeper-3.4.5/data

目录自己准备

保存后进入zookeeper的bin目录

cd bin

启动zookeeper

./zkServer.sh start

连接zookeeper客户端

./zkCli.sh

查看zookeeper是否启动成功

jps

集群安装

准备至少三台有java环境的机器

推荐准备奇数数量的机器

安装步骤和单机版一致,参考单机版安装

修改配置文件

vim zoo.cfg

加上如下配置

进行这一步之前请确保dataDir的值不在etc目录下

在配置文件里,需要在加上如下的配置:

有几个节点就配置几个节点

sever.后面的值一定要和myid下的值一样(后面会讲怎么设置myid的值)

server.1=192.168.234.10:2888:3888

server.2=192.168.234.11:2888:3888

server.3=192.168.234.12:2888:3888

保存退出

说明:2888为原子广播端口,3888为选举端口

当然你可以自定义成其他端口,只要端口不被占用都行

zookeeper有几个节点,就配置几个server,

说明:为什么dataDir的值不能是etc目录

dataDir。这个参数是存放zookeeper集群环境配置信息的。这个参数默然是配置在 /tmp/zookeeper下的 。但是注意,tmp是一个临时文件夹,这个是linux自带的一个目录,是linux本身用于存放临时文件用的目录。但是这个目录极有可能被清空,所以,重要的文件一定不要存在这个目录下*

将集群的其他配置文件也做出修改

可以使用scp命令来远程拉取文件

scp -r 目录 远程ip地址:存放的路径

配置文件配置好以后需要在dataDir目录下创建myid

注意是配置文件里的dataDir指向的那个目录,并不是dataDir目录

vim myid

值就是配置文件里server.后面的值

如server.1=192.168.234.10:2888:3888

值就是1

保存并退出

将集群里的所有机器都配置myid

关闭防火墙

service iptables stop

启动zookeeper,

进入zookeeper的bin目录

./zkServer.sh start

集群的机器都启动,然后查看zookeeper的信息

./zkServer.sh status

注意一定要等集群里所有的zookeeper都启动完成在查看zookeeper信息,否则会报错

配置ssh免密登录

这一步是为了集群之间能互相通信

打开host配置文件

vim etc/hosts

在其中添加所有服务器或虚拟机节点ip和对应的域名,如下所示:

因为我是为了搭建hadoop集群所以给机器取名hadoop

192.168.25.101 机器的名字

192.168.25.102 hadoop02

192.168.25.103 hadoop03

保存退出

然后给每台机器设置hostname,刚刚在配置文件里写的什么名字现在就设置什么名字,名字要和ip对应

如配置文件里是:192.168.25.102 hadoop02

那么192.168.25.102 这台机器的hostnam设置

hostname hadoop02

其他机器以此类推

创建密钥:

ssh-keygen

连续回车即可

注意在执行该命令之前应该检查是不是在root家目录

复制公钥到其他节点

ssh-copy-id -i .ssh/id_rsa.pub root@192.168.135.102 #复制密钥

ssh-copy-id -i .ssh/id_rsa.pub root@192.168.135.103 #复制密钥

别忘了自己也要配置

ssh-copy-id -i .ssh/id_rsa.pub root@192.168.135.101 #复制密钥

其他机器也要按照这个步骤完成配置

配置完成之后试试免密登录其他机器

ssh ip

Zookeeper基础概念

知识点1:zk有一个 根节点(/),所有节点的注册和操作基于 根节点来实现的

知识点2:每个节点都可以拥有自己的子节点

知识点3:每个节点都称为 znode节点

知识点4:多个znode节点共同形成了一个znode树,维系在内存中,供用户快速查询数据

知识点5:每个znode节点都可以存储数据

知识点6:创建节点时,要为此节点分配初始数据

知识点7:znode节点分4类,①普通持久节点(create) ②普通临时节点(create -e)

③顺序持久节点(create -s) ④临时顺序(create -e -s )

知识点8:znode节点路径是唯一的。根据这个特性,可以实现统一命名服务(要求命名的唯一性)

知识点9:不要用zookeeper存储大量数据,从功能来讲,zookeeper做的集群的协调服务,所以存储的信息是很少量的。此外,因为znode树是维系在内存中,海量数据也会占用大量的内存。

Zookeeper服务端指令

指令说明
sh zkServer.sh start启动zk服务的
sh zkServer.sh stop停止zk服务
sh zkServer.sh restart重启zk服务
sh zkServer.sh status查看zk服务角色,有: Standalone Leader Follower Observer
sh zkCli.sh进入zk客户端

Zookeeper客户端指令

指令说明示例
ls查看ls / 查看根路径 ls /park01 查看park01路径
create创建create /park02 “” create /park02/node01 创建park02的子节点
get获取指定节点信息cZxid = 0x2 #创建此节点的事务id ctime = Wed Jan 17 10:55:25 PST 2018 #创建此节点的时间戳 mZxid = 0x2 #修改此节点的事务id mtime = Wed Jan 17 10:55:25 PST 2018 #修改此节点的时间戳 pZxid = 0x4 cversion = 1 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 #如果此节点不是临时节点,则为0 dataLength = 9 #数据长度 numChildren = 1 #子节点数量
set更新节点数据set /park01 hellozk
delete删除子节点为空的节点delete /park01
rmr递归删除指定节点rmr /park02
quit (或ctrl+c)退出客户端quit
create -e创建临时节点,当创建此节点的客户端下线时,节点被删除。cetete -e /park02 “hello”
create -s创建顺序节点,每次创建节点时,会跟上一个递增的顺序号cetete -s /park02 “hello”
create -e -s创建临时顺序节点cetete -e -s /park02 “hello”

zookeeper javaApi

依赖

<dependencies>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.6.1</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
创建zookeeper节点
@Test
public void testCreate() throws Exception{
    //初始化递减锁
    CountDownLatch countDownLatch = new CountDownLatch(1);
    //创建zookeeper对象
    ZooKeeper zk = new ZooKeeper("192.168.19.5:2181", 3000, new Watcher() {
        //第一个参数,连接的zookeeper节点ip,
//第二个参数,会话超时时间,以毫秒为单位。比如设置1000*30 是30秒。如果30秒内zookeeper没有收到客户端节点的心跳,则断开连接
//第三个参数,Watcher,观察者,当zookeeper里有事件发生时,会通知到这个process方法里

        @Override
        public void process(WatchedEvent watchedEvent) {
            if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
                countDownLatch.countDown();//递减锁的初始值减一
            }
        }
    });
    //等待
    countDownLatch.await();
    zk.create("/hellozookeeper","hellozook".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}

重要参数

//1.path 节点路径;

//2.data[] 节点数据;

//3.acl 权限策略,一般设为所用用户都可以操作这个节点,并且具有所有权限,所以选:Ids.OPEN_ACL_UNSAFE;

//4.createMode 创建模式:

//PERSISTENT 持久

//PERSISTENT_SEQUENTITAL 持久顺序

//EPHEMERAL 临时

//EPHEMERAL_SEQUENTITAL 临时顺序

更新zookeeper节点信息
@Test
    public void testSet() throws Exception{
//        初始化递减锁
        final CountDownLatch cdl = new CountDownLatch(1);
        ZooKeeper zk = new ZooKeeper("192.168.19.5:2181", 3000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                if(watchedEvent.getState()== Event.KeeperState.SyncConnected){
                    cdl.countDown();
                }
            }
        });
        cdl.await();
        //-1为版本号
        zk.setData("/hellozookeeper","aaa".getBytes(),-1);
    }

注意://version版本号的作用:在指定路径下,有一个dataVersion,这个是数据更改的版本号,从0开始,在此节点下

//每更改一次数据,版本号递增1

//而setData(, ,version)的version的意思是:指定一个版本号,基于这个版本号来修改,但是需要注意:

//如果指定的版本号是3,而当前的dataVersion是4,即指定的版本号不存在(已过时),则会报错

//如果指定的版本号是-1,则说明无论当前dataVersion是多少,都会进行数据的更新,最常用的就是-1

获取节点信息
@Test
    public void testGetData() throws Exception{
        final CountDownLatch cdl = new CountDownLatch(1);
        ZooKeeper zk = new ZooKeeper("192.168.19.5:2181", 3000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                cdl.countDown();//递减锁减一
            }
        });
        cdl.await();
        //watch,当指定节点发生监听的事件时,会触发Watcher里的方法,
		//监听的事件有:1.节点数据发生变化 2.节点创建 3.节点删除 4.节点的子节点发生变化。但是监听的事件只会触发一次回调方法。
		//stat,节点环境信息,一般设置为null即可。如果想要,可以传入一个空的Stat对象来接收

        byte[] data=zk.getData("/hellozookeeper", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                if (watchedEvent.getType()== Event.EventType.NodeChildrenChanged){
                    System.out.println("节点数据发生变化");
                }
            }
        },null);
        System.out.println(new String(data));
        Stat s = new Stat();
        List<ACL> list = zk.getACL("/hellozookeeper",s);
        for (ACL acl : list) {
            System.out.println(acl);
        }
        System.out.println(s);
        while (true){}
}
获取子节点
@Test
public void testGetChildren() throws Exception{
    final CountDownLatch cdl = new CountDownLatch(1);
    ZooKeeper zk = new ZooKeeper("192.168.19.5:2181", 3000, new Watcher() {
        @Override
        public void process(WatchedEvent watchedEvent) {
            if (watchedEvent.getState()== Event.KeeperState.SyncConnected){
                cdl.countDown();
            }
        }
    });
    cdl.await();
    List<String> list = zk.getChildren("/",null);
    for (String s : list) {
        System.out.println(s);
    }
}
删除节点
@Test
public void testDelete() throws Exception{
    CountDownLatch cdl = new CountDownLatch(1);
    ZooKeeper zk = new ZooKeeper("192.168.19.5:2181", 3000, new Watcher() {
        @Override
        public void process(WatchedEvent watchedEvent) {
            if (watchedEvent.getState()== Event.KeeperState.SyncConnected){
                cdl.countDown();//递减锁减一
            }
        }
    });
    cdl.await();
    //删除节点
    zk.delete("/hello",-1);
    System.out.println("删除成功");
}
判断节点是否存在
@Test
public void testNotNode() throws Exception{
    CountDownLatch cdl = new CountDownLatch(1);
    ZooKeeper zk = new ZooKeeper("192.168.19.5:2181", 3000, new Watcher() {
        @Override
        public void process(WatchedEvent watchedEvent) {
            if (watchedEvent.getState()== Event.KeeperState.SyncConnected){
                cdl.countDown();//递减锁减一
            }
        }
    });
    cdl.await();
    Stat stat = zk.exists("/hell",null);
    System.out.println(stat);
    System.out.println("111:"+stat);
}

zookeeper选举

zookeeper选举分两个阶段

数据恢复阶段

每台zk服务在启动时,从本地目录找自己所拥有的Zxid(最大事务id),每有一次写操作,都是一个事务,每次事务都会递增。事务id越大,事务越新

选举阶段

每台zk服务器都会提交一个选举协议,协议中的内容:

①自己的zxid

②选举id(myid文件里的数字)

③逻辑时钟值(和选举轮数有关),作用是确保每台zk服务处于同一论选举中

④状态—Looking(选举) | Leader | Follower | Observer

选举pk 制度

先比较Zxid,谁大谁当Leader

如果Zxid比较不出来,

再比较选举id,谁大谁当领导

注意:选举最基本的原则是满足过半性

过半存活,比如3台服务器,挂1个可以,挂两个则集群不能工作

zk集群数量最好是奇数个,能够更好的满足过半性。(偶数也可以)

zookeeper 原子广播

Leader选举之后

首先要做的是数据同步,目的是确保zk集群的数据一致性。一是可以保证当Leader挂掉之后,其他follower可以顶替工作。此外要确保客户端无论从哪个zk服务器获取数据都是一致的。这种实现数据一致的过程称为原子广播(Atomic Brodcast)

对于客户端的读请求,任何一个zk服务器都可以处理,但是对于写请求,会交给Leader来处理,Leader会通过原子广播端口,请写请求(事务)广播给其他的节点,还会收集每个台服务器的ack信息,然后统计是否满足过半性,如果满足,则此事务成功提交。最后反馈客户端一个确认信息。

zookeeper观察者

观察者不参加投票选举,他只监听投票选举的结果

观察者和追随者一样转发这些请求到领导者,他们和追随者的区别就是只监听投票结果而步参加投票

应该在步影响投票的情况下尽可能多的设置管擦者的数量

设置观察者

使用观察者设置Zookeeper全员非常简单,只需要在原来的配置文件上改两个地方。

第一,在要设置的那个节点的配置文件设置为观察者,必须放置这一行:

peerType=observer

这一行告诉Zookeeper的服务是一个观察者。

第二,在每个服务配置文件里,必须在观察者定义行添加:observer。例如:

server.1:localhost:2181:3181:observer

这个告诉其他服务server.1是一个观察者,并且他们不需要期望他选举。

zookeeper配置详解

参数名说明
clientPort客户端连接server的端口,即对外服务端口,一般设置为2181吧。
dataDir存储快照文件snapshot的目录。默认情况下,事务日志也会存储在这里。 ZK会在特定条件下会触发一次快照(snapshot),将当前服务节点的状态以快照文件的形式dump到磁盘上去,即snapshot文件。此外,每生成一次快照文件,就会生成一个对应的事务日志文件 快照数据文件名为:snapshot.x,而事务日志文件对应为:log.x+1。 其中,x是生成快照时的Zxid。
dataLogDir事务日志输出目录。 正常运行过程中,针对所有事务操作,在返回客户端“事务成功”的响应前,ZK会确保已经将本次事务操作的事务日志写到磁盘上,只有这样,事务才会生效。
tickTimeZK中的一个时间单元。ZK中所有时间都是以这个时间单元为基础,进行整数倍配置的。例如,session的最小超时时间是2*tickTime。
initLimitFollower在启动过程中,会从Leader同步所有最新数据,然后确定自己能够对外服务的起始状态。Leader允许F在 initLimit 时间内完成这个工作。通常情况下,我们不用太在意这个参数的设置。如果ZK集群的数据量确实很大了,F在启动的时候,从Leader上同步数据的时间也会相应变长,因此在这种情况下,有必要适当调大这个参数了。 默认是:****10*ticktime
syncLimit在运行过程中,Leader负责与ZK集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。如果L发出心跳包在syncLimit之后,还没有从F那里收到响应,那么就认为这个F已经不在线了。 默认是:****5*ticktime
minSessionTimeout maxSessionTimeoutSession超时时间限制,如果客户端设置的超时时间不在这个范围,那么会被强制设置为最大或最小时间。默认的Session超时时间是在2 * tickTime ~ 20 * tickTime 这个范围
snapCount每进行snapCount次事务日志输出后,触发一次快照(snapshot), 此时,ZK会生成一个snapshot.文件,同时创建一个新的事务日志文件log.。默认是100000。这是一种情况 此外,在产生新Leader时,也会生成新的快照文件,(同时会生成对应的事务文件)
autopurge.purgeInterval3.4.0及之后版本,ZK提供了自动清理事务日志和快照文件的功能,这个参数指定了清理频率,单位是小时,需要配置一个1或更大的整数,默认是0,表示不开启自动清理功能。
server.x=[hostname]:nnnnn[:nnnnn]这里的x是一个数字,与myid文件中的id是一致的。右边可以配置两个端口,第一个端口用于F和L之间的数据同步和其它通信,第二个端口用于Leader选举过程中投票通信。
jute.maxbuffer每个节点最大数据量,是默认是1M。
globalOutstandingLimit最大请求堆积数。默认是1000。ZK运行的时候, 尽管server已经没有空闲来处理更多的客户端请求了,但是还是允许客户端将请求提交到服务器上来,以提高吞吐性能。当然,为了防止Server内存溢出,这个请求堆积数还是需要限制下的。
preAllocSize预先开辟磁盘空间,用于后续写入事务日志。默认是64M,每个事务日志大小就是64M。
electionAlg默认为3,即 fast paxos election 选举算法。在3.4版本后,1 2对应的选举算已弃用,所以此项配置不要更改。
leaderServes默认情况下,Leader是会接受客户端连接,并提供正常的读写服务。但是,如果你想让Leader专注于集群中机器的协调,那么可以将这个参数设置为no,这样一来,会提高整个zk集群性能。

zookeeper 特性总结

数据一致性

client不论连接到哪个Zookeeper,展示给它都是同一个视图,即查询的数据都是一样的。这是zookeeper最重要的性能。

原子性

对于事务决议的更新,只能是成功或者失败两种可能,没有中间状态。要么都更新成功,要么都不更新。即,要么整个集群中所有机器都成功应用了某一事务,要么都没有应用,一定不会出现集群中部分机器应用了改事务,另外一部分没有应用的情况。

可靠性

一旦服务端成功的应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会一直保留下来,除非有另一个事务又对其进行了改变。

实时性

Zookeeper保证客户端将在非常短的时间间隔范围内获得服务器的更新信息,或者服务器失效的信息,或者指定监听事件的变化信息。(前提条件是:网络状况良好)

顺序性

如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布。

过半性

zookeeper集群必须有半数以上的机器存活才能正常工作。因为只有满足过半数,才能满足选举机制选出Leader。因为只有过半,在做事务决议时,事务才能更新。

所以一般来说,zookeeper集群的数量最好是奇数个。

zookeeper应用场景

统一命名服务(Name Service)

分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住, 就像数据库中产生一个唯一的数字主键一样。我们利用Zookeeper 可以轻松实现这一功能——利用znode路径。因为znode路径是全局唯一的(可以用某个路径来代表服务名)。

集群管理

通过zookeeper知道集群里机器的状态,实现思路:集群里每台机器都在zookeeper里注册自己的临时节点,并上传自己的运行状态,我们可以查看这些临时节点,来得知节点的数据信息,加入某个临时节点消失了,意味着这台节点挂掉了,从而也可以达到集群监控的目的。

数据订阅发布

这个最典型的就是集群配置信息要发布到集群的客户机节点上,实现配置信息的集中式管理和动态更新。

配置管理(Configuration Management)

配置的管理在分布式应用环境中很常见,例如同一个应用系统需要多台 PC Server 运行,但是它们运行的应用系统的某些配置项是相同的,如果要修改这些相同的配置项,那么就必须同时修改每台运行这个应用系统的 PC Server,这样非常麻烦而且容易出错。像这样的配置信息完全可以交给 Zookeeper 来管理,将配置信息保存在 Zookeeper 的某个目录节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中。

分布式同步协调通知

可以联想赛马的案例,在分布式环境下,这种效果称为屏障。

负载均衡

实现思路:每台机器可以将自己的状态信息注册zk服务上(比如cpu,带宽,磁盘等使用率)zk可以根据这些信息做负载均衡。

分布式锁

可以通过顺序节点来实现。


版权声明:本文为xu2414506319原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。