赞
踩
在上一篇博客中只是简单提及到了namespace
,并没有详细介绍namespace
,本篇博客,博主给大家详细介绍Curator
框架中的namespace
。
博主使用的Curator
框架版本是5.2.0
,ZooKeeper
版本是3.6.3
。
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.0</version>
</dependency>
5.2.0
版本的Curator
使用3.6.3
版本的ZooKeeper
。
Curator
框架中的命名空间对应到ZooKeeper
中就是一个节点,而这个命名空间节点的节点类型是什么呢?
首先不可能是临时节点,因为临时节点不能创建子节点,这种情况肯定不能满足业务需求。
使用TTL Znode
需要配置extendedTypesEnabled=true
,不然创建TTL Znode
时会收到Unimplemented
的报错,在重要概念这篇博客中进行了介绍。所以命名空间节点肯定不会是临时节点。
也不可能是持久节点,因为当客户端关闭一段时间后,该命名空间节点就被移除了,这显然不是持久节点。那就只剩下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; /** * @Author: ITKaven * @Date: 2021/11/20 10:30 * @Blog: https://kaven.blog.csdn.net * @Leetcode: https://leetcode-cn.com/u/kavenit * @Notes: */ 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; /** * @Author: ITKaven * @Date: 2021/11/20 10:30 * @Blog: https://kaven.blog.csdn.net * @Leetcode: https://leetcode-cn.com/u/kavenit * @Notes: */ 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<Object>() { public Object call() throws Exception { ZKPaths.mkdirs(zookeeperClient.getZooKeeper(), ZKPaths.makePath("/", NamespaceImpl.this.namespace), true, NamespaceImpl.this.client.getAclProvider(), true); return null; } }); this.ensurePathNeeded.set(false); } catch (Exception var4) { ThreadUtils.checkInterrupted(var4); this.client.logError("Ensure path threw exception", var4); } } return ZKPaths.fixForNamespace(this.namespace, path, isSequential); }
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> 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
版本不支持容器节点,就会以持久节点类型创建它们),如果节点存在不会进行覆盖。如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。