→ zookeeper version 3.6.1
一、临时节点
相信我们对 zookeeper 的临时节点都不陌生,与持久节点的最大区别就是会随着会话的消失而删除,这种特性常被用来作注册中心分布式锁等,下面来分析下临时节点的实现:
单机模式下由三个 processor 构成了整个处理链路
PrepRequestProcessor 主要负责请求的反序列化,串行化处理和权限校验等
SyncRequestProcessor 主要负责数据的持久化
FinalRequestProcessor 主要负责对内存中数据的更新
这里主要看下第三个处理器是怎样处理临时节点的
org.apache.zookeeper.server.DataTree#createNode(java.lang.String, byte[], java.util.List<org.apache.zookeeper.data.ACL>, long, int, long, long, org.apache.zookeeper.data.Stat)
public void createNode(final String path, byte[] data, List<ACL> acl, long ephemeralOwner, int parentCVersion, long zxid, long time, Stat outputStat) throws KeeperException.NoNodeException, KeeperException.NodeExistsException {
// ... 省略部分代码
// 拿到当前节点的类型
EphemeralType ephemeralType = EphemeralType.get(ephemeralOwner);
// 是否是 container 节点
if (ephemeralType == EphemeralType.CONTAINER) {
containers.add(path);
// 是否是带过期时间的节点
} else if (ephemeralType == EphemeralType.TTL) {
ttls.add(path);
// 处理临时节点的逻辑
} else if (ephemeralOwner != 0) {
HashSet<String> list = ephemerals.get(ephemeralOwner);
if (list == null) {
list = new HashSet<String>();
ephemerals.put(ephemeralOwner, list);
}
synchronized (list) {
list.add(path);
}
}
// 省略部分代码
}
其实不难看懂,临时节点底层就是维护了一个 Map<Long, <Set<String>>>,key保存了 ephemeralOwner,value 则是临时节点的 path,现在只要看下 ephemeralOwner 是什么就好了,下面看下该方法调用方的入参
public ProcessTxnResult processTxn(TxnHeader header, Record txn, boolean isSubTxn) {
ProcessTxnResult rc = new ProcessTxnResult();
try {
//...省略部分代码
case OpCode.create:
CreateTxn createTxn = (CreateTxn) txn;
rc.path = createTxn.getPath();
createNode(
createTxn.getPath(),
createTxn.getData(),
createTxn.getAcl(),
// 判断是否是临时节点,是临时节点就传入 clientId
createTxn.getEphemeral() ? header.getClientId() : 0,
createTxn.getParentCVersion(),
header.getZxid(),
header.getTime(),
null);
break;
//...省略部分代码
}
} catch (KeeperException e) {
LOG.debug("Failed: {}:{}", header, txn, e);
rc.err = e.code().intValue();
} catch (IOException e) {
LOG.debug("Failed: {}:{}", header, txn, e);
}
}
上述代码能发现入参是 clientId,可能这个 id 还是不太明确,接下来去找下构造 TxnHeader 的地方,也就是第一个 processor
org.apache.zookeeper.server.PrepRequestProcessor#pRequest2Txn
protected void pRequest2Txn(int type, long zxid, Request request, Record record, boolean deserialize) throws KeeperException, IOException, RequestProcessorException {
if (request.getHdr() == null) {
// cxid 是客户端生成的 xid,zxid 是服务端生成的
request.setHdr(new TxnHeader(request.sessionId, request.cxid, zxid,
Time.currentWallTime(), type));
}
//...省略部分代码
}
clientId 就是 TxnHeader 的第一个构造参数,也就是 sessionId,其实也不难想象,要想达到这种效果需要跟会话产生关系
二、单机 Session
这里就简单过一下不会讲的很细,主要回顾下与临时节点有关的特性
1、发送协商请求:这部分主要是对 session 创建以及过期时间的协商,为什么叫协商是因为这两边的任何一方都不能擅自决定这两个参数
session 的创建:如果请求方发送协商请求的时候 sessionId = 0 ,那么服务端会生成一个 sessionId 返回给请求端,正常情况下初始化都为 0 ,只有当进行重连的时候才会携带上次请求的 sessionId
过期时间:请求方协商请求的时候也会携带过期时间,服务端启动也会初始化过期时间,如果两端不一致服务端会返回较小的时间作为过期时间
2、协商结果响应
主要就是返回协商过后的过期时间与 sessionId
3、增删改查 与 ping
其实这里做的主要的就是对 session 的续期,每请求一次都会给 session 续期,每次增删改查都会,期间没有增删改查请求端会记录上次请求的时间,如果发现 session 快过期则会发送 ping 请求
4、session 的关闭:session 关闭主要可能来自两方面
zk server 内部线程轮询发现有过期的 session 于是构造 session 过期的请求发送到上面介绍的 3个 处理器中
请求端发送关闭 session 的请求
看了以上的逻辑那我们来聊下当 processor 接收到 close 请求的时候会做些什么处理呢
org.apache.zookeeper.server.PrepRequestProcessor#pRequest2Txn
protected void pRequest2Txn(int type, long zxid, Request request, Record record, boolean deserialize) throws KeeperException, IOException, RequestProcessorException {
// ... 省略部分代码
case OpCode.closeSession:
long startTime = Time.currentElapsedTime();
synchronized (zks.outstandingChanges) {
// 拿到 session 对应的临时节点
Set<String> es = zks.getZKDatabase().getEphemerals(request.sessionId);
for (ChangeRecord c : zks.outstandingChanges) {
if (c.stat == null) {
// Doing a delete
es.remove(c.path);
} else if (c.stat