Zookeeper单机以及集群的使用

 

1.1 Zookeeper介绍

Zookeeper是分布式应用程序的协调服务框架,是Hadoop的重要组件。ZK要解决的问题:

1.分布式环境下的数据一致性。

2.分布式环境下的统一命名服务

3.分布式环境下的配置管理

4.分布式环境下的分布式锁

单台机器使用的锁:同步代码块、重入锁。但是在分布式环境这个锁就发挥不出来作用。分布锁分为共享锁和排他锁。

5.集群管理问题

1.2  分布式概念(集群)

分布式的思想就是人多干活快,即用多台机器同时处理一个任务。分布式的编程和单机的编程 思想是不同的,随之也带来新的问题和挑战。

1.3  分布式编程容易出现的问题

1.死锁2.活锁。

活锁定义:在程序里,由于某些条件的发生碰撞,导致重新执行,再碰撞=》再执 行,如此循环往复,就形成了活锁。活锁的危害:多个线程争用一个资源,但是没有任何一个 线程能拿到这个资源。(死锁是有一个线程拿到资源,但相互等待互不释放造成死锁),活锁 是死锁的变种。补充:活锁更深层次的危害,很耗尽Cpu资源(在做无意义的调度)

扩展:zk是根据Google的一篇论文

《The Chubby lock service for loosely coupled distributed systems

3.需要考虑集群的管理问题,需要有一套机制来检测到集群里节点的状态变化。(可以用心跳 机制来做,但zk不是用心跳机制来做的)

4.如果用一台机器做集群管理,存在单点故障问题,所以针对集群管理,也需要形成一个集群

5.管理集群里Leader的选举问题(要根据一定的算法和规则来选举),包括要考虑Leader挂掉 之后,如何从剩余的follower里选出Leader

6.分布式锁的实现,用之前学的重入锁,同步代码块是做不了的

  1.4  Zookeeper的名字

动物园管理员

2 Zookeeper单机模式安装

实现步骤:

1.      准备虚拟机

克隆一个纯净的虚拟机

连接克隆占用的空间较少,但是母体坏掉的话,克隆出来的虚拟机将不能使用

2.安装和配置jdk

3.上传和安装zk

  A.上传目录:/usr/soft  上传 rz 或者使用ssh工具

  B.tar -zxvf zookeeper-3.4.7.tar.gz

 4.配置zk的配置文件

目录结构:

bin  指令

conf 配置文件

lib  运行jar包库

进入conf目录,执行:

cp zoo_sample.cfg zoo.cfg

5.启动zk

进入bin目录,执行:

sh zkServer.sh start  或者./zkServer.sh start

可以通过jps指令查看活动的java进程

zk的进程是:QuorumPeerMain

6.进入zk客户端,操作zk

进入bin目录,执行:  ./zkCli.sh

 

关闭zk服务:

sh zkServer.sh stop  或者

jps查看zk的进程id

kill -15  zk进程id

 

3   Zookeeper指令与数据结构

Zk数据结构

 

1.      ZK有一个最开始的节点

2.      ZK的节点叫做znode节点

3.      每个znode节点都可存储数据

4.      每个znode节点都可创建自己的子节点

5.      多个znode节点共同形成了znode树

6.      Znode树的维系实在内存中,目的是供用户快速的查询

7.      每个znode节点都是一个路径(通过路径来定位这个节点)

8.      每个路径名都是唯一的。

 

 

ZK指令

指令

示例

ls查看指令

ls /

create创建节点指令,注意,在创建节点时,要分配初始数据。

create /zk01  ''

create /zk02 'hello'

get查看节点数据指令

hello  数据

cZxid = 0x2

ctime = Mon May 15 05:58:32 PDT 2017创建节点的时间戳

mZxid = 0x2

mtime = Mon May 15 05:58:32 PDT 2017修改此节点数据的最新时间戳

pZxid = 0x2

cversion = 0

dataVersion = 0数据版本号,每当数据发生编号,版本号递增1

aclVersion = 0

ephemeralOwner = 0x0

dataLength = 5数据大小

numChildren = 0子节点个数

get /zk02

set更新节点数据指令(执行后mtime、dataVersion可定会放生变化,dataLength可能会变化)

set /zk01 hellozk

