Zookeeper内部实现分布式数据一致性(客户端)(六)

Zookeeper的客户端主要由以下几个核心组件:

  Zookeeper实例:客户端入口;

  ClientWatchManager:客户端Watcher管理器;

  HostProvider:客户端地址列表管理器。

  ClientCnxn:客户端核心线程。其内部包含两个线程:SendThread和EventThread。前一个是I/O线程,主要负责Zookeeper客户端和服务端之间的网络I/O通信,后一个是事件线程,主要负责符服务端事件进行处理。

  <1> 先从构造函数入手看一下Zookeeper客户端的初始化与启动环节; 

    1)初始化Zookeeper对象;

    2)设置默认Watcher;

    3)构造Zookeeper服务器地址列表管理器:HostProvider

    4)创建并初始化客户端网络连接器,ClientCnxn

    5)初始化SendThread和EventThread;

    下面从源码来看这个流程;

//构造参数传入,服务器地址列表,超时时间,自定义Watcher;
ZooKeeper zookeeper = new ZooKeeper("127.0.0.1:2181", 5000,new ZooKeeper_Constructor_Usage_Simple());

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)throws IOException
    {
        this(connectString, sessionTimeout, watcher, false);
    }

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly) throws IOException {
        this(connectString, sessionTimeout, watcher, canBeReadOnly,
                createDefaultHostProvider(connectString));
    }

//说一下这个createDefaultHostProvider,创建客户单列表管理器;
private static HostProvider createDefaultHostProvider(String connectString) {
        return new StaticHostProvider( new ConnectStringParser(connectString).getServerAddresses());
    }


//然后看一下ConnectStringParser方法怎么解析:省略了一些不是很关键的代码;
    public ConnectStringParser(String connectString) {
        int off = connectString.indexOf('/');
        if (off >= 0) {
             …………

            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) {
                if (pidx < host.length() - 1) {
                    port = Integer.parseInt(host.substring(pidx + 1));
                }
                host = host.substring(0, pidx);
            }
            // 封装成InetSocketAddress,放入Arrylist中;
          serverAddresses.add(InetSocketAddress.createUnresolved(host, port));
        }
    }    
//将ArrayList<InetSocketAddress>封装  
 public StaticHostProvider(Collection<InetSocketAddress> serverAddresses) {
        init(serverAddresses,
                System.currentTimeMillis() ^ this.hashCode(),
                new Resolver() {
            @Override
            public InetAddress[] getAllByName(String name) throws UnknownHostException {
                return InetAddress.getAllByName(name);
            }
        });
    }
//先记住init方法中的参数;
//init方法中的Resolver是一个内部类,非原生库中东西;
//InetAddress.getAllByName(host):通过host来获取一个可用地址;
private void init(Collection<InetSocketAddress> serverAddresses, long randomnessSeed, Resolver resolver) {
         //生成一个随机数,将多个服务器地址打算,为生成一个环行队列准备
        this.sourceOfRandomness = new Random(randomnessSeed);
        this.resolver = resolver;
        if (serverAddresses.isEmpty()) {
            throw new IllegalArgumentException(
                    "A HostProvider may not be empty!");
        }
       //shuffle方法最终调用Collections.shuffle方法,执行地址打散;
        this.serverAddresses = shuffle(serverAddresses);
        //表示当前遍历到的那个地址位置,
        currentIndex = -1;
        //表示当前正在使用哪个服务器位置;
        lastIndex = -1;

       //循环的实现:
      //在每一次尝试获取一个服务器地址时,都会首先将currentIndex向前    
      //移动1,如果发现右边移动超过了整个地址列表的长度,那就重置为0
      //这样就实现了循环;
     
      //如果lastIndex和currentIndex相等,即如果发现当前游标位置和上    
      // 次使用过的地址位置一样,就行spinDelay的时间等待
    }  

 

总结一下:此时源码才分析到:createDefaultHostProvider方法返回:以上只做了一件事:就是构造了服务器地址列表;

  有个细节:

public interface HostProvider {
    public int size();
    
    //主要介绍一个这个next方法:返回一个服务器地址,以便客户端连接;  
    public InetSocketAddress next(long spinDelay);
   
    //回调方法,如果客户端与服务器成功创建连接,就通过这个方法来通知
    //HostProvider 
    public void onConnected();

    boolean updateServerList(Collection<InetSocketAddress> serverAddresses,
        InetSocketAddress currentHost);
}

//
StaticHostProvider实现了HostProvider,对next方法做了实现,上述将了lastIndex和currentIndex的逻辑就在此next方法中;

