zookeeper底层源码理解-zookeeper客户端类解读

ZooKeeper类的主要功能就是创建节点,修改节点数据,删除节点,读取节点数据,添加/移除监听器,鉴权(限制不同ip对特定节点的读写权限) 

zookeeper连接

1.zookeeper的构造方法  
设置地址,
超时时间:session超时时间设置
监听 :其中的watcher能够根据路径监听各个znode的变化,一旦znode代表的服务器挂掉就能进行及时的处理,zk可以作为服务器集群的管理角色
作用:zookeeper的作用主要是,创建节点,删除节点,读取节点,以及对对ip地址进行读写的权限分配
ZooKeeper zooKeeper = new ZooKeeper(connectString,sessionTimeout,watcher);
//以下是最简单的两种构造方法



    public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher) throws IOException {
//默认情况下只读属性为false
        this(connectString, sessionTimeout, watcher, false);
    }


 public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly) throws IOException {
        //创建ZkwatchManager即watch事件管理器
        this.watchManager = new ZooKeeper.ZKWatchManager();
        LOG.info("Initiating client connection, connectString=" + connectString + " sessionTimeout=" + sessionTimeout + " watcher=" + watcher);
//创建默认的watch监听
        this.watchManager.defaultWatcher = watcher;
//解析对应的地址列表,并放在list里面
        ConnectStringParser connectStringParser = new ConnectStringParser(connectString);
//来维护整个地址列表,地址列表的随机和地址获取这两个过程
        HostProvider hostProvider = new StaticHostProvider(connectStringParser.getServerAddresses());
//创建连接
        this.cnxn = new ClientCnxn(connectStringParser.getChrootPath(), hostProvider, sessionTimeout, this, this.watchManager, getClientCnxnSocket(), canBeReadOnly);
//启动连接
        this.cnxn.start();
    }
ConnectStringParser方法
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.zookeeper.client;

import java.net.InetSocketAddress;
import java.util.ArrayList;

import org.apache.zookeeper.common.PathUtils;

/**
 * A parser for ZooKeeper Client connect strings.
 * 
 * This class is not meant to be seen or used outside of ZooKeeper itself.
 * 
 * The chrootPath member should be replaced by a Path object in issue
 * ZOOKEEPER-849.
 * 
 * @see org.apache.zookeeper.ZooKeeper
 */
public final class ConnectStringParser {
    private static final int DEFAULT_PORT = 2181;

    private final String chrootPath;
    //维护一个list存放地址
    private final ArrayList<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>();

    /**
     * 
     * @throws IllegalArgumentException
     *             for an invalid chroot path.
     *如`127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a`,则`chrootPath=/app/a
final String chrootPath;   
     */
    public ConnectStringParser(String connectString) {
        // parse out chroot, if any
//第一个/在的位置,如果不在首位
        int off = connectString.indexOf('/');
        if (off >= 0) {
//则从off开始截取表示创建该节点(根节点)
            String chrootPath = connectString.substring(off);
            // ignore "/" chroot spec, same as null
//如果长度是1
            if (chrootPath.length() == 1) {
                this.chrootPath = null;
            } else {
                PathUtils.validatePath(chrootPath);
                this.chrootPath = chrootPath;
            }
//截取00到off作为地址列表
            connectString = connectString.substring(0, off);
        } else {
            this.chrootPath = null;
        }
    //截取ip:port,ip:port,ip:port格式的列表                    //192.168.1.1:2181,192.168.1.1:2181,192.168.1.2:2181
        String hostsList[] = connectString.split(",");
        for (String host : hostsList) {
//port默认2181
            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等于pidx+1开始截取
                    port = Integer.parseInt(host.substring(pidx + 1));
                }
//host从0开始截取

                host = host.substring(0, pidx);
            }
//添加进list,以IP作为InetSocketAddress的hostname,port作为port
            serverAddresses.add(InetSocketAddress.createUnresolved(host, port));
        }
    }

    public String getChrootPath() {
        return chrootPath;
    }

    public ArrayList<InetSocketAddress> getServerAddresses() {
        return serverAddresses;
    }
}

        private InetSocketAddressHolder(String hostname, InetAddress addr, int port) {
            this.hostname = hostname;
            this.addr = addr;
            this.port = port;
        }
StaticHostProvider继承自HostProvider

 

package org.apache.zookeeper.client;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class StaticHostProvider implements HostProvider {
    private static final Logger LOG = LoggerFactory
            .getLogger(StaticHostProvider.class);

    private final List<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>(
            5);
//记录当前客户端奕尝试连接完所有服务端的位置
    private int lastIndex = -1;
//记录当前客户端尝试连接的服务端位置
    private int currentIndex = -1;

    /**
     * Constructs a SimpleHostSet.
     * 
     * @param serverAddresses
     *            possibly unresolved ZooKeeper server addresses
     * @throws UnknownHostException
     * @throws IllegalArgumentException
     *             if serverAddresses is empty or resolves to an empty list
     */
    public StaticHostProvider(Collection<InetSocketAddress> serverAddresses)
            throws UnknownHostException {
//循环地址列表
        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 {
//如果不以/开始,则将地址添加进list
                    this.serverAddresses.add(new InetSocketAddress(resolvedAddress.getHostAddress(), address.getPort()));
                }  
            }
        }
        
        if (this.serverAddresses.isEmpty()) {
            throw new IllegalArgumentException(
                    "A HostProvider may not be empty!");
        }
//对列表进行随意排序
        Collections.shuffle(this.serverAddresses);
    }

    public int size() {
        return serverAddresses.size();
    }
    
