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