zookeeper源码分析-事件注册及通信原理

zookeeper源码分析-事件注册及通信原理


欢迎查看Eetal的第二十六篇博客–zookeeper源码分析-事件注册及通信原理

zookeeper基本命令代码

依赖

  		<groupId>org.apache.zookeeper</groupId>
  		<artifactId>zookeeper</artifactId>
  		<version>3.5.5</version>

demo代码

		final CountDownLatch coutnDownLatch = new CountDownLatch(1);
		ZooKeeper zookeeper = new ZooKeeper("192.168.156.171:2181,192.168.156.171:2182,192.168.156.171:2183",
								3000,
								new Watcher() {
   

									public void process(WatchedEvent event) {
   
										//注册默认的watcher,当连接上时,会触发该watcher
										//对该zookeeper实例的节点进行操作,watcher填true使用此默认watcher
										System.out.println("defaultWatcher process eventType : "+event.getPath()+"-"+event.getType());
										coutnDownLatch.countDown();
									}
			
		});
		System.out.println(zookeeper.getState());//connecting---连接中
		coutnDownLatch.await();
		System.out.println(zookeeper.getState());//connected---已连接
		//createMode 模式,PERSISTENT---持久化节点
		Stat stat = new Stat();
		/**
		 * 节点状态,包含version
		 * create、delete、setData语句会更新节点stat
		 * 并且create、delete子节点会更新父节点的stat
		 * 在create、getData语句传入stat,执行后会把最新的stat属性复制到传入的stat对象中
		 * setData、delete语句传入stat的version,通过乐观锁验证版本号保证数据同步,版本不对时抛出BadVersionException(如果version是-1,代表不进行版本验证)
		 * setData、exists语句返回值为最新的stat
		 */
		zookeeper.delete("/java",-1);
		String path = zookeeper.create("/java", "2019-07-29".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT,stat);//返回路径
		System.out.println("path : "+path);
		byte[] byteV = zookeeper.getData(path, null, stat);
		System.out.println("version:"+stat.getVersion()+"-"+new String(byteV));
		/**
		 * 
		 * OPEN_ACL_UNSAFE---完全开放
		 * CREATOR_ALL_ACL---创建者拥有权限
		 * READ_ACL_UNSAFE---开放读权限
		 * ANYONE_ID_UNSAFE---有验证的用户就不被全部权限
		 * AUTH_IDS---只对指定用户开放
		 */
		stat = zookeeper.setData(path, "2019-07-30".getBytes(), stat.getVersion());
		zookeeper.getData(path, true, stat);//true代表注册watcher,使用默认watcher即创建Zookeeper实例时注册的watcher,只响应一次事件
		zookeeper.getData(path, new Watcher() {
   //注册专用的watcher,只响应一次事件
			public void process(WatchedEvent event) {
   
			}
		}, stat);
		
		zookeeper.setData(path, "2019-07-30".getBytes(), stat.getVersion());
		byteV = zookeeper.getData(path, null, stat);
		System.out.println("version:"+stat.getVersion()+"-"+new String(byteV)); 

源码分析

Zookeeper对于集群信息提供jmx的监控支持,详情欢迎查看本人另一篇博客——zookeeper源码分析-选举算法
而对于客户端连接使用的是netty做socket的io处理,实现类为ClientCnxnSocketNetty,还有另一个可选的nio实现类是ClientCnxnSocketNIO
Zookeeper构造函数,实例化最终会到下面的构造函数,根据默认配置,创建一个ZKWatchManager对象,将默认wathcer注入该wathcerManager的defaultWatcher成员
将该watcherManager以及包含了集群信息的zookeeper对象传递用于实例化一个ClientCnxn对象,并启动

    protected ZKWatchManager defaultWatchManager() {
   
        return new ZKWatchManager(getClientConfig().getBoolean(ZKClientConfig.DISABLE_AUTO_WATCH_RESET));
    }
    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 = defaultWatchManager();
        watchManager.defaultWatcher = watcher;
        ConnectStringParser connectStringParser = new ConnectStringParser(
                connectString);
        hostProvider = aHostProvider;

        cnxn = createConnection(connectStringParser.getChrootPath(),
                hostProvider, sessionTimeout, this, watchManager,
                getClientCnxnSocket(), canBeReadOnly);
        cnxn.start();
    }

    // @VisibleForTesting
    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);
    }

