Zookeeper入门学习

目录

概述

使用zookeeperl理由

Zookeeper单机安装

Zookeeper特点

Znode树

Znode节点

路径

缓存机制

命令

javaAPI

连接

创建

删除

更新

获取节点数据

判断节点存在

获取所有/的子节点的路径

监听节点数据变化

监听子节点个数是否发生变化

监听节点的变化

代码实现

Zookeeper集群搭建

选举机制

数据恢复阶段

选举阶段

节点状态

注意

过半性

ZAB协议

2PC

Paxos

原子广播

崩溃恢复

事务日志

快照


概述

Zookeeper用于管理分布式架构的组件

使用zookeeperl理由

1.分布式带来的问题

    死锁:锁之间形成嵌套,并且线程之间是相互把持着对方需要的资源 - 线程把持了资源,线程都是在相互阻塞,把CPU就给空闲下来了

     活锁:线程之间相互让需要的资源,导致所有的线程都没有占用资源 - 线程没有把持资源,CPU要不断地调度线程,导致其他线程也无法把持资源继续执行

2. 解决办法

    需要确定一个管理节点,管理节点存在单点故障,为了避免这个问题,需要引入管理节点的集群,在管理节点集群中需要选举一个leader,确定一套选举算法,管理集群中的节点之间要实现信息的共享。这个节点就是zookeeper

Zookeeper单机安装

关闭Linux(CentOS6.X版本)的防火墙

临时关闭防火墙:service iptables stop

永久关闭防火墙:chkconfig iptables off

下载安装JDK

下载Zookeeper的安装包: wget http://bj-yzjd.ufile.cn-north-02.ucloud.cn/zookeeper-3.4.8.tar.gz

解压Zookeeper的安装包:tar  -xvf zookeeper-3.4.8

进入Zookeeper的安装目录下的子目录conf目录中:cd zookeeper-3.4.8/conf

将conf目录下的zoo_sample.cfg文件复制为zoo.cfg。Zookeeper在启动的时候会自动寻找zoo.cfg,根据其中的配置来启动服务:cp  zoo_sample.cfg zoo.cfg

编辑zoo.cfg文件:vim zoo.cfg

修改其中的属性dataDir,指定数据的存储目录:dataDir=/home/software/zookeeper-3.4.8/tmp

保存并且关闭zoo.cfg

进入Zookeeper的安装目录下的子目录bin目录中:cd ../bin

执行zkServer.sh文件,来启动Zookeeper服务器端:sh zkServer.sh start

执行zkCli.sh文件,来启动进入Zookeeper客户端:sh zkCli.sh

检测是否安装成功:

当服务器端启动成功之后,可以执行jps命令查看是否有Zookeeper的QuorumPeer进程

在服务器端启动成功之后,可以查看服务器端状态:sh zkServer.sh status, 如果出现了Standalone,说明启动成功

Zookeeper特点

Znode

Zookeeper本身是一个树状结构 - Znode树,

   Znode树维系在内存以及磁盘上

              Znode树维系在内存中,目的是为了快速查询

               Znode树维系在磁盘上,目的是崩溃恢复,是以SnapShot - 快照形式存在

Znode节点

Zookeeper的每一个节点称之为是一个Znode节点,每一个Znode节点都必须存储数据

路径

Zookeeper的根节点是/,所有的路径都必须以根节点为起点进行计算,每一个路径都是唯一的。

缓存机制

理论上可以利用Zookeeper来存储大量的数据构建一个缓存机制,实际上很少这么做:

①每一个节点虽然可以存储数据,但是数据量有限(默认1M),数据内容是对这个节点的描述

属性

说明

cZxid

表示这个节点的创建事务id,    create好之后就不变了

ctime

创建时间

mZxid

表示这个节点的数据修改的事务id,set /new 'lj',只变mZxid,不变cZxid

mtime

修改时间

pZxid

记录子节点的增删事务id,pZxid是一个父节点下的全部子节点,cversion只有一个子节点

cversion

记录子节点的增删次数,同一个子节点cversion和pZxid变化一样。

