Zookeeper源码阅读(十三) Seesion(2)

前言

前一篇主要介绍了zookeeper的session的状态,状态之间的切换以及和session有关的实体session接口和sessiontrackimpl类的相关属性。这一篇主要详细说下session相关的流程。

session的创建

在ZookeeperServer的processConnectRequest方法中处理了session的创建和重新激活的请求。

public void processConnectRequest(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOException {
    ...
    cnxn.setSessionTimeout(sessionTimeout);
    // We don't want to receive any packets until we are sure that the
    // session is setup
    cnxn.disableRecv();
    long sessionId = connReq.getSessionId();
    if (sessionId != 0) {//如果sessionId已经存在
        long clientSessionId = connReq.getSessionId();
        LOG.info("Client attempting to renew session 0x"
                 + Long.toHexString(clientSessionId)
                 + " at " + cnxn.getRemoteSocketAddress());
        serverCnxnFactory.closeSession(sessionId);
        cnxn.setSessionId(sessionId);
        reopenSession(cnxn, sessionId, passwd, sessionTimeout);//重新激活session
    } else {
        LOG.info("Client attempting to establish new session at "
                 + cnxn.getRemoteSocketAddress());
        createSession(cnxn, passwd, sessionTimeout);//创建session
    }
}

之后经过ZookeeperServer(createSession)->SessionTrackerImpl(createSession)->SessionTrackerImpl(addSession),在addSession方法中新建了session对象。

synchronized public void addSession(long id, int sessionTimeout) {
    sessionsWithTimeout.put(id, sessionTimeout);
    if (sessionsById.get(id) == null) {
        SessionImpl s = new SessionImpl(id, sessionTimeout, 0);//创建session对象,id的初始化和生成在前一篇有详细讲过
        sessionsById.put(id, s);//设置属性
        if (LOG.isTraceEnabled()) {
            ZooTrace.logTraceMessage(LOG, ZooTrace.SESSION_TRACE_MASK,//打log
                    "SessionTrackerImpl --- Adding session 0x"
                    + Long.toHexString(id) + " " + sessionTimeout);
        }
    } else {
        if (LOG.isTraceEnabled()) {
            ZooTrace.logTraceMessage(LOG, ZooTrace.SESSION_TRACE_MASK,
                    "SessionTrackerImpl --- Existing session 0x"
                    + Long.toHexString(id) + " " + sessionTimeout);
        }
    }
    touchSession(id, sessionTimeout);//session激活,后面详细介绍
}

除了sessionid的生成外,关于timeout也需要强调下,timeout由客户端确定(在客户端),但必须在服务器规定的最大的(20ticktime)和最小的timeout(ticktime2)之间。

可以看到,session创建的场景是比较简单的,但是这是zookeeper server端处理session请求的部分,而zookeeper client端更新session的场景主要分为两大类:

  1. 普通的读写请求,从上面可以看到,server在每次处理客户端的读写请求时都会有激活session的操作;
  2. ping请求。在第十篇client-server(2)中有简单提到过。
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);//根据和上次发送时间的间隔和根据api传入的sessiontimeout来决定,readtimeout = 1/2的sessiontimeout(SendThread的onconnected方法中赋值)
    //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;
        }
    }
}

可以看到,如果没有其他的请求,client端大约会在1/3的sessiontimeout时间后会去尝试ping server,而这个请求在server端也会被processconnectrequest处理,也会达到更新session的作用。

在client端发送请求后,server端会创建/激活session,在激活session的过程中会有一些策略的处理,首先先看下session的分桶策略处理。

分桶

Zookeeper的会话管理主要是通过SessionTracker来负责,其采用了分桶策略(将类似的会话放在同一区块中进行管理)进行管理,以便Zookeeper对会话进行不同区块的隔离处理以及同一区块的统一处理。

在之前提到的,在SessionTracker中主要存了三分和session有关的数据,sessionById:这是一个HashMap<Long,SessionImpl>类型的数据结构,用于根据sessionID来管理Session实体。sessionWithTimeout:这是一个ConcurrentHashMap<Long,Integer>类型的数据结构,用于根据sessionID来管理会话的超时时间;sessionSets:这是一个HashMap<Long,SessionSet>类型的数据结构,用于根据下次会话超时时间点来归档会话,便于进行会话管理和超时检查。

