Zookeeper简介
Zookeeper常见的应用场景
- 维护配置信息-----将配置信息保存在Zookeeper节点中
- 分布式锁服务------创建临时有序节点,排序,当前节点监听上一临时节点,若上一节点删除,则相当于获得锁
- 集群管理
- 生成分布式唯一 id------创建临时有序节点,根据节点名称后的唯一数字来创建唯一ID
Zookeeper的设计目标
- 高性能:Zookeeper将全量数据存储在内存中,并直接服务于客户端的所有非事务请求,尤其适用于以读为主的应用场景
- 高可用:Zookeeper以集群的方式提供服务,每台机器都会在内存中维护当前的服务器状态,并且每台机器之间都相互保持着通信,只要集群超过一半的机器都能够正常工作,那么集群局能够正常对外服务
- 严格顺序访问:对于来自客户端的每个更新请求,Zookeeper都会分配一个全局唯一的递增编号,这个编号反映了所有事务操作的先后顺序
Zookeeper的数据模型
Zookeeper的数据节点可以视为树状结构(和Linux类似)树中的每个节点被称为ZNode,一个ZNode可以有多个子节点,ZNode兼具文件和目录两种特点,既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分
一个ZNode大体分为3各部分
- 节点的数据:即ZNode data,其关系就像java map中的(key,value)的关系
- 节点的子节点children
- 节点的状态stat:用来描述当前节点的创建、修改记录、包括cZxid、ctime等
节点状态stat的属性
在Zookeeper shell中使用get命令查看指定路径节点的data、stat信息
其属性说明:
-
cZxid:数据节点创建时的事务ID
-
ctime:数据节点创建时的时间
-
mZxid:数据节点最后一次更新时的时间
-
pZxid:数据节点的子节点最后一次被修改时的事务ID
-
cversion:子节点的更改次数
-
dataVersion:节点数据的更改次数
-
aclVersion:节点ACL的更改次数
-
ephemeralOwner:如果节点是临时节点,则表示创建该节点的会话SessionID,如果节点是持久节点,则该属性为0
-
dataLength:数据内容的长度
-
numChildren:数据节点当前子节点的个数
若Zxid1小于Zxid2,则Zxid1的更改发生在Zxid2之前
节点类型
Zookeeper中节点有两种,分别为临时节点和永久节点,节点类型在创建时即被确定,并且不能改变
- 临时节点:该节点的生命周期依赖于创建他们的会话,一旦会话结束,临时节点将被自动删除,当然也可手动删除,虽然每个临时节点都会绑定到一个客户端会话,但他们对所有客户端还是可见的,另外,Zookeeper的临时节点不允许拥有子节点
- 持久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作时候,他们才能被删除
Zookeeper常用Shell命令
- 新增节点
create [-s] [-e] path data #其中-s为有序节点,-e为临时节点
例如:创建持久化节点并写入数据,
create /Hadoop "123456"
注:创建持久化有序节点,此时创建的节点名为指定节点名+自增序号,例如
create -s /a "aaaa" #相当于create /a00000...(即a+自增序号)
创建临时节点,临时节点会在会话过期后删除,例如:
create -e /tmp "tmp"
创建临时有序节点,临时节点也会在会话过期后删除
create -s -a /aa "aaa"
-
更新节点
set path data [version]
更新节点的命令是set,可以直接进行修改,如
set /Hadoop "345"
也可以基于版本号进行更改,此时类似于乐观锁机制,当你传入数据版本号(dataVersion)和当前节点的数据版本号不符合,Zookeeper就会拒绝更改,如:
set /Hadoop "1234" 1
-
删除节点
删除节点语法如下
delete path [version]
和更新节点数据一样,也可传入版本号,当你传入数据版本号[dataVersion]和当前节点数据版本号符合时,Zookeeper才会执行删除操作
delete /Hadoop 1
注:要想删除某个节点及其所有后代节点,可以使用递归删除,命令为 rmr path.
- 查看节点
get path
- 查看节点状态
可以使用stat命令来查看节点状态,它的返回值和get命令类似,但不会返回节点数据
stat path
- 查看节点列表
ls path
ls2 path
#查看节点列表有上述两条命令,ls2 path 是ls path 的增强,不仅可以查看指定路径下的所有节点,还可查看当前节点信息
- 监听器get path watch
get path watch
#使用此命令注册的监听器能够在节点内容发生改变的时候,向客户端发出通知,注意的是,Zookeeper的触发器是一次性的(one-time trigger),即触发一次就会立即失效
- 监听器stat path watch
stat path watch
#使用此命令注册的监听器能够在节点状态发生改变时,向客户端发出通知
- 监听器ls/ls2 path watch
ls path watch
ls2 path watch
#使用上述节点注册的监听器能够监听该节点下所有子节点的增加和删除操作
Zookeeper的ACL权限控制
概述
Zookeeper类似文件系统,client可以创建节点、更新节点、删除节点。Zookeeper的access control list 访问控制列表可以控制节点的权限
ACL权限控制,使用scheme:id:permission来标识,主要涵盖三个方面:
- 权限模式(scheme):授权的策略
- 授权的对象(id):授权的对象
- 权限(permission):授予的权限
其特性如下:
- Zookeeper的权限控制是基于每个ZNode节点,需要对每个节点设置权限
- 每个ZNode支持设置多种权限控制方案和多个权限
- 子节点不会继承父节点的权限,客户端无权访问某节点,但可能可以访问他的子节点
权限模式
描述 | 方案 |
---|---|
world | 只有一个用户:anyone,代表登录Zookeeper所有人 |
ip | 对客户端使用IP地址认证 |
auth | 使用已添加认证的用户认证 |
digest | 使用”用户名:密码“方式认证 |
授权的对象
给谁授予权限
授权对象id是指,权限赋予的实体,例如:IP地址或用户
授予的权限
create、delete、read、write、admin也就是增、删、查、改、管理权限。这五种简写为cdrwa,注意:这5中权限中,delete是指对子节点的删除权限,其他四种权限指对自身节点的操作权限
权限 | ACL简写 | 描述 |
---|---|---|
create | c | 可以创建子节点 |
delete | d | 可以删除子节点(即下一级节点) |
read | r | 可以读取节点数据及显示子节点列表 |
write | w | 可以设置节点数据 |
admin | a | 可以设置节点访问控制列表权限 |
授权的相关命令
命令 | 使用方式 | 描述 |
---|---|---|
getAcl | getAcl
| 读取ACL权限 |
setAcl | setAcl
| 设置ACL权限 |
addauth | addauth | 添加认证用户 |
world授权模式
setAcl <path> world:anyone:<acl>
ip授权模式
setAcl <path> ip:<ip>:<acl>
Auth授权模式
addauth digest <user>:<password> #添加认证用户
setAcl <path> auth:<user>:<acl>
Digest授权模式
setAcl <path> digest:<user>:<password>:<acl>
上文中的密码是经过SHA1即base64处理的密文,在SHELL中可以通过以下命令计算:
echo -n <user>:<password> | openssl dgst -binary -sha1 | openssl base64
Zookeeper java-api
Znode 是Zookeeper集合的核心组件,Zookeeper api提供了操作ZNode的细节
客户端应该遵循以下步骤,与Zookeeper服务器进行清晰和干净的交互
- 连接到Zookeeper服务器,Zookeeper为客户端分配会话ID
- 定期向服务器发送心跳,否则Zookeeper服务器将过期会话ID,客户端需要重新连接
- 只要会话ID处于活动状态,就可以获取/设置ZNode
- 所有任务完成后,断开与Zookeeper服务器的连接,如果客户端长时间不活动,则Zookeeper服务器将自动断开连接
连接Zookeeper
Zookeeper(String connectionString ,int sessionTimeout,Watcher watcher)
- connectionString -----Zookeeper主机
- sessionTimeout ------会话超时(以毫秒为单位)
- watcher -----------实现“监视器”对象,Zookeeper集合会通过监视器对象返回连接状态
...
//计数器对象
CountDownLatch countDownLatch=new CountDownLatch(1);
//arg1:服务器的IP和端口
//arg2:客户端与服务器之间的会话超时时间,以毫秒为单位
//arg3:监视器对象
Zookeeper zooKeeper =new Zookeeper("192.168.***.***:***",5000,new Watcher(){
public void process(WatchedEvent event){
if(event.getState()==Event.KeeperState.SyncConnected){
conutDownLatch.countDown();
}
}
});
//由于连接是异步连接,所以主线程阻塞等待连接对象创建成功
countDownLatch.await();
....
zooKeeper.close();
.....
新增节点
//同步方式
create(String path,byte[] data,List<ACL>acl,CreateMode createMode);
//异步方式
create(String path,byte[] data,List<ACL>acl,CreateMode createMode,AsyncCallback.StringCallback callBack,Object ctx);
- path -Znode路径,例如:/node1/node2/node3
- data --要存储在指定ZNode路径的数据
- acl --要创建节点的访问控制列表,Zookeeper api提供一个静态接口ZooDefs.Ids来获取一些基本的acl列表,如:ZooDefs.Ids.OPEN_ACL_UNSAFE返回打开ZNode的ACL列表
- createMode --节点类型,这是一个枚举
- callBack —异步回调接口
- ctx----传递上下文参数
//连接Zookeeper
...
//arg1:节点的路径
//arg2:节点的数据
//arg3:权限列表 world:anyone:cdrwa READ_ACL_UNSAFE------>world:anyone:r
//arg4:节点类型 持久化节点
zooKeeper.create("/create/node1","node1".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
//异步创建
zooKeeper.create("/create/node1","node1".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT,new AsyncCallback.StringCallback(){
public void processResult(int rc,String path,Object ctx,String name){
//0代表创建成功
System.out.println("rc");
//节点的路径
System.out.println("path");
System.out.println("name");
//上下文参数
System.out.println("ctx");
}
},"this is a node");
......
更新节点
//同步方式
setData(String path,bytes[] data,int version);
//异步方式
setData(String path,bytes[] data,int version,AsynCallback.statCallback callBack,Object ctx);
- path --znode路径
- data —要存储在指定ZNode路径中的数据
- version --znode的当前版本,每当数据更改时,Zookeeper会更新ZNode的版本号
- callBack:异步回调接口
- ctx --传递上下文参数
//同步更新
....
//arg1:节点的路径
//arg2:修改的数据
//arg3:数据的版本号,-1代表版本后不参与更新
setData(String "/set/node1","node1".getBytes(),-1);
....
//异步更新
....
删除节点
//同步方式
delete(String path,int version);
//异步方式
delete(String path,int version,AsyncCallback.voidCallback callBack,Object ctx);
- path —ZNode路径
- version --ZNode的当前版本
- callBack --异步回调接口
- ctx —传递上下文参数
查看节点
//同步方式
getData(String path,boolean b,Stat stat);
//异步方式
getData(String path,boolean b,AsyncCallback.DataCallback callBack,Object ctx);
- path —ZNode路径
- b —是否使用连接对象中注册的监视器
- stat --返回ZNode的元素据
- callBack —异步回调接口
- ctx —传递上下文参数
查看子节点
//同步方式
getChildren(String path,boolean b);
//异步方式
getChildren(String path,boolean b,AsyncCallback.ChildrenCallback callBack,Object ctx );
- path --znode路径
- b ----是否使用连接对象中注册的监视器
- callBack —异步回调接口
- ctx —传递上下文参数
检查节点是否存在
//同步方法
exists(String path,boolean b);
//异步方法
exists(String path,boolean b,AsyncCallback.StatCallback callback,Object ctx);
- path --znode路径
- b ----是否使用连接对象中注册的监视器
- callBack —异步回调接口
- ctx —传递上下文参数
watcher(时间监听机制)
watcher实现由三部分组成:
-
Zookeeper服务端
-
Zookeeper客户端
-
客户端的ZKWatchManager对象
客户端将Watcher注册到服务端,同时将watcher对象保存在客户端的Watch管理中,当Zookeeper服务端监听的数据状态发生变化时,服务端会主动通知客户端,接着客户端的Watch管理器会触发相关watcher来回调相应处理逻辑,从而完成整体的数据发布/订阅流程
watcher特性
特性 | 说明 |
---|---|
一次性 | watcher是一次性的,一旦触发就会移除,再次使用时需要重新注册 |
客户端顺序回调 | watcher回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态,一个watcher回调逻辑不应该太多,以免影响别的watcher执行 |
轻量级 | WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容 |
时效性 | watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍可接受通知 |
Watcher通知状态(KeeperState)
keeperstate是客户端与服务器连接状态发生变化时对应的通知类型,其是一个枚举类,其枚举属性如下:
枚举属性 | 说明 |
---|---|
SyncConnected | 客户端与服务器正常连接时 |
Disconnected | 客户端与服务器断开连接时 |
Expired | 会话Session失效时 |
AuthFailed | 身份认证失败时 |
Watcher事件类型
EventType是数据节点(ZNode)发生变化时对应的通知类型。EventType变化时KeeperState永远处于SyncConnectedted通知状态下,当KeepState发生变化时,EventType永远为None,EventType是一个枚举类,其枚举属性为:
枚举属性 | 说明 |
---|---|
None | 无 |
NodeCreate | Watcher监听的数据节点被创建时 |
NodeDelete | Watcher监听的数据节点被删除时 |
NodeDataChanged | Watcher监听的数据节点内容发生变更时(无论内容数据是否变化) |
NodeChildrenChanged | Watcher监听的数据节点的子节点列表发生变更时 |
注:客户端接受到的相关事件通知中只包含状态及类型等信息,不包括节点变化前后的具体内容,变化前的数据需业务自身存储,变化后的数据需调用get等方法获取
捕获相应的事件
在Zookeeper中采用zk.getChildrenn(path,watch)/zk.exists(path,watch)、zk.getData(path,watch,stat)这些为某个ZNode注册监听
下表以node-x节点为例 ,说明调用的注册方法和可监听事件间的关系:
注册方式 | Create | ChildrenChanged | changed | Deleted |
---|---|---|---|---|
zk.exists("/node-x",watcher) | 可监控 | 可监控 | 可监控 | |
zk.getData("/node-x",watcher) | 可监控 | 可监控 | ||
zk.getChildren("/node-x",watcher) | 可监控 | 可监控 |
集群
一致性协议----zab
zab协议的全称是Zookeeper Atomic Broadcast —Zookeeper原子广播)Zookeeper是通过zab协议来保证分布式事务的最终一致性
基于zab协议,Zookeeper集群中的角色主要有以下三类,如下表所示:
zab广播模式工作原理,通过类似两阶段提交协议的方式解决数据的一致性:
- leader从客户端收到一个写请求
- leader生成一个新的事务并为这个事务生成一个唯一的ZXID
- leader将这个事务提议(propose)发送给所有的follower节点
- follower节点将收到的事务请求加入到历史队列(history queue)中,并发送ack给leader
- 当leader收到大多数follower(半数以上节点)的ack消息,leader会发送commit请求
- 当follower收到commit请求时,从历史队列中将事务请求commit
Zookeeper的leader选举
服务器状态:
-
looking:寻找leader状态,当服务器处于该状态时,他会认为当前集群中没有leader,因此需要进入leader选举状态:
-
leading:领导者状态,表明当前服务器角色是leader
-
following:跟随者状态,表明当前服务器角色是follower
-
observing:观察者状态,表明当前服务器角色是observer
leader选举流程:
…
…
observer角色及配置
observer角色特点
-
不参与集群的leader选举
-
不参与集群中写数据时的ack反馈
为了使用observer角色,在任何想变成observer角色的配置文件中加入如下配置:
peerType=observer
并在所有的server配置文件中,配置成observer模式的server的那行追加:observer
server.3=192.168.60.130:2282:3389:observer
Zookeeper api集群的连接
Zookeeper(String connectionString,int sessionTimeout,Watcher watcher)
- connectionString ----Zookeeper集合主机
- sessionTimeout-----会话超时(以毫秒为单位)
- watcher ------实现“监视器”界面的对象,Zookeeper集合通过监视器对象返回连接状态
Zookeeper开源客户端Curator介绍
原生ZookeeperApi的不足
- 连接对象异步创建,需要开发人员自行编码等待
- 连接没有自动重连超时机制
- watcher一次注册生效一次
- 不支持递归创建树形节点
curator特点:
- 解决session会话超时重连
- watcher反复注册
- 简化开发api
- 遵循Fluent风格的api
- 提供分布式锁服务,共享计数器,缓存机制等
watcherApi
curator提供了两种watcher(cache)来监听节点变化
- Node Cache:只是监听某一特定节点,监听节点的新增和修改
- PathChildren Cache:监控一个ZNode的子节点,当一个子节点增加,更新,删除时,Path Cache会改变他的状态,会包含最新的子节点、子节点的数据和状态
分布式锁
- InterProcessMutex:分布式可重入排它锁
- InterProcessReadWriteLock:分布式读写锁
监控命令
Zookeeper支持某些特定的四字命令与其交互,用户在客户端可以通过Telnet或nc向Zookeeper提交相应的命令,常见四字命令如下表
命令 | 描述 |
---|---|
conf | 输出相关服务配置的详细信息,比如端口、zk数据及日志配置路径,最大连接数、session超时时间、serverld等 |
cons | 列出所有连接到这台服务器的客户端/会话的详细信息,包括”接受/发送“的包的数量,session id、操作延迟、最后操作执行等信息 |
crst | 重置当前这台服务器所有连接/会话的统计信息 |
dump | 列出未经处理的会话和临时节点 |
envi | 输出关于服务器环境详细信息 |
ruok | 测试服务器是否处于正确运行状态,如果正常返回”imok“,否则返回空 |
stat | 输出服务器的详细信息:接受/发送包数量、连接数、模式(leader/follower)、节点总数、延迟。所有客户端的列表 |
srst | 重置server状态 |
wchs | 列出服务器watches的简洁信息:连接总数、watching节点总数和watches总数 |
wchc | 通过session分组,列出watch的所有节点,它的输出是一个与watch相关的会话的节点列表 |
mntr | 列出集群的健康状态,包括“接受/发送”的包数量,操作延迟、当前服务模式(leader/follower)、节点总数、watch总数、临时节点总数 |
conf命令
#conf:输出相关的服务配置详细信息
encho conf | nc ip 端口
属性 | 含义 |
---|---|
clientPort | 客户端端口号 |
dataDir | 数据快照文件目录 默认情况下100000次事务操作生成一次快照 |
dataLogDir | 事务日志文件目录、生产环境中放在独立的磁盘上 |
tickTime | 服务器之间或客户端与服务器之间维持心跳的时间间隔(以毫秒为单位) |
maxClientCnxns | 最大连接数 |
minSessionTimeout | 最小session超时minSessionTimeout=tickTime*2 |
maxSessionTimeout | 最大session超时maxSessionTimeout=tickTime*20 |
serverId | 服务器编号 |
InitLimit | 急群众的follower服务器(F)与leader服务器之间初始连接时能容忍的最多心跳数 |
syncLimit | 集群中的follower服务器(F)与leader服务器之间请求和应答之间能容忍的最多心跳数 |
electionAlg | 0:基于udp的LeaderElection ,1:基于udpde fastLeaderElection ,2:基于udp和认证的FastLeaderElection ,3:基于tcp的FastLeaderElection |
electionPort | 选举端口 |
quorumPort | 数据通信端口 |
peerType | 是否为观察者 1为观察者 |
…
…
|
| serverId | 服务器编号 |
| InitLimit | 急群众的follower服务器(F)与leader服务器之间初始连接时能容忍的最多心跳数 |
| syncLimit | 集群中的follower服务器(F)与leader服务器之间请求和应答之间能容忍的最多心跳数 |
| electionAlg | 0:基于udp的LeaderElection ,1:基于udpde fastLeaderElection ,2:基于udp和认证的FastLeaderElection ,3:基于tcp的FastLeaderElection |
| electionPort | 选举端口 |
| quorumPort | 数据通信端口 |
| peerType | 是否为观察者 1为观察者 |
…
…
…