dataVersion

数据版本,记录当前节点的数据发生变化的次数

aclVersion

记录节点的权限改变次数

ephemeralOwner

如果是持久节点,那么此属性值为0x0;如果是临时节点,那么此属性的值就是sessionid

dataLength

数据的字节个数

numChildren

子节点个数

②Zookeeper本身是用于做分布式的协调服务的框架,如果存储大量数据占用大量内存,则会降低协调服务的效率

在Zookeeper中,有事务,事务就是编号,会对每一次的写操作(创建、删除、更新)分配一个全局递增的编号,称之为事务id - Zxid

临时节点不能挂载子节点,退出客户端,在进入后,临时节点不存在。

节点类型

持久

临时

非顺序

Persistent

Ephemeral

顺序

Persistent_Sequential

Ephemeral_Sequential (-e -s)

 

 

 

命令

命令

说明

create /picture 'pictureservers'

表示在根节点下创建一个子节点picture,存储的数据时picture servers

ls /

查看根节点的子节点

delete /video

删除video节点,要求节点没有子节点

rmr /video

递归删除

get /picture

获取picture节点的数据以及节点信息

set /picture 'hello picture'

更新节点数据

create -e /tmp ''

创建临时节点

create -s /picture/phost ''

创建顺序节点

javaAPI

常用操作:

连接

new ZooKeeper("ip:2181",5000, new Watcher() { vent.getState() == KeeperState.SyncConnected  });

创建

zk.create("/log", "日志服务器".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

删除

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

更新

zk.setData("/video", "视频服务器".getBytes(), -1);

获取节点数据

zk.getData("/video", null,new Stat());

判断节点存在

zk.exists("/video", null);

获取所有/的子节点的路径

zk.getChildren("/", null);遍历循环这个路径。

监听节点数据变化

zk.getData("/video", new Watcher() { event.getType() == EventType.NodeDataChanged }, null);

监听子节点个数是否发生变化

zk.getChildren("/video", new Watcher() { event.getType() == EventType.NodeChildrenChanged  });

监听节点的变化

zk.exists("/log", new Watcher() {  event.getType() == EventType.NodeCreated

                                          else if (event.getType() == EventType.NodeDeleted)   });

代码实现

导包:junit,slf4j,zookeeper,

public class ZookeeperDemo {
	ZooKeeper zk;

	// 连接Zookeeper
	@Before
	public void connect() throws IOException, InterruptedException {

		// connectString - 连接地址
		// sessionTimeout - 会话超时时间,单位默认是毫秒
		// watcher - 监控者,监控这个连接
		// 这个过程中,连接操作和监控操作是两个不同的线程,这两个线程都是非阻塞的,底层是NIO
		final CountDownLatch cdl = new CountDownLatch(1);
		zk = new ZooKeeper("106.75.66.249:2181", 5000, new Watcher() {
			public void process(WatchedEvent event) {
				// 利用这个监控可以确定连接是否建立
				if (event.getState() == KeeperState.SyncConnected) {
					System.out.println("连接成功~~~");
				}
				cdl.countDown();
			}
		});
		cdl.await();
	}

	// 创建节点
	@Test
	public void create() throws KeeperException, InterruptedException {

		// path - 路径
		// data - 数据
		// acl - 权限 任何人都可以用
		// createMode - 指定节点类型
		// 返回值表示创建的节点实际名称
		String cpath = zk.create("/log", "日志服务器".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
		System.out.println(cpath);
	}

	// 删除节点
	@Test
	public void delete() throws InterruptedException, KeeperException {
		// path - 路径
		// version - 数据版本 -
		// dataVersion,在删除节点的时候会比较数据版本是否一致,如果一致则删除,反之该操作无效,数据版本为-1,表示强制执行
		zk.delete("/server", -1);
	}

	// 更新数据
	@Test
	public void setData() throws KeeperException, InterruptedException {
		// path, data, version
		// 返回值表示的节点信息
		Stat s = zk.setData("/video", "视频服务器".getBytes(), -1);
		System.out.println(s);
	}

	// 获取数据
	@Test
	public void getData() throws KeeperException, InterruptedException {

		// path - 路径
		// watch - 监控者
		// stat - 节点信息,不想要节点信息给null
		Stat s = new Stat();
		byte[] data = zk.getData("/video", null, s);
		System.out.println(new String(data));
	}

	// 获取子节点
	@Test
	public void getChildren() throws KeeperException, InterruptedException {

		// 获取所有/的子节点的路径
		List<String> cpaths = zk.getChildren("/", null);
		for (String cpath : cpaths) {
			System.out.println(cpath);
		}
	}

	// 判断节点是否存在
	@Test
	public void exist() throws KeeperException, InterruptedException {

		// 返回值表示这个节点的信息,如果这个节点不存在,则返回null
		Stat s = zk.exists("/video", null);
		System.out.println(s);
	}

	// 监控某个节点的数据是否发生了变化
	@Test
	public void dataChanged() throws KeeperException, InterruptedException {
		// 因为监控线程是非阻塞的,所以可能会产生测试线程已经结束但是监控线程还没有监控到数据的情况,因此需要阻塞测试线程,在监控到数据之前,测试线程不能结束
		CountDownLatch cdl = new CountDownLatch(1);
		// 获取的改变之前的数据
		byte[] data = zk.getData("/video", new Watcher() {

			@Override
			public void process(WatchedEvent event) {
				if (event.getType() == EventType.NodeDataChanged)
					System.out.println("节点数据发生变化~~~");
				cdl.countDown();
			}
		}, null);
		cdl.await();
		System.out.println(new String(data));
	}

	// 监控子节点个数是否发生变化
	@Test
	public void childChanged() throws KeeperException, InterruptedException {
		CountDownLatch cdl = new CountDownLatch(1);

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

			@Override
			public void process(WatchedEvent event) {
				if (event.getType() == EventType.NodeChildrenChanged)
					System.out.println("子节点发生了变化~~~");
				cdl.countDown();
			}
		});
		cdl.await();
	}

	// 监控节点的增删变化
	@Test
	public void nodeChanged() throws KeeperException, InterruptedException {
		CountDownLatch cdl = new CountDownLatch(1);
		zk.exists("/log", new Watcher() {
			@Override
			public void process(WatchedEvent event) {
				if (event.getType() == EventType.NodeCreated)
					System.out.println("新添了log节点");
				else if (event.getType() == EventType.NodeDeleted)
					System.out.println("删除了log节点");
				cdl.countDown();
			}
		});
		cdl.await();
	}
}

