Zookeeper重载了几个构造函数,其中构造者可以提供参数最多,可定制性最多的构造函数是
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly)
/** * To create a ZooKeeper client object, the application needs to pass a * connection string containing a comma separated list of host:port pairs, * each corresponding to a ZooKeeper server. * <p> * Session establishment is asynchronous. This constructor will initiate * connection to the server and return immediately - potentially (usually) * before the session is fully established. The watcher argument specifies * the watcher that will be notified of any changes in state. This * notification can come at any point before or after the constructor call * has returned. * <p> * The instantiated ZooKeeper client object will pick an arbitrary server * from the connectString and attempt to connect to it. If establishment of * the connection fails, another server in the connect string will be tried * (the order is non-deterministic, as we random shuffle the list), until a * connection is established. The client will continue attempts until the * session is explicitly closed (or the session is expired by the server). * <p> * Added in 3.2.0: An optional "chroot" suffix may also be appended to the * connection string. This will run the client commands while interpreting * all paths relative to this root (similar to the unix chroot command). * <p> * Use {@link #getSessionId} and {@link #getSessionPasswd} on an established * client connection, these values must be passed as sessionId and * sessionPasswd respectively if reconnecting. Otherwise, if not * reconnecting, use the other constructor which does not require these * parameters. * * @param connectString * comma separated host:port pairs, each corresponding to a zk * server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" * If the optional chroot suffix is used the example would look * like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" * where the client would be rooted at "/app/a" and all paths * would be relative to this root - ie getting/setting/etc... * "/foo/bar" would result in operations being run on * "/app/a/foo/bar" (from the server perspective). * @param sessionTimeout * session timeout in milliseconds * @param watcher * a watcher object which will be notified of state changes, may * also be notified for node events * @param sessionId * specific session id to use if reconnecting * @param sessionPasswd * password for this session * @param canBeReadOnly * (added in 3.4) whether the created client is allowed to go to * read-only mode in case of partitioning. Read-only mode * basically means that if the client can't find any majority * servers but there's partitioned server it could reach, it * connects to one in read-only mode, i.e. read requests are * allowed while write requests are not. It continues seeking for * majority in the background. * * @throws IOException in cases of network failure * @throws IllegalArgumentException if an invalid chroot path is specified */ //sessionId和sessionPasswd用于重连, public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly) throws IOException { LOG.info("Initiating client connection, connectString=" + connectString + " sessionTimeout=" + sessionTimeout + " watcher=" + watcher + " sessionId=" + Long.toHexString(sessionId) + " sessionPasswd=" + (sessionPasswd == null ? "<null>" : "<hidden>")); //设置作为Zookeeper的默认Watcher watchManager.defaultWatcher = watcher; //解析链接串,链接是IP:Port,用逗号分割的串 ConnectStringParser connectStringParser = new ConnectStringParser( connectString); HostProvider hostProvider = new StaticHostProvider( connectStringParser.getServerAddresses()); //封装IO和Event相关的线程 cnxn = new ClientCnxn(connectStringParser.getChrootPath(), hostProvider, sessionTimeout, this, watchManager, getClientCnxnSocket(), sessionId, sessionPasswd, canBeReadOnly); cnxn.seenRwServerBefore = true; // since user has provided sessionId cnxn.start(); }
ConnectionStringParser类
public ConnectStringParser(String connectString) { // parse out chroot, if any int off = connectString.indexOf('/'); if (off >= 0) { String chrootPath = connectString.substring(off); // ignore "/" chroot spec, same as null if (chrootPath.length() == 1) { this.chrootPath = null; } else { PathUtils.validatePath(chrootPath); this.chrootPath = chrootPath; } connectString = connectString.substring(0, off); } else { this.chrootPath = null; } String hostsList[] = connectString.split(","); for (String host : hostsList) { int port = DEFAULT_PORT; int pidx = host.lastIndexOf(':'); if (pidx >= 0) { // otherwise : is at the end of the string, ignore if (pidx < host.length() - 1) { port = Integer.parseInt(host.substring(pidx + 1)); } host = host.substring(0, pidx); } serverAddresses.add(InetSocketAddress.createUnresolved(host, port)); } }
ConnectionStringParser类主要是构造函数,构造函数包括两个功能,1.从connectString中解析出chrootPath 2.从connectString解析出Zookeeper服务器列表,解析成InetSocketAddress列表
chrootPath的语法同znode的path一样,以/开头,chrootPath有什么功能呢?
HostProvider和StaticHostProvider类
/** * A set of hosts a ZooKeeper client should connect to.//Zookeeper客户端尝试建立Zookeeper服务器的服务器列表 * * Classes implementing this interface must guarantee the following://HostProvider类实现规范 * * * Every call to next() returns an InetSocketAddress. So the iterator never //next是循环或者随即选取,但是不能返回null * ends. * * * The size() of a HostProvider may never be zero.//HostProvider的服务器列表不能为空 * * A HostProvider must return resolved InetSocketAddress instances on next(), * but it's up to the HostProvider, when it wants to do the resolving. //HostProvider的next返回resolved InetSocketAddress实例,HostProvider的实现者负责解析InetSocketAddress * * Different HostProvider could be imagined: //HostProvider的可能实现 * * * A HostProvider that loads the list of Hosts from an URL or from DNS //域名或者IP列表 * * A HostProvider that re-resolves the InetSocketAddress after a timeout. * * A HostProvider that prefers nearby hosts. */ public interface HostProvider { public int size(); /** * The next host to try to connect to. * * For a spinDelay of 0 there should be no wait. * * @param spinDelay * Milliseconds to wait if all hosts have been tried once. */ public InetSocketAddress next(long spinDelay); /** * Notify the HostProvider of a successful connection. * * The HostProvider may use this notification to reset it's inner state. */ public void onConnected(); }
StaticHostProivder类的看点是它的构造方法,构造方法实现Resolved Address和Unresolved Address之间的转换,什么是解析了的地址(Resolved Address),什么是未解析的地址(Unresolved Address)呢?
/** * Constructs a SimpleHostSet. * * @param serverAddresses * possibly unresolved ZooKeeper server addresses //传入的参数有可能是没有解析的Zookeeper服务器地址 * @throws UnknownHostException * @throws IllegalArgumentException * if serverAddresses is empty or resolves to an empty list */ public StaticHostProvider(Collection<InetSocketAddress> serverAddresses) throws UnknownHostException { //对传入的InetSocketAddress进行解析 for (InetSocketAddress address : serverAddresses) { InetAddress ia = address.getAddress(); InetAddress resolvedAddresses[] = InetAddress.getAllByName((ia!=null) ? ia.getHostAddress(): address.getHostName()); for (InetAddress resolvedAddress : resolvedAddresses) { // If hostName is null but the address is not, we can tell that // the hostName is an literal IP address. Then we can set the host string as the hostname // safely to avoid reverse DNS lookup. // As far as i know, the only way to check if the hostName is null is use toString(). // Both the two implementations of InetAddress are final class, so we can trust the return value of // the toString() method. if (resolvedAddress.toString().startsWith("/") && resolvedAddress.getAddress() != null) { this.serverAddresses.add( new InetSocketAddress(InetAddress.getByAddress( address.getHostName(), resolvedAddress.getAddress()), address.getPort())); } else { this.serverAddresses.add(new InetSocketAddress(resolvedAddress.getHostAddress(), address.getPort())); } } } if (this.serverAddresses.isEmpty()) { throw new IllegalArgumentException( "A HostProvider may not be empty!"); } //对服务器列表进行洗牌打乱次序,传说中可以提升可靠性,这里的原因我猜想是在写服务器列表的时候,往往是把按机房一个一个的写,A机房3个Zookeeper,B机房4个Zookeeper,这7个可能是先写完A这里的3个,然后写B的4个 //打乱次序可以使得A挂了,找到B的可能性大点 Collections.shuffle(this.serverAddresses); }
ClientCnxn是客户端跟Zookeeper通信的核心类,单独进行分析