一、节点
Zookeeper的数据节点称为ZNode,ZNode是Zookeeper中数据的最小单元,每个ZNode都可以保存数据,同时还可以挂载子节点,因此构成了一个层次化的命名空间,称为树,如下图所示:
每个数据节点都是由生命周期的,类型不同则会不同的生命周期,节点类型可以分为持久节点(PERSISTENT)、临时节点(EPHEMERAL)、顺序节点(SEQUENTIAL)三大类,可以通过组合生成如下四种类型节点:
- 持久节点(PERSISTENT):节点创建后便一直存在于Zookeeper服务器上,直到有删除操作来主动清楚该节点;
- 持久顺序节点(PERSISTENT_SEQUENTIAL):相比持久节点,其新增了顺序特性,每个父节点都会为它的第一级子节点维护一份顺序,用于记录每个子节点创建的先后顺序。在创建节点时,会自动添加一个数字后缀,作为新的节点名,该数字后缀的上限是整形的最大值;
- 临时节点(EPEMERAL):临时节点的生命周期与客户端会话绑定,客户端失效,节点会被自动清理。同时Zookeeper规定不能基于临时节点来创建子节点,即临时节点只能作为叶子节点;
- 临时顺序节点(EPEMERAL_SEQUENTIAL):在临时节点的基础添加了顺序特性;
每个节点除了存储数据外还存储了节点本身的一些状态信息,可通过get命令获取。节点的所有信息都包装在Stat对象中,主要包含以下通用信息:
- czxid:即created zxid,表示该数据节点被创建时的事务id
- mzxid:即modified zxid,表示该节点最后一次被更新时的事务id
- ctime:即created time,表示节点被创建的时间
- mtime:即modified time,表示该节点最后一次被更新的时间
- version:数据节点的版本号
- cversion:子节点的版本号
- aversion:节点的acl版本号
- ephemeralOwner:创建该临时节点的会话的sessionid,如果该节点是持久节点,那么这个属性值为0
- dataLength:数据内容的长度
- numChildren:当前节点的子节点个数
- pzxid:表示该节点的子节点列表最后一次被修改时的事务id,注意只有子节点列表变更了才会变更pzxid,子节点内容变更不会影响pzxid
比如在一个数据节点/zk-book被创建完毕后节点的version值是0,表示的含义是:当前节点自从创建之后,被更新过0次。如果现在对该节点的数据内容进行更新操作,那么随后version值就会变成1,同时需要注意的是,其表示的是对数据节点数据内容的变更次数,强调的是变更次数,因此即使前后两次变更并没有使得数据内容的值发生变化,version的值任然会变更。
二、Watcher
Zookeeper使用Watcher机制实现分布式数据的发布/订阅功能。Zookeeper的Watcher机制主要包括客户端线程、客户端WatcherManager、Zookeeper服务器三部分。客户端在向Zookeeper服务器注册的同时,会将Watcher对象存储在客户端的WatcherManager当中。当Zookeeper服务器触发Watcher事件后,会向客户端发送通知,客户端线程从WatcherManager中取出对应的Watcher对象来执行回调逻辑。
简单实现如下例所示:
public class Sample_CONNECTIONLOSS_SESSIONEXPIRED implements Watcher {
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("domain1.book.zookeeper,domain2.book.zookeeper", 5000,
new Sample_CONNECTIONLOSS_SESSIONEXPIRED());
connectedSemaphore.await();
while (true) {
try {
System.out.println("Get data: " + zk.getData("/$7_4", true, null));
} catch (Exception e) {
e.printStackTrace();
}
Thread.sleep(200);
}
}
public void process(WatchedEvent event) {
if (event.getType() == EventType.None && event.getState() == KeeperState.SyncConnected) {
connectedSemaphore.countDown();
}
System.out.println(event.getType().toString() + "-" + event.getState().toString());
}
}
三、ACL
Zookeeper内部存储了分布式系统运行时状态的元数据,这些元数据会直接影响基于Zookeeper进行构造的分布式系统的运行状态,如何保障系统中数据的安全,从而避免因误操作而带来的数据随意变更而导致的数据库异常十分重要,Zookeeper提供了一套完善的ACL权限控制机制来保障数据的安全。
我们可以从三个方面来理解ACL机制:权限模式(Scheme)、授权对象(ID)、权限(Permission),通常使用"**scheme : id : permission **"来标识一个有效的ACL信息。权限模式用来确定权限验证过程中使用的检验策略,有如下四种模式:
- IP,通过IP地址粒度来进行权限控制,如"ip:192.168.0.110"表示权限控制针对该IP地址,同时IP模式可以支持按照网段方式进行配置,如"ip:192.168.0.1/24"表示针对192.168.0.*这个网段进行权限控制;
- Digest,使用"username:password"形式的权限标识来进行权限配置,便于区分不同应用来进行权限控制。Zookeeper会对其进行SHA-1加密和BASE64编码;
- World,最为开放的权限控制模式,数据节点的访问权限对所有用户开放;
- Super,超级用户,是一种特殊的Digest模式,超级用户可以对任意Zookeeper上的数据节点进行任何操作。
授权对象是指权限赋予的用户或一个指定实体,如IP地址或机器等。不同的权限模式通常有不同的授权对象。权限是指通过权限检查可以被允许执行的操作,Zookeeper对所有数据的操作权限分为CREATE(节点创建权限)、DELETE(节点删除权限)、READ(节点读取权限)、WRITE(节点更新权限)、ADMIN(节点管理权限),这5种权限简写为crwda。在zoo.cfg文件中进行配置。
3.1、扩展ACL
开发人员也可自己扩展ACL,权限控制器需要实现AuthenticationProvider接口,注册自定义权限控制器通过在zoo.cfg配置文件中配置如下配置项:authProvider.1=com.zkbook.CustomAuthenticationProvider。
3.1.1、使用scheme进行授权控制
public class Sample_AuthFailed1 implements Watcher {
final static String SERVER_LIST = "domain1.book.zookeeper:2181";
static ZooKeeper zkClient = null;
static ZooKeeper zkClient_error = null;
static List<ACL> acls = new ArrayList<ACL>(1);
static {
for (ACL ids_acl : Ids.CREATOR_ALL_ACL) {
acls.add(ids_acl);
}
}
public static void main(String[] args) throws Exception {
try {
zkClient = new ZooKeeper(SERVER_LIST, 3000, new Sample_AuthFailed1());
zkClient.addAuthInfo("digest", "taokeeper:true".getBytes());
zkClient.create("/zk-book", "".getBytes(), acls, CreateMode.EPHEMERAL);
zkClient_error = new ZooKeeper(SERVER_LIST, 3000, new Sample_AuthFailed1());
zkClient_error.addAuthInfo("digest", "taokeeper:error".getBytes());
zkClient_error.getData("/zk-book", true, null);
} catch (NoAuthException e) {
e.printStackTrace();
}
Thread.sleep(Integer.MAX_VALUE);
}
public void process(WatchedEvent event) {
System.out.println(event.getState());
System.out.println(event.getType());
}
}
3.1.2、使用Super权限模式进行权限控制
public class AuthSample_Super {
final static String PATH = "/zk-book";
public static void main(String[] args) throws Exception {
ZooKeeper zookeeper1 = new ZooKeeper("domain1.book.zookeeper:2181",5000,null);
zookeeper1.addAuthInfo("digest", "foo:true".getBytes());
zookeeper1.create( PATH, "init".getBytes(), Ids.CREATOR_ALL_ACL, CreateMode.EPHEMERAL );
ZooKeeper zookeeper2 = new ZooKeeper("domain1.book.zookeeper:2181",50000,null);
zookeeper2.addAuthInfo("digest", "foo:zk-book".getBytes());
System.out.println(zookeeper2.getData( PATH, false, null ));
ZooKeeper zookeeper3 = new ZooKeeper("domain1.book.zookeeper:2181",50000,null);
zookeeper3.addAuthInfo("digest", "foo:false".getBytes());
System.out.println(zookeeper3.getData( PATH, false, null ));
}
}
3.1.3、使用World权限模式进行权限控制
public class AuthSample_World {
final static String PATH = "/zk-book";
public static void main(String[] args) throws Exception {
ZooKeeper zookeeper1 = new ZooKeeper("domain1.book.zookeeper:2181",5000,null);
zookeeper1.create( PATH, "init".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL );
ZooKeeper zookeeper2 = new ZooKeeper("domain1.book.zookeeper:2181",50000,null);
zookeeper2.addAuthInfo("digest", "foo:true".getBytes());
System.out.println(zookeeper2.getData( PATH, false, null ));
}
}
3.1.4、使用pwd权限模式进行权限控制
public class DigestAuthenticationProviderUsage {
public static void main( String[] args ) throws NoSuchAlgorithmException {
System.out.println( DigestAuthenticationProvider.generateDigest( "super:zk-book" ) );
}
}