数据模型
ZooKeeper的数据结构与Unix文件系统的树结构非常相似,但没有目录和文件的概念。每一个数据节点被称为ZNode,节点路径与Unix文件系统路径非常相似,我们可以向一个节点写入数据,也可以在节点下面创建子节点。
在ZooKeeper中,事务是指能够改变ZooKeeper服务器状态的操作,一般包括数据节点创建与删除、内容更新和客户端会话创建与失效等操作。对于每一个事务请求,ZooKeeper会为其分配一个全局唯一的事务ID,用ZXID来表示,通常是一个64位的数字,每一个ZXID对应一次更新操作。
节点特性
节点类型
节点的类型分为:持久节点(PERSISTENT),持久顺序节点(PERSISTENT_SEQUENTIAL),临时节点(EPHEMERAL),临时顺序节点(EPHEMERAL_SEQUENTIAL)。
持久节点是指该数据节点被创建后,就会一直存在于ZooKeeper服务器上,直到有删除操作来主动清除这个节点。
临时节点是指其生命周期和客户端的会话绑定在一起,如果客户端会话失效,那么这个节点就会被自动清除。
顺序节点是指在创建节点过程中,ZooKeeper会自动为给定节点名加上一个数字后缀,作为一个新的、完整的节点名。这个数字后缀的上限是整形的最大值。
Stat状态属性
数据节点除了存储的数据内容之外,还存储了数据节点本身的一些状态信息。这些状态信息封装在Stat对象,下面是Stat对象状态属性说明:
状态属性 | 说明 |
---|---|
czxid | 表示该数据节点被创建时的事务ID |
mzxid | 表示该节点最后一次被更新时的事务ID |
pzxid | 表示该节点的子节点列表最后一次被修改时的事务ID(只有子节点列表发生变更才会更新,子节点内容变更不会影响pzxid) |
ctime | 表示节点被创建的时间 |
mtime | 表示节点最后一次被更新的时间 |
version | 数据节点的版本号 |
cversion | 子节点的版本号 |
aversion | 节点的ACL版本号 |
ephemeralOwner | 创建该临时节点的会话的sessionID,如果该节点是持久节点,那么这个属性值为0 |
datalength | 数据内容的长度 |
numChildren | 当前节点的子节点个数 |
版本
这里的版本表示的是数据节点的数据内容、子节点列表或者节点ACL信息的修改次数。当一个数据节点被创建成功后,其version值为0,表示该节点自创建之后被更新过0次。如果对该节点数据内容进行更新操作,那么version的值就会变成1。
版本用于避免一些分布式更新的并发问题,例如分布式锁服务等(保证分布式数据原子性操作)。假如客户端A试图进行更新操作,它会携带上次获得到的version值进行更新。如果在这段时间内,ZooKeeper服务器上该节点的数据恰好被其他客户端更新了,那么数据版本一定发生了改变。最新的数据版本与客户端A携带的version无法匹配,于是客户端A无法更新成功。
如果对ZooKeeper数据节点的更新操作没有原子性要求,那么数据版本参数可以用 -1,告诉服务器,客户端需要基于数据的最新版本进行更新操作。
Watcher
在ZooKeeper中,引入Watcher机制来实现分布式数据的发布/订阅功能。ZooKeeper允许客户端向服务端注册一个Watcher监听,当服务端的一些指定事件触发了这个Watcher,就会向指定客户端发送一个事件通知。
WatchedEvent
在ZooKeeper中,接口类Watcher用于表示一个标准的事件处理器,其定义了事件通知相关的逻辑,包含了KeeperState和EventType两个枚举类,分别代表了通知状态和事件类型,同时定义了事件的回调方法process(WatchedEvent event)。
KeeperState | EventType | 触发条件 |
---|---|---|
SyncConnected | None | 客户端与服务器成功建立会话 |
SyncConnected | NodeCreated | Watcher监听的对应数据节点被创建 |
SyncConnected | NodeDeleted | Watcher监听的对应数据节点被删除 |
SyncConnected | NodeDataChanged | Watcher监听的对应数据节点的数据内容发生变更 |
SyncConnected | NodeChildrenChanged | Watcher监听的对应数据节点的子节点列表发生变更 |
Disconnected | None | 客户端与ZooKeeper服务器断开连接 |
Expired | None | 会话超时 |
AuthFailed | None | 使用错误的scheme进行权限检查或者SASL权限检查失败 |
ConnectedReadOnly | None | 只读模式 |
package org.apache.zookeeper;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.zookeeper.proto.WatcherEvent;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
/**
* A WatchedEvent represents a change on the ZooKeeper that a Watcher
* is able to respond to. The WatchedEvent includes exactly what happened,
* the current state of the ZooKeeper, and the path of the znode that
* was involved in the event.
*/
@InterfaceAudience.Public
public class WatchedEvent {
final private KeeperState keeperState;
final private EventType eventType;
private String path;
/**
* Create a WatchedEvent with specified type, state and path
*/
public WatchedEvent(EventType eventType, KeeperState keeperState, String path) {
this.keeperState = keeperState;
this.eventType = eventType;
this.path = path;
}
/**
* Convert a WatcherEvent sent over the wire into a full-fledged WatcherEvent
*/
public WatchedEvent(WatcherEvent eventMessage) {
keeperState = KeeperState.fromInt(eventMessage.getState());
eventType = EventType.fromInt(eventMessage.getType());
path = eventMessage.getPath();
}
public KeeperState getState() {
return keeperState;
}
public EventType getType() {
return eventType;
}
public String getPath() {
return path;
}
@Override
public String toString() {
return "WatchedEvent state:" + keeperState
+ " type:" + eventType + " path:" + path;
}
/**
* Convert WatchedEvent to type that can be sent over network
*/
public WatcherEvent getWrapper() {
return new WatcherEvent(eventType.getIntValue(),
keeperState.getIntValue(),
path);
}
}
WatchedEvent封装了每个服务器事件的三个基本属性:通知状态(KeeperState),事件类型(EventType),节点路径(Path)。服务器在生成WatchedEvent事件后,会调用getWrapper方法将自己包装成一个可序列化的WatcherEvent事件,然后通过网络传输到客户端。客户端在接收到服务端的事件对象后,会将WatcherEvent事件还原成一个WatchedEvent事件,并传递给process方法处理。所以,WatchedEvent是一个逻辑事件,用于服务端和客户端程序执行过程中所需要的逻辑对象。WatcherEvent实现了序列化接口,用于网络传输。
无论是WatchedEvent还是WatcherEvent,都对服务器事件进行简单的封装,客户端无法直接从该事件中获取到对应数据节点的原始数据内容以及变更后的新数据内容,需要客户端主动去获取数据(ZooKeeper采用推拉结合的方式,客户端监听,服务器通知,客户端主动获取数据)。
Watcher特性
Watcher工作机制可以概括为:客户端注册Watcher,服务器处理Watcher,客户端回调Watcher。
客户端向服务器端注册Watcher时,不会把Watcher对象传到服务器端,只是在客户端请求中使用boolean类型属性进行标记。同时服务端也仅仅保存了当前连接的ServerCnxn对象(ServerCnxn代表了客户端与服务器的连接),这样做是为了减少网络传输的开销。
一次性:无论是服务端还是客户端,一旦一个Watcher被触发,ZooKeeper都会将其从相应的存储中删除。
客户端Watcher回调的过程是一个串行同步的过程,保证了执行顺序性。
ACL权限控制
在Unix/Linux文件系统中使用的权限控制方式是UGO(User,Group,Others)权限控制机制。一个文件或目录,对创建者(User),创建者所在的组(Group),其他用户(Others)分别配置不同的权限。UGO是一种粗粒度的文件系统权限控制模式,它无法解决下面这个场景:用户 U1 创建了文件 F1,希望 U1 所在的用户组 G1 拥有对 F1 读写和执行的权限,另一个用户组 G2 拥有读权限,而另外一个用户 U3 则没有任何权限。
访问控制列表(ACL)可以针对任意用户和组进行细粒度的权限控制。通常使用权限模式(Scheme)、授权对象(ID)、权限(Permission),“Scheme:ID:Permission”来表示一个有效ACL的信息。
权限模式是用来确定权限验证过程中使用的检验策略。通常使用以下四种权限模式:
权限模式 | 说明 |
---|---|
IP | 通过IP地址来进行权限控制,例如配置了“IP:192.168.0.1”,表示权限控制都是针对这个IP地址。同时,IP模式也支持按照网段的方式进行配置,例如“IP:192.168.0.1/24”,表示针对 192.168.0.* 这个网段进行权限控制。 |
Digest | Digest是最常用的权限控制模式。采用 “username:password” 形式的权限标识进行权限配置,便于区分不同应用来进行权限控制。当我们通过“username:password”形式配置了权限标识后,ZooKeeper会对其先后进行两次编码处理,分别是SHA-1算法加密和BASE64编码。 |
World | World是一种最开放的权限控制模式。数据节点的访问权限对所有用户开放,所有用户都可以在不进行任何权限检验的情况下操作ZooKeeper上的数据。World模式可以看成是一种特殊的Digest模式,它只有一个权限标识,即 “world:anyone”。 |
Super | Super模式也是一种特殊的Digest模式。在Super模式下,超级用户可以对任意数据节点进行任何操作。 |
授权对象是指权限赋予的用户或一个指定的实体。例如IP地址或是机器。
权限模式 | 说明 |
---|---|
IP | IP地址或IP网段 |
Digest | 自定义,通常是 “username:BASE64(SHA-1(username:password))” |
World | 只有一个ID:anyone |
Super | 与Digest模式一致 |
权限是指通过权限检查后可以被允许执行的操作
权限 | 说明 |
---|---|
CREATE(c) | 数据节点的创建权限,允许授权对象在该数据节点下创建子节点 |
DELETE(d) | 数据节点的删除权限 |
READ(r) | 数据节点的读取权限,允许授权对象访问该数据节点并读取其数据内容或子节点列表等 |
WRITE(w) | 数据节点的更新权限 |
ADMIN(a) | 数据节点的管理权限,允许授权对象对该数据节点进行相关的ACL设置操作 |
设置ACL方式一:create [-s] [-e] path data acl
create -e /temp nothing digest:abc:123456:cdrwa
设置ACL方式二:setAcl path acl
setAcl /temp digest:abc:123456:cdrwa