而其实所谓的分桶策略,就是按照超时时间把不同的session放到一起统一管理,对session超时等判断或处理也是按超时时间为单位进行操作,这样也能大大提高效率,也就是说sessionSets中,每一个SessionSet(实际上是一个SessionImpl的hashset)就是一个桶,而桶的标识就是过期时间。

过期时间计算
long expireTime = roundToInterval(Time.currentElapsedTime() + timeout);//timeout = 接口传入的sessiontimeout

过期时间是在roundToInterval方法中计算的。

private long roundToInterval(long time) {
    // We give a one interval grace period
    return (time / expirationInterval + 1) * expirationInterval;//expirationInterval一般情况是ticktime
}

这里计算出的expireTime就是sessionSets中每一个桶的key,可以知道的是,每一个桶的expireTime最终计算结果一定是expirationInterval的倍数,而不同的session会根据他们激活时间的不同放到不同的桶里。

Session激活

在创建session后,每次client发起请求(ping或者读写请求),server端都会重新激活session,而这个过程就是session的激活,也就是所谓的touchSession。会话激活的过程使server可以检测到client的存活并让client保持连接状态。整个过程的流程图如下:

在代码中主要由SessionTrackerImpl的touchSession方法处理:

synchronized public boolean touchSession(long sessionId, int timeout) {
    if (LOG.isTraceEnabled()) {//打log
        ZooTrace.logTraceMessage(LOG,
                                 ZooTrace.CLIENT_PING_TRACE_MASK,
                                 "SessionTrackerImpl --- Touch session: 0x"
                + Long.toHexString(sessionId) + " with timeout " + timeout);
    }
    SessionImpl s = sessionsById.get(sessionId);//根据id从(id, session)mapping关系中取出对应的session
    // Return false, if the session doesn't exists or marked as closing
    if (s == null || s.isClosing()) {//判断session的状态,如果已经关闭就返回
        return false;
    }
    long expireTime = roundToInterval(Time.currentElapsedTime() + timeout);//计算新的超时时间
    if (s.tickTime >= expireTime) {//表明和上次激活还在同一个桶中
        // Nothing needs to be done
        return true;
    }
    SessionSet set = sessionSets.get(s.tickTime);//从老桶中删除
    if (set != null) {
        set.sessions.remove(s);
    }
    s.tickTime = expireTime;//更新过期时间
    set = sessionSets.get(s.tickTime);
    if (set == null) {
        set = new SessionSet();
        sessionSets.put(expireTime, set);
    }
    set.sessions.add(s);//放入新的桶中
    return true;
}

这样整个session激活的流程就很清晰了,client会在读写或者ping时更新session,而server端收到请求后激活session,激活的过程分为下面几步:

  1. 检查会话是否已经被关闭;
  2. 计算新的超时时间;
  3. 定位session当前的桶,并从老桶中删除;
  4. 根据新的超时时间把session迁移到新的桶中。

session超时检查

Zk内部利用SessionTracker管理Session的整个会话生命周期。SessionTracker的实现类SessionTrackerImpl本身就是一个session的超时检查线程逐个地对会话桶中剩下的会话进行清理。之前已经说过,session被激活后会被转移到新的桶中,现在按时间先后有t1, t2两个时间点,如果到了t2时间点,t1时间对应的桶内还有元素,那么代表t1对应的桶内的session全部过期,需要进行相关的清理工作,而实际上zk就是这么工作的,也即SessionTrackerImpl线程的检查内容。

@Override
synchronized public void run() {
    try {
        while (running) {
            currentTime = Time.currentElapsedTime();
            if (nextExpirationTime > currentTime) {//如果还没有到过期检测时间
                this.wait(nextExpirationTime - currentTime);//wait住
                continue;
            }
            SessionSet set;
            set = sessionSets.remove(nextExpirationTime);//把nextExpirationTime对应桶的session全部删掉,已经全部过期
            if (set != null) {
                for (SessionImpl s : set.sessions) {
                    setSessionClosing(s.sessionId);//设置关闭状态
                    expirer.expire(s);//发起会话关闭请求
                }
            }
            nextExpirationTime += expirationInterval;//更新下一次session超时检查时间
             }
    } catch (InterruptedException e) {
        handleException(this.getName(), e);
    }
    LOG.info("SessionTrackerImpl exited loop!");
}

