调用Zookeeper的构造方法来实例化一个Zookeeper。我们以一个构造方法进行一步一步的分析。
下面是一个Zookeeper的构造方法:
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, HostProvider aHostProvider) throws IOException { LOG.info("Initiating client connection, connectString=" + connectString + " sessionTimeout=" + sessionTimeout + " watcher=" + watcher); watchManager = defaultWatchManager(); watchManager.defaultWatcher = watcher; ConnectStringParser connectStringParser = new ConnectStringParser( connectString); hostProvider = aHostProvider; cnxn = new ClientCnxn(connectStringParser.getChrootPath(), hostProvider, sessionTimeout, this, watchManager, getClientCnxnSocket(), canBeReadOnly); cnxn.start(); }
在《Zoopkeeper-会话创建流程》文章中已经介绍了初始化阶段所做的事情。这里就从每行代码开始分析。
第一行代码:
LOG.info("Initiating client connection, connectString=" + connectString
+ " sessionTimeout=" + sessionTimeout + " watcher=" + watcher);
这了一看就是在进行日志的记录。日志我们每个项目都在,而且定义LOG的方式也见过很多。这里看看zookeeper是如何创建的,并且思考why!跟踪的代码如下:
private static final Logger LOG; static { //Keep these two lines together to keep the initialization order explicit LOG = LoggerFactory.getLogger(ZooKeeper.class); Environment.logEnv("Client environment:", LOG); }
LOG的定义是static final 形式的。那么为什么会这样定义呢?那就需要知道这样定义的好处。网上找到了2片文章写的不错。地址:
- http://www.importnew.com/7553.html
- http://www.importnew.com/7440.html
总结一下就是这样定义可以提高性能。
这里除了初始化一个LOG还多了一行代码:Environment.logEnv("Client environment:", LOG);这行代码会对客户端一些环境参数进行日志记录。哪些是环境参数呢?譬如客户端ip地址、内存。那么java中怎么获取这些信息。平时很少会关注这样的方式。今天看源码就当扩展一下对java基础类的了解。
put(l, "host.name",InetAddress.getLocalHost().getCanonicalHostName()); 获取主机地址 // Get memory information. Runtime runtime = Runtime.getRuntime(); int mb = 1024 * 1024; put(l, "os.memory.free",Long.toString(runtime.freeMemory() / mb) + "MB"); jvm空闲的内存 put(l, "os.memory.max",Long.toString(runtime.maxMemory() / mb) + "MB");jvm能获取的最大内存 put(l, "os.memory.total",Long.toString(runtime.totalMemory() / mb) + "MB");jvm当前占用的内存
参考文档:http://7sunet.iteye.com/blog/285007
这行代码引发了我的一个思考:当我们开发的不是web程序时,是否需要对一些环境信息进行一些记录呢。
接下来的2行代码:
watchManager = defaultWatchManager(); watchManager.defaultWatcher = watcher;
创建了一个ZKWatchManager对象,然后把watcher赋值给了ZKWatchManager对象的defaultWatcher 属性。ZKWatchManager实现了ClientWatchManager接口。也就是创建了一个客户端Watcher管理器。
接下来的3行代码:
ConnectStringParser connectStringParser = new ConnectStringParser(
connectString);
首先创建了一个ConnectStringParser类,从字面上可以理解这个类是一个连接字符串的解析类。跟踪代码去了解一下这类的真是面貌:
首先印入眼帘的就是这个类是final类型的。
/** * A parser for ZooKeeper Client connect strings. * * This class is not meant to be seen or used outside of ZooKeeper itself. * * The chrootPath member should be replaced by a Path object in issue * ZOOKEEPER-849. * * @see org.apache.zookeeper.ZooKeeper */ public final class ConnectStringParser
注解写的很清楚,这个类不能被ZooKeeper 以外的类看见或者使用。这个类具体解析连接字符串的逻辑是在构造函数中编写的。这里对字符串的处理并非使用了String自带的split方法。而是使用Zookeeper项目中StringUtils类。看来对字符串的处理是每个项目都必须要做的啊。我见过的项目几乎都会自己写一个名叫StringUtils的类。我们看看Zookeeper怎么实现split方法的。
/** * This method returns an immutable List<String>, but different from String's split() * it trims the results in the input String, and removes any empty string from * the resulting List. * */ public static List<String> split(String value, String separator) { String[] splits = value.split(separator); List<String> results = new ArrayList<String>(); for (int i = 0; i < splits.length; i++) { splits[i] = splits[i].trim(); if (splits[i].length() > 0) { results.add(splits[i]); } } return Collections.unmodifiableList(results); }
从注解上看只是对字符串进行了一些自己业务需求的处理。底层用到的还是String的split方法。但是有一点是值的关注的。那就是返回的是immutable 集合。记得在《重构》这本书有一种思想叫做:Encapsulate Collection (封装集群) 。也就是这个集合在返回之后是不允许再修改的。那么调用者怎么知道不能修改呢?所以这里使用了Collections.unmodifiableList方法返回一个不可变的集合。如果修改则会抛出java.lang.UnsupportedOperationException。使用这个方式之后代码变的更加优美,更加健壮。
我们再回到ConnectStringParser 类中,最终地址是放到了一个集合中:serverAddresses。而这个集合的初始化时这样的:
private final ArrayList<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>();
再次用到了final字段。目的应该就是上面文章中分析的。然而这里使用到了InetSocketAddress。对于IP地址和端口的存放,Zookeeper是存放在了jdk net包中的InetSocketAddress。存放的代码为:serverAddresses.add(InetSocketAddress.createUnresolved(host, port)); 如果是我的话,我可能就直接自己定义一个类表示serverAddress了。我理解使用这个类的好处有:
- 这个类会对IP和端口做响应的校验。保证了serverAddress的正确性。
- 减少了项目的复杂度。jdk有合适的类尽量使用现成的类而不是什么都自己去实现创造。
接下来的4行代码:
hostProvider = aHostProvider;
在ZooKeeper的构造函数中最后一个参数是传递一个HostProvider(接口)。然而一般不会自己去创建,ZooKeeper自己有默认的创建实现:
// default hostprovider
private static HostProvider createDefaultHostProvider(String connectString) {
return new StaticHostProvider(
new ConnectStringParser(connectString).getServerAddresses());
}
最终返回的是StaticHostProvider(HostProvider的实现类)。这个构造方法实现了些什么呢,我们继续跟踪看看:
/** * Most simple HostProvider, resolves only on instantiation. * */ public final class StaticHostProvider implements HostProvider 首先这个类也是final 类型的。其次是我们要看的构造方法: public StaticHostProvider(Collection<InetSocketAddress> serverAddresses) { sourceOfRandomness = new Random(System.currentTimeMillis() ^ this.hashCode()); this.serverAddresses = resolveAndShuffle(serverAddresses); if (this.serverAddresses.isEmpty()) { throw new IllegalArgumentException( "A HostProvider may not be empty!"); } currentIndex = -1; lastIndex = -1; }
这里构造方法大致可以理解为:构造一个随机种子,然后对服务器地址进行解析和打乱操作:
private List<InetSocketAddress> resolveAndShuffle(Collection<InetSocketAddress> serverAddresses) { List<InetSocketAddress> tmpList = new ArrayList<InetSocketAddress>(serverAddresses.size()); for (InetSocketAddress address : serverAddresses) { try { InetAddress ia = address.getAddress(); String addr = (ia != null) ? ia.getHostAddress() : address.getHostString(); InetAddress resolvedAddresses[] = InetAddress.getAllByName(addr); for (InetAddress resolvedAddress : resolvedAddresses) { InetAddress taddr = InetAddress.getByAddress(address.getHostString(), resolvedAddress.getAddress()); tmpList.add(new InetSocketAddress(taddr, address.getPort())); } } catch (UnknownHostException ex) { LOG.warn("No IP address found for server: {}", address, ex); } } Collections.shuffle(tmpList, sourceOfRandomness); return tmpList; }
在StaticHostProvider的成员变量中,有一个地方让我在意了一下:
private final List<InetSocketAddress> oldServers = new ArrayList<InetSocketAddress>( 5);
代码非常的简单。就是创建一个集合。但是它设置了大小。那么问题来了,我们一般创建ArrayList不会去设置大小。那么设置大小和不设置大小有什么区别呢?我们看看集合的构造方法。
public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this(10); }
上面的代码解释了区别。默认是10.然后就这么点不同吗?我在代码中发现了一段如何扩充大小的代码:
/** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
结合注释我们可以理解:
- 当放入集合的元素超出了容器大小的时候,容器是按照构造方法中的initialCapacity参数的倍数来增加的。
- 容器的大小是有阀值的,大小就是Integer.MAX_VALUE;
- Integer.MAX_VALUE=2147483647 int 的最大值。
- MAX_ARRAY_SIZE 数组最大的容量=Integer.MAX_VALUE - 8; 解释是一些vm会保留一些数组的头信息。
- 分配一个大的容器可能会出现OutOfMemoryError异常。
- 这里为什么会减去8.我个人的猜测是。数组是java对象。在Java中,一个空Object对象的大小是8byte。参考文章:http://hllvm.group.iteye.com/group/wiki/2860-JVM
相关推荐
Zookeeper客户端API的初始化过程也非常重要: 在创建`ZooKeeper`对象时,我们传递了一个`Watcher`实例给构造函数。这个`Watcher`作为默认的会话级Watcher,会被保存在客户端的`ZKWatchManager`中的`defaultWatcher`...
通过对ZooKeeper客户端初始化过程、与服务器间的交互逻辑、API调用方式、Watch机制以及容错机制的分析,我们可以更深入地理解ZooKeeper的工作原理及其应用场景。这不仅有助于开发者更好地利用ZooKeeper提供的功能,...
- **ZooKeeper实例创建**:使用`ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)`构造方法初始化ZooKeeper客户端。 - **创建节点**:`create(String path, byte[] data, List<ACL> acl, ...
源码中可能包含`Connect`方法,该方法负责初始化连接,包括设置会话超时时间、处理心跳机制和重连策略。 2. **会话管理**: 客户端与服务器之间的会话是状态感知的,会话超时可能导致重新连接。`ClientTests`可能...
1. **Zookeeper安装**:安装Zookeeper涉及到下载最新稳定版的源码或二进制包,配置环境变量,以及初始化数据目录。在不同的操作系统上(如Linux、Windows)会有不同的安装步骤。 2. **集群配置**:Zookeeper采用...
#### 2.1 初始化Zookeeper客户端 使用`ZooKeeper`类的构造函数,传入服务器地址、会话超时时间及Watcher对象: ```java ZooKeeper zookeeper = new ZooKeeper("server:port", sessionTimeout, watcher); ``` ####...
1. 连接ZooKeeper:每个工作节点都需要初始化一个ZooKeeper客户端,通过提供服务器列表、会话超时时间等参数来建立连接。 2. 创建临时顺序节点:在ZooKeeper的指定路径下,每个节点都会尝试创建一个临时且有序的...
此外,Zookeeper中还存在members节点,它记录了集群中所有的成员信息,以及initialize节点,用来初始化集群配置。 通过这些关键的key值,Patroni集群能够实现对节点状态的监控、故障检测和自动故障转移。当主节点不...
【zooInspector 源码Build.rar】是一个...总的来说,zooInspector的源码分析和编译是理解其工作原理、进行定制开发或者贡献代码的良好起点。通过学习和实践,我们可以更好地利用这个工具来优化Zookeeper的管理和维护。
6. **ZooKeeper**: ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。在Dubbo等分布式环境中,ZooKeeper作为服务...
1. **初始化ZKClient**:创建一个`ZkClient`实例,传入ZooKeeper服务器的地址列表和连接超时时间。 2. **读写数据**:通过`ZkClient`提供的接口,可以方便地读取和修改ZooKeeper中的数据。 3. **监听事件**:注册...
- **简介**:Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目标是简化新 Spring 应用的初始搭建以及开发过程。 - **特点**:通过约定优于配置的原则简化了 Spring 应用的整个搭建过程,使开发者可以专注于...
- **Spring Cloud**:Spring Cloud为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态)操作的常见模式的实现。...
1. **SpringBoot框架**:SpringBoot简化了Spring应用的初始搭建以及开发过程。通过自动配置、起步依赖和内嵌Web服务器,SpringBoot使得创建一个独立的、生产级别的基于Spring的应用变得极其简单。 2. **RESTful API...