ZooKeeper一次会话的创建过程

ZooKeeper一次会话的创建过程

初始化阶段

    private final ZKWatchManager watchManager = new ZKWatchManager();    //2(直接被默认初始话的)
    
	public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
            boolean canBeReadOnly)
        throws IOException
    {
      //1
        watchManager.defaultWatcher = watcher; //1

        ConnectStringParser connectStringParser = new ConnectStringParser(
                connectString);
        HostProvider hostProvider = new StaticHostProvider(
                connectStringParser.getServerAddresses()); //3
        cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
                hostProvider, sessionTimeout, this, watchManager,
                getClientCnxnSocket(), canBeReadOnly); //4
        cnxn.start(); //6
    }
    
	//pendingQueue/outgoingQueue时在ClientCnxn类中直接被初始化的(默认初始化)
    **private final LinkedList<Packet> pendingQueue = new LinkedList<Packet>();
   ** private final LinkedList<Packet> outgoingQueue = new LinkedList<Packet>();
  1. 初始化ZooKeeper对象
    通过调用ZooKeeper的构造方法来实例化一个ZooKeeper对象,在初始化过程中,会创建一个客户端的Watcher管理器:ClientWatchManager
    private final ZKWatchManager watchManager=new ZKWatchManager ();
  2. 设置会话的默认Watcher
    如果在构造方法中传入一个Watcher对象,则ZooKeeper将默认的watcher设置为该Watcher对象
  3. 构造ZooKeeper服务器地址列表管理器: HostProvider
    在ZooKeeper的构造函数中,客户端会将器存放在服务器地址列表管理器HostProvider中
  4. 创建并初始化客户端网络连接器: ClientCnxn
    ClientCnxn用来管理客户端与服务器的网络交互。另外在创建ClientCnxn的同时,还会初始化客户端的两个核心队列outgoingQueue和pendingQueue;分别作为客户端请求发送队列和服务端响应的等待队列
  5. 初始化SendThread和EventThread
    SendThread用于管理客户端和服务端之间的所有网络I/O,后者则用于处理客户端的事件(当收到服务器响应时,产生的序列)

会话创建阶段

  1. 启动SendThread和EventThread
    SendThread搜寻会判断当前客户端的状态,进行一系列清理性工作,为客户端发送“会话创建”请求做准备

  1. 获取一个服务器的地址(这个应该是在请求之前获取的)
    SendThread首先需要获取一个ZooKeeper服务器的目标地址,通常是从HostProvider中随机获取出一个地址(根据源码分析,是依次获取的),然后与ZooKeeper服务器之间的TCP连接
public InetSocketAddress next(long spinDelay) {
        currentIndex = ++currentIndex % serverAddresses.size();//可以看到是依次获取的服务端地址。进行请求的
		.......
        InetSocketAddress curAddr = serverAddresses.get(currentIndex);
        .......
    }
  1. 创建一个TCP连接
    获取到一个服务器地址后,ClientCnxnSocket负责和服务器创建一个TCP长连接
	 startConnect(serverAddress); //建立连接
    
     private void startConnect(InetSocketAddress addr) throws IOException {
			.......
            clientCnxnSocket.connect(addr); //这个应该是最重要的
     }

    @Override
    void connect(InetSocketAddress addr) throws IOException {
        SocketChannel sock = createSock();
        try {
           registerAndConnect(sock, addr);  //在NIO中注册了该socket
        }
    }
      void registerAndConnect(SocketChannel sock, InetSocketAddress addr) 
   	   {
        sockKey = sock.register(selector, SelectionKey.OP_CONNECT);
        boolean immediateConnect = sock.connect(addr);
        if (immediateConnect) {
            sendThread.primeConnection(); //这个最关键了 ste 9
        }
    }
     void primeConnection()  {
            isFirstConnect = false;
            long sessId = (seenRwServerBefore) ? sessionId : 0;
            ConnectRequest conReq = new ConnectRequest(0, lastZxid,
                    sessionTimeout, sessId, sessionPasswd);
			......
// SendThread会根据当前客户端的实际设置,构造一个ConnectReques请求,即**ConnectRequest
       }
  1. 构造ConnectedRequest请求
    在TCP连接创建完毕后,可能会有人认为(就是我),这样是否说明已经和ZooKeeper服务器完成连接了呢? 其实不然(打脸),步骤8只是纯粹地从网络TCP层面完成了客户端与服务端之间的socket连接,但未完成ZooKeeper客户端的会话创建.(需要知道传输层上方存储一个会话层)

  2. 发送请求
    当客户端请求准备完毕后,就可以开始向服务端发送请求了。ClientCnxnSocket负责从outgoingQueue中取出一个待发送的Packet对象,并将其序列化为ByteBuffer,向服务端进行发送。


响应处理阶段

  1. 接收服务端响应
    ClientCnxnSocket接收到服务端的响应后,会首先判断当前的客户端状态时否“已初始化”,如果尚未初始化,那么就认为该响应一定时会话创建请求的响应,直接交由readConnectResult方法来处理该响应