注意:之前提到session桶的key一定是expirationInterval的整数倍,而在SessionTrackerImpl中超时检测的过程中,是以expirationInterval为单位去进行检测的,每次增加一个expirationInterval的时间,这两边互相的配合保证了能检测到所有session的桶。

会话清理

在进行了session的超时检查后,除了把session从SessionTrackerImpl中删除,还需要针对每个session进行清理。SessionTrackerImpl中的expirer.expire(s)便发起了session清理的请求,开始了这个过程。

标记session状态为closing

因为进行会话清理的工作相对来说较为耗时,而setSessionClosing(s.sessionId)把isClosing设置为了false,这样在server收到来自客户端的请求时在PrepRequestProcessor的pRequest2Txn和pRequest中会去checkSession来检查session的状态,发现session处于closing状态后便不会处理响应的请求了。

case OpCode.create:                
    zks.sessionTracker.checkSession(request.sessionId, request.getOwner());

这是收到客户端create请求后检查的代码,其他的请求也是一样,无法通过checksession状态。

发起关闭session的请求

为了使对该会话的关闭操作在整个服务端集群都生效,Zookeeper使用了提交会话关闭请求的方式,并立即交付给PreRequestProcessor进行处理。

public void expire(Session session) {//zookeeperserver
    long sessionId = session.getSessionId();
    LOG.info("Expiring session 0x" + Long.toHexString(sessionId)
            + ", timeout of " + session.getTimeout() + "ms exceeded");
    close(sessionId);
}
private void close(long sessionId) {//zookeeperserver
    submitRequest(null, sessionId, OpCode.closeSession, 0, null, null);//通过zookeeper server的submitrequest方法加入到队列中等待PrepRequestProcessor处理
}
public void submitRequest(Request si) {
    ...
    try {
            touch(si.cnxn);
            boolean validpacket = Request.isValid(si.type);
            if (validpacket) {
                firstProcessor.processRequest(si);//加入队列
    ...
public void processRequest(Request request) {//
    // request.addRQRec(">prep="+zks.outstandingChanges.size());
    submittedRequests.add(request);
}

通过发起关闭请求的过程可以看到这是一个异步的请求过程,和client-server间的异步交互有异曲同工之妙,这里完成了生产者的部分,把请求加入到了一个待处理队列中。

清理临时节点

熟悉Zk的同学都知道,zk的session一旦失效,那么在失效session建立的临时节点信息就会全部丢失。因此,在这里做session相关的清理工作时需要把session相关的临时节点信息全部清除。Zookeeper在内存数据库中会为每个会话都单独保存了一份由该会话维护的所有临时节点集合,因此只需要根据失效session的id把临时节点列表取到并删除即可。但是在实际场景中有两种特殊情况:

  1. 节点删除请求,且删除的节点正是上述临时节点列表中的一个;
  2. 临时节点更新(创建,修改)请求,创建目标节点正好是上述临时节点列表中的一个。

对于第一类请求,需要将所有请求对应的数据节点路径从当前临时节点列表中移出,以避免重复删除,对于第二类请求,需要将所有这些请求对应的数据节点路径添加到当前临时节点列表中,以删除这些即将被创建但是尚未保存到内存数据库中的临时节点。

前面在发起关闭session请求中把请求加入到了submittedRequests队列中,在PrepRequestProcessor的run -> pRequest方法中进行了消费。

protected void pRequest(Request request) throws RequestProcessorException {
    // LOG.info("Prep>>> cxid = " + request.cxid + " type = " +
    // request.type + " id = 0x" + Long.toHexString(request.sessionId));
    request.hdr = null;
    request.txn = null;
    ...
    case OpCode.closeSession:
        pRequest2Txn(request.type, zks.getNextZxid(), request, null, true);
        break;
case OpCode.closeSession:
    // We don't want to do this check since the session expiration thread
    // queues up this operation without being the session owner.
    // this request is the last of the session so it should be ok
    //zks.sessionTracker.checkSession(request.sessionId, request.getOwner());
    HashSet<String> es = zks.getZKDatabase()
            .getEphemerals(request.sessionId);//从zk的内存数据库中取出临时节点集合
    synchronized (zks.outstandingChanges) {
        for (ChangeRecord c : zks.outstandingChanges) {//遍历zk的事务队列
            //addChangeRecord(new ChangeRecord(request.hdr.getZxid(), path,
            //            null, -1, null));
            //这是zk delete节点是增加的changeRecord,根据stat是null可以知道有事务是删除节点的,需要把此节点从临时节点列表中删除
            if (c.stat == null) {
                // Doing a delete
                es.remove(c.path);//从临时节点列表中删除
        } else if (c.stat.getEphemeralOwner() == request.sessionId) {//创建和修改时stat不为null,且只要sessionid为当前session的id
                es.add(c.path);//把临时节点路径加入set
            }
        }
        for (String path2Delete : es) {//遍历
            addChangeRecord(new ChangeRecord(request.hdr.getZxid(),//添加临时节点删除的事件
                    path2Delete, null, 0, null));
        }

        zks.sessionTracker.setSessionClosing(request.sessionId);//把
    }

    LOG.info("Processed session termination for sessionid: 0x"
            + Long.toHexString(request.sessionId));
    break;
void addChangeRecord(ChangeRecord c) {
    synchronized (zks.outstandingChanges) {
        zks.outstandingChanges.add(c);
        zks.outstandingChangesForPath.put(c.path, c);
    }
}

这样可以看到事务变更的过程也是一个异步的过程,这里添加了临时节点删除的事件到队列中,但是这里其实并不是典型的队列,因为outstandingChanges并不是严格意义上的队列,而是一个arraylist。

而FinalRequestProcessor是此队列的消费者,在processRequest方法中完成了事务队列的处理。

synchronized (zks.outstandingChanges) {
    ...
    while (!zks.outstandingChanges.isEmpty()
        && zks.outstandingChanges.get(0).zxid <= request.zxid) {//遍历outstandingChanges
        ChangeRecord cr = zks.outstandingChanges.remove(0);
        if (cr.zxid < request.zxid) {//zxid比request的版本要早,过期
            LOG.warn("Zxid outstanding "
                    + cr.zxid
                    + " is less than current " + request.zxid);
        }
        if (zks.outstandingChangesForPath.get(cr.path) == cr) {//路径和事务记录mapping关系删除
            zks.outstandingChangesForPath.remove(cr.path);
        }
    }
    if (request.hdr != null) {
       TxnHeader hdr = request.hdr;
       Record txn = request.txn;

       rc = zks.processTxn(hdr, txn);//根据事务类型处理事务,closeseesion就是在zookeeperserver中sessionTracker.removeSession(sessionId);
    }
    // do not add non quorum packets to the queue.
    if (Request.isQuorum(request.type)) {
        zks.getZKDatabase().addCommittedProposal(request);//集群操作,提出proposal,保证对集群中所有实例可见
    }
}

在processRequest中可以看到在消费事务的记录过程中,会根据request的类型进行事务处理,调用了zookeeperserver的processTxn方法。

public ProcessTxnResult processTxn(TxnHeader hdr, Record txn) {
    ProcessTxnResult rc;
    int opCode = hdr.getType();
    long sessionId = hdr.getClientId();//获取sessionid
    //case OpCode.closeSession:
    //killSession(header.getClientId(), header.getZxid());
    //从datatree中删除session相关的临时节点
    rc = getZKDatabase().processTxn(hdr, txn);
    if (opCode == OpCode.createSession) {//createsession
        if (txn instanceof CreateSessionTxn) {
            CreateSessionTxn cst = (CreateSessionTxn) txn;
            sessionTracker.addSession(sessionId, cst//增加session
                    .getTimeOut());
        } else {
            LOG.warn("*****>>>>> Got "
                    + txn.getClass() + " "
                    + txn.toString());
        }
    } else if (opCode == OpCode.closeSession) {
        sessionTracker.removeSession(sessionId);//利用sessionTracker删除session
    }
    return rc;
}

具体删除session的操作:

synchronized public void removeSession(long sessionId) {
    SessionImpl s = sessionsById.remove(sessionId);//从sessionsById中移除
    sessionsWithTimeout.remove(sessionId);//移除sessionid和对应的timeout
    if (LOG.isTraceEnabled()) {
        ZooTrace.logTraceMessage(LOG, ZooTrace.SESSION_TRACE_MASK,//打log
                "SessionTrackerImpl --- Removing session 0x"
                + Long.toHexString(sessionId));
    }
    if (s != null) {
        SessionSet set = sessionSets.get(s.tickTime);//把session从桶里删除
        // Session expiration has been removing the sessions   
        if(set != null){
            set.sessions.remove(s);
        }
    }
}

到这里session及相关的临时节点的有关数据就被删除完了。其实清理session临时节点的过程还是比较长的,总结下可以分为下面几步:

  1. 收集需要清理的临时节点,收集的方法是从zkdatabase中利用mapping关系取出,并对两种特殊情况有相应的处理;
  2. 添加节点删除事务变更,这里是把outstandingChanges当做了一个队列(也可以理解为不是,但是作用类似),在outstandingChanges储存了新来的事务变更的记录;
  3. 真正删除临时节点,在zkdatabase中把datatree中的和失效session有关的临时节点全部删除;
  4. 删除失效session,这是通过把SessionTrackerImpl中的sessionsById, sessionSets, sessionsWithTimeout失效session的记录都删除做到的。

关闭NIOServerCnxn

在FinalRequestProcessor的processRequest方法中会根据请求类型处理serverCnxn,如果是closeSession就会通过NIOServerCnxnFactory找到对应的NIOServerCnxn并将其删除。

if (request.hdr != null && request.hdr.getType() == OpCode.closeSession) {
    ServerCnxnFactory scxn = zks.getServerCnxnFactory();
    // this might be possible since
    // we might just be playing diffs from the leader
    if (scxn != null && request.cnxn == null) {
        // calling this if we have the cnxn results in the client's
        // close session response being lost - we've already closed
        // the session/socket here before we can send the closeSession
        // in the switch block below
        scxn.closeSession(request.sessionId);//关闭connection
        return;
    }
}

在server端关闭连接后,server还会把buffer中的关闭连接请求发送给客户端。

case OpCode.closeSession: {
    lastOp = "CLOS";
    closeSession = true;//设置closeSession状态
    err = Code.get(rc.err);
    break;
}
try {
    cnxn.sendResponse(hdr, rsp, "response");
    if (closeSession) {
        cnxn.sendCloseSession();//发送关闭连接请求给客户端
    }

session重连

当客户端与服务端之间的网络连接断开时,Zookeeper客户端会自动进行反复的重连,直到最终成功连接上Zookeeper集群中的一台机器。此时,再次连接上服务端的客户端有可能处于以下两种状态之一。

  1. connected:在会话超时时间内连接上了集群中的任意一台server;
  2. expired:连接上server时已经超时,服务端其实已经进行了会话清理操作,再次连接上的session会被视为非法会话。

在客户端与服务端之间维持的是一个长连接,在sessionTimeout时间内,服务端会不断地检测该客户端是否还处于正常连接(SessionTrackerImpl),服务端会将客户端的每次操作视为一次有效的心跳检测来反复地进行会话激活。因此,在正常情况下,客户端会话时一直有效的。然而,当客户端与服务端之间的连接断开后,用户在客户端可能主要看到两类异常:CONNECTION_LOSS(连接断开)和SESSION_EXPIRED(会话过期)。

CONNECTION_LOSS

若客户端在setData时出现了CONNECTION_LOSS现象,此时客户端会收到None-Disconnected通知,同时会抛出异常。应用程序需要捕捉异常并且等待Zookeeper客户端自动完成重连,一旦重连成功,那么客户端会收到None-SyncConnected通知,之后就可以重试setData操作。

这里给出客户端判断connection丢失重连的情景:

  1. 无法连接上server
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);//抛出session超时异常
}

在clientCnxn的sendthread的run方法中对几种exception进行了处理:

// this is ugly, you have a better way speak up
if (e instanceof SessionExpiredException) {
    LOG.info(e.getMessage() + ", closing socket connection");
} else if (e instanceof SessionTimeoutException) {
    LOG.info(e.getMessage() + RETRY_CONN_MSG);
} else if (e instanceof EndOfStreamException) {
    LOG.info(e.getMessage() + RETRY_CONN_MSG);
} else if (e instanceof RWServerFoundException) {
    LOG.info(e.getMessage());
} else if (e instanceof SocketException) {
    LOG.info("Socket error occurred: {}: {}", serverAddress, e.getMessage());
} else {
    LOG.warn("Session 0x{} for server {}, unexpected error{}",
                    Long.toHexString(getSessionId()),
                    serverAddress,
                    RETRY_CONN_MSG,
                    e);
}
cleanup();//清理客户端的队列等的残留
private void cleanup() {
    clientCnxnSocket.cleanup();
    synchronized (pendingQueue) {//清除pendingqueue中的request
        for (Packet p : pendingQueue) {
            conLossPacket(p);
        }
        pendingQueue.clear();
    }
    synchronized (outgoingQueue) {//清除outgoingqueue中的packet
        for (Packet p : outgoingQueue) {
            conLossPacket(p);
        }
        outgoingQueue.clear();
    }

Session Expired

客户端与服务端断开连接后,重连时间耗时太长,超过了会话超时时间限制后没有成功连上服务器,服务器会进行会话清理,此时,客户端不知道会话已经失效,状态还是DISCONNECTED,如果客户端重新连上了服务器,此时状态为SESSION_EXPIRED,用于需要重新实例化Zookeeper对象,并且看应用的复杂情况,重新恢复临时数据。

  1. 连接server时间过长:
void onConnected(int _negotiatedSessionTimeout, long _sessionId,
        byte[] _sessionPasswd, boolean isRO) throws IOException {
    negotiatedSessionTimeout = _negotiatedSessionTimeout;
    if (negotiatedSessionTimeout <= 0) {
        state = States.CLOSED;
       ...
        warnInfo = "Unable to reconnect to ZooKeeper service, session 0x"
            + Long.toHexString(sessionId) + " has expired";
        LOG.warn(warnInfo);
        throw new SessionExpiredException(warnInfo);//抛出session过期异常
    }

这里跑出了session超时的异常,也会被上面的函数捕捉并清理队列重新进行连接。

Session Moved

客户端会话从一台服务器转移到另一台服务器,即客户端与服务端S1断开连接后,重连上了服务端S2,此时会话就从S1转移到了S2。当多个客户端使用相同的sessionId/sessionPasswd创建会话时,会收到SessionMovedException异常。因为一旦有第二个客户端连接上了服务端,就被认为是会话转移了。

synchronized public void checkSession(long sessionId, Object owner) throws KeeperException.SessionExpiredException, KeeperException.SessionMovedException {
    SessionImpl session = sessionsById.get(sessionId);
    if (session == null || session.isClosing()) {
        throw new KeeperException.SessionExpiredException();
    }
    if (session.owner == null) {
        session.owner = owner;
    } else if (session.owner != owner) {//session的owner不一致
        throw new KeeperException.SessionMovedException();
    }
}

思考

针对zkdatabase 临时节点列表的两种特殊处理

针对删除的请求对应的节点直接把节点从临时节点列表删除,这样在删除的事务中会把节点真正删掉;而更新的则不需要判断,因为除了删除请求外其他事务请求中只要sessionid和失效sessionid一致的都加入待删除队列就OK。而之所以要处理更新的节点是因为如果增加节点那么很有可能这些几点还没有写入内存数据库中,需要在这里再扫描一次事务队列,确保不会有临时节点的残留。

 for (ChangeRecord c : zks.outstandingChanges) {//遍历zk的事务队列
            //addChangeRecord(new ChangeRecord(request.hdr.getZxid(), path,
            //            null, -1, null));
            //这是zk delete节点是增加的changeRecord,根据stat是null可以知道有事务是删除节点的,需要把此节点从临时节点列表中删除
            if (c.stat == null) {
                // Doing a delete
                es.remove(c.path);//从临时节点列表中删除
        } else if (c.stat.getEphemeralOwner() == request.sessionId) {//创建和修改时stat不为null,且只要sessionid为当前session的id
                es.add(c.path);//把临时节点路径加入set
            }
        }

session owner

每一个ServerCnxn对象都有一个me属性(object)标识自己,在zookeeperserver中si.setOwner(ServerCnxn.me);设置了request的owner,也初始化为session的owner。

参考

https://www.jianshu.com/p/594129a44814

http://www.cnblogs.com/leesf456/p/6103870.html

https://blog.csdn.net/pwlazy/article/details/8157368

https://www.jianshu.com/p/3b7f9a032ded

https://www.cnblogs.com/davidwang456/p/5009659.html

zk session代码在3.5之后有修改,可参考https://blog.csdn.net/jpf254/article/details/80804458

《从paxos到zookeeper》

猜你喜欢

转载自www.cnblogs.com/gongcomeon/p/10211798.html