zookeeper( 转)
转自:http://qindongliang.iteye.com/category/299318
分布式助手Zookeeper(一)
Zookeeper最早是Hadoop的一个子项目,主要为Hadoop生态系统中一些列组件提供统一的分布式协作服务,在2010年10月升级成Apache Software
Foundation(ASF)顶级项目,它主要提供以下的四个功能:
功能名 |
组管理服务 |
分布式配置服务 |
分布式同步服务 |
分布式命名服务 |
Zookeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户;
Zookeeper的架构图如下:
Zookeeper的特点如下:
特点 | 说明 |
最终一致性 | 为客户端展示同一个视图,这是zookeeper里面一个非常重要的功能 |
可靠性 | 如果消息被到一台服务器接受,那么它将被所有的服务器接受。 |
实时性 | Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。 |
独立性 | 各个Client之间互不干预 |
原子性 | 更新只能成功或者失败,没有中间状态。 |
顺序性 | 所有Server,同一消息发布顺序一致。 |
zookeeper的工作原理,
1.每个Server在内存中存储了一份数据;
2.Zookeeper启动时,将从实例中选举一个leader(Paxos协议)
3.Leader负责处理数据更新等操作(Zab协议);
4.一个更新操作成功,当且仅当大多数Server在内存中成功修改数据。
zookeeper中的几个重要角色:
角色名 | 描述 |
领导者(Leader) | 领导者负责进行投票的发起和决议,更新系统状态,处理写请求 |
跟随者(Follwer) | Follower用于接收客户端的读写请求并向客户端返回结果,在选主过程中参与投票 |
观察者(Observer) | 观察者可以接收客户端的读写请求,并将写请求转发给Leader,但Observer节点不参与投票过程,只同步leader状态,Observer的目的是为了,扩展系统,提高读取速度。 |
客户端(Client) | 执行读写请求的发起方 |
为什么,在3.3.0版本之后,引入Observer角色?
Zookeeper需保证高可用和强一致性;
为了支持更多的客户端,需要增加更多Server;
Server增多,投票阶段延迟增大,影响性能;
权衡伸缩性和高吞吐率,引入Observer
Observer不参与投票;
Observers接受客户端的连接,并将写请求转发给leader节点;
加入更多Observer节点,提高伸缩性,同时不影响吞吐率。
为什么zookeeper集群的数目,一般为奇数个?
Leader选举算法采用了Paxos协议;
Paxos核心思想:当多数Server写成功,则任务数据写成功
如果有3个Server,则两个写成功即可;
如果有4或5个Server,则三个写成功即可。
Server数目一般为奇数(3、5、7)
如果有3个Server,则最多允许1个Server挂掉;
如果有4个Server,则同样最多允许1个Server挂掉
由此,我们看出3台服务器和4台服务器的的容灾能力是一样的,所以
为了节省服务器资源,一般我们采用奇数个数,作为服务器部署个数。
zookeeper的数据模型:
基于树形结构的命名空间,与文件系统类似
节点(znode)都可以存数据,可以有子节点
节点不支持重命名
数据大小不超过1MB(可配置)
数据读写要保证完整性
层次化的目录结构,命名符合常规文件系统规范;
每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识;
节点Znode可以包含数据和子节点(EPHEMERAL类型的节点不能有子节点);
Znode中的数据可以有多个版本,比如某一个路径下存有多个数据版本,那么查询这个路径下的数据需带上版本;
客户端应用可以在节点上设置监视器(Watcher);
节点不支持部分读写,而是一次性完整读写。
Znode有两种类型,短暂的(ephemeral)和持久的(persistent);
Znode的类型在创建时确定并且之后不能再修改;
短暂znode的客户端会话结束时,zookeeper会将该短暂znode删除,短暂znode不可以有子节点;
持久znode不依赖于客户端会话,只有当客户端明确要删除该持久znode时才会被删除;
Znode有四种形式的目录节点,PERSISTENT、PERSISTENT_SEQUENTIAL、EPHEMERAL、EPHEMERAL_SEQUENTIAL。
Zookeeper的应用场景一(统一命名服务)
分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务;
类似于域名与ip之间对应关系,域名容易记住;
通过名称来获取资源或服务的地址,提供者等信息
按照层次结构组织服务/应用名称
可将服务名称以及地址信息写到Zookeeper上,客户端通过Zookeeper获取可用服务列表类
Zookeeper的应用场景二(配置管理)
分布式环境下,配置文件管理和同步是一个常见问题;
一个集群中,所有节点的配置信息是一致的,比如Hadoop;
对配置文件修改后,希望能够快速同步到各个节点上
配置管理可交由Zookeeper实现;
可将配置信息写入Zookeeper的一个znode上;
各个节点监听这个znode
一旦znode中的数据被修改,zookeeper将通知各个节点
Zookeeper的应用场景三(集群管理)
分布式环境中,实时掌握每个节点的状态是必要的;
可根据节点实时状态作出一些调整;
可交由Zookeeper实现;
可将节点信息写入Zookeeper的一个znode上;
监听这个znode可获取它的实时状态变化
典型应用
Hbase中Master状态监控与选举
Zookeeper的应用场景四(分布式通知和协调)
分布式环境中,经常存在一个服务需要知道它所管理的子服务的状态;
NameNode须知道各DataNode的状态
JobTracker须知道各TaskTracker的状态
心跳检测机制可通过Zookeeper实现;
信息推送可由Zookeeper实现(发布/订阅模式)
Zookeeper的应用场景五(分布式锁)
Zookeeper是强一致的;
多个客户端同时在Zookeeper上创建相同znode,只有一个创建成功。
实现锁的独占性
多个客户端同时在Zookeeper上创建相同znode ,创建成功的那个客户端得到锁,其他客户端等待。
控制锁的时序
各个客户端在某个znode下创建临时znode (类型为CreateMode.EPHEMERAL_SEQUENTIAL),这样,该znode可掌握全局访问时序。
Zookeeper的应用场景六(分布式队列)
两种队列;
当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。
队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。(可通过分布式锁实现)
同步队列
一个job由多个task组成,只有所有任务完成后,job才运行完成。
可为job创建一个/job目录,然后在该目录下,为每个完成的task创建一个临时znode,一旦临时节点数目达到task总数,则job运行完成。
分布式助手Zookeeper(二)
zookeeper的安装模式也有三种,分别是单机模式,伪分布模式,和完全分布式模式,
本篇,散仙要介绍的是完全分布式模式。
我们需要先下载好zookeeper的安装包,然后解压后,配置zookeeper,修改其config目录下zoo_simple.cfg重命名为zoo.cfg,并在其data目录(自己手动创建)下,新建一个myid文件,server.x后面的x数字一直即可,修改其内容如下:
- //tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 毫秒时间就会发送一个心跳。
- tickTime=2000
- //initLimit:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。
- //当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 10*2000=20 秒。
- initLimit=10
- //syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 5*2000=10 秒
- syncLimit=5
- //dataLogDir:日志文件保存的位置
- dataDir=/root/zookeeper/data
- //客户端的端口号
- clientPort=2181
- //server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。
- //如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。
- server.1=10.2.143.5:2887:3887
- server.2=10.2.143.36:2888:3888
- server.3=10.2.143.37:2889:3889
注意,如上的配置是在3台节点非观察者角色的配置,下面我们来看下,观察者角色的配置内容:
- //tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 毫秒时间就会发送一个心跳。
- tickTime=2000
- //initLimit:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。
- //当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 10*2000=20 秒。
- initLimit=10
- //syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 5*2000=10 秒
- syncLimit=5
- //dataLogDir:日志文件保存的位置
- dataDir=/root/zookeeper/data
- //客户端的端口号
- clientPort=2181
- //server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。
- //如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。
- //此处声明表示作为一个观察者角色存在
- peerType=observer
- server.1=10.2.143.5:2887:3887
- server.2=10.2.143.36:2888:3888
- server.3=10.2.143.37:2889:3889
- //注意观察者角色的末尾,需要拼接上observer
- server.4=10.2.143.38:2886:3886:observer
通过,以上信息,我们发现,观察者角色的配置还是比较简单的,只在原来的配置中增加了,两处改动,然后我们就可可以启动集群信息,通过查看节点状态,我们可以发现观察者角色的状态为observer,观察者角色,并不会参入投票过程,所以在高并发的情况下,大大的增强了系统的可扩展性和吞吐率。另外需要注意的在启动zookeeper集群的时候,需要把系统的防火墙给关闭,除非你已经对外开放了zookeeper的客户端端口号,否则将有可能导致启动失败。
分布式助手Zookeeper(三)
Zookeeper的Session:
(1)客户端和server间采用长连接
(2)连接建立后,server产生session ID(64位)返还给客户端
(3)客户端定期发送ping包来检查和保持和server的连接
(4)一旦session结束或超时,所有ephemeral节点会被删除
(5)客户端可根据情况设置合适的session超时时间
Zookeeper的Watchs:
Watch是客户端安装在server的事件侦听方法
(1) 当侦听的变化发生时,server发消息给客户端进行通知
(2) 客户端使用单线程对所有事件按顺序同步回调
(3) 触发回调条件:
• 客户端连接、断开连接
• 节点数据发生改变
• 节点本身发生变化
(4)Watch是单发的,每次触发后会被自动删除
(5)如果需要再次侦听事件,必须重新安装watch
(6)无法保证跟踪到每一个变化
(7)避免安装大量watches侦听在同一个节点
Zookeeper的一些注意事项:
在客户端事件回调实现有阻塞调用
• 试图跟踪每个状态变化
• 大量watch侦听同一个znode的状态变化
• 客户端会有需要长时间处理的GC(garbage collection)
• Session超时后上层应用不进行恢复处理
可以把zookper看成一个文件系统,文件系统中的所有文件形成一个数状结构,zookeeper维护着这样的树形层次结构,树中的节点称为znode。每个znode有一个与之相关联的ACL(Access Control List)
znode通过路径被引用,而且要采用绝对路径,即必须以/开头。znode存储的数据要小于1M,这个可以配置,建议不要存储太大的东西,避免同步操作时间过长。
znode类型
短暂znode:回话结束,zookeeper就会把短暂znode删除,短暂znode不可以有子节点。
持久znode:回话结束也不会被删除,除非客户端明确要删除此znode,持久znode可以有子节点。
对于在特定时刻需要知道有哪些分布式资源可用的应用来说,使用短暂znode比较合适。
znode的观察机制
znode以某种方式发生变化时,“观察”(watch)机制可以让客户端得到通知。可以针对ZooKeeper服务的“操作”来设置观察,该服务的其他操作可以触发观察。比如,客户端可以对某个客户端调用exists操作,同时在它上面设置一个观察,如果此时这个znode不存在,则exists返回false,如果一段时间之后,这个znode被其他客户端创建,则这个观察会被触发,之前的那个客户端就会得到通知。
zookeeper的一些基本操作如下:
操作 | 描述 |
create | 创建一个znode(必须有父节点) |
delete | 删除一个znode(该znode不能有任何子节点) |
exists | 测试一个znode是否存在,并且查询它的元数据 |
getACL,setACL | 获取/设置一个znode的ACL |
getChildren | 获取一个znode的子节点名字列表 |
getData,setData | 获取/设置一个znode所保存的数据 |
sync | 将客户端的znode视图与ZooKeeper服务端同步 |
Zookeeper中的更新操作是有条件的。在使用delete或者setData操作时必须提供被更新znode的版本号,如果版本号不匹配,则更新操作失败。一般情况下设置-1即可。
API
目前主要有java和C两种客户端,每种操作都有同步和异步两种执行方式。
观察触发器
可以设置观察的操作:exists,getChildren,getData
可以触发观察的操作:create,delete,setData
下面给出一些事件截图:
NodeCreated:节点创建事件
NodeDeleted:节点被删除事件
NodeDataChanged:节点数据改变事件
NodeChildrenChanged:节点的子节点改变事件
下面我们再来看下ACL(zookeeper的访问控制列表),每个znode被创建时都会带有一个ACL列表,用于决定谁可以对它执行何种操作。
ACL权限 | 允许的操作 |
CREATE | 创建节点create("name") |
READ | getChildren() getData() |
WRITE | setData |
DELETE | delete("name")删除节点 |
ADMIN | setACL()设置权限 |
每个ACL都是身份验证模式、符合该模式的一个身份和一组权限的组合。身份验证模式有三种:
digest:用户名,密码
host:通过客户端的主机名来识别客户端
ip: 通过客户端的ip来识别客户端
所以我们可以类似这样构建一个ACL类:
new ACL(Perms.READ,new Id("host","example.com"));
这个ACL对应的身份验证模式是host
符合该模式的身份是example.com
权限的组合是:READ
下面给出一个API连接zookeeper的示例:
- package com.util;
- import java.util.concurrent.CountDownLatch;
- import org.apache.zookeeper.WatchedEvent;
- import org.apache.zookeeper.Watcher;
- import org.apache.zookeeper.ZooKeeper;
- /**
- * 测试zookeeper的连接
- * @author 三劫散仙
- *
- * ***/
- public class Test {
- public static void main(String[] args)throws Exception {
- ZooKeeper zk=new ZooKeeper("10.2.143.5:2181", 5000, new Watcher() {
- CountDownLatch down=new CountDownLatch(1);//同步阻塞状态
- @Override
- public void process(WatchedEvent event) {
- if(event.getState()==Event.KeeperState.SyncConnected){
- down.countDown();//连接上之后,释放计数器
- }
- }
- });
- System.out.println("连接成功:"+zk.toString());
- zk.close();//关闭连接
- }
- }
打印效果如下:
- 连接成功:State:CONNECTING sessionid:0x0 local:null remoteserver:null lastZxid:0 xid:1 sent:0 recv:0 queuedpkts:0 pendingresp:0 queuedevents:0
分布式助手Zookeeper(四)
Zookeeper是分布式环境下一个重要的组件,因为它能在分布式环境下,给我带来很多便利,大大简化了分布式编程的复杂性,本篇散仙将给出一个模拟例子,来演示下如何使用Zookeeper的API编程,来完成分布式环境下配置的同步。大家都知道在一个中大型的规模的集群中,配置文件通常是必不可少的的东西,很多时候,我都需要将在Master上配置好的配置文件,给分发到各个Slave上,以确保整体配置的一致性,在集群规模小的时候我们可能简单的使用远程拷贝或复制即可完成,但是,当集群规模越来越大的时候,我们发现这种方式不仅繁琐,而且容易出错,最要命的是,以后如果改动配置文件的很少一部分的东西,都得需要把所有的配置文件,给重新远程拷贝覆盖一次,那么,怎样才能避免这种牵一发而动全身的事情呢?
事实上,利用Zookeeper,就能够很容易的,高可靠的帮我们完成这件事,我们只需要把配置文件保存在Zookeeper的znode里,然后通过Watch来监听数据变化,进而帮我们实现同步。一个简单的工作图如下所示:
总结流程如下:
序号 | 实现 |
1 | 启动ZK集群 |
2 | 客户端在ZK创建一个znode,并写入数据 |
3 | 启动各个Server上的Watcher,无限休眠 |
4 | 客户端更新znode里数据 |
5 | Watcher的read方法发现数据更新,下拉至本地,更新本地数据 |
代码如下:
- package com.sanjiesanxian;
- import java.util.concurrent.CountDownLatch;
- import org.apache.zookeeper.CreateMode;
- import org.apache.zookeeper.WatchedEvent;
- import org.apache.zookeeper.Watcher;
- import org.apache.zookeeper.ZooKeeper;
- import org.apache.zookeeper.ZooDefs.Ids;
- import org.apache.zookeeper.data.Stat;
- /***
- * Zookeeper实现分布式配置同步
- *
- * @author 秦东亮
- *
- * ***/
- public class SyscConfig implements Watcher{
- //Zookeeper实例
- private ZooKeeper zk;
- private CountDownLatch countDown=new CountDownLatch(1);//同步工具
- private static final int TIMIOUT=5000;//超时时间
- private static final String PATH="/sanxian";
- public SyscConfig(String hosts) {
- try{
- zk=new ZooKeeper(hosts, TIMIOUT, new Watcher() {
- @Override
- public void process(WatchedEvent event) {
- if(event.getState().SyncConnected==Event.KeeperState.SyncConnected){
- //防止在未连接Zookeeper服务器前,执行相关的CURD操作
- countDown.countDown();//连接初始化,完成,清空计数器
- }
- }
- });
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- /***
- * 写入或更新
- * 数据
- * @param path 写入路径
- * @param value 写入的值
- * **/
- public void addOrUpdateData(String path,String data)throws Exception {
- Stat stat=zk.exists(path, false);
- if(stat==null){
- //没有就创建,并写入
- zk.create(path, data.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
- System.out.println("新建,并写入数据成功.. ");
- }else{
- //存在,就更新
- zk.setData(path, data.getBytes(), -1);
- System.out.println("更新成功!");
- }
- }
- /**
- * 读取数据
- * @param path 读取的路径
- * @return 读取数据的内容
- *
- * **/
- public String readData()throws Exception{
- String s=new String(zk.getData(PATH, this, null));
- return s;
- }
- /**
- * 关闭zookeeper连接
- * 释放资源
- *
- * **/
- public void close(){
- try{
- zk.close();
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- public static void main(String[] args)throws Exception {
- SyscConfig conf=new SyscConfig("10.2.143.5:2181");
- conf.addOrUpdateData(PATH, "修真天劫,九死一生。");
- conf.addOrUpdateData(PATH, "圣人之下,皆为蝼蚁,就算再大的蝼蚁,还是蝼蚁.");
- conf.addOrUpdateData(PATH, "努力奋斗,实力才是王道! ");
- //System.out.println("监听器开始监听........");
- // conf.readData();
- // Thread.sleep(Long.MAX_VALUE);
- //conf.readData();
- conf.close();
- }
- @Override
- public void process(WatchedEvent event){
- try{
- if(event.getType()==Event.EventType.NodeDataChanged){
- System.out.println("变化数据: "+readData());
- }
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- }
模拟客户端输出如下:
- //客户端监听代码
- SyscConfig conf=new SyscConfig("10.2.143.5:2181");
- conf.addOrUpdateData(PATH, "修真天劫,九死一生。");
- conf.addOrUpdateData(PATH, "圣人之下,皆为蝼蚁,就算再大的蝼蚁,还是蝼蚁.");
- conf.addOrUpdateData(PATH, "努力奋斗,实力才是王道! ");
- //System.out.println("监听器开始监听........");
- // conf.readData();
- // Thread.sleep(Long.MAX_VALUE);
- //conf.readData();
- conf.close();
模拟服务端输出如下:
- public static void main(String[] args)throws Exception {
- //服务端监听代码
- SyscConfig conf=new SyscConfig("10.2.143.36:2181");
- //conf.addOrUpdateData(PATH, "");
- System.out.println("模拟服务监听器开始监听........");
- conf.readData();
- Thread.sleep(Long.MAX_VALUE);
- conf.close();
- }
至此,使用zookeeper来完成配置同步的服务就完成了,我们可以发现,使用zookeeper来编写分布式程序是非常简单可靠的。
分布式助手Zookeeper(五)
单点故障问题,在分布式系统中是一个很有可能发生的场景,比如说在Hadoop2.x之前的HDFS的NameNode和MapReduce的JobTracker的单点故障,当然这个问题已经在Hadoop2.x中得到解决,解决的方式,大部分是基于Zookeeper来实现的。另外一个例子,在Hbase中的Hmaster的单点问题,也是使用Zookeeper解决的。
下面,我们先来看下,简单的实现图:
总结流程如下:
序号 | 描述 |
1 | 创捷父节点类型为Persistent |
2 | 创捷子节点类型为ephemeral + sequential |
3 | 客户端启动时创建子节点 |
4 | 序列号最小的子节点选为master,其他子节点都是slave |
5 | 每个slave侦听序列号比它小的子节点中最大的子节点的NodeDeleted事件 |
6 | 一旦NodeDeleted事件被触发,该slave客户端会重新选定侦听对象,如果不存在可侦听对象,该slave自动晋升成master |
代码,如下:
- package com.automicswitch;
- import java.nio.charset.Charset;
- import java.nio.charset.StandardCharsets;
- import java.text.SimpleDateFormat;
- import java.util.Collections;
- import java.util.Date;
- import java.util.List;
- import java.util.concurrent.CountDownLatch;
- import org.apache.zookeeper.CreateMode;
- import org.apache.zookeeper.WatchedEvent;
- import org.apache.zookeeper.Watcher;
- import org.apache.zookeeper.ZooDefs.Ids;
- import org.apache.zookeeper.ZooKeeper;
- import org.apache.zookeeper.data.Stat;
- import com.util.ConnectionWatcher;
- /**
- * 模拟Zookeeper实现单点故障
- * 自动切换
- * @author 秦东亮
- *
- * ***/
- public class Slave implements Watcher{
- /**
- * zk实例
- * **/
- public ZooKeeper zk;
- /**
- * 同步工具
- *
- * **/
- private CountDownLatch count=new CountDownLatch(1);
- private static final Charset CHARSET=StandardCharsets.UTF_8;
- public Slave() {
- // TODO Auto-generated constructor stub
- }
- /**
- * hosts,
- * zookeeper的访问地址
- *
- * **/
- public Slave(String hosts) {
- try{
- zk=new ZooKeeper(hosts, 7000, new Watcher() {
- @Override
- public void process(WatchedEvent event) {
- // TODO Auto-generated method stub
- if(event.getState()==Event.KeeperState.SyncConnected){
- count.countDown();
- }
- }
- });
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- /***
- *
- * 此方法是写入数据
- * 如果不存在此节点
- * 就会新建,已存在就是
- * 更新
- *
- * **/
- public void write(String path,String value)throws Exception{
- Stat stat=zk.exists(path, false);
- if(stat==null){
- zk.create(path, value.getBytes(CHARSET), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
- }else{
- zk.setData(path, value.getBytes(CHARSET), -1);
- }
- }
- public String read(String path,Watcher watch)throws Exception{
- byte[] data=zk.getData(path, watch, null);
- return new String(data,CHARSET);
- }
- SimpleDateFormat f=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- public void automicSwitch()throws Exception{
- System.out.println("Master故障,Slave自动切换......., 时间 "+f.format(new Date()));
- }
- public void startMaster(){
- System.out.println("A的Master 启动了........");
- }
- public void createPersist()throws Exception{
- zk.create("/a", "主节点".getBytes(), Ids.OPEN_ACL_UNSAFE , CreateMode.PERSISTENT);
- System.out.println("创建主节点成功........");
- }
- public void createTemp()throws Exception{
- zk.create("/a/b", "a".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
- System.out.println("a创建子节点成功...........");
- }
- public void check()throws Exception{
- List<String> list=zk.getChildren("/a", null);
- Collections.sort(list);
- if(list.isEmpty()){
- System.out.println("此父路径下面没有节点");
- }else{
- String start=list.get(0);
- String data=new String(zk.getData("/a/"+start, false,null));
- if(data.equals("a")){//等于本身就启动作为Master
- if(list.size()==1){
- startMaster();//作为Master启动
- }else{
- automicSwitch();
- }
- }else{
- //非当前节点
- for(int i=0;i<list.size();i++){
- //获取那个节点存的此客户端的模拟IP
- String temp=new String(zk.getData("/a/"+list.get(i), false, null));
- if(temp.equals("a")){
- //因为前面作为首位判断,所以这个出现的位置不可能是首位
- //需要监听小节点里面的最大的一个节点
- String watchPath=list.get(i-1);
- System.out.println("a监听的是: "+watchPath);
- zk.exists("/a/"+watchPath, this);//监听此节点的详细情况
- break;//结束循环
- }
- }
- }
- }
- }
- public void close()throws Exception{
- zk.close();
- }
- @Override
- public void process(WatchedEvent event) {
- if(event.getType()==Event.EventType.NodeDeleted){
- //如果发现,监听的节点,挂掉了,那么就重新,进行监听
- try{
- System.out.println("注意有节点挂掉,重新调整监听策略........");
- check();
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- }
- public static void main(String[] args)throws Exception {
- Slave s=new Slave("10.2.143.5:2181");
- //s.createPersist();//创建主节点
- s.createTemp();
- s.check();
- Thread.sleep(Long.MAX_VALUE);
- s.close();
- }
- }
散仙起了,3个客户端,作为模拟Slave,本机上的一个eclipse,一个Myeclipse,和服务器上的一个Myeclipse来实现,模拟单点故障,自动切换的功能。
初始状态截图如下:
散仙停掉,2的监听后发生的变化,如下:
最后,散仙停掉A节点的Master,模拟Master宕机。
到此,散仙已经模拟实现了主从单点故障的自动切换,使用Zookeeper可以非常简单可靠的来完成这个功能,当然,我们在这里只是模拟的简单实现,真正的单点问题的实现,肯定要比散仙的这个要复杂的多,在这里只是提供给大家一个解决的思路。
分布式助手Zookeeper(六)
散仙,在关于zookeeper的前几篇文章中,除了记录了zookeeper的一些基础知识,也介绍了怎么使用zookeeper来完成,配置文件同步,和主从自动切换的功能,那么,本篇散仙将会介绍下如何使用,zookeeper来完成分布式锁的功能,其实本质上是与主从切换的实现代码是非常类似的,但是功能上强调的重点不一样。
至于,为什么需要分布式锁(公平锁)?为什么不使用JAVA 自带的锁的应用?
1,为什么需要分布式锁? 因为在分布式环境下,可能会出现一些事务,这时候我们除了可以在存储层的数据库进行控制,也可以在应用层控制,举个例子来讲,中国的飞机路线,我们都知道任何时候,都只能由一架飞机通过,而这个控制这个由谁通过,什么时候通过,是由一个信号控制台来决定的,分布式的环境下由于节点分散在各个地方,各个区域,所以控制起来比较麻烦,这时候我们就可以使用zookeeper来轻松的完成,分布式锁的功能。
2,为什么不使用JAVA自带的锁?JAVA JDK提供了公平锁,与非公平锁,但这种实现是基于同一个JVM来说的,如果同一台机器上,不同的JVM,则可以使用文件锁,来实现,但是这些并不是分布式的模式,虽然可以通过RMI的方式来实现,但比较繁琐。
使用zookeeper来完成分布式锁的步骤如下:
序号 | 内容 |
1 | 创建一个持久znode |
2 | 多个程序并发的去zk服务上,创建一个短暂有时序性的节点路径。 |
3 | 各个节点监听,比它小的里面,最大的节点的动态。 |
4 | 如果发现,比它小的里面,最大的节点发生锁释放或退出,就自动接替为独占锁 |
5 | 没发生改变的节点,继续重复步骤,2,3,4 |
拓扑图如下所示:
注意上图中的master指的就是,获取锁的实例,这其实跟集群环境里只能有一个master的道理一样。
代码如下:
- package com.test;
- import java.nio.charset.Charset;
- import java.nio.charset.StandardCharsets;
- import java.text.SimpleDateFormat;
- import java.util.Collections;
- import java.util.Date;
- import java.util.List;
- import java.util.concurrent.CountDownLatch;
- import org.apache.zookeeper.CreateMode;
- import org.apache.zookeeper.WatchedEvent;
- import org.apache.zookeeper.Watcher;
- import org.apache.zookeeper.ZooDefs.Ids;
- import org.apache.zookeeper.ZooKeeper;
- import org.apache.zookeeper.Watcher.Event.KeeperState;
- import org.apache.zookeeper.data.Stat;
- /***
- * 基于zookeeper实现的
- * 分布式公平锁
- *
- * @author qin dong liang
- * QQ技术群交流:324714439
- *
- * */
- public class Lock1 implements Watcher {
- /**
- * ZK实例
- * */
- private ZooKeeper zk;
- /**原子计数锁,防止在zk没有连上前,执行CURD操作*/
- private CountDownLatch down=new CountDownLatch(1);
- public Lock1() {
- // TODO Auto-generated constructor stub
- }
- public Lock1(String host)throws Exception {
- this.zk=new ZooKeeper(host, 5000 , new Watcher() {
- @Override
- public void process(WatchedEvent event) {
- // TODO Auto-generated method stub
- /**链接上zk服务,岂可取消阻塞计数**/
- if(event.getState()==KeeperState.SyncConnected){
- down.countDown();
- }
- }
- });
- }
- /**
- * 字符编码
- *
- * **/
- private static final Charset CHARSET=StandardCharsets.UTF_8;
- /***
- *
- * 此方法是写入数据
- * 如果不存在此节点
- * 就会新建,已存在就是
- * 更新
- *
- * **/
- public void write(String path,String value)throws Exception{
- Stat stat=zk.exists(path, false);
- if(stat==null){
- zk.create(path, value.getBytes(CHARSET), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
- }else{
- zk.setData(path, value.getBytes(CHARSET), -1);
- }
- }
- /**
- *
- * 切换锁
- *
- * **/
- public void check()throws Exception{
- List<String> list=zk.getChildren("/a", null);
- Collections.sort(list);//排序使得节点有次序
- if(list.isEmpty()){
- System.out.println("此父路径下面没有节点,分布式锁任务完成或还没启动!");
- }else{
- String start=list.get(0);//获取第一个节点
- String data=new String(zk.getData("/a/"+start, false,null));
- if(data.equals("a")){//等于本身就启动作为Master
- if(list.size()==1){
- startMaster();//作为Master启动
- }else{
- automicSwitch();//对于非第一个启动的节点,会调用此方法,因为他的第一个挂了
- //或释放锁了,所以它是抢占的
- }
- }else{
- //非当前节点,就打印当前节点,监控的节点
- for(int i=0;i<list.size();i++){
- //获取那个节点存的此客户端的模拟IP
- String temp=new String(zk.getData("/a/"+list.get(i), false, null));
- if(temp.equals("a")){
- //因为前面作为首位判断,所以这个出现的位置不可能是首位
- //需要监听小节点里面的最大的一个节点
- String watchPath=list.get(i-1);
- System.out.println("Lock1监听的是: "+watchPath);
- zk.exists("/a/"+watchPath, this);//监听此节点的详细情况,如果发生节点注销事件
- //则会触发自身的process方法
- break;//结束循环
- }
- }
- }
- }
- }
- @Override
- public void process(WatchedEvent event) {
- // TODO Auto-generated method stub
- if(event.getType()==Event.EventType.NodeDeleted){
- //如果发现,监听的节点,挂掉了,那么就重新,进行监听
- try{
- System.out.println("注意有锁退出或释放,公平锁开始抢占........");
- check();
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- }
- /**
- *
- * 读取数据,给定一个路径和
- * 监听事件
- *
- * ***/
- public String read(String path,Watcher watch)throws Exception{
- byte[] data=zk.getData(path, watch, null);
- return new String(data,CHARSET);
- }
- SimpleDateFormat f=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- /**
- * 关闭zk连接
- *
- * **/
- public void close()throws Exception{
- zk.close();
- }
- /**
- * 释放锁
- * @throws Exception
- */
- public void automicSwitch()throws Exception{
- // System.out.println("有节点释放锁,Lock1锁占入......., 时间 "+f.format(new Date()));
- System.out.println("Lock1的上级锁节点退出或释放锁了,Lock1锁占入......., 时间 "+f.format(new Date()));
- }
- /**
- * 创建一个持久node,
- *
- * **/
- public void createPersist()throws Exception{
- zk.create("/a", "主节点".getBytes(), Ids.OPEN_ACL_UNSAFE , CreateMode.PERSISTENT);
- System.out.println("创建主节点成功........");
- }
- /***
- * 创建锁node,注意是抢占 的
- *
- *
- * */
- public void createTemp()throws Exception{
- zk.create("/a/b", "a".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
- System.out.println("Lock1注册锁成功,进入公平队列...........");
- }
- public static void main(String[] args)throws Exception {
- //Slave s=new Slave("192.168.120.128:2181");
- Lock1 lock=new Lock1("192.168.120.128:2181");
- // lock.createPersist();//创建主节点
- lock.createTemp();//注册临时有序节点
- lock.check();
- Thread.sleep(Long.MAX_VALUE);
- //lock.close();
- }
- /***
- * 获取锁成功
- *
- * */
- public void startMaster(){
- System.out.println("Lock1节点获取锁了,其他节点等待........");
- }
- }
代码如上,所示,测试的时候,需要搭建一个3个节点的zookeeper集群,关于怎么搭建zookeeper集群,散仙前面的文章里有介绍,需要注意的是myid文件不要漏掉。
上面这个类,需要拷贝多份,并改变里面的节点的值,放在不同的eclipse中,进行模拟测试。
分布式助手Zookeeper(七)
上篇文章,散仙介绍了,分布式环境下,基于zookeeper实现的公平的锁,这篇,我们来看下,如何使用zookeeper来完成非公平锁的模拟,在这之前,我们先来,了解下公平锁和非公平锁的区别。
JAVA JDK提供了公平锁,与非公平锁,但这种实现是基于同一个JVM来说的,
如果同一台机器上,不同的JVM,则可以使用文件锁,来实现,但是这些并不是分布式的模式,虽然可以通过RMI的方式来实现,
但比较繁琐。在分布式的场景里,我们可以轻松的使用zookeeper来实现公平锁与非公平锁
基于zookeeper实现的公平锁与非公平锁的区别
先来通俗的看下二者的区别
公平锁,即先来者先得,只有一个厕所的卫生间,想进去只能是按排队顺序来的,比较公平,first挂掉或释放后,会由secend得到锁,依次类推。
非公平锁,比较暴力,只有一个厕所的卫生间,不用排队,外面围了一堆人等着上厕所,当里面的人出来时,外面的人谁强势,而且力气大,谁就能进去,
极端情况下,如果两个人一样力气大,这时候就该厕所门发挥作用了,一次只能挤进去一个人,反映到我们的程序中,这时候就需要代码同步了,保证
任何时候,只有一个人可以拿到锁。
二者的相同点,都保证了,任何情况下,都只能一个人得到某种资源。但实现的方式不同。
实现简述:分布式非公平锁的创建,除了得到锁外,其他的多个监听器,监听同一个锁的情况
实现的流程步骤如下:
序号 | 介绍 |
1 | 创建一个持久znode |
2 | 多个程序并发的去zk服务上,创建同一个短暂无时序性的节点路径,当一个程序,得到锁时,其他程序,只能监听,不能再次创建,创建时需要同步策略 |
3 | 同一时刻只能有一个创建成功者,能得到锁 |
4 | 没成功者,统一监视得到锁的节点 |
5 | 如果中间得到锁的节点,释放了,或者出意外挂掉了,则重复步骤1,2,3,4 |
拓扑图如下:
代码如下:
- /***
- * @author qin dong liang
- *
- * */
- public class LockUnFair3 implements Watcher {
- /**
- * ZK实例
- * */
- private ZooKeeper zk;
- /**原子计数锁,防止在zk没有连上前,执行CURD操作*/
- private CountDownLatch down=new CountDownLatch(1);
- public LockUnFair3() {
- // TODO Auto-generated constructor stub
- }
- public LockUnFair3(String host)throws Exception {
- this.zk=new ZooKeeper(host, 5000 , new Watcher() {
- @Override
- public void process(WatchedEvent event) {
- // TODO Auto-generated method stub
- /**链接上zk服务,岂可取消阻塞计数**/
- if(event.getState()==KeeperState.SyncConnected){
- down.countDown();
- }
- }
- });
- }
- /**
- * 字符编码
- *
- * **/
- private static final Charset CHARSET=StandardCharsets.UTF_8;
- @Override
- public void process(WatchedEvent event) {
- // TODO Auto-generated method stub
- if(event.getType()==Event.EventType.NodeDeleted){
- //如果发现,监听的节点,挂掉了,那么就重新,进行监听
- try{
- // System.out.println("注意有锁退出或释放,公平锁开始抢占........");
- System.out.println("3我可以去抢占了");
- createTemp();
- // check();
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- }
- SimpleDateFormat f=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- /**
- * 关闭zk连接
- *
- * **/
- public void close()throws Exception{
- zk.close();
- }
- Random random=new Random();
- /***
- * 创建锁node,注意是抢占 的
- *
- *
- * */
- public void createTemp()throws Exception{
- Thread.sleep(random.nextInt(2500));//加个线程休眠,实现模拟同步功能
- if(zk.exists("/a/b", this) != null){
- System.out.println("锁被占用,监听进行中......");
- }else{
- String data=zk.create("/a/b", "a".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
- System.out.println("Lock3创建锁成功,节点路径: "+data);
- }
- // System.out.println("2"+data);
- }
- public static void main(String[] args)throws Exception {
- //Slave s=new Slave("192.168.120.128:2181");
- LockUnFair3 lock=new LockUnFair3("192.168.120.128:2181");
- // lock.createPersist();//创建主节点
- lock.createTemp();
- // lock.check();
- Thread.sleep(Long.MAX_VALUE);
- lock.close();
- }
- }
以上是实现的代码,需要注意的是,在最后抢占锁时,可能会一下多个节点同时去建立名字一样的节点,由于zookeeper的特点,只能由一个建立成功,其他的会抛出异常,为了避免这种情况,散仙,目前的想到的是,在创建一个节点时,通过线程随机休眠,来达到一个同步情况,但这扔有极端情况,虽然几率很小,就是分布式环境下可能有多个节点随机休眠的时间是一样的,所以第二种做法,可以在zk节点维持一个有序的分布式队列,每次只能第一个得到锁,其他的继续等待,下一次的抢占,如此一来,就能保证任何时刻只有一个节点得到锁。
如有什么不足之处,欢迎指正! ^_^
分布式助手Zookeeper(八)
散仙,在前几篇关于zookeeper的文章中,介绍了基于zookeeper实现的分布式公平锁,以及非公平锁,那么本篇呢,散仙就来介绍下关于使用zookeeper如何模拟实现一个分布式队列。
那么为什么需要分布式队列呢?,我们都知道队列,在我们的编程开发中,是一种比不可少的数据结构,最典型莫过于,生产者与消费者的例子了,我们在程序过经常使用的队列是基于非分布式的环境,JAVA JDK也自带了非常多的队列的实现,有基于阻塞模式的,也有基于非阻塞模式的,这些我们可以在不同的场景下使用。
要想把JDK自带的队列给设计成分布式的队列,是一件非常繁琐的事,这时候我就可以轻而易举的使用zookeeper来完成这个功能,zookeeper由于其特殊的文件系统,所以在分布式环境下,能做许多有用的事。
例如在Hadoop中,map任务完成后,我们启动reduce,我们都知道Hadoop的MapReduce是一种分布式的计算框架,所以这时候我们就可以使用zookeeper来完成这里的分布式队列的运用,虽然,hadoop本身用的并不是zookeeper来实现的。
代码如下:
- package com.qin.queue;
- import java.util.Collections;
- import java.util.List;
- import java.util.concurrent.CountDownLatch;
- import org.apache.zookeeper.CreateMode;
- import org.apache.zookeeper.WatchedEvent;
- import org.apache.zookeeper.Watcher;
- import org.apache.zookeeper.ZooKeeper;
- import org.apache.zookeeper.Watcher.Event.EventType;
- import org.apache.zookeeper.ZooDefs.Ids;
- import com.test.Lock1;
- /**
- *
- * 基于zookeeper实现的
- * 分布式队列
- * @author qindongliang
- *
- * **/
- public class DistributeQueue1 {
- private ZooKeeper zk;
- private CountDownLatch down=new CountDownLatch(1);
- public DistributeQueue1(String zkHost) {
- try{
- zk=new ZooKeeper(zkHost, 5000, new Watcher(){
- @Override
- public void process(WatchedEvent event) {
- if(event.getState()==Event.KeeperState.SyncConnected){
- down.countDown();
- }
- }
- });
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- /**
- * 创建一个持久node,
- *
- * **/
- public void createPersist()throws Exception{
- zk.create("/a", "主节点".getBytes(), Ids.OPEN_ACL_UNSAFE , CreateMode.PERSISTENT);
- System.out.println("创建主节点成功........");
- }
- /**
- *
- * 校验队列情况
- *
- * **/
- public void check()throws Exception{
- List<String> list=zk.getChildren("/a", null);
- if(list.size()==3){
- execute();
- }else{
- System.out.println("阻塞中......,队列成员数量:"+list.size());
- }
- }
- public void execute(){
- System.out.println("队列已满,开始执行任务......");
- }
- /***
- *
- * 注册队列
- *
- *
- * */
- public void createTemp()throws Exception{
- zk.create("/a/b", "a".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
- System.out.println("有任务注册队列成功");
- check();
- }
- public static void main(String[] args)throws Exception {
- DistributeQueue1 dq=new DistributeQueue1("192.168.120.128:2181");
- //dq.createPersist();
- dq.createTemp();
- Thread.sleep(Integer.MAX_VALUE);
- }
- }
代码如上,散仙在代码里设定队列的总数为3,就执行干某一件事,我可以去zk的主节点上注册临时有序的节点,每注册一次都会校验,当前队列是不是以及满队,如果满队的话,就开始执行某个任务,从而达到分布式队列的模拟,这与JDK自带的CountDownLatch和CyclicBarrier是非常类似的,当然我们也可也使用zk模拟,这两个非分布式的功能,从而为我们的应用程序在分布式的环境下提供便利。
已有 0 人发表留言,猛击->> 这里<<-参与讨论
ITeye推荐