// 这是在SendThread中的run代码中的一句// this code 是在一个while(connect.isAlive)的循环中
    clientCnxnSocket.doTransport(to, pendingQueue, outgoingQueue, ClientCnxn.this);

    void doTransport(int waitTimeOut, List<Packet> pendingQueue, LinkedList<Packet> outgoingQueue, ClientCnxn cnxn)
            throws IOException, InterruptedException {
        selector.select(waitTimeOut);
        Set<SelectionKey> selected;
        synchronized (this) {
            selected = selector.selectedKeys(); //典型的epoll() 多路复用模式
        }
        下面这段话表明:在下面的代码中都是非阻塞的
        // Everything below and until we get back to the select is
        // non blocking, so time is effectively a constant. That is
        // Why we just have to do this once, here
        updateNow();
        for (SelectionKey k : selected) { //选择事件完成的;IO完成的,服务响应收到的
            SocketChannel sc = ((SocketChannel) k.channel());
            if ((k.readyOps() & SelectionKey.OP_CONNECT) != 0) {
                if (sc.finishConnect()) { //这里是不是重连的意思
                    updateLastSendAndHeard();
                    sendThread.primeConnection();
                }
            } else if ((k.readyOps() & (SelectionKey.OP_READ | SelectionKey.OP_WRITE)) != 0) {
                doIO(pendingQueue, outgoingQueue, cnxn); //这句话是重点;当selector的状态是read or write -> 进行IO读取 //在doIO中会发现 step11中介绍的readConnectResult方法
            }
        }
        if (sendThread.getZkState().isConnected()) {
            synchronized(outgoingQueue) {
                if (findSendablePacket(outgoingQueue,
                        cnxn.sendThread.clientTunneledAuthenticationInProgress()) != null) {
                    enableWrite();
                }
            }
        }
        selected.clear();
    }
	
  1. 处理Response
    在ReadConnectResult中会对接收到的服务端响应进行反序列化操作,从而得到ConnectResponse对象,并从中获取sessionID等信息
    void readConnectResult() throws IOException {
        ByteBufferInputStream bbis = new ByteBufferInputStream(incomingBuffer);
        BinaryInputArchive bbia = BinaryInputArchive.getArchive(bbis);
        ConnectResponse conRsp = new ConnectResponse();
        conRsp.deserialize(bbia, "connect"); //反序列话,生成ConnectResponse对象
        
		........
		
        this.sessionId = conRsp.getSessionId(); //step12 获得sessionID
        sendThread.onConnected(conRsp.getTimeOut(), this.sessionId,
                conRsp.getPasswd(), isRO);
    }
  1. 连接成功
    连接成功后,一方面需要通知SendThread线程,进一步对客户端进行会话参数的更新;另一方面,需要通知地址管理器HostProvider当前成功连接的服务器地址
        void onConnected(int _negotiatedSessionTimeout, long _sessionId,
                byte[] _sessionPasswd, boolean isRO) throws IOException {
                ...... //可以看到下面都在更新SendThread中的参数
            readTimeout = negotiatedSessionTimeout * 2 / 3;
            connectTimeout = negotiatedSessionTimeout / hostProvider.size();
            hostProvider.onConnected(); //通知HostProvider当前成功连接的服务器地址
            sessionId = _sessionId;
            sessionPasswd = _sessionPasswd;
            state = (isRO) ?
                    States.CONNECTEDREADONLY : States.CONNECTED;
            seenRwServerBefore |= !isRO;
            KeeperState eventState = (isRO) ?
                    KeeperState.ConnectedReadOnly : KeeperState.SyncConnected;
            eventThread.queueEvent(new WatchedEvent(
                    Watcher.Event.EventType.None,
                    eventState, null));  //可以看到这里就是step15中所提到的事件
        }
  1. 生成事件: SyncConnected-None
    为了能够让上层应用感知到会话的成功创建,SendThread会生成一个事件SyncConnected-None,代表客户端与服务器会话创建成功,并传递给EventThread线程
  2. 查询Watcher
    EventThread线程收到事件后,会从ClientWatchManager管理器中查询对应的Watcher-》这里就有疑惑了 如何查找呢(首先服务端发送到客户端时通过IP地址的,那么当服务端发送到客户端是,其实客户端所注册的Watcher都可以查找到的。并且不会存在其他客户端的干扰
        void readResponse(ByteBuffer incomingBuffer) throws IOException //SendThread中的函数目的是读取服务器的响应,其中有下面这一行代码
        WatchedEvent we = new WatchedEvent(event); //we为响应的事件
        eventThread.queueEvent( we ); //将we事件加入到EventThread中
        
        public void queueEvent(WatchedEvent event) {
			......
            WatcherSetEventPair pair = new WatcherSetEventPair(
                    watcher.materialize(event.getState(), event.getType(),
                            event.getPath()),
                            event); //可以看到这里有一个materialize函数 ,其中watcher就是
                            ......
            waitingEvents.add(pair);
        }
上面的materialize被ZooKeeper实现,其中watcher就是ClientWatchManager
该方法就是先了查找出对应的Wactcher,并进行回调

  1. 处理事件
    EventThread不断地从waitingEvents队列中取出待处理的Watcher对象,然后直接调用该对象的process接口方法,已达到Watcher的目的

总结: ZooKeeper客户端完整的一次会话就完成了。!!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值