delete删除节点:如果存在子节点,不让删除。

只有删除子节点后才能删除。

delete /zk01

create指令补充:

1.       创建子节点

2.       Zk节点分四种类型:分别是:

普通持久节点:

普通临时节点:创建此临时节点的客户端失去和zk连接后,此节点消失.zk是通过临时节点监控哪个服务器挂掉的。

 

顺序持久节点:会根据用户指定的节点路径,自动分配一个递增的顺序号。(顺序节点实现分布式锁的效果,服务器1抢到zk05分配zk050001,服务器2抢到zk05分配zk050002)

顺序临时节点:

1.       create /zk01/node01 hello

2.        

2.1.create /zk01 hello

2.2. create –e /zk02 abc
2.3.create –s /zk03 abc

2.4.create –s -e /zk05 abcd

zk050000000003

再创建一个就是:

zk050000000004

quit退出zk客户端

 

 

 

4   Zk API

4.1 项目搭建

实现步骤:

1.      关闭虚拟机的防火墙 service iptables stop(临时关闭)

永久关闭:chkconfigiptables  off

2.      创建一个maven工程

修改maven工程的几个参数

(1).修改默认的jdk,从1.5改为1.7

(2).修改JavaCompiler版本,从1.5改为1.7

点击Apply->OK

(3).从提供的资料中将pom.xml文件拷贝过来替换项目中的同名文件。

zk需要的核心jar包

环境就搭建好了,接下来我们建测试类。(删除不需要的App.java)

4.2 maven补充

 

学生机由于不能联网,所以maven可能不能用,如果maven不能用:

1、  将apache-maven-3.3.1.zip解压的D:\Tool

2、  将zebra_mvnrepository(私服)拷贝到D:\Tool

3、  修改D:\Tool\apache-maven-3.3.1\conf\settings.xml

<localRepository>D:\Tool\zebra_mvnrepository</localRepository>

修改该属性后,表示只要使用该maven的所有项目下载时全部使用该私服。

4、  修改Eclipse使用本地的maven

5、  修改maven使用的配置文件,指向私服

扩展阅读:maven超级pom(最顶层的父pom.xml)

maven-model-builder-3.3.1.jar->org\apache\maven\model\pom-4.0.0.xml 

<directory>${project.basedir}/target</directory>

<!—发布完后的项目保存位置-->

<outputDirectory>${project.build.directory}/classes</outputDirectory>

<finalName>${project.artifactId}-${project.version}</finalName>    <testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>

<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>

<scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory>

<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>

 

<repositories>

    <repository>

      <id>central</id>

      <name>Central Repository</name>

      <url>https://repo.maven.apache.org/maven2</url>

      <layout>default</layout>

      <snapshots>

        <enabled>false</enabled>

      </snapshots>

    </repository>

  </repositories>

 

  <pluginRepositories>

    <pluginRepository>

      <id>central</id>

      <name>Central Repository</name>

      <url>https://repo.maven.apache.org/maven2</url>

      <layout>default</layout>

      <snapshots>

        <enabled>false</enabled>

      </snapshots>

      <releases>

        <updatePolicy>never</updatePolicy>

      </releases>

    </pluginRepository>

  </pluginRepositories>

将以上两个节点指定默认的中央仓库,可以将之拷贝不同项目的pom.xml文件,分别修改,可以实现不同项目引用不同私服或公服。

 

4.3 API演示

4.3.1   编写测试类,测试连接

    /**connectString连接zk服务器的ip(192.168.80.50)和port (2181)

     * sessionTimeout:回话的超时时间,单位为毫秒

     * watcher:监听器对象

     * @throws Exception

     */

    @Test

    public void testConect() throws Exception{

       ZooKeeper zk = new ZooKeeper("192.168.80.50:2181", 3000,

              new Watcher() {

                  @Override

                  public void process(WatchedEvent event) {

                     System.out.println("连接成功");

                  }

              });

       while(true);

    }

}

为何添加while(true)就可以,原因是:

zk连接是一个非阻塞连接方法,连接还没有来的急建立,该线程已经结束。

在连接成功后,使用zk.create(…)或zk.get(),那么如何保证执行到该行时保证连接成功,可以使用闭锁。