Zookeeper集群搭建

安装步骤

关闭Linux(版本是Centos6.X)的防火墙

      临时关闭防火墙:service iptables stop
      永久关闭防火墙:chkconfig iptables off

下载安装JDK

下载或者上传Zookeeper的压缩包

解压压缩包:tar -xvf zookeeper-3.4.8

进入Zookeeper的安装目录下的子目录conf目录中:cd zookeeper-3.4.8/conf

将conf目录下的zoo_sample.cfg复制为zoo.cfg。Zookeeper在启动的时候会自动寻找zoo.cfg,根据其中的配置来启动存储数据:cp zoo_sample.cfg zoo.cfg

编辑zoo.cfg文件:vim zoo.cfg

修改其中的属性dataDir,指定数据的存储目录:dataDir=/home/software/zookeeper-3.4.8/tmp

在zoo.cfg文件的添加要构建集群的服务器地址
格式:server.编号=IP地址:原子广播端口:选举端口。

编号要求是数字并且不能重复,原子广播端口号和选举端口号只要不和当前已经使用的端口号冲突即可
例如:

server.1=10.42.156.58:2888:3888
server.2=10.42.36.109:2888:3888
server.3=10.42.178.114:2888:3888

关闭并且保存zoo.cfg

在dataDir的指定目录下创建数据存储目录:mkdir tmp

进入数据存储目录:cd tmp

在数据存储目录下编辑新的文件,文件名为myid:vim myid

