1. Zookeeper简介
1.1 Zookeeper是什么?
Zookeeper 是一个分布式协调服务的开源框架。 主要用来解决分布式集群中应用系统的一致性问题, 例如怎样避免同时操作同一数据造成脏读的问题。分布式系统中数据存在一致性的问题!!
ZooKeeper 本质上是一个分布式的小文件存储系统。 提供基于类似于文件系统的目录树方式的数据存储,并且可以对树中的节点进行有效管理。
ZooKeeper 提供给客户端监控存储在zk内部数据的功能,从而可以达到基于数据的集群管理。 诸如: 统一命名服务(dubbo)、分布式配置管理(solr的配置集中管理)、分布式消息队列(sub/pub)、分布式锁、分布式协调等功能。
1.2 zookeeper的架构组成
ZK也是Master/slave架构,但是与之前不同的是zk集群中的Leader不是指定而来,而是通过选举产生。
LeaderZookeeper 集群工作的核心角色
集群内部各个服务器的调度者。
事务请求(写操作) 的唯一调度和处理者,保证集群事务处理的顺序性;对于 create,setData, delete 等有写操作的请求,则需要统一转发给leader 处理, leader 需要决定编号、执行操作,这个过程称为一个事务。
Follower处理客户端非事务(读操作) 请求,转发事务请求给 Leader;
参与集群 Leader 选举投票 2n-1台可以做集群投票。
此外,针对访问量比较大的 zookeeper 集群, 还可新增观察者角色。Observer
观察者角色,观察 Zookeeper 集群的最新状态变化并将这些状态同步过来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给 Leader 服务器进行处理。不会参与任何形式的投票只提供非事务服务,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。增加了集群增加 并发的读请求 。
1.3 Zookeeper 特点
1. Zookeeper:一个领导者(leader:老大),多个跟随者(follower:小弟)组成的集群。
2. Leader负责进行投票的发起和决议,更新系统状态
3. Follower用于接收客户请求并向客户端返回结果,在选举Leader过程中参与投票
4. 集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。
5. 全局数据一致:每个server保存一份相同的数据副本,Client无论连接到哪个server,数据都是一致的。
6. 更新请求顺序进行
7. 数据更新原子性,一次数据更新要么成功,要么失败。
2. Zookeeper环境搭建
tar -zxvf zookeeper-3.4.14.tar.gz -C ../servers/
# 创建 zk 存储数据目录mkdir -p /opt/lagou/servers/zookeeper-3.4.14/data# 创建 zk 日志文件目录mkdir -p /opt/lagou/servers/zookeeper-3.4.14/data/logs# 修改 zk 配置文件cd /opt/lagou/servers/zookeeper-3.4.14/conf# 文件改名mv zoo_sample.cfg zoo.cfgvim zoo.cfg# 更新 datadirdataDir = /opt/lagou/servers/zookeeper-3.4.14/data# 增加 logdirdataLogDir = /opt/lagou/servers/zookeeper-3.4.14/data/logs# 增加集群配置##server. 服务器 ID= 服务器 IP 地址:服务器之间通信端口:服务器之间投票选举端口server .1 = linux121:2888:3888server .2 = linux122:2888:3888server .3 = linux123:2888:3888# 打开注释#ZK 提供了自动清理事务日志和快照文件的功能,这个参数指定了清理频率,单位是小时autopurge .purgeInterval = 1
cd /opt/lagou/servers/zookeeper-3.4.14/dataecho 1 > myid
rsync-script /opt/lagou/servers/zookeeper-3.4.14修改 myid 值 linux122echo 2 >/opt/lagou/servers/zookeeper-3.4.14/data/myid修改 myid 值 linux123echo 3 >/opt/lagou/servers/zookeeper-3.4.14/data/myid
启动命令(三个节点都要执行)/opt/lagou/servers/zookeeper-3.4.14/bin/zkServer.sh start查看 zk 启动情况/opt/lagou/servers/zookeeper-3.4.14/bin/zkServer.sh status
集群启动停止脚本
#!/bin/sh
echo "start zookeeper server...."
if(($#==0));then
echo "no params";
exit;
fi
hosts="test1.bigload.com test2.bigload.com test3.bigload.com"
for host in $hosts
do
ssh $host "source /etc/profile;/opt/lagou/servers/zookeeper-3.4.14/bin/zkServer.sh $1"
done
3. Zookeeper数据结构与监听机制
在ZooKeeper中,数据信息被保存在一个个数据节点上,这些节点被称为znode。ZNode 是 Zookeeper 中最小数据单位,在 ZNode 下面又可以再挂 ZNode,这样一层层下去就形成了一个层次化命名空间 ZNode 树,我们称为 ZNode Tree,它采用了类似文件系统的层级树状结构进行管理。见下图
3.1 ZNode 的类型
持久性节点(Persistent)
临时性节点(Ephemeral)
顺序性节点(Sequential)
持久节点:是Zookeeper中最常见的一种节点类型,所谓持久节点,就是指节点被创建后会一直存在服务器,直到删除操作主动清除
持久顺序节点:就是有顺序的持久节点,节点特性和持久节点是一样的,只是额外特性表现在顺序上。 顺序特性实质是在创建节点的时候,会在节点名后面加上一个数字后缀,来表示其顺序。
临时节点:就是会被自动清理掉的节点,它的生命周期和客户端会话绑在一起,客户端会话结束,节点会被删除掉。与持久性节点不同的是,临时节点不能创建子节点。
临时顺序节点:就是有顺序的临时节点,和持久顺序节点相同,在其创建的时候会在名字后面加上数字后缀。
首先,先了解,事务是对物理和抽象的应用状态上的操作集合。往往在现在的概念中,狭义上的事务通常指的是数据库事务,一般包含了一系列对数据库有序的读写操作,这些数据库事务具有所谓的ACID特性,即原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
而在ZooKeeper中,事务是指能够改变ZooKeeper服务器状态的操作,我们也称之为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新等操作。对于每一个事务请求,ZooKeeper都会为其分配一个全局唯一的事务ID,用 ZXID 来表示,通常是一个 64 位的数字。每一个 ZXID 对应一次更新操作,从这些ZXID中可以间接地识别出ZooKeeper处理这些更新操作请求的全局顺序
zk中的事务指的是对zk服务器状态改变的操作(create,update data,更新字节点);zk对这些事务操作都会编号,这个编号是自增长的被称为ZXID。
3.2 ZNode 的状态信息
#使用bin/zkCli.sh 连接到zk集群
[zk: localhost:2181(CONNECTED) 2 ] get /zookeepercZxid = 0x0ctime = Wed Dec 31 19 :00:00 EST 1969mZxid = 0x0mtime = Wed Dec 31 19 :00:00 EST 1969pZxid = 0x0cversion = -1dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 0numChildren = 1
cZxid 就是 Create ZXID ,表示节点被创建时的事务 ID 。ctime 就是 Create Time ,表示节点创建时间。mZxid 就是 Modified ZXID ,表示节点最后一次被修改时的事务 ID 。mtime 就是 Modified Time ,表示节点最后一次被修改的时间。pZxid 表示该节点的子节点列表最后一次被修改时的事务 ID 。只有子节点列表变更才会更新 pZxid,子节点内容变更不会更新。cversion 表示子节点的版本号。dataVersion 表示内容版本号。aclVersion 标识 acl 版本ephemeralOwner 表示创建该临时节点时的会话 sessionID ,如果是持久性节点那么值为 0dataLength 表示数据长度。numChildren 表示直系子节点数。
- Zookeeper使用Watcher机制实现分布式数据的发布/订阅功能
- 一个典型的发布/订阅模型系统定义了一种 一对多的订阅关系,能够让多个订阅者同时监听某一个主题对象,当这个主题对象自身状态变化时,会通知所有订阅者,使它们能够做出相应的处理。
- 在 ZooKeeper 中,引入了 Watcher 机制来实现这种分布式的通知功能。ZooKeeper 允许客户端向服务端注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,那么Zk就会向指定客户端发送一个事件通知来实现分布式的通知功能。
整个Watcher注册与通知过程如图所示。
客户端在向Zookeeper服务器注册的同时,会将Watcher对象存储在客户端的WatcherManager当中
当Zookeeper服务器触发Watcher事件后,会向客户端发送通知
客户端线程从WatcherManager中取出对应的Watcher对象来执行回调逻辑
4. Zookeeper的基本使用
4.1 ZooKeeper命令行操作
./zkcli.sh 连接本地的 zookeeper 服务器./zkCli.sh -server ip:port(2181) 连接指定的服务器
使用 create 命令,可以创建一个 Zookeeper节点, 如 create [-s][-e] path data其中, -s 或 -e 分别指定节点特性,顺序或临时节点,若不指定,则创建持久节点① 创建顺序节点使用 create -s /zk-test 123 命令创建zk-test顺序节点
执行完后,就在根节点下创建了一个叫做/zk-test的节点,该节点内容就是123,同时可以看到创建的zk-test节点后面添加了一串数字以示区别
② 创建临时节点使用 create -e /zk-temp 123 命令创建zk-temp临时节
临时节点在客户端会话结束后,就会自动删除,下面使用quit命令退出客户端
再次使用客户端连接服务端,并使用ls / 命令查看根目录下的节点
可以看到根目录下已经不存在zk-temp临时节点了
③ 创建永久节点使用 create /zk-permanent 123 命令创建zk-permanent永久节点 ,可以看到永久节点不同于顺序节点,不会自动在后面添加一串数字
与读取相关的命令有 ls 命令和 get 命令ls 命令可以列出 Zookeeper 指定节点下的所有子节点,但只能查看指定节点下的第一级的所有子节点;ls path其中,path表示的是指定数据节点的节点路径
get命令可以获取Zookeeper指定节点的数据内容和属性信息。
get path若获取根节点下面的所有子节点,使用ls / 命令即可
若想获取/zk-permanent的数据内容和属性,可使用如下命令:get /zk-permanent
从上面的输出信息中,我们可以看到,第一行是节点/zk-permanent 的数据内容,其他几行则是创建该节点的事务ID(cZxid)、最后一次更新该节点的事务ID(mZxid)和最后一次更新该节点的时间(mtime)等属性信息
使用 set 命令,可以更新指定节点的数据内容,用法如下set path data
其中, data 就是要更新的新内容, version 表示数据版本,在 zookeeper中,节点的数据是有版本概念的,这个参数用于指定本次更新操作是基于 Znode 的哪一个数据版本进行的,如将/zk-permanent 节点的数据更新为 456 ,可以使用如下命令: set /zk-permanent 456现在 dataVersion 已经变为 1 了,表示进行了更新
使用delete命令可以删除Zookeeper上的指定节点,用法如下
delete path
其中version也是表示数据版本,使用delete /zk-permanent 命令即可删除/zk-permanent节点
可以看到,已经成功删除/zk-permanent节点。值得注意的是,若删除节点存在子节点,那么无法删除该节点,必须先删除子节点,再删除父节点
4.2 Zookeeper-开源客户端
ZkClient是Github上一个开源的zookeeper客户端,在Zookeeper原生API接口之上进行了包装,是一个更易用的Zookeeper客户端,同时,zkClient在内部还实现了诸如Session超时重连、Watcher反复注册等功能,接下来,还是从创建会话、创建节点、读取数据、更新数据、删除节点等方面来介绍如何使用zkClient这个zookeeper客户端
在pom.xml文件中添加如下内容
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.14</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.2</version> </dependency>
使用ZkClient可以轻松的创建会话,连接到服务端。
public class zkTest { public static void main(String[] args) { /*使用ZkClient连接到服务端*/ ZkClient zkClient = new ZkClient("test1.bigload.com:2181"); System.out.println("zookeeper连接成功"); } }
运行结果:zookeeper连接成功
结果表明已经成功创建会话。
ZkClient提供了递归创建节点的接口,即其帮助开发者先完成父节点的创建,再创建子节点
public class zkTest { public static void main(String[] args) { /*使用ZkClient连接到服务端*/ ZkClient zkClient = new ZkClient("test1.bigload.com:2181"); System.out.println("zookeeper连接成功"); /*createParents设置为true,递归创建/lg-zkClient/lg-c1 */ zkClient.createPersistent("/lg-zkClient/lg-c1",true); System.out.println("创建成功"); } }
运行结果:创建成功.
可以到命令行去查看创建的节点
结果表明已经成功创建了节点,值得注意的是,ZkClient通过设置createParents参数为true可以递归的 先创建父节点,再创建子节点
ZkClient.delete(path)可以删除空的节点,但如果节点还得包含有子节点就会报错
ZkClient提供了递归删除节点的接口,即其帮助开发者先删除所有子节点(存在),再删除父节点。
public class zkTest { public static void main(String[] args) { /*使用ZkClient连接到服务端*/ ZkClient zkClient = new ZkClient("test1.bigload.com:2181"); System.out.println("zookeeper连接成功"); /*递归删除节点*/ zkClient.deleteRecursive("/lg-zkClient"); System.out.println("删除成功"); } }
运行结果: 删除成功
结果表明ZkClient可直接删除带子节点的父节点,因为其底层先删除其所有子节点,然后再删除父节点
public class zkTest { public static void main(String[] args) throws InterruptedException { /*使用ZkClient连接到服务端*/ ZkClient zkClient = new ZkClient("test1.bigload.com:2181"); System.out.println("zookeeper连接成功"); /*监听节点*/ zkClient.subscribeChildChanges("/lg-client", new IZkChildListener() { @Override public void handleChildChange(String path, List<String> list) throws Exception { System.out.println(path+"中包含子节点"+list); } }); /*使用zkClient创建节点,删除节点,验证监听器是否运行*/ zkClient.createPersistent("/lg-client/client1",true); Thread.sleep(1000); zkClient.createPersistent("/lg-client/client2"); Thread.sleep(1000); zkClient.delete("/lg-client/client2"); Thread.sleep(1000); zkClient.deleteRecursive("/lg-client"); Thread.sleep(Integer.MAX_VALUE); } }
运行结果:
当监听期间可以使用命令行创建一个临时节点lg-client(临时节点不能添加子节点) ,之后退出zk,即能看到结果
结果表明:
-
客户端可以对一个不存在的节点进行子节点变更的监听。
-
一旦客户端对一个节点注册了子节点列表变更监听之后,那么当该节点的子节点列表发生变更时,服务端都会通知客户端,并将最新的子节点列表发送给客户端该节点本身的创建或删除也会通知到客户端。
public class zkTest { public static void main(String[] args) throws InterruptedException { /*使用ZkClient连接到服务端*/ ZkClient zkClient = new ZkClient("test1.bigload.com:2181"); System.out.println("zookeeper连接成功"); /*设置自定义的序列化类型*/ // zkClient.setZkSerializer(new ZkStrSeriallizer()); /*是否存在/lg-client1 */ boolean exists = zkClient.exists("/lg-client1"); /*如果不存在就创建*/ if (!exists){ zkClient.createPersistent("/lg-client1"); } /*注册监听器,节点数据改变的类型,接受通知后的处理逻辑定义*/ zkClient.subscribeDataChanges("/lg-client1", new IZkDataListener() { @Override public void handleDataChange(String s, Object o) throws Exception { /*定义数据变化后的通知处理逻辑*/ System.out.println(s+"的数据变了,新的内容是:"+o); } @Override public void handleDataDeleted(String s) throws Exception { /*当节点删除后的通知处理逻辑*/ System.out.println(s+"被删了"); } }); /*获取数据*/ Object o = zkClient.readData("/lg-client1"); System.out.println(o); /*修改数据*/ zkClient.writeData("/lg-client1","123"); Thread.sleep(1000); /*删除节点*/ zkClient.delete("/lg-client1"); } }class ZkStrSeriallizer implements ZkSerializer{ @Override public byte[] serialize(Object o) throws ZkMarshallingError { /*序列化,数据--》byte[] */ return String.valueOf(o).getBytes(); } @Override public Object deserialize(byte[] bytes) throws ZkMarshallingError { /*反序列化,byte[]--> 数据 */ return new String(bytes); } }
运行结果:
zookeeper连接成功
null
/lg-client1的数据变了,新的内容是:123
结果表明可以成功监听节点数据变化或删除事件。
5. Zookeeper内部原理
5.1 Leader选举
半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器。
Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其它为Follower,Leader是通过内部的选举机制产生的。
假设有五台服务器组成的Zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动,来看看会发生什么,
Zookeeper的选举机制( 1 )服务器 1启动,此时只有它一台服务器启动了,它发出去的报文没有任何响应,所以它的选举状态一直是 LOOKING 状态。( 2 )服务器 2 启动,它与最开始启动的服务器 1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以 id 值较大的服务器 2 胜出,但是由于没有达到超过半数以上的服务器都同意选举它 (这个例子中的半数以上是 3) ,所以服务器 1 、 2 还是继续保持 LOOKING 状态。( 3 )服务器 3 启动,根据前面的理论分析,服务器 3 成为服务器 1 、 2 、 3中的老大,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的 Leader 。( 4 )服务器 4 启动,根据前面的分析,理论上服务器 4 应该是服务器 1 、 2 、 3 、 4中最大的,但是由于前面已经有半数以上的服务器选举了服务器 3 ,所以它只能接收当小弟的命了。( 5 )服务器 5 启动,同 4 一样称为 follower 。
每个节点在选举时都会参考自身节点的zxid值(事务ID);优先选择zxid值大的节点称为Leader!!
5.2 ZAB一致性协议
为什么会出现分布式数据一致性问题?
- 将数据复制到分布式部署的多台机器中,可以消除单点故障,防止系统由于某台(些)机器宕机导致的不可用。
-
通过负载均衡技术,能够让分布在不同地方的数据副本全都对外提供服务。有效提高系统性能。
当客户端Client1将系统中的一个值K1由V1更新为V2,但是客户端Client2读取的是一个还没有同步更新的副本,K1的值依然是V1,这就导致了数据的不一致性。其中,常见的就是主从数据库之间的复制延时问题。
ZK就是分布式一致性问题的工业解决方案,paxos是其底层理论算法(晦涩难懂著名),其中zab,raft和众多开源算法是对paxos的工业级实现。ZK没有完全采用paxos算法,而是使用了一种称为Zookeeper Atomic Broadcast(ZAB,Zookeeper原子消息广播协议)的协议作为其数据一致性的核心算法。
ZAB 协议是为分布式协调服务 Zookeeper 专门设计的一种支持崩溃恢复和原子广播协议。
主备模式保证一致性
ZK怎么处理集群中的数据?
所有客户端写入数据都是写入Leader中,然后,由 Leader 复制到 Follower中。ZAB会将服务器数据的状态变更以事务Proposal的形式广播到所有的副本进程上,ZAB协议能够保证了事务操作的一个全局的变更序号(ZXID)。
广播消息
ZAB 协议的消息广播过程类似于 二阶段提交过程。对于客户端发送的写请求,全部由 Leader 接收,Leader 将请求封装成一个事务 Proposal(提议),将其发送给所有 Follwer ,如果收到超过半数反馈ACK,则执行 Commit 操作(先提交自己,再发送 Commit 给所有 Follwer)。
1. 发送Proposal到Follower
2. Leader接收Follower的ACK
3. 超过半数ACK则Commit
不能正常反馈Follower恢复正常后会进入数据同步阶段最终与Leader保持一致!!
- Leader接收到Client请求之后,会将这个请求封装成一个事务,并给这个事务分配一个全局递增的唯一 ID,称为事务ID(ZXID),ZAB 协议要求保证事务的顺序,因此必须将每一个事务按照 ZXID进行先后排序然后处理。
- ZK集群为了保证任何事务操作能够有序的顺序执行,只能是 Leader 服务器接受写请求,即使是Follower 服务器接受到客户端的请求,也会转发到 Leader 服务器进行处理。
- zk提供的应该是最终一致性的标准。zk所有节点接收写请求之后可以在一定时间内保证所有节点都能看到该条数据!!
- Leader宕机后,ZK集群无法正常工作,ZAB协议提供了一个高效且可靠的leader选举算法。
- Leader宕机后,被选举的新Leader需要解决的问题
- ZAB 协议确保那些已经在 Leader 提交的事务最终会被所有服务器提交。
- ZAB 协议确保丢弃那些只在 Leader 提出/复制,但没有提交的事务。
- 基于上面的目的,ZAB协议设计了一个选举算法:能够确保已经被Leader提交的事务被集群接受,丢弃还没有提交的事务。
- 这个选举算法的关键点:保证选举出的新Leader拥有集群中所有节点最大编号(ZXID)的事务!!
6. Zookeeper应用实践
ZooKeeper是一个典型的发布/订阅模式的分布式数据管理与协调框架,我们可以使用它来进行分布式数据的发布与订阅。另一方面,通过对ZooKeeper中丰富的数据节点类型进行交叉使用,配合 Watcher事件通知机制,可以非常方便地构建一系列分布式应用中都会涉及的核心功能,如数据发布/订 阅、命名服务、集群管理、Master选举、分布式锁和分布式队列等。那接下来就针对这些典型的分布式应用场景来做下介绍
1.客户端如果对Zookeeper的数据节点注册Watcher监听,那么当该数据节点的内容或是其子节点 列表发生变更时,Zookeeper服务器就会向订阅的客户端发送变更通知。
2.对在Zookeeper上创建的临时节点,一旦客户端与服务器之间的会话失效,那么临时节点也会被 自动删除
6.1 服务器动态上下线监听
分布式系统中,主节点会有多台,主节点可能因为任何原因出现宕机或者下线,而任意一台客户端都要能实时感知到主节点服务器的上下线。
public class ServerMain {
private ZkClient zkClient;
/*获取到zk对象*/
private void connectZk(){
zkClient = new ZkClient("test1.bigload.com:2181,test2.bigload.com:2181");
/*如果不存在/servers 就创建*/
if(!zkClient.exists("/servers")){
zkClient.createPersistent("/servers");
}
}
/*注册服务端信息到zk节点*/
private void registerServerInfo(String ip,String port){
/*创建临时顺序节点*/
zkClient.createEphemeralSequential("/servers/server",ip+":"+port);
System.out.println("服务器注册成功===》"+ip+":"+port);
}
public static void main(String[] args) {
ServerMain server = new ServerMain();
server.connectZk();
server.registerServerInfo(args[0],args[1]);
/*启动一个服务线程提供时间查询*/
new TimeServer(Integer.parseInt(args[1])).start();
}
}
public class TimeServer extends Thread {
private int port;
public TimeServer(int port) {
this.port = port;
}
@Override
public void run() {
/*启动serversocket监听一个端口*/
try {
ServerSocket serverSocket = new ServerSocket(port);
while (true){
Socket socket = serverSocket.accept();
OutputStream out = socket.getOutputStream();
out.write(new Date().toString().getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
client端
public class client {
ZkClient zkClient=null;
/*维护一个servers信息集合*/
ArrayList<String> infos=new ArrayList<String>();
private void connectZk(){
zkClient = new ZkClient("test1.bigload.com:2181,test2.bigload.com:2181");
/*第一次获取服务器信息,所有的子节点*/
List<String> children = zkClient.getChildren("/servers");
/*存储数据*/
for (String child : children) {
Object o = zkClient.readData("/servers/" + child);
infos.add(String.valueOf(o));
}
/*队server目录进行监听*/
zkClient.subscribeChildChanges("/servers", new IZkChildListener() {
@Override
public void handleChildChange(String s, List<String> list) throws Exception {
/*变化的话就更新infos中的数据*/
ArrayList<String> childs=new ArrayList<String>();
for (String child : list) {
Object o = zkClient.readData("/servers/" + child);
childs.add(String.valueOf(o));
}
infos=childs;
System.out.println("数据更新:"+infos);
}
});
}
/*发送查询时间请求*/
public void sendRequest() throws IOException {
//目标服务器地址
Random random = new Random();
String ipport = infos.get(random.nextInt(infos.size()));
String[] arr = ipport.split(":");
//建立socket连接
Socket socket = new Socket(arr[0], Integer.parseInt(arr[1]));
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
/*发送数据*/
outputStream.write("query time".getBytes());
outputStream.flush();
/*接受返回结果*/
byte[] b=new byte[1024];
inputStream.read(b);
System.out.println("client接收到server:"+ipport+",返回结果:"+new String(b));
/*释放资源*/
inputStream.close();
outputStream.close();
socket.close();
}
public static void main(String[] args) throws IOException, InterruptedException {
client client = new client();
client.connectZk();//监听
while (true){
client.sendRequest();//发送请求
Thread.sleep(2000);
}
}
}
6.2 分布式锁
- 在单机程序中,当存在多个线程可以同时改变某个变量(可变共享变量)时,为了保证线程安全 (数据不能出现脏数据)就需要对变量或代码块做同步,使其在修改这种变量时能够串行执行消除并发修改变量。
- 对变量或者堆代码码块做同步本质上就是加锁。目的就是实现多个线程在一个时刻同一个代码块只能有一个线程可执行
用锁把 2、3、4 步锁住,让他们执行完之后,另一个线程才能进来执行。
公司业务发展迅速,系统应对并发不断提高,解决方案是要增加一台机器,结果会出现更大的问题
利用Zookeeper可以创建临时带序号节点的特性来实现一个分布式锁
实现思路
- 锁就是zk指定目录下序号最小的临时序列节点,多个系统的多个线程都要在此目录下创建临时的顺序节点,因为Zk会为我们保证节点的顺序性,所以可以利用节点的顺序进行锁的判断。
- 每个线程都是先创建临时顺序节点,然后获取当前目录下最小的节点(序号),判断最小节点是不是当前节点,如果是那么获取锁成功,如果不是那么获取锁失败。
- 获取锁失败的线程获取当前节点上一个临时顺序节点,并对对此节点进行监听,当该节点删除的时候(上一个线程执行结束删除或者是掉线zk删除临时节点)这个线程会获取到通知,代表获取到了锁。
流程图
分布式锁的实现可以是 Redis、Zookeeper,相对来说生产环境如果使用分布式锁可以考虑使用Redis实现而非Zk。
7 Hadoop HA
7.1 HA 概述
1. 所谓HA(High Available),即高可用(7*24小时不中断服务)。
2. 实现高可用最关键的策略是消除单点故障。Hadoop-HA严格来说应该分成各个组件的HA机制: HDFS的HA和YARN的HA。
3. Hadoop2.0之前,在HDFS集群中NameNode存在单点故障(SPOF)。
4. NameNode主要在以下两个方面影响HDFS集群
NameNode机器发生意外,如宕机,集群将无法使用,直到管理员重启
NameNode机器需要升级,包括软件、硬件升级,此时集群也将无法使用
7.2 HDFS-HA 工作机制
通过双NameNode消除单点故障(Active/Standby)
7.2.1 HDFS-HA工作要点
- 内存中各自保存一份元数据;
- Edits日志只有Active状态的NameNode节点可以做写操作;
- 两个NameNode都可以读取Edits;
- 共享的Edits放在一个共享存储中管理(qjournal和NFS两个主流实现);
实现了一个zkfailover,常驻在每一个namenode所在的节点,每一个zkfailover负责监控自己所在NameNode节点,利用zk进行状态标识,当需要进行状态切换时,由zkfailover来负责切换,切换时需要防止brain split现象的发生(集群中出现两个Active的Namenode)。
7.2.2 HDFS-HA工作机制
配置部署HDFS-HA进行自动故障转移。自动故障转移为HDFS部署增加了两个新组件:ZooKeeper和 ZKFailoverController(ZKFC)进程,ZooKeeper是维护少量协调数据,通知客户端这些数据的改变和 监视客户端故障的高可用服务。HA的自动故障转移依赖于ZooKeeper的以下功能:
- 故障检测
集群中的每个NameNode在ZooKeeper中维护了一个临时会话,如果机器崩溃,ZooKeeper中的会话将终止,ZooKeeper通知另一个NameNode需要触发故障转移。
现役NameNode选择ZooKeeper提供了一个简单的机制用于唯一的选择一个节点为active状态。如果目前现役NameNode崩溃,另一个节点可能从ZooKeeper获得特殊的排外锁以表明它应该成为现役NameNode。
ZKFC是自动故障转移中的另一个新组件,是ZooKeeper的客户端,也监视和管理NameNode的状态。
每个运行NameNode的主机也运行了一个ZKFC进程,ZKFC负责:
- 健康监测
ZKFC使用一个健康检查命令定期地ping与之在相同主机的NameNode,只要该NameNode及时地回复健康状态,ZKFC认为该节点是健康的。如果该节点崩溃,冻结或进入不健康状态,健康监测器标识该节点为非健康的。
- ZooKeeper会话管理
当本地NameNode是健康的,ZKFC保持一个在ZooKeeper中打开的会话。如果本地NameNode 处于active状态,ZKFC也保持一个特殊的znode锁,该锁使用了ZooKeeper对短暂节点的支持,如果会话终止,锁节点将自动删除。
- 基于ZooKeeper的选择
如果本地NameNode是健康的,且ZKFC发现没有其它的节点当前持有znode锁,它将为自己获取该锁。如果成功,则它已经赢得了选择,并负责运行故障转移进程以使它的本地NameNode为 Active。故障转移进程与前面描述的手动故障转移相似,首先如果必要保护之前的现役NameNode,然后本地NameNode转换为Active状态。
7.3 HDFS-HA集群配置
https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HDFSHighAvailabilityWithQJM.html
7.3.1 环境准备
1. 修改 IP2. 修改主机名及主机名和 IP 地址的映射3. 关闭防火墙4. ssh 免密登录5. 安装 JDK ,配置环境变量等
7.3.2 集群规划
7.3.3 启动Zookeeper集群
启动 zookeeper 集群zk.sh start查看状态zk.sh status
7.3.4 配置HDFS-HA集群
1. 停止原先HDFS集群
stop-dfs.sh
2. 在所有节点,/opt/lagou/servers目录下创建一个ha文件夹
cd /opt/lagou/servers/
mkdir ha
3. 将/opt/lagou/servers/目录下的 hadoop-2.9.2拷贝到ha目录下
cp -r hadoop-2.9.2/ ha
4. 删除原集群data目录
cd ha/hadoop-2.9.2/
rm -rf data/
5. 配置hdfs-site.xml
<property><name>dfs.nameservices</name><value>lagoucluster</value></property><property><name>dfs.ha.namenodes.lagoucluster</name><value>nn1,nn2</value></property><property><name>dfs.namenode.rpc-address.lagoucluster.nn1</name><value>linux121 : 9000</value></property><property><name>dfs.namenode.rpc-address.lagoucluster.nn2</name><value>linux122 : 9000</value></property><property><name>dfs.namenode.http-address.lagoucluster.nn1</name><value>linux121 : 50070</value></property><property><name>dfs.namenode.http-address.lagoucluster.nn2</name><value>linux122 : 50070</value></property><property><name>dfs.namenode.shared.edits.dir</name><value>qjournal : //linux121 : 8485;linux122 : 8485;linux123 : 8485/lagou</value></property><property><name>dfs.client.failover.proxy.provider.lagoucluster</name><value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value></property><property><name>dfs.ha.fencing.methods</name><value>sshfence</value></property><property><name>dfs.ha.fencing.ssh.private-key-files</name><value>/root/.ssh/id_rsa</value></property><property><name>dfs.journalnode.edits.dir</name><value>/opt/journalnode</value></property><property><name>dfs.ha.automatic-failover.enabled</name><value>true</value></property>
6. 配置core-site.xml
<property><name>fs.defaultFS</name><value>hdfs : //lagoucluster</value></property><property><name>hadoop.tmp.dir</name><value>/opt/lagou/servers/ha/hadoop-2.9.2/data/tmp</value></property><property><name>ha.zookeeper.quorum</name><value>linux121 : 2181,linux122 : 2181,linux123 : 2181</value></property>
rsync-script /opt/lagou/servers/ha/hadoop-2.9.2/
7.3.5 启动HDFS-HA集群
/opt/lagou/servers/ha/hadoop-2.9.2/sbin/hadoop-daemon.sh start journalnode
/opt/lagou/servers/ha/hadoop-2.9.2/bin/hdfs namenode -format/opt/lagou/servers/ha/hadoop-2.9.2/sbin/hadoop-daemon.sh start namenode
/opt/lagou/servers/ha/hadoop-2.9.2/bin/hdfs namenode -bootstrapStandby
/opt/lagou/servers/ha/hadoop-2.9.2/bin/hdfs zkfc -formatZK
/opt/lagou/servers/ha/hadoop-2.9.2/sbin/start-dfs.sh
将 Active NameNode 进程 killkill -9 namenode 的进程 id
7.4 YARN-HA配置
7.4.1 YARN-HA工作机制
https://hadoop.apache.org/docs/stable/hadoop-yarn/hadoop-yarn-site/ResourceManagerHA.html
修改 IP修改主机名及主机名和 IP 地址的映射关闭防火墙ssh 免密登录安装 JDK ,配置环境变量等配置 Zookeeper 集群
<property><name>yarn.nodemanager.aux-services</name><value>mapreduce_shuffle</value></property><!-- 启用 resourcemanager ha--><property><name>yarn.resourcemanager.ha.enabled</name><value>true</value></property><!-- 声明两台 resourcemanager 的地址 --><property><name>yarn.resourcemanager.cluster-id</name><value>cluster-yarn</value></property><property><name>yarn.resourcemanager.ha.rm-ids</name><value>rm1,rm2</value></property><property><name>yarn.resourcemanager.hostname.rm1</name><value>linux122</value></property><property><name>yarn.resourcemanager.hostname.rm2</name><value>linux123</value></property><!-- 指定 zookeeper 集群的地址 --><property><name>yarn.resourcemanager.zk-address</name><value>linux121 : 2181,linux122 : 2181,linux123 : 2181</value></property><!-- 启用自动恢复 --><property><name>yarn.resourcemanager.recovery.enabled</name><value>true</value></property><!-- 指定 resourcemanager 的状态信息存储在 zookeeper 集群 --><property><name>yarn.resourcemanager.store.class</name><value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value></property>
rsync-script yarn-site.xml
在linux123上sbin/start-yarn.sh在linux122上sbin/yarn-daemon.sh start resourcemanager