ClientCnxn在实例化时会实例化两个线程成员,作为发送消息的线程以及处理事件的线程
因为zookeeper内部使用netty所以发送消息的响应式异步的,当netty收到响应时,将响应封装为一个event,数据的封装返回在evetThread处理
要发送的包由一个阻塞队列保存,已发送待确认的包由一个链表保存

    private final LinkedList<Packet> pendingQueue = new LinkedList<Packet>();//等待响应的队列

    private final LinkedBlockingDeque<Packet> outgoingQueue = new LinkedBlockingDeque<Packet>();//要发送的队列

    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();
    }

    public void start() {
   
        sendThread.start();
        eventThread.start();
    }

sendThread的处理逻辑为,当服务器状态为活跃时,循环检查计算心跳间隔时间,添加pin包到待发送队列,后面发送以保持连接
每一轮最后调用clientCnxnSocket.doTransport(to, pendingQueue, ClientCnxn.this)去开始传输

 public void run() {
   
            clientCnxnSocket.introduce(this, sessionId, outgoingQueue);
            clientCnxnSocket.updateNow();
            clientCnxnSocket.updateLastSendAndHeard();
            int to;
            long lastPingRwServer = Time.currentElapsedTime();
            final int MAX_SEND_PING_INTERVAL = 10000; //10 seconds
            InetSocketAddress serverAddress = null;
            while (state.isAlive()) {
   
                try {
   
                    if (!clientCnxnSocket.isConnected()) {
   
                        // don't re-establish connection if we are closing
                        if (closing) {
   
                            break;
                        }
                        if (rwServerAddress != null) {
   
                            serverAddress = rwServerAddress;
                            rwServerAddress = null;
                        } else {
   
                            serverAddress = hostProvider.next(1000);
                        }
                        startConnect(serverAddress);
                        clientCnxnSocket.updateLastSendAndHeard();
                    }

                    if (state.isConnected()) {
   
                        // determine whether we need to send an AuthFailed event.
                        if (zooKeeperSaslClient != null) {
   
                            boolean sendAuthEvent = false;
                            if (zooKeeperSaslClient.getSaslState() == ZooKeeperSaslClient.SaslState.INITIAL) {
   
                                try {
   
                                    zooKeeperSaslClient.initialize(ClientCnxn.this);
                                } catch (SaslException e) {
   
                                   LOG.error("SASL authentication with Zookeeper Quorum member failed: " + e);
                                    state = States.AUTH_FAILED;
                                    sendAuthEvent = true;
                                }
                            }
                            KeeperState authState = zooKeeperSaslClient.getKeeperState();
                            if (authState != null) {
   
                                if (authState == KeeperState.AuthFailed) {
   
                                    // An authentication error occurred during authentication with the Zookeeper Server.
                                    state = States.AUTH_FAILED;
                                    sendAuthEvent = true;
                                } else {
   
                                    if (authState == KeeperState.SaslAuthenticated) {
   
                                        sendAuthEvent = true;
                                    }
                                }
                            }

                            if (sendAuthEvent) {
   
                                eventThread.queueEvent(new WatchedEvent(
                                      Watcher.Event.EventType.None,
                                      authState,null));
                                if (state == States.AUTH_FAILED) {
   
                                  eventThread.queueEventOfDeath();
                                }
                            }
                        }
                        to = readTimeout - clientCnxnSocket.getIdleRecv();
                    } else {
   
                        to = connectTimeout - clientCnxnSocket.getIdleRecv();
                    }
                    
                    if (to <= 0) {
   
                        String warnInfo;
                        warnInfo = "Client session timed out, have not heard from server in "
                            + clientCnxnSocket.getIdleRecv()
                            + "ms"
                            + " for sessionid 0x"
                            + Long.toHexString(sessionId);
                        LOG.warn(warnInfo);
                        throw new SessionTimeoutException(warnInfo);
                    }
                    if (state.isConnected()) {
   
                    	//1000(1 second) is to prevent race condition missing to send the second ping
                    	//also make sure not to send too many pings when readTimeout is small 
                        int timeToNextPing = readTimeout / 2 - clientCnxnSocket.getIdleSend() - 
                        		((clientCnxnSocket.getIdleSend() > 1000) ? 1000 : 0);
                        //send a ping request either time is due or no packet sent out within MAX_SEND_PING_INTERVAL
                        if (timeToNextPing <= 0 || clientCnxnSocket.getIdleSend() > MAX_SEND_PING_INTERVAL) {
                            sendPing();
                            clientCnxnSocket.updateLastSend();
                        } else {
                            if (timeToNextPing < to) {
                                to = timeToNextPing;
                            }
                        }
                    }

                    // If we are in read-only mode, seek for read/write server
                    if (state == States.CONNECTEDREADONLY) {
                        long now = Time.currentElapsedTime();
                        int idlePingRwServer = (int) (now - lastPingRwServer);
                        if (idlePingRwServer >= pingRwTimeout) {
                            lastPingRwServer = now;
                            idlePingRwServer 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值