在myid文件中填入当前服务器所对应的编号。例如当前服务器的地址为10.42.156.58,则在zoo.cfg文件中指定编号为1,那么就在myid文件中添加数字1

保存退出myid

将要配置的集群中的其他服务器按照上述步骤配置。或者可以将当前服务器中的配置拷到其他服务器上,修改对应的myid
格式 scp -r 目录或者文件 IP:目录
例如:scp -r zookeeper-3.4.8 10.42.36.109:/home/software

集群全部配置好之后,依次启动每一台服务器

进入Zookeeper安装目录下的bin目录:cd ../bin

执行zkServer.sh文件,启动Zookeeper的服务器:sh zkServer.sh start

执行zkCli.sh文件,启动Zookeeper的客户端:sh zkCli.sh

配置完成检测是否成功:

在Zookeeper集群中,如果单独启动一台服务器,是无法对外提供服务的

当把集群中的服务器都启动之后,可以利用:sh zkServer.sh status。来查看当前节点的状态,如果出现leader或者follower,则说明启动成功

 

选举机制

数据恢复阶段

当Zookeeper启动之后,每一个Zookeeper服务器都会找到当前节点中最大的事务id

选举阶段

  1. 每一个节点都会选举自己当leader,并且将自己当前节点的选举信息发送给其他节点
  2. 选举信息:
    1. 节点的最大事务id
    2. 选举编号/节点编号 - myid
    3. 逻辑时钟值 - 确定所有的节点的选举是处在同一个轮次上
  3. 比较原则:
    1. 先比较两个节点的最大事务id,谁的事务id大谁就胜出
    2. 如果两个节点的事务id一致,则比较两个节点的myid,谁的myid大谁就胜出 - myid配置的要不一样
  4. 如果某个节点胜出了一半及以上的节点(过半性),那么这个节点就会成为leader

节点状态

  1. looking - 选举状态
  2. follower - 追随者
  3. leader - 领导者
  4. observer - 观察者

观察者:

   配置:

  1. 在zoo.cfg中添加如下属性:peerType=observer
  2. 在要配置为观察者的主机后添加观察者标记。例如:
    server.1=10.42.156.58:2888:3888
    server.2=10.42.36.109:2888:3888
    server.3=10.42.178.114:2888:3888:observer #表示将该节点设置为观察者

   特点:

  1. 如果一个节点是观察者,那么这个节点不会参与投票以及选举,监控投票以及选举情况,根据投票以及选举情况来决定是否执行请求 - 将观察者认为成是没有选举权的follower
  2. 使用于异地网络或者是网络情况不稳定的场景
  3. observer的存活状态并不影响集群是否对外服务,如果观察者产生故障或者从集群断开连接都不会影响Zookeeper服务的可用性
  4.  

注意

1..如果整个集群中确定了leader,那么后续新添的节点的事务id或者myid无论是多少,都只能成为follower

2.集群中的节点个数最好是奇数个,奇数个节点能够有效的进行选举,也能够防止脑裂 - 奇数个容易满足过半性

因为集群分裂产生了多个leader,这种情况在集群中称之为脑裂

3.在Zookeeper中,如果集群中存活的节点个数不满足过半性的时候,整个集群停止对外提供服务也不选

过半性

1.过半选举:即只有一个节点胜过一半的节点之后才能成为leader

2.过半服务:即只有Zookeeper集群中超过一半的节点存活才能对外提供服务

3.过半操作:即只有Zookeeper集群中超过一半的节点同意才会提交请求

ZAB协议

ZAB是专门为Zookeeper设计的协议,基于了2PC算法和PAXOS来进行的设计

功能:原子广播和崩溃恢复

2PC

1.2PC - Two Phase Commit - 二阶段提交,将请求的提交过程分为2个阶段,核心思想是"一票否决"

2.确认阶段协调者收到请求之后会将请求去分发给每一个参与者,等待每一个参与者反馈的信息

3.执行/中止阶段:

     如果协调者收到所有参与者的消息,并且所有消息都是yes,则协调者就会命令每一个参与者执行这个请求

     如果协调者收到了一个及以上的no,或者协调者没有收到全部参与者的yes,则协调者认为这个请求不能执行,命令所有的参与者中止执行这个请求

