【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
1. Java客户端api
1.1 创建会话
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher);
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly);
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd);
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly);
参数名 | 说明 |
---|
connectString | 指zookeeper服务器列表,由英文逗号分开的host:port字符串组成,每一个都代表一台zookeeper机器。另外,也可以在connectString中设置客户端连接上zookeeper后的根目录,方法是在host:port字符串最后加上这个根目录,这样就指定了该客户端连接上zookeeper服务器之后,所有对zookeeper的操作,都会基于这个根目录。这个目录也叫Chroot,即客户端隔离命名空间 |
sessinoTimeout | 指会话的超时时间,是一个以“毫秒”为单位的整型值。在zookeeper中有会话的概念,在一个会话周期内,zookeeper客户端和服务器之间会通过心跳检测机制来维持会话的有效性,一旦在sessionTimeout时间内没有进行有效的心跳检测,会话就会失效。 |
watcher | zookeeper允许客户端在构造方法中传入一个接口watcher的实现类对象来作为默认的watcher事件通知处理器。 |
canBeReadOnly | 这是一个boolean类型参数,用于标识当前会话是否支持“read-only”模式。默认情况下,在zookeeper集群中,一个机器如果和集群中过半及以上机器时区了网络连接,那么这个机器将不再处理客户端请求(包括读请求)。但是在某些使用场景下,当zookeeper服务器发生此类故障的时候,我们还是希望zookeeper服务器能够提供读服务,这就是zookeeper的“read-only”模式。 |
sessionId和sessionPasswd | 分别代表会话ID和会话秘钥。这两个参数能够唯一确定一个会话,同时,客户端使用这两个参数可以实现客户端会话复用,从而达到恢复会话的效果。 |
- zookeeper的会话创建是一个异步的过程,这里需要开发人员自行控制将其同步化。具体的方式是在其事件监听程序中监听WatchedEvent.SyncConnected和EventType.Node类型的事件;
1.2 创建节点
String create(final String path, byte data[], List<ACL> acl, CreateMode createMode);
void create(final String path, byte data[], List<ACL> acl, CreateMode createMode, StringCallback cb, Object ctx)
参数名 | 说明 |
---|
path | 需要创建的数据节点的节点路径 |
data[] | 一个字节数组,是节点创建后的初始内容 |
acl | 节点的ACL策略 |
createMode | 节点类型,是一个枚举类型,通常有4中可选的节点类型:<br/>a. 持久(PERSISTENT);<br />b. 持久顺序(PERSISTENT_SEQUENTIAL);<br />c. 临时(EPHEMERAL);<br />d. 临时顺序(EPHEMERAL_SEQUENTIAL) |
cb | 注册一个异步回调函数,开发人员需要实现StringCallback接口,主要是对下面这个方法进行重写:<br />public void processResult(int rc, String path, Object ctx, String name);<br />当服务器节点创建完毕后,zookeeper客户端就会自动调用这个方法,这样就可以处理相关的业务逻辑了 |
ctx | 用于传递一个对象,可以在回调方法执行的时候使用,通常是方一个上下文(Context)信息 |
- 无论是同步接口还是异步接口,zookeeper都不支持递归创建,即无法在父节点不存在的情况下创建一个子节点;
- 如果一个节点已经存在了,那么创建同名节点的时候,会抛出NodeExistsException异常;
- 关于权限控制,如果你的应用场景没有太高的权限要求,那么可以不关注这个参数,只需要在acl参数中传入参数
Ids.OPEN_ACL_UNSAFE
,这就表明之后对这个节点的任何操作都不受权限控制;
参数名 | 说明 |
---|
rc | Result Code,服务端响应码。客户端可以从这个响应码中识别出API调用的结果,常见的响应码如下:<br />a. 0(OK):接口调用成功<br />b. -4(ConnectionLoss):客户端与服务端连接已断开<br />c. -100(NodeExists):指定节点已存在<br />d. -112(SessionExpired):会话已过期 |
path | 接口调用时传入API的数据节点的节点路径参数值 |
ctx | 接口调用时传入API的ctx参数值 |
name | 实际在服务端创建的节点名(包括完整路径) |
1.3 删除节点
void delete(final String path, int version);
void delete(final String path, int version, VoidCallback cb, Object ctx);
参数名 | 说明 |
---|
path | 指定数据节点的节点路径,即API调用的目的是 |
version | 指定节点的数据版本,即表明本次删除操作是针对该数据版本进行的 |
cb | 注册一个异步回调函数 |
ctx | 用于传递上下文信息的对象 |
- 在zookeeper中,只允许删除叶子节点。也就是说,如果一个节点存在至少一个子节点的话,那么该节点将无法被直接删除,必须删除掉其所有子节点。
1.4 读取子节点
List<String> getChildren(final String path, Watcher watcher);
List<String> getChildren(String path, boolean watch);
void getChildren(final String path, Watcher watcher, ChildrenCallback cb, Object ctx);
void getChildren(String path, boolean watch, ChildrenCallback cb, Object ctx);
List<String> getChildren(final String path, Watcher watcher, Stat stat);
List<String> getChildren(String path, boolean watch, Stat stat);
void getChildren(final String path, Watcher watcher, Children2Callback cb, Object ctx);
void getChildren(String path, boolean watch, Children2Callback cb, Object ctx);
参数名 | 说明 |
---|
path | 指定数据节点的节点路径,即API调用的目的是获取该节点的 |
watcher | 注册的watcher。一旦在本次子节点获取之后,子节点列表发生变更的话,那么就会向客户端发送通知。该参数允许传入null |
watch | 表明是否需要注册一个watcher。在创建节点时我们提到过一个默认watcher的概念,这里就是指定是否使用该默认watcher。 |
cb | 注册一个异步回调函数 |
ctx | 用于传递上下文信息的对象 |
stat | 指定数据节点的节点状态信息。用法是在接口中传入一个旧的stat变量,该stat变量会在方法执行过程中,被来自服务端响应的新stat对象替换 |
- 如果zookeeper客户端在获取到指定节点的子节点列表后,还需要订阅这个子节点列表的变化通知,那么就可以通过注册一个watcher来实现。当有子节点被添加或是删除时,服务端就会向客户端发送一个NodeChildrenChanged(EventType.NodeChildrenChanged)类型的事件通知。需要注意的是,在服务端发送给客户端的事件通知中,是不包含最新的节点列表的,客户端必须主动重新进行获取。通常客户端在接收到这个事件通知后,就可以再次获取最新的子节点列表了;
- stat对象中记录了一个节点的基本属性信息,例如节点创建时的事务ID、最后一次修改的事务ID和节点数据内容的长度等。有时候,我们不仅需要获取节点最新的子节点列表,还要获取这个节点最新的节点状态信息。对于这种情况,我们可以将一个旧的stat变量传入API接口,该stat变量会在方法执行过程中,被来自服务器响应的新stat对象替换;
- 调用getChildren获取到的节点列表,都是数据节点的相对节点路径;
- 关于watcher,zookeeper服务端在向客户端发送watcher“NodeChildrenChanged”时间通知的时候,仅仅只会发送出一个通知,而不会把节点的变化情况发送给客户端,需要客户端自己重新获取;
- 由于watcher通知是一次性的,即一旦触发一次通知后,该watcher就失效了,因此客户端需要反复注册watcher;
1.5 读取数据
byte[] getData(final String path, Watcher watcher, Stat stat);
byte[] getData(String path, boolean watch, Stat stat);
void getData(String path, boolean watch, DataCallback cb, Object ctx);
void getData(String path, boolean watch, DataCallback cb, Object ctx);
参数名 | 说明 |
---|
path | 指定数据节点的节点路径,即API调用的目的是获取该节点的数据 |
watcher | 注册的watcher。一旦之后节点内容有变更,就会向客户端发送通知。该参数允许传入null |
stat | 指定数据节点的节点状态信息。用法是在接口中传入一个旧的stat变量,该stat变量会在方法执行过程中,被来自服务器响应的新stat对象替换 |
watch | 表明是否需要注册一个watcher,这里的watcher指的是在创建该节点时指定的默认watcher |
cb | 注册一个异步回调函数 |
ctx | 用于传递上下文信息的对象 |
- 客户端在获取一个节点的数据内容的时候,是可以进行watcher注册的,这样一来,一旦该节点的状态发生变更,那么zookeeper服务端就会向客户端发送一个NodeDataChanged(EventType.NodeDataChanged)的事件通知;
- 节点数据内容或者节点版本的变化都被看做是zookeeper节点的变化。
1.6 更新数据
Stat setData(final String path, byte data[], int version);
void setData(final String path, byte data[], int version, StatCallback cb, Object ctx);
参数名 | 说明 |
---|
path | 指定数据节点的节点路径,即API调用的目的是更新该节点 |
data[] | 一个字节数组,即需要使用该数据内容来覆盖节点现在的数据内容 |
version | 指定节点的数据版本,即表明本次更新操作是针对该数据版本进行的 |
cb | 注册一个异步回调函数 |
ctx | 用于传递上下文信息的对象 |
- 在调用更新操作的时候,就可以添加version这个参数,该参数可以对应于CAS原理中的“预期值”,表明是针对该数据版本进行更新的;
- 在zookeeper中,数据版本都是从0开始计数的,所以严格的讲,“-1”并不是一个合法的数据版本,它仅仅是一个标识符,如果客户端传入的版本参数是“-1”,就是告诉zookeeper服务器,客户端需要基于数据的最新版本进行更新操作。如果对zookeeper数据节点的更新操作没有原子性要求,那么就可以使用“-1”。
1.7 检测节点是否存在
Stat exists(final String path, Watcher watcher);
Stat exists(String path, boolean watch);
void exists(final String path, Watcher watcher, StatCallback cb, Object ctx);
void exists(String path, boolean watch, StatCallback cb, Object ctx);
参数名 | 说明 |
---|
path | 指定数据节点的节点路径,即API调用的目的是检测该 |
watcher | 注册的watcher,用于监听一下三类事件:<br />a. 节点被创建<br />b. 节点被删除<br />c. 节点被更新 |
watch | 指定是否复用zookeeper中默认的watcher |
cb | 注册一个异步回调函数 |
ctx | 用于传递上下文信息的对象 |
- 无论指定节点是否存在,通过调用exists()接口都可以注册watcher;
- exists()接口中注册的watcher,能够对节点创建、节点删除和节点数据更新事件进行监听;
- 对于指定节点的子节点的各种变化,都不会通知客户端。
1.8 权限控制
void addAuthInfo(String scheme, byte auth[]);
参数名 | 说明 |
---|
scheme | 权限控制模式,分为world、auth、digest、ip |
auth | 具体的权限信息 |
- 对于节点的权限,需要注意的是,当一个客户端为一个节点添加权限信息的时候,该权限信息是添加到了该节点的叶子节点上,操作这些节点需要权限信息,但如果操作该父节点,是不需要权限的。
2. 开源客户端-ZkClient
2.1 创建会话
public ZkClient(String serverstring);
public ZkClient(String zkServers, int connectionTimeout);
public ZkClient(String zkServers, int sessionTimeout, int connectionTimeout);
public ZkClient(String zkServers, int sessionTimeout, int connectionTimeout, ZkSerializer zkSerializer);
public ZkClient(IZkConnection connection);
public ZkClient(IZkConnection connection, int connectionTimeout);
public ZkClient(IZkConnection zkConnection, int connectionTimeout, ZkSerializer zkSerializer);
参数名 | 说明 |
---|
zkServers | 指zookeeper服务器列表,由英文状态逗号分开的host:port字符串组成,每一个都代表一台zookeeper机器 |
sessionTimeout | 会话超时时间,单位为毫秒,默认是30000ms |
connectionTimeout | 连接创建超时时间,单位为毫秒。此参数表明如果在这个时间段内还是无法和zookeeper建立连接,那么就放弃连接,直接抛出异常 |
connection | IZkConnection接口的实现类 |
zkSerializer | 自定义序列化器 |
- 这里ZkClient在创建客户端连接时,将创建的过程进行了同步化;
- 对于序列化方式,ZkClient中定义了ZkSerializer来传入一个序列化实现,如Hessian和Kryo,默认使用Java自带的序列化方式进行对象的序列化;
2.2 创建节点
String create(final String path, Object data, final CreateMode mode);
void createEphemeral(final String path);
void createEphemeral(final String path, final Object data);
void createPersistent(String path);
void createPersistent(String path, boolean createParents);
void createPersistent(String path, Object data);
String createPersistentSequential(String path, Object data);
String createEphemeralSequential(final String path, final Object data);
参数名 | 说明 |
---|
path | 指定数据节点的节点路径,即API调用的目的是创建 |
data | 节点的初始数据内容,可以传如null |
mode | 节点类型,是一个枚举类型,通常有4中可选的节点类型 |
acl | 节点的ACL策略 |
callback | 注册一个异步回调函数 |
context | 用于传递一个对象,可以在执行回调函数的时候使用 |
createParents | 指定是否创建父节点 |
- 由于ZkClient支持了自定义序列化器,因此可以传入复杂对象作为参数;
- 对于节点的类型,这里是通过提供不同的方法来进行指定的;
- 对于zookeeper原生API,其无法在没有父节点的情况下创建一个子节点,这里ZkClient则可以通过createParents参数来控制是否递归地创建父节点。
2.3 删除节点
boolean delete(final String path);
boolean deleteRecursive(String path);
- 对于删除节点,如果该节点存在子节点,那么是无法删除该节点的,而必须先删除其所有的子节点。这里ZkClient提供了deleteRecursive()方法递归的删除一个节点及其子节点。
2.4 读取子节点数据
List<String> getChildren(String path);
List<String> getChildren(final String path, final boolean watch)
List<String> subscribeChildChanges(String path, IZkChildListener listener);
2.4.1 IZkChildListener参数说明
void handleChildChange(String parentPath, List<String> currentChilds);
参数名 | 参数值 |
---|
parentPath | 子节点变更通知对应的父节点的节点 |
currentChilds | 子节点的相对路径列表,如果没有子节点,那么会传入null |
2.4.2 IZkChildListener事件说明
事件类型 | 说明 |
---|
新增子节点 | 指定节点nodeA新增子节点。此时在handleChildChange()方法中,parentPath收到的是nodeA的全路径,currentChilds是最新的子节点列表 |
减少子节点 | 指定节点nodeA减少子节点。此时在handleChildChange()方法中,parentPath收到的是nodeA的全路径,currentChilds是最新的子节点列表,可能是null |
删除节点nodeA | 指定节点nodeA被删除。此时在handleChildChange()方法中,parentPath收到的是nodeA的全路径,currentChilds是null |
- ZkClient中没有像zookeeper客户端API一样提供子节点的时间监听程序,而是通过注册的方式提供了IZkChildListener的监听;
- 客户端可以对一个不存在的节点进行子节点变更的监听;
- 一旦客户端对一个节点注册了子节点列表变更监听之后,那么当该节点的子节点列表发生变更的时候,服务端都会通知客户端,将最新的子节点列表发送给客户端;
- 该节点本身的创建和删除也会通知到客户端;
- 与zookeeper原生提供的watcher不同的是,ZkClient的Listener不是一次性的,客户端只需要注册一次就会一直生效。
2.5 读取节点数据
<T extends Object> T readData(String path);
<T extends Object> T readData(String path, boolean returnNullIfPathNotExists);
<T extends Object> T readData(String path, Stat stat);
参数名 | 说明 |
---|
returnNullIfPathNotExists | 默认情况下,在调用该API的手,如果指定的节点不存在,那么会抛出异常:KeeperException.NoNodeException。如果设置了这个参数,那么如果节点不存在,就会直接返回null,而不会 |
stat | 指定数据节点的节点状态信息。用法是在接口中传入一个旧的stat变量,该stat变量会在方法执行过程中,被来自服务器响应的新stat对象替换 |
- 通过调用该接口,就可以获取指定节点的数据内容,方法的返回值,在ZkClient内部已经被反序列化成指定对象;
2.5.1 注册节点数据变更监听器
void subscribeDataChanges(String path, IZkDataListener listener)
2.5.2 节点变更监听器
public interface IZkDataListener {
public void handleDataChange(String dataPath, Object data) throws Exception;
public void handleDataDeleted(String dataPath) throws Exception;
}
参数名 | 说明 |
---|
dataPath | 事件通知对应的节点 |
data | 最新的数据内容 |
2.5.3 节点变更事件
事件类别 | 说明 |
---|
节点数据变化 | 指定节点nodeA的数据内容(content)或是数据版本(version)发生变更,都会出发这个事件。此时在handleDataChange()方法中,dataPath收到的是nodeA的全路径,data是最新 |
删除节点nodeA | 指定节点nodeA被删除。此时在handleDataChange()方法中,dataPath收到的是nodeA的全路径 |
2.6 更新数据
void writeData(String path, Object object);
void writeData(final String path, Object datat, final int expectedVersion);
参数名 | 说明 |
---|
path | 数据节点的完整 |
data | 数据内容,可以是null |
expectedVersion | 预期的数据版本 |
2.7 检测节点是否存在
boolean exists(final String path);
boolean exists(final String path, final boolean watch);
3. 开源客户端-Curator
3.1 创建会话
static CuratorFramework newClient(String connectString, RetryPolicy retryPolicy);
static CuratorFramework newClient(String connectString, int sessionTimeoutMs, int connectionTimeoutMs, RetryPolicy retryPolicy);
参数名 | 说明 |
---|
connectString | 指zookeeper服务器列表,由英文逗号分开的host:port字符串组成,每一个都代表一台zookeeper机器 |
retryPolicy | 重试策略。默认主要有四种实现,分别是ExponentialBackoffRetry、RetryNTimes、RetryOneTime、RetryUntilElapsed |
sesstionTimeoutMs | 会话超时时间,单位为毫秒。默认是60000ms |
connectionTimeoutMs | 连接创建超时时间,单位为毫秒,默认是15000 |
- 在重试策略上,Curator通过一个接口RetryPolicy来让用户实现自定义的重试策略
public interface RetryPolicy {
public boolean allowRetry(int retryCount, long elapsedTimeMs, RetrySleeper sleeper);
}
参数名 | 说明 |
---|
retryCount | 已经重试的次数。如果是第一次重试,该参数 |
elapsedTimeMs | 从第一次重试开始已经花费的时间,单位为毫秒 |
sleeper | 用于sleep指定时间。Curator建议不要使用Thread.sleep()来进行sleep操作 |
CuratorFramework CuratorFrameworkFactory.builder()
.connectString("domain1.book.zookeeper:2181")
.sessionTimeoutMs(5000)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.namespace("base")
.build();
3.2 创建节点
// 创建一个节点,初始内容为空
CuratorFramework.create().forPath(path);
// 创建一个节点,附带初始内容
CuratorFramework.create().forPath(path, "init".getBytes());
// 创建一个临时节点,初始内容为空
CuratorFramework.create().withMode(CreateMode.EPHEMERAL).forPath(path);
// 创建一个临时节点,并自动递归创建父节点
CuratorFramework.create().creatingParentssIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path);
- 关于上面第四个API,这里可以指定是否递归的创建父节点,因为默认情况下,如果父节点不存在是不允许创建子节点的;
- zookeeper规定,所有非叶子节点必须为持久节点,因而如果使用了creatingParentssIfNeeded(),并且指定节点类型为CreateMode.EPHEMERAL,那么只有path参数对应的叶节点为临时节点,其父节点均为持久节点。
3.3 删除节点
// 删除一个节点,该接口只能删除叶节点
CuratorFramework.delete().forPath(path);
// 删除一个节点,并递归地删除其子节点
CuratorFramework.delete().deletingChildrenIfNeeded().forPath(path);
// 删除一个节点,强制指定版本进行删除
CuratorFramework.delete().withVersion(version).forPath(path);
// 删除一个节点,强制保证删除
CuratorFramework.delete().guaranteed().forPath(path);
- 对于guaranteed()方法,如果初次尝试删除没成功,其会在后台创建一个线程持续进行删除操作,直到节点删除成功;
3.4 读取数据
// 读取一个节点的数据内容,该方法返回值的类型是byte[]
CuratorFramework.getData().forPath(path);
// 读取一个节点的数据内容,同时获取该节点的stat
CuratorFramework.getData().storingStatIn(stat).forPath(path);
3.5 更新数据
// 更新一个节点的数据内容,调用该接口后,将返回一个stat对象
CuratorFramework.setData().forPath(path);
// 更新一个节点的数据内容,强制指定版本进行更新
CuratorFramework.setData().withVersion(version).forPath(path);
3.6 异步接口
public interface BackgroundCallback{
public void processResult(CuratorFramework client, CuratorEvent event)
throws Exception;
}
- 对于Curator前面的增删改查操作,如果没指定inBackground(BackgroundCallback),那么默认就是同步的,如果指定了则调用过程是异步的,并且在操作完成后会调用上述回调接口。该接口中主要有一个CuratorEvent用于指定回调的数据,主要包含事件类型和响应码。事件类型和响应码主要有以下几种:
public enum CuratorEventType
{
/**
* Corresponds to {@link CuratorFramework#create()}
*/
CREATE,
/**
* Corresponds to {@link CuratorFramework#delete()}
*/
DELETE,
/**
* Corresponds to {@link CuratorFramework#checkExists()}
*/
EXISTS,
/**
* Corresponds to {@link CuratorFramework#getData()}
*/
GET_DATA,
/**
* Corresponds to {@link CuratorFramework#setData()}
*/
SET_DATA,
/**
* Corresponds to {@link CuratorFramework#getChildren()}
*/
CHILDREN,
/**
* Corresponds to {@link CuratorFramework#sync(String, Object)}
*/
SYNC,
/**
* Corresponds to {@link CuratorFramework#getACL()}
*/
GET_ACL,
/**
* Corresponds to {@link CuratorFramework#setACL()}
*/
SET_ACL,
/**
* Corresponds to {@link Watchable#usingWatcher(Watcher)} or {@link Watchable#watched()}
*/
WATCHED,
/**
* Event sent when client is being closed
*/
CLOSING
}
响应码 | 说明 |
---|
0 | OK,接口调用 |
-4 | ConnectioLoss,客户端与服务器断开连接 |
-110 | NodeExists,节点已存在 |
-112 | SessionExpired,会话已过期 |