public void testConect() throws Exception{

       final CountDownLatch cdl = new CountDownLatch(1);

       ZooKeeper zk = new ZooKeeper("192.168.80.50:2181", 3000,

              new Watcher() {

                  @Override

                  public void process(WatchedEvent event) {

                     System.out.println("连接成功");

                     cdl.countDown();

                  }

              });

       //while(true);

       cdl.await();

    }

 

4.3.2   编写测试创建节点

@Test

    publicvoid testCreate() throws Exception{

       final CountDownLatch cdl = new CountDownLatch(1);

       ZooKeeper zk = new ZooKeeper("192.168.80.50:2181", 3000,

              new Watcher() {

                  @Override

                  publicvoid process(WatchedEvent event) {

                     System.out.println("连接成功");

                     cdl.countDown();

                  }

              });

       cdl.await();

       //path:节点路径

       //data:节点的内容

       //acl:节点的权限

       //createMode:节点的类型

       //PERSISTENT(persistent):普通持久节点

       //EPHEMERAL(ephemeral):普通临时节点

       //PERSISTENT_SEQUENTIAL:顺序持久节点

       //EPHEMERAL_SEQUENTIAL:顺序临时节点

        zk.create("/zk02","hellozk".getBytes(),

               Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);

        while(true);

    }

如果创建不成功,有可能是该节点已经存在了。

4.3.3   测试get方法

    @Test

    public void testGet() throws Exception{

       ……//省略zk的创建。

       //path:获取节点的地址

       //watcher监听器

       //stat:查询后,会将节点的其它信息封装stat中

       Stat stat = new Stat();

       byte[] data = zk.getData("/zk01", new Watcher() {

           @Override

           public void process(WatchedEvent event) {

              System.out.println("获取到数据");

           }

       }, stat);

       System.out.println(new String(data));

       System.out.println(stat);

    }

 

4.3.4   测试set

  @Test

    public void testSet() throws Exception{

       ……

       //path:修改节点的地址

       //data:修改后节点的内容。

       //version:修改节点对应的版本号

       zk.setData("/zk01", "you can't sleep".getBytes(), -1);

       while(true);

    }

 

4.3.5   获取子节点

@Test

    publicvoid testGetChild() throws Exception{

       ……

       //注意:statzk01的节点信息

       Stat stat = new Stat();

       List<String> paths = zk.getChildren("/zk01", null,stat);

       for (String path : paths) {

           System.out.println(path);

       }

       System.out.println(stat);

    }

 

4.3.6   手动删除节点

@Test

    publicvoid testDelete() throws Exception{

       ……

       /**path:删除的节点路径

        * version:节点的版本,-1支持各种版本号

        * 删除节点使用的场景不是太多,可以创建临时节点,连接断了之后,

        * 节点会自动被删除。

        * 注意:只用空节点才能删除。

        */

       zk.delete("/zk01", -1);

    }

4.3.7   观察节点数据变化

@Test

    public void testGetDataWatcher() throws Exception{

       ……

       zk.getData("/zk01", new Watcher() {

           @Override

           public void process(WatchedEvent event) {

              System.out.println("数据放生变化");

           }

       }, null);

       while(true);

    }

运行程序后,在终端上执行以下命令:

set /zk01 newdata

控制台输出“数据发生变化”

目前只能监听一次,如何实现永久监听:

    @Test

    publicvoid testGetDataWatcher() throws Exception{

       ……

 

       while(true){

           final CountDownLatch cdl2 = new CountDownLatch(1);

           zk.getData("/zk01", new Watcher() {

              @Override

              publicvoid process(WatchedEvent event) {

                  System.out.println("数据放生变化");

                  cdl2.countDown();

              }

           }, null);

           cdl2.await();

       }

    }


4.3.8   监听子节点变化(创建或删除)

@Test

    publicvoid testGetChildWatcher() throws Exception{

       ……

 

       zk.getChildren("/zk01",new Watcher(){

           @Override

           publicvoid process(WatchedEvent event) {

               if(event.getType()==EventType.NodeChildrenChanged){

                  System.out.println("子节点发生了变化");

              }

           }

       });

       while(true);

    }

运行程序,终端上执行以下命令(在/zk01下创建子节点)

create /zk01/zk02 jxf

控制台输出“子节点变化”

4.3.9   监听节点删除

@Test