接着往下走

 public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
            boolean canBeReadOnly, HostProvider aHostProvider)
            throws IOException {
        this(connectString, sessionTimeout, watcher, canBeReadOnly,
                aHostProvider, null);
 }
 public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, HostProvider aHostProvider,  ZKClientConfig clientConfig) throws IOException {
     
        if (clientConfig == null) {
            clientConfig = new ZKClientConfig();
        }
        this.clientConfig = clientConfig;
       //创建一个WatchManager;
        watchManager = defaultWatchManager();
       //注册Watcher当做默认watcher
        watchManager.defaultWatcher = watcher;
        ConnectStringParser connectStringParser = new ConnectStringParser(connectString);
        hostProvider = aHostProvider;
        cnxn = createConnection(connectStringParser.getChrootPath(),
                hostProvider, sessionTimeout, this, watchManager,
                getClientCnxnSocket(), canBeReadOnly);
        cnxn.start();
    }

 关注 createConnection 方法:

 protected ClientCnxn createConnection(String chrootPath,
            HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper,
            ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket,
            boolean canBeReadOnly) throws IOException {
        return new ClientCnxn(chrootPath, hostProvider, sessionTimeout, this,
                watchManager, clientCnxnSocket, canBeReadOnly);
    }
 public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper,
            ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket, boolean canBeReadOnly)
            throws IOException {
        this(chrootPath, hostProvider, sessionTimeout, zooKeeper, watcher,
             clientCnxnSocket, 0, new byte[16], canBeReadOnly);
    }

在初始化之前(ClientCnxn):

public class ClientCnxn {

    private static final int SET_WATCHES_MAX_LENGTH = 128 * 1024;

    private final LinkedList<Packet> pendingQueue = new LinkedList<Packet>();

    private final LinkedBlockingDeque<Packet> outgoingQueue = new LinkedBlockingDeque<Packet>();

     ………………
    //初始化了两个队列,outgoingQueue队列和pendingQueue队列,分别是客户端待发送请求队列和服务端响应的等待队列;
}
public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper,
            ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket,
            long sessionId, byte[] sessionPasswd, boolean canBeReadOnly) {
        this.zooKeeper = zooKeeper;
        this.watcher = watcher;
        this.sessionId = sessionId;
        this.sessionPasswd = sessionPasswd;
        this.sessionTimeout = sessionTimeout;
        this.hostProvider = hostProvider;
        this.chrootPath = chrootPath;

        connectTimeout = sessionTimeout / hostProvider.size();
        readTimeout = sessionTimeout * 2 / 3;
        readOnly = canBeReadOnly;

        sendThread = new SendThread(clientCnxnSocket);
        eventThread = new EventThread();
        this.clientConfig=zooKeeper.getClientConfig();
        initRequestTimeout();
    }

主要关注SendThread和EventThread线程;

  SendThread管理服务端和客户端所有IO连接。clientCnxnSocket是一个底层网络处理器。

  EventThread和SendThread继承自ZookeeperThread--->Thread;

  SendThread的run方法中包含了所有客户端处理的核心;这里不列出run方法,大致说一些作用;

    zookeeper的最后一行是cnxn.start();就是启动以上两个线程:

 进入会话创建的正真阶段(run方法的逻辑):

  SendThread:

    1.判断当前客户端状态,进行一系列清理工作,为发起会话做准备;

    2.获取一个服务端地址(HostProvider的next()方法);

    3.创建TCP长连接;

    4.构造ConnectRequest请求(建立TCP连接不等于zookeeper客户端会话创建完成)。将该请求包装成Packet对象,放入outgoingQueue队列。

    5.发送请求;

   响应处理阶段:

    6.还是ClientCnxnSocket:接收到服务端的响应后,判断客户端是否已初始化,即该响应是会话创建的请求响应,还是其他响应;

    7.处理响应:得到ConnectResponse对象,获取Zookeeper服务端分配的会话sessionId.

    8.连接成功后,通知SendThread线程对客户端进行会话参数设置,更新客户端状态,另外通知HostProvider当前成功连接的服务器地址。

    9.生成事件,让上层应用感知:SendThread生成事件(SyncConnected-None),表示客户端与会话服务器创建成功,将该事件传递给EventThread线程。

    10.EventThread收到事件后,从ClientWatchManager中查询对应Watcher,将其放到EventThread的waitingEvents队列当中。

    11.处理事件:EventThread不断从waitingEvents队列中取出待处理的Watcher对象,然后调用process方法;

至此,客户端会话创建包括处理逻辑都已结束。

    

 

posted @ 2019-11-19 19:02  艾尔猿  阅读(321)  评论(0)    收藏  举报