//将地址作为一个类似于圆环的链表
    public InetSocketAddress next(long spinDelay) {
//如果当前的index等于长度,则从0开始遍历尝试
        ++currentIndex;
        if (currentIndex == serverAddresses.size()) {
            currentIndex = 0;
        }
//如果当前下标为-1 并且时间大于0,则进行休眠
        if (currentIndex == lastIndex && spinDelay > 0) {
            try {
                Thread.sleep(spinDelay);
            } catch (InterruptedException e) {
                LOG.warn("Unexpected exception", e);
            }
//如果当前下标为-1 则从0开始再遍历循环
        } else if (lastIndex == -1) {
            // We don't want to sleep on the first ever connect attempt.
            lastIndex = 0;
        }

        return serverAddresses.get(currentIndex);
    }

    public void onConnected() {
        lastIndex = currentIndex;
    }
}

 ClientCnxn

//Packet:封装了客户端一次请求或服务端一次响应的完整数据
//已经发送但是等待服务端响应的packet集合
    private final LinkedList<Packet> pendingQueue = new LinkedList<Packet>();

    //需要发送的packet集合
    private final LinkedList<Packet> outgoingQueue = new LinkedList<Packet>();
//建立连接的超时时间
    private int connectTimeout;
 //服务端认为的下一次会话过期的具体时间
    private volatile int negotiatedSessionTimeout;
//客户端认为的最大会话超时时间,默认为sessionTimeout * 2 / 3
    private int readTimeout;
//会话的超时时间
    private final int sessionTimeout;
    private long sessionId;
    private byte sessionPasswd[] = new byte[16];
//客户端的命名空间,客户端所有的数据节点的路径都会默认在这层路径下创建。可通过`connectString`参数
//传入,如`127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a`,则`chrootPath=/app/a
final String chrootPath;
//用于记录客户端请求发起的先后顺序,没发送一个packet出去加1
    private int xid = 1;
//客户端连接状态,面向与服务端的连接状态
    private volatile States state = States.NOT_CONNECTED;
//send线程
    final SendThread sendThread;
//event线程
    final EventThread eventThread;
  private final ZooKeeper zooKeeper;
    private final ClientWatchManager watcher;
private final HostProvider hostProvider;


//参数 节点路径   地址   超时时间  zookeeper实例  客户端监听  客户端xocket连接  是否只读    
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);
    }

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

    }

zookeeper类常用方法

1.创建节点

参数列表为节点名称,数据,是否ACL(权限)控制,节点的模式
public String create(final String path, byte data[], List<ACL> acl,
            CreateMode createMode)
        throws KeeperException, InterruptedException
    {
        final String clientPath = path;
//对节点和状态进行校验,是否符合要求
        PathUtils.validatePath(clientPath, createMode.isSequential());
        //设置节点目录,
        //如果有根节点,则设置为根节点+path,
        //如果path长度是1,则还是根节点,
        //没有根节点,则设置为path
        final String serverPath = prependChroot(clientPath);

        RequestHeader h = new RequestHeader();
//设置类别为create
        h.setType(ZooDefs.OpCode.create);
        CreateRequest request = new CreateRequest();
        CreateResponse response = new CreateResponse();
        request.setData(data);
        request.setFlags(createMode.toFlag());
        request.setPath(serverPath);
        if (acl != null && acl.size() == 0) {
            throw new KeeperException.InvalidACLException();
        }
        request.setAcl(acl);
//提交请求
//构建Packet 如果状态不是为连接或者是存活的,否则放进outgoingQueue队列
        ReplyHeader r = cnxn.submitRequest(h, request, response, null);
        if (r.getErr() != 0) {
            throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                    clientPath);
        }
        if (cnxn.chrootPath == null) {
            return response.getPath();
        } else {
            return response.getPath().substring(cnxn.chrootPath.length());
        }
    }

delete方法

//节点名称,版本号
public void delete(final String path, int version)
        throws InterruptedException, KeeperException
    {
        final String clientPath = path;
//对节点进行校验
        PathUtils.validatePath(clientPath);

        final String serverPath;

        // maintain semantics even in chroot case
        // specifically - root cannot be deleted
        // I think this makes sense even in chroot case.
//如果是/ 则删除节点之下所有
        if (clientPath.equals("/")) {
            // a bit of a hack, but delete(/) will never succeed and ensures
            // that the same semantics are maintained
            serverPath = clientPath;
        } else {
//对目录进行判断是否包含根节点进行拼接处理
            serverPath = prependChroot(clientPath);
        }
        RequestHeader h = new RequestHeader();
        h.setType(ZooDefs.OpCode.delete);
        DeleteRequest request = new DeleteRequest();
        request.setPath(serverPath);
        request.setVersion(version);
//提交请求
        ReplyHeader r = cnxn.submitRequest(h, request, null, null);
        if (r.getErr() != 0) {
            throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                    clientPath);
        }
    }

getData获取节点的数据

  public byte[] getData(final String path, Watcher watcher, Stat stat)
        throws KeeperException, InterruptedException
     {
        final String clientPath = path;
        PathUtils.validatePath(clientPath);

        // the watch contains the un-chroot path
        WatchRegistration wcb = null;
//创建data数据监听处理
        if (watcher != null) {
            wcb = new DataWatchRegistration(watcher, clientPath);
        }
//对路径进行拼接处理
        final String serverPath = prependChroot(clientPath);
        RequestHeader h = new RequestHeader();
        h.setType(ZooDefs.OpCode.getData);
        GetDataRequest request = new GetDataRequest();
        request.setPath(serverPath);
        request.setWatch(watcher != null);
        GetDataResponse response = new GetDataResponse();
//提交处理
        ReplyHeader r = cnxn.submitRequest(h, request, response, wcb);
        if (r.getErr() != 0) {
            throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                    clientPath);
        }
        if (stat != null) {
            DataTree.copyStat(response.getStat(), stat);
        }
        return response.getData();
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值