publicvoid testDeleteWatcher() throws Exception{

    ……

    zk.exists("/zk030000000004",new Watcher(){

       @Override

       publicvoid process(WatchedEvent event) {

           if(event.getType()==EventType.NodeDeleted){

              System.out.println("节点被删除。。。");

           }

       }

    });

    while(true);

}

运行程序,在终端上执行删除:delete /zk02

控制台输出:节点被删除

 

4.3.10  监听创建节点的相关信息

    @Test

    publicvoid testCreateCallBack() throws Exception{

       ……

       /**path:节点路径

        * data:节点内容

        * acl:节点的相关权限

        * createMode:节点的类型

        * ctx:传入的附件

        */

       zk.create("/zk04/node1", "Kill sleeper".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT,

              new StringCallback(){

                  @Override

                  publicvoid processResult(intrc, String path, Object ctx, String name) {

                     //int rc:状态码  0代表创建成功,-110 代表创建失败

                     //String path:用户指定的路径

                     //Object ctx:传入的附件

                     //String name:实际创建节点的路径名

                      System.out.println("rc:"+rc+",path:"+path+",ctx:"+ctx+",name:"+name);

                  }

          

       },"over" );

       while(true);

    }

集群搭建和配置

 

1.      克隆三台空虚拟机(含有lrzsz),修改网络ip,并关闭虚拟机的防火墙

临时关闭:service iptables stop

永久关闭:chkconfig iptables  off

2.      安装和配置jdk

3.      安装和配置zookeeper

mkdir /use/soft

tar –zxvf /use/soft/zookeeper-3.4.7.tar.gz

[root@localhostconf]# cp zoo_sample.cfg  zoo.cfg

4.      配置zoo.cfg

配置说明:

tickTime=2000  心跳间隔周期  毫秒。

initLimit=10初始连接超时阈值=10*tickTime。指的是follower初始连接leader的超时时间。 如果网络环境不好,适当调大。  

syncLimit=5连接超时阈值=syncLimit*tickTime。指的是follower和leader做数据交互的超时时间。如果网络环境不好,适当调大。

dataDir=/usr/soft/zookeeper-3.4.7/tmp  dataDir数据目录指的是zookeeper znode树的持久化目录,

server.1=192.168.80.51:2888:3888

server.2=192.168.80.52:2888:3888

server.3=192.168.80.53:2888:3888

server后的数字是选举id,在选举过程中会用到。注意:数字一定要能比较出大小。

2888 端口原子广播端口,可以自定义

3888 端口选举端口,可以自定义

在zk安装目录下创建tmp文件,创建myid文件(名字固定),并编辑当前虚拟机的选举id

   Mkdir /usr/soft/zookeeper-3.4.7/tmp

   tmp#vim myid    (内容为1)

远程拷贝zk的安装目录到zk2、zk3上

[root@localhostsoftware]# scp -r zookeeper-3.4.7/ root@192.168.80.22:/home/software

[root@localhostsoftware]# scp -r zookeeper-3.4.7/ root@192.168.80.23:/home/software

分别修改myid,zk2->2,zk3->3

如果拷贝不过去或者拷贝太慢,可以先打包并压缩:

[root@localhostsoftware]# tar -zcvf zk.tar.gz zookeeper-3.4.7/

然后在scp

然后在解压:[root@localhost home]# tar -zxvf zk.tar.gz

5.启动zk集群测试

分别启动zk

[root@localhost bin]# ls

README.txt    zkCli.cmd  zkEnv.cmd  zkServer.cmd

zkCleanup.sh  zkCli.sh   zkEnv.sh   zkServer.sh

[root@localhost bin]# ./zkServer.sh start

ZooKeeper JMX enabled by default

Using config: /usr/soft/zookeeper-3.4.7/bin/../conf/zoo.cfg

Starting zookeeper ... STARTED

[root@localhost bin]# jps

3490 Jps

3464 QuorumPeerMain

 

[root@localhostbin]# ./zkServer.sh status

ZooKeeper JMX enabled by default

Using config: /usr/soft/zookeeper-3.4.7/bin/../conf/zoo.cfg

Mode: follower

如果启动顺序为zk1->zk2->zk3,通常zk2为leader。

Leader是如何选举出来的?

6   选举机制

