在上一篇博客中只是简单提及到了namespace,并没有详细介绍namespace,本篇博客,博主给大家详细介绍Curator框架中的namespace。
- ZooKeeper : Curator框架重试策略和Session API介绍
博主使用的Curator框架版本是5.2.0,ZooKeeper版本是3.6.3。
org.apache.curator curator-recipes5.2.0
5.2.0版本的Curator使用3.6.3版本的ZooKeeper。
Curator框架中的命名空间对应到ZooKeeper中就是一个节点,而这个命名空间节点的节点类型是什么呢?
首先不可能是临时节点,因为临时节点不能创建子节点,这种情况肯定不能满足业务需求。
使用TTL Znode需要配置extendedTypesEnabled=true,不然创建TTL Znode时会收到Unimplemented的报错,在ZooKeeper :重要概念 & 客户端命令介绍这篇博客中进行了介绍。所以命名空间节点肯定不会是临时节点。
也不可能是持久节点,因为当客户端关闭一段时间后,该命名空间节点就被移除了,这显然不是持久节点。那就只剩下TTL节点和容器节点这两种类型。其实也不可能是TTL节点,因为ZooKeeper服务端并不能创建TTL节点(没有添加extendedTypesEnabled=true这个配置),所以命名空间这个节点的默认类型是容器节点。
由于Curator框架是基于ZooKeeper的Java客户端原生API来实现更高级、更易用的API,所以在创建命名空间这个节点时,还是会调用ZooKeeper类的create方法(由ZooKeeper的Java客户端提供),因此通过Debug就可以知道命名空间节点的类型了。先在ZooKeeper类的create方法打上Debug标记,如下图所示:
测试代码:
package com.kaven.zookeeper; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.Curatorframework; import org.apache.curator.framework.CuratorframeworkFactory; import org.apache.curator.framework.imps.CuratorframeworkState; import org.apache.curator.retry.ExponentialBackoffRetry; public class Application{ private static final String SERVER_PROXY = "192.168.1.184:9000"; private static final int TIMEOUT = 40000; public static void main(String[] args) throws Exception { RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); Curatorframework curator = CuratorframeworkFactory.builder() .connectString(SERVER_PROXY) .namespace("curator") .retryPolicy(retryPolicy) .connectionTimeoutMs(TIMEOUT) .sessionTimeoutMs(TIMEOUT) .build(); curator.start(); if (curator.getState().equals(CuratorframeworkState.STARTED)) { System.out.println("连接成功!"); curator.checkExists() .forPath("/"); } Thread.sleep(10000000); } }
这里先不管Curator框架相关API的使用,checkExists方法表示会检查节点是否存在,如果存在就返回该节点的状态信息。如下图所示,命名空间节点默认是容器节点类型。
Curator框架对创建节点的API进行了增强,当需要创建的节点的Parents不存在时,会先创建它的Parents。
测试代码:
package com.kaven.zookeeper; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.Curatorframework; import org.apache.curator.framework.CuratorframeworkFactory; import org.apache.curator.framework.imps.CuratorframeworkState; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.zookeeper.CreateMode; public class Application{ private static final String SERVER_PROXY = "192.168.1.184:9000"; private static final int TIMEOUT = 40000; public static void main(String[] args) throws Exception { RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); Curatorframework curator = CuratorframeworkFactory.builder() .connectString(SERVER_PROXY) .namespace("curator") .retryPolicy(retryPolicy) .connectionTimeoutMs(TIMEOUT) .sessionTimeoutMs(TIMEOUT) .build(); curator.start(); if (curator.getState().equals(CuratorframeworkState.STARTED)) { System.out.println("连接成功!"); curator.create() .creatingParentsIfNeeded() .withMode(CreateMode.EPHEMERAL) .forPath("/kaven/docker", "data".getBytes()); } Thread.sleep(10000000); } }
creatingParentsIfNeeded方法表示当需要创建的节点的Parents不存在时,会先创建它的Parents(必须先创建父节点,才能创建子节点),并且以持久节点类型创建这些Parents。如下图所示,命名空间节点默认还是容器节点类型。
而/curator/kaven节点是持久节点类型,这是调用creatingParentsIfNeeded方法的结果。
/curator/kaven/docker是临时节点类型,这是通过withMode(CreateMode.EPHEMERAL)直接指定的。
因此,可以知道命名空间节点默认是容器节点类型。
如果想将命名空间节点设置成/curator/namespace这种形式,即更深层的节点,可以如下所示进行定义(以此类推,不需要加/前缀):
namespace("curator/namespace")
如果加/前缀会报错:
并且这些节点都将以容器节点类型被创建(都不存在的情况下)。
如果只是部分节点存在,不会覆盖存在的节点,只会创建不存在的节点,还是以容器节点类型进行创建。
这些只是通过Debug得到的结论,可能存在偶然情况,接下来博主通过分析Curator框架的源码来验证上述的结论。
问题
- 命名空间节点什么时候被创建的?
- 命名空间节点如何创建的?
带着这两个问题博主来分析一下Curator框架的相关源码。命名空间节点什么时候被创建的?其实是在Curator框架第一次对ZooKeeper服务端进行 *** 作的时候,Curator框架每次 *** 作都会指定一个路径(需要知道 *** 作哪个节点),通过forPath方法来指定,而这个路径是相对于命名空间而言,因此命名空间节点必须提前被创建。在每个 *** 作的实现类中的forPath方法都会调用CuratorframeworkImpl类中的fixForNamespace方法。
如下图所示(以CreateBuilderImpl类为例):
调用CuratorframeworkImpl类中的fixForNamespace方法:
这些 *** 作最终都会调用CuratorframeworkImpl类中的fixForNamespace方法:
String fixForNamespace(String path, boolean isSequential) { return this.namespace.fixForNamespace(path, isSequential); }
而CuratorframeworkImpl类中的fixForNamespace方法会调用NamespaceImpl类中的fixForNamespace方法:
String fixForNamespace(String path, boolean isSequential) { if (this.ensurePathNeeded.get()) { try { final CuratorZookeeperClient zookeeperClient = this.client.getZookeeperClient(); RetryLoop.callWithRetry(zookeeperClient, new Callable
ensurePathNeeded属性是AtomicBoolean类型(保证 *** 作的原子性),表示命名空间这个节点是否需要被创建,只要namespace != null,该属性的值就为true(如果为null,即没有指定namespace或者直接指定为null,即namespace(null),这样命名空间就是ZooKeeper中的根节点/,前缀/拼接null,还是/),即需要被创建。
NamespaceImpl(CuratorframeworkImpl client, String namespace) { if ( namespace != null ) { try { PathUtils.validatePath("/" + namespace); } catch ( IllegalArgumentException e ) { throw new IllegalArgumentException("Invalid namespace: " + namespace + ", " + e.getMessage()); } } this.client = client; this.namespace = namespace; ensurePathNeeded = new AtomicBoolean(namespace != null); }
下面这一行是关键:
ZKPaths.mkdirs(zookeeperClient.getZooKeeper(), ZKPaths.makePath("/", NamespaceImpl.this.namespace), true, NamespaceImpl.this.client.getAclProvider(), true);
调用了ZKPaths类中的mkdirs方法,并且最后一个参数的值为true,而最后一个参数是asContainers,很显然命名空间节点默认是容器节点。
public static void mkdirs(ZooKeeper zookeeper, String path, boolean makeLastNode, InternalACLProvider aclProvider, boolean asContainers) throws InterruptedException, KeeperException { PathUtils.validatePath(path); int pos = 1; do { pos = path.indexOf(47, pos + 1); if (pos == -1) { if (!makeLastNode) { break; } pos = path.length(); } String subPath = path.substring(0, pos); if (zookeeper.exists(subPath, false) == null) { try { List acl = null; if (aclProvider != null) { acl = aclProvider.getAclForPath(subPath); if (acl == null) { acl = aclProvider.getDefaultAcl(); } } if (acl == null) { acl = Ids.OPEN_ACL_UNSAFE; } zookeeper.create(subPath, new byte[0], (List)acl, getCreateMode(asContainers)); } catch (NodeExistsException var8) { } } } while(pos < path.length()); }
这里的path参数已经将命名空间加了/前缀,通过调用makePath方法实现。
public static String makePath(String parent, String child) { int maxPathLength = nullableStringLength(parent) + nullableStringLength(child) + 2; StringBuilder path = new StringBuilder(maxPathLength); // 给定一个父节点和一个子节点,将它们加入给定的path joinPath(path, parent, child); return path.toString(); }
简化ZKPaths类中的mkdirs方法如下:
int pos = 1; do { pos = path.indexOf(47, pos + 1); if (pos == -1) { if (!makeLastNode) { break; } pos = path.length(); } String subPath = path.substring(0, pos); if (zookeeper.exists(subPath, false) == null) { try { zookeeper.create(subPath, new byte[0], (List)acl, getCreateMode(asContainers)); } catch (NodeExistsException var8) { } } } while(pos < path.length());
47是字符/的int值,pos代表当前已经创建好的节点的后一个位置(默认值为1,代表/已经被创建好了,因为这是ZooKeeper内置的根节点,而它的后一个位置就是1),因此path.indexOf(47, pos + 1)就是查询当前已经创建好的节点的后一个位置后面出现字符/的第一个位置,subPath变量就是当前需要创建的节点的路径, 通过path.substring(0, pos)得到,然后检查该节点是否存在(zookeeper.exists(subPath, false),false表示不在该节点上留下Watcher),如果不存在,即返回null,就创建该节点,还是通过ZooKeeper的Java客户端原生API来进行创建的,如果节点存在不会覆盖该节点;而节点类型通过getCreateMode方法获得,而这里的asContainers参数默认为true,也再一次说明命名空间节点默认是容器节点;makeLastNode参数表示是否创建最后一个节点,默认是true,因为最后一个节点的结尾没有/字符,因此path.indexOf(47, pos + 1)的结果是-1,如果makeLastNode为true(pos = path.length()),subPath的值就和path一样,所以会创建最后一个节点,而makeLastNode为false,就会通过break跳出do-while循环;该方法以do-while循环的形式将命名空间节点及其不存在的父节点全部创建(依次先创建父节点,再创建子节点)。
private static CreateMode getCreateMode(boolean asContainers) { return asContainers ? getContainerCreateMode() : CreateMode.PERSISTENT; } public static CreateMode getContainerCreateMode() { return CreateModeHolder.containerCreateMode; }
命名空间节点一定是容器节点吗?答案是不一定,前提是使用的ZooKeeper版本支持容器节点,不然命名空间节点将是持久节点。
private static final CreateMode NON_CONTAINER_MODE = CreateMode.PERSISTENT; private static class CreateModeHolder { private static final Logger log = LoggerFactory.getLogger(ZKPaths.class); private static final CreateMode containerCreateMode; static { CreateMode localCreateMode; try { localCreateMode = CreateMode.valueOf("CONTAINER"); } catch ( IllegalArgumentException ignore ) { localCreateMode = NON_CONTAINER_MODE; log.warn("The version of ZooKeeper being used doesn't support Container nodes. CreateMode.PERSISTENT will be used instead."); } containerCreateMode = localCreateMode; } }
The version of ZooKeeper being used doesn’t support Container nodes. CreateMode.PERSISTENT will be used instead.
正在使用的ZooKeeper版本不支持容器节点。将改用CreateMode.PERSISTENT。
创建命名空间节点成功后ensurePathNeeded的值会被设置为false,这样以后的 *** 作就不会再次创建命名空间节点了。
this.ensurePathNeeded.set(false);
创建好了命名空间节点,关于forPath方法指定的路径该如何处理?这个问题留到Znode API的原理分析中再进行介绍。
总结- 命名空间不能加/前缀,不然会报错,事实上Curator框架会自动加上,并且命名空间可以使用更深层的节点,如/a/b/c/d,而对应的命名空间是a/b/c/d。
- 命名空间节点在Curator框架对ZooKeeper服务端进行第一次 *** 作时被创建(指定该 *** 作的路径时被创建,即在调用forPath方法的时候)。
- 命名空间节点默认是容器节点(Curator框架版本不同,设定可能不一样),但前提是使用的ZooKeeper版本支持容器节点,不然命名空间节点将以持久节点类型被创建;如果命名空间表示一个深层的节点,如/a/b/c/d, Curator框架只会以默认方式创建ZooKeeper服务端中不存在的节点(通过do-while循环的方式,依次先创建父节点,再创建子节点,并且默认为容器节点类型,除非使用的ZooKeeper版本不支持容器节点,就会以持久节点类型创建它们),如果节点存在不会进行覆盖。
如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)