Zookeeper概述
Zookeeper是开源的分布式的协调服务框架,是Apache Hadoop的子件,适用于绝大部分分布式集群的管理。
分布式引发问题:
- 死锁:至少有一个线程占用了资源,但是不占用CPU
- 活锁:所有线程都没有把持资源,但是线程却是在不断地调度占用CPU
- 需要引入一个管理节点
- 为了防止入口的单点问题,需要引入管理节点的集群
- 需要在管理阶段中选举出一个主节点
- 需要确定一套选举算法
- 主节点和从节点之间要保证数据的一致
Zookeeper的单机版安装
Zookeeper可以安装在Linux下,也可以安装在Windows中,但是官网上声明在Windows中Zookeeper不保证稳定性
- 关闭Linux的防火墙
- 临时关闭:service iptables stop
- 永久关闭:chkconfig iptables off
- 下载并且安装jdk,要求jdk是1.6以上的版本
- 下载Zookeeper的安装包
- 解压安装包:tar - xvf zookeeper-3.4.8.tar.gz
- 进入Zookeeper的安装目录中的conf目录:cd zookeeper-3.4.8/conf
- 将zoo_sample.cfg复制为zoo.cfg:cp zoo_sample.cfg zoo.cfg
- Zookeeper在启动的时候会自动加载zoo.cfg,从里面读取配置信息,需要修改zoo.cfg,将其中的dataDir进行修改:dataDir=/home/software/zookeeper-3.4.8/tmp
- 创建指定的数据存储目录:mkdir tmp
- 进入bin目录:cd bin
- 启动服务器端:sh zkServer.sh start
- 启动客户端:sh zkCli.sh
注意:
Zookeeper返回Started不代表启动成功,可以通过jps或者是sh zkServer.sh status来查看是否启动成功
如果使用的是jps,查看是否有QuorumPeerMain
如果使用的是sh zkServer.sh status, 查看是否有Mode:standalone
当Zookeeper启动之后,在bin目录下会出现zookeeper.out文件 --- 记录Zookeeper的启动过程的日志文件
Zookeeper的特点
- 本身是一个树状结构 --- Znode树
- 每一个节点称之为znode节点
- 根节点是 /
- Zookeeper的所有操作都必须以根节点为基准进行计算 /
- 每一个znode节点都必须存储数据
- 任意一个持久节点都可以有子节点
- 任意一个节点的路径都是唯一的
- Znode树是维系在内存中 --- 目的是为了快速查询
- Zookeeper不适合存储海量数据。原因:1)维系在内存中,如果存储大量数据会耗费内存 2) 不是一个存储框架而是一个服务协调框架
- Zookeeper会为每一次事务(除了读取以外的所有操作都是事务)分配一个全局的事务id ---Zxid
Zookeeper的命令
- create /node01 "hello zookeeper" 在根节点下创建子节点node01并且赋值为hello zookeeper
- set /node01 "hi" 将node01的数据更新为hi
- get /node01 获取node01的数据
- ls / 查看根节点下的所有的子节点
- delete /node02 删除子节点,要求这个节点中没有子节点
- rmr /node01 删除node01及其子节点
- quit 退出客户端
- create -e /node02 '' 表示在根目录下创建临时节点/node02
- create -s /node03 '' 表示在根目录下创建持久顺序节点/node03000000000X
- create -e -s /node04 '' 表示在根目录下创建临时顺序节点/node04000000000X
Zookeeper的节点类型
- 持久节点
- 临时节点:在客户端退出之后就自动删除
- 持久顺序节点
- 临时顺序节点
Zookeeper的节点信息
- cZxid - 全局分配的创建的事务id --- 创建这个节点是Zookeeper的第n个操作 --- 定义好之后就不变了
- ctime - 创建时间
- mZxid - 修改的事务id --- 记录自己本身修改
- mtime - 修改时间
- pZxid - 表示子节点的变化的事务id --- 记录直接子节点的创建或者删除
- cversion - 记录子节点变化次数
- dataVersion - 数据版本 --- 记录当前节点的数据的变化次数
- aclVersion - acl版本 --- 记录当前节点的acl的变化次数
- ephemeralOwner - 如果当前节点不是临时节点,那么这个属性的值为0;如果是临时节点,那么这个属性的值记录的是当前临时节点的session id
- dataLength - 数据长度 - 实际上是字节个数
- numChildren - 子节点个数
Zookeeper的API操作
连接操作 @Test public void connect() throws IOException, InterruptedException{ CountDownLatch cdl = new CountDownLatch(1); // connectString - 连接地址+端口号 // sessionTimeout - 会话超时时间 - 表示连接超时时间,单位默认为毫秒 // watcher - 监控者 - 监控连接状态 // Zookeeper本身是一个非阻塞式连接 ZooKeeper zk = new ZooKeeper("117.50.90.232: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 - acl策略 // createMode - 节点类型 // 返回值表示节点的实际路径 String str = zk.create("/node08", "hello,1807".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); System.out.println(str); } 删除节点 @Test public void delete() throws InterruptedException, KeeperException { // path - 节点路径 // version - 数据版本 // 在删除节点的时候回比较指定的数据版本和节点的实际数据版本是否一致 // 如果一致则删除,如果不一致则放弃该操作 // zk.delete("/node04", 0); // -1表示强制执行 zk.delete("/node04", -1); } 更新数据 @Test public void set() throws KeeperException, InterruptedException{ // path - 节点路径 //data - 数据 // version - 数据版本 // 返回节点信息 Stat s = zk.setData("/node02", "hi,zk".getBytes(), -1); System.out.println(s); } 获取数据 @Test public void get() throws KeeperException, InterruptedException, UnsupportedEncodingException { // path - 节点路径 // watch - 监控 // stat - 节点信息 Stat s = new Stat(); byte[] data = zk.getData("/node01", null, s ); System.out.println(new String(data, "utf-8")); } 判断节点的存在 @Test public void exist() throws KeeperException, InterruptedException{ // path - 节点路径 // watch - 监控者 // 返回的节点的信息 Stat s = zk.exists("/node07", null); System.out.println(s); } 获取子节点 @Test public void getChidren() throws KeeperException, InterruptedException { // path - 节点路径 // watch - 监控者 // 返回子节点的路径 List<String> nodes = zk.getChildren("/" ,null); for (String p : nodes) { System.out.println(p); } } 监听节点数据变化 @Test public void dataChanged() throws KeeperException, InterruptedException { CountDownLatch cdl = new CountDownLatch(1); // 获取的是监听之前的数据 // 获取数据和监听变化实际上是两个线程 byte[] data = zk.getData("/node01", 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("/node01", 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("/node06", new Watcher() { @Override public void process(WatchedEvent event) { if (event.getType() == EventType.NodeCreated) System.out.println("这个节点被创建~~~"); else if (event.getType() == EventType.NodeDeleted) System.out.println("这个节点被删除~~~"); cdl.countDown(); } }); cdl.await(); }
Zookeeper的集群安装
- 关闭防火墙
- 安装jdk
- 下载Zookeeper的安装包
- 解压Zookeeper的安装包
- 进入Zookeeper的安装目录中conf目录
- 将zoo_sample.cfg复制为zoo.cfg
- 编辑zoo.cfg,修改dataDir属性:
dataDir=/home/software/zookeeper-3.4.8/tmp server.1=10.9.162.133:2888:3888 # 1是编号,要求每一个节点的编号是数字且不重复; server.2=10.9.152.65:2888:3888 #2888,3888是端口号,只要不和已经占用的端口号冲突即可 server.3=10.9.130.83:2888:3888
- 创建存储数据的目录
- 进入数据存储目录
- 编辑文件myid, 将当前机器的编号写到myid中
- 将配置好的Zookeeper的安装目录拷到其他集群主机中:scp -r zookeeper-3.4.8 10.9.130.83:/home/software/
- 根据指定的编号修改对应的myid
在Zookeeper集群中,单独启动一台主机是不对外提供服务的
Zookeeper在使用过程中,会进行选举,选举出主节点-leader,其他的节点就会成为从节点-follower
Zookeeper的选举
第一阶段:数据恢复阶段
会从数据目录(dataDir)中恢复数据
第二阶段:选举阶段
- 所有的节点都会推荐自己当leader并且发送自己的选举信息(最大事务id - pZxid,编号 - myid,逻辑时钟值)
- 选举原则:先比较最大事务id,谁的事务id大谁就胜出;如果最大事务id一样,则比较myid,谁的myid大谁就胜出
- 选举出的leader的胜出要满足过半性:即要比至少一半的节点大
- 如果在集群中新加入一个节点,节点的事务id比leader的事务id大,新的节点是否会成为leader? --- 不会。只要选定了一个leader,那么后续节点的事务id和myid无论是多少,一律都是follower
- 如果leader宕机,集群中会自动选举一个新的leader
如果超过一半的服务器宕机,那么此时Zookeeper不再对外提供服务 --- 过半性
01 02 03 => 02成为leader
01 03 02 => 03成为leader
节点的状态
- Looking - 选举状态
- follower - 追随者
- leader - 领导者
- observer - 观察者
Zookeeper的投票是ZAB协议。ZAB是2PC协议的基础上来进行的延伸。
2PC:将节点分为了协调者和参与者。当来一个请求的时候,协调者是将请求分发给每一个参与者,如果所有的参与者决定执行这个请求,那么协调者就真正提交操作,有参与者执行,参与者在执行完成之后返回Ack表示执行成功。如果协调者将请求分发给每一个参与者之后,有一个或者多个参与者不同意执行或者是没有返回消息,那么将这个操作回滚,不执行 --- 一票否决
在Zookeeper中,接收一个请求之后,leader会将请求分发给每一个节点,由所有的节点投票确定是否执行这个请求 --- 如果有超过一半的节点同意执行这个请求,那么这个时候leader才会决定执行这个操作
过半性:
- 选举leader的要满足过半性
- 请求操作也要满足过半性
- 过半集群才能对外提供服务 --- 防止脑裂 --- 脑裂:集群中产生2个及以上的leader --- 一般将集群节点设置为奇数
观察者
执行操作,但是不参与选举和投票。
21个节点 --- 14个观察者,6个follower以及1个leader
- 12个观察者宕机,依然对外提供服务
- 4个follower宕机,不对外提供服务
观察者不参与投票,所以就不影响集群的运行状态 --- 适用于网络不稳定的情况
找到要设置为观察者的主机,
编辑zoo.cfg,添加:peerType=observer
再在要设置的主机之后添加observer标记
例如:server.3=10.9.130.83:2888:3888:observer,然后重新启动这一个节点