6.1 Zookeeper事务概念 

1.每一个写操作都是一个事务,每一个事务都用一个事务id来代表,叫:zxid

2.zxid 是全局唯一,并且全局递增的。作用就是可以根据最大事务id,找到最新的事务

6.2 Zookeeper选举机制

 

6.2.1   选举分两个阶段

1.      数据恢复阶段:

 当zk服务器启动时,会先从本地磁盘找到本机的最大事务id。

2.      选举阶段:

zk服务器会提交选举协议 1.Zxid(最大事务id) 2.本机的选举id(myid文件里的数字) 3.逻辑时钟值  记录当前的选举轮数,确保每个zk在同一轮选举中 4.当前zk服务器状态,分4种: Looking=>选举阶段 Following=>当小弟阶段 Leading=>当领导阶段 Observering=>观察者阶段

6.2.2   选举的pk原则

1.首先比较最大事务id,Zxid,谁大谁当领导

2.如果Zxid比较不出来,比较myid(选举id),谁大谁当领导

3.选举的前提满足过半同意

 

6.2.3   Leader选举出来之后

Leader身上肯定是有最新数据的(有最大事务id的),所以首先做的就是原子广播(通过原子 广播端口)。 原子广播的目的就是为了确保数据一致性(即客户端无论通过哪个zk服务器查看数据,数据都是一样的)

 目的二就是为了防止leader挂掉之后,数据的丢失问题

对于事务更新,Leader会通过原子广播征询其他follower,只要满足过半同意机制,事务才能被更新

 

针对下图,Leader挂掉之后,第二个机器成为Leader

 

针对下图,不满足过半机制,集群就工作不了了

 

修改testCreateCallBack()方法

ZooKeeper zk=new ZooKeeper("192.168.80.51:2181,

192.168.80.52:2181,192.168.80.53:2181",3000,new Watcher(){…

测试发现,三台zk上都创建了该节点。

 

Zxid 最大事务id 是全局的, cZxid、mZxid、pZxid是针对某个节点路径而言的。

扩展Zookeeper的选举机制根据Paxos 算法来实现。 Paxos算法解决的问题:在分布式环境下就某一个决议达成一致性问题。 Paxos算法存在活锁问题,Zk用的是FastPaxos算法,解决了活锁问题。

 

  • czxid. 节点创建时的zxid.
  • mzxid. 节点最新一次更新发生时的zxid.
  • cversion. 其子节点的更新次数.
  • aclVersion. 节点ACL(授权信息)的更新次数.
  • ephemeralOwner. 如果该节点为ephemeral节点, ephemeralOwner值表示与该节点绑定的session id. 如果该节点不是ephemeral节点, ephemeralOwner值为0. 至于什么是ephemeral节点, 请看后面的讲述.
  • dataLength. 节点数据的字节数.
  • numChildren. 子节点个数.

 

6.2.4 zxid

znode节点的状态信息中包含czxid和mzxid, 那么什么是zxid呢?

ZooKeeper状态的每一次改变, 都对应着一个递增的Transaction id, 该id称为zxid. 由于zxid的递增性质, 如果zxid1小于zxid2, 那么zxid1肯定先于zxid2发生. 创建任意节点, 或者更新任意节点的数据, 或者删除任意节点, 都会导致Zookeeper状态发生改变, 从而导致zxid的值增加

6.2.5   session

在client和server通信之前, 首先需要建立连接, 该连接称为session.连接建立后, 如果发生连接超时, 授权失败, 或者显式关闭连接, 连接便处于CLOSED状态, 此时session结束.

6.2.6   节点类型

讲述节点状态的ephemeralOwner字段时, 提到过有的节点是ephemeral节点, 而有的并不是. 那么节点都具有哪些类型呢? 每种类型的节点又具有哪些特点呢?
persistent.persistent节点不和特定的session绑定, 不会随着创建该节点的session的结束而消失, 而是一直存在, 除非该节点被显式删除.
ephemeral.ephemeral节点是临时性的, 如果创建该节点的session结束了, 该节点就会被自动删除. ephemeral节点不能拥有子节点. 虽然ephemeral节点与创建它的session绑定, 但只要该该节点没有被删除, 其他session就可以读写该节点中关联的数据. 使用-e参数指定创建ephemeral节点.

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值