4.优点:原理简单

5.缺点:效率很低,协调者存在单点故障

Paxos

在集群(岛屿)中存在很多的服务器(执法者),这些服务器要决定是否执行某个请求(法律),这些服务器之间的通信是通过网络(服务员)进行传递。如果这些服务器决定执行某个请求,则会将这个请求的执行记录在自己本机上。服务器可能会因为网路问题或者本身故障而导致脱离集群,并且也可能因为网络修复或者节点添加的方式使得服务器重新连入集群,如何确定这些服务器到底是否执行这个请求,就是Paxos算法所要做的事儿 - Paxos用于在不稳定的集群中有效的决定某请个求的执行的场景

原子广播

1.实现信息的共享问题,基于2PC算法改进,引入一部分PAXOS算法

原子广播的流程:

 

2.如果一个节点新连入集群,这个节点会找这个集群中的leader,比较两者的最大事务id,然后leader会将新节点所缺的请求放入一个队列中发给新节点,新节点收到队列之后再从队列中依次取出执行,恢复到和整个集群相同的事务id

崩溃恢复

1.当leader出现的宕机或者从集群中离开的时候,整个集群要选举出一个新的leader,这个过程就称之为崩溃恢复的过程

2.在崩溃恢复过程中,为了防止集群中出现多个leader共同"执政"的情况,在选举leader的时候会给leader一个编号 - epochid,epochid是全局递增的。当节点被选为leader的时候,它会将它的epochid分发给每一个follower

3.在集群中,事务id实际上是64位二进制,其中高32位表示epochid,低32位才是真正的事务id

4.崩溃恢复可以避免单点故障,在选举过程中,集群不对外提供服务。

事务日志

概述

  1. 事务日志指Zookeeper系统在正常运行过程中,针对所有的事务操作,在返回客户端ACK的响应前,Zookeeper会保证已经将本次更新操作的事务日志已经写到磁盘上
  2. Zookeeper的事务日志为二进制文件,不能通过vim等工具直接访问。其实可以通过zookeeper自带的jar包读取事务日志文件

查看事务log

  1. 进入Zookeeper的安装目录的lib目录下:cd zookeeper-3.4.8/lib
  2. 将lib目录下的slf4j-api-1.6.1.jar复制到数据目录的version-2目录下:cp slf4j-api-1.6.1.jar ../tmp/version-2/
  3. 回到Zookeeper的安装目录:cd ..
  4. 将Zookeeper的安装目录下的zookeeper-3.4.8.jar复制到数据目录下的version-2目录下:cp zookeeper-3.4.8.jar tmp/version-2/
  5. 进入数据目录下的version-2目录下:cd tmp/version-2/
  6. 执行: java -cp .:zookeeper-3.4.8.jar:slf4j-api-1.6.1.jar org.apache.zookeeper.server.LogFormatter ./log.100000001

查看日志文件

快照

概述

  1. 快照指Zookeeper系统在所存储的部分节点信息写到了磁盘上。
  2. Zookeeper的快照文件为二进制文件,不能通过vim等工具直接访问。其实可以通过Zookeeper自带的jar包读取事务日志文件

查看快照文件

  1. 进入Zookeeper的安装目录的lib目录下:cd zookeeper-3.4.8/lib
  2. 将lib目录下的slf4j-api-1.6.1.jar复制到数据目录的version-2目录下:cp slf4j-api-1.6.1.jar ../tmp/version-2/
  3. 回到Zookeeper的安装目录:cd ..
  4. 将Zookeeper的安装目录下的zookeeper-3.4.8.jar复制到数据目录下的version-2目录下:cp zookeeper-3.4.8.jar tmp/version-2/
  5. 进入数据目录下的version-2目录下:cd tmp/version-2/
  6. 执行: java -cp .:zookeeper-3.4.8.jar:slf4j-api-1.6.1.jar org.apache.zookeeper.server.SnapshotFormatter ./log.100000001
    查看快照文件
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值