Zookeeper源代码分析之HostProvider地址列表管理器

    首先我们的知道Zookeeper 客户端是怎么知道连那台服务器, 或者当前连接的机器挂掉需要重连,

    在ConnectionStringParser解释器中会对服务器地址做一个简单的处理,并将服务器地址和相应的端口封装成一个InetSocketAddress对象,以ArrayList形式保存在ConnectStringParser. serverAddresses集合中,然会返回处理后的地址信息会进一步通过StaticHostProvider类中;

/**
     *
     * @throws IllegalArgumentException
     *             for an invalid chroot path.
     *  //解析链接串,链接是IP:Port,用逗号分割的串 
     */
    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;
        }
 
        List<String> hostsList = split(connectString,",");
        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));
        }
    }

        ConnectStringParser做两个主要的处理,解析chrootPath和解析服务器地址列表。

         Chroot:客户端隔离命名空间

               在3.2.0之后版本的zookeeper中,添加了“Chroot”特性,该特性允许每个客户端为自己设置已给命名空间。如果一个zookeeper客户端设置了Chroot,那么该客户端对服务器的任何操作,都将会被限制在自己的命名空间下。

        客户端可以通过在connectString中添加后缀的方式来设置Chroot,如下所示:

                192.168.0.1:2181,192.168.0.2:2181,192.168.0.3:2181/apps/X

         将这样一个connectString传入客户端的ConnectStringParser后就能够解析出Chroot并保存在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. 
     *  返回一个服务器地址InetSocketAddress
     * @param spinDelay 
     *            Milliseconds to wait if all hosts have been tried once. 
     */  
    public InetSocketAddress next(long spinDelay);  
  
    /** 
     * Notify the HostProvider of a successful connection. 
     *  回调方法,链接成功,就会调用这个方法来通知HostProvider
     * The HostProvider may use this notification to reset it's inner state. 
     */  
    public void onConnected();  
}

解析服务器地址

        针对ConnectStringParser.serverAddresses集合中哪些没有被解析的服务器地址,staticHostProvider首先会对这些地址逐个进行解析,然后再放入serverAddresses结合中去。同时使用Collections工具类的shuffle方法来将这个服务器地址列表进行随机的打算。

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的next()方法,能够从staticHostProvider中获取一个可用的服务器地址。这个next()方法并非简单地从serverAddresses中一次获取一个服务器地址,而是现将随机打散后的服务器地址列表拼装成一个环形的循环队列,如下图,注意这个随机过程是一次性的,也就是说,之后的使用过程中一直是按照这样的顺利来获取服务器地址的。

     blob.png                                        

 

      举个例子来说,假如客户端传入这样一个地址列表:“host1,host2,host3,host4,host5”。经过一轮随机打散后,可能的一种顺序变成了“host2,host4,host1,host5,host1”,并且形成了上图的循环队列。此外HostProvider还会为该环形队列创建两个游标:currentIndex俄lastIndex。currentIndex标识环形队列中当前遍历到的那个元素位置,lastIndex则表示当前正在使用的服务器地址位置。初始化的时候,curre和lastIndex的值都为-1。

        在每次尝试获取一个服务器地址的时候,都会首先将currentIndex游标向前移动1位,如果发现游标移动超过了整个地址列表的长度,那么就重置为0,回到开始的位置重新开始,这样一来,就实现了循环队列。当然对于那些服务器地址列表提供的比较少的场景,staticHostProvider中做了一个小技巧,就是如果发现当前游标的位置和上次已经使用过的地址位置一样,即当currentIndex和lastIndex游标值相同时,就进行spinDelay毫秒时间的等待。        

        总的来时,staticHostProvider就是不断从上图所示的环形地址列表队列中去获取已给地址,整个过程非常类似于“Round Robin”的调度策略。

 

对于HostProvider的几个设想

           staticHostProvider只是zookeeper官方提供的对于地址列表管理器的默认实现方式,也是最通用和最简单的一种实现方式。读者如果有些要的话,满足接口要求的前提下,可以实现自己的服务器地址列表管理器。

1.配置文件方式

          实现从配置文件中加载服务器地址列表。

2.动态变更的地址列表管理器

          从DNS或一个配置管理中心上解析出zookeeper服务器地址列表。

          DNS 解析的方案:

               http://www.cnblogs.com/kakaxisir/p/6701408.html

3.实现同机房优先策略

          在目前大规模的分布式系统设计中,多机房的情况下,考虑引入“同机房优先”的策略。所谓的“同机房优先”是指服务的消费者优先小飞同一个机房中提供的服务。举个例子来说,一个服务F在杭州机房和北京机房中都有部署,那么对于杭州机房中的服务消费者,会优先调用杭州机房中的服务,对于北京机房的客户端也一样。

发表评论