zk源码—4.会话的实现原理二

大纲

1.创建会话

(1)客户端的会话状态

(2)服务端的会话创建

(3)会话ID的初始化实现

(4)设置的会话超时时间没生效的原因

2.分桶策略和会话管理

(1)分桶策略和过期队列

(2)会话激活

(3)会话超时检查

(4)会话清理

2.分桶策略和会话管理

(1)分桶策略和过期队列

(2)会话激活

(3)会话超时检查

(4)会话清理

zk作为分布式系统的核心组件,经常要处理大量的会话请求。zk之所以能快速响应大量客户端操作,与它自身的会话管理策略密不可分。

(1)分桶策略和过期队列

一.会话管理中的心跳消息和过期时间

二.分桶策略的原理

三.分桶策略的过期队列和bucket

一.会话管理中的心跳消息和过期时间

在zk中为了保持会话的存活状态,客户端要向服务端周期性发送心跳信息。客户端的心跳信息可以是一个PING请求,也可以是一个普通的业务请求。

zk服务端收到请求后,便会更新会话的过期时间,来保持会话的存活状态。因此zk的会话管理,最主要的工作就是管理会话的过期时间。

zk服务端的会话管理是由SessionTracker负责的,会话管理器SessionTracker采用了分桶策略来管理会话的过期时间。

二.分桶策略的原理

会话管理器SessionTracker会按照不同的时间间隔对会话进行划分,超时时间相近的会话将会被放在同一个间隔区间中。

具体的划分原则就是:每个会话的最近过期时间点ExpirationTime,ExpirationTime是指会话最近的过期时间点。

对于一个新会话创建完毕后,zk服务端都会计算其ExpirationTime,会话管理器SessionTracker会每隔ExpirationInterval进行会话超时检查。

复制代码
//CurrentTime是指当前时间,单位毫秒
//SessionTimeout指会话的超时时间,单位毫秒
//SessionTrackerImpl会每隔ExpirationInterval进行会话超时检查
ExpirationTime = CurrentTime + SessionTimeout
ExpirationTime = (ExpirationTime / ExpirationInterval + 1) * ExpirationInterval

这种方式避免了对每一个会话进行检查。采用分批次的方式管理会话,可以降低会话管理的难度。因为每次小批量的处理会话过期可以提高会话处理的效率。

三.分桶策略的过期队列和bucket

zk服务端所有会话过期的相关操作都是围绕过期队列来进行的,可以说zk服务端底层就是通过这个过期队列来管理会话过期的。过期队列就是ExpiryQueue类型的sessionExpiryQueue。

复制代码
public class SessionTrackerImpl extends ZooKeeperCriticalThread implements SessionTracker {
    private final ExpiryQueue<SessionImpl> sessionExpiryQueue;//过期队列
    private final AtomicLong nextSessionId = new AtomicLong();//当前生成的会话ID
    ConcurrentHashMap<Long, SessionImpl> sessionsById;//根据会话ID来管理具体的会话实体
    ConcurrentMap<Long, Integer> sessionsWithTimeout;//根据不同的会话ID管理每个会话的超时时间
    ...
}

什么是bucket:

SessionTracker的过期队列是ExpiryQueue类型的,ExpiryQueue类型的过期队列会由若干个bucket组成。每个bucket是以expirationInterval为单位进行时间区间划分的。每个bucket中会存放一些在某一时间点内过期的会话。

如何实现过期队列:

在zk中会使用ExpiryQueue类来实现一个会话过期队列。ExpiryQueue类中有两个HashMap:elemMap和一个expiryMap。elemMap中存放会话对象SessionImpl及其对应的最近过期时间点,expiryMap中存放的就是过期队列。

expiryMap的key就是bucket的时间划分,即会话的最近过期时间点。expiryMap的value就是bucket中存放的某一时间内过期的会话集合。所以bucket可以理解为一个Set会话对象集合。expiryMap是线程安全的HaspMap,可根据不同的过期时间区间存放会话。expiryMap过期队列中的一个过期时间点就对应一个bucket。

ExpiryQueue中也实现了remove()、update()、poll()等队列的操作方法。超时检查的定时任务一开始会获取最近的会话过期时间点看看当前是否已经到达,然后从过期队列中poll出bucket时会更新下一次的最近的会话过期时间点。

复制代码
public class ExpiryQueue<E> {
    //存放会话对象SessionImpl及其对应的最近的过期时间点
    private final ConcurrentHashMap<E, Long> elemMap = new ConcurrentHashMap<E, Long>();
    //存放过期队列,bucket可以理解为一个Set<SessionImpl>会话对象集合
    private final ConcurrentHashMap<Long, Set<E>> expiryMap = new ConcurrentHashMap<Long, Set<E>>();
    //最近的一批会话的过期时间点
    private final AtomicLong nextExpirationTime = new AtomicLong();
    //将会话划分到一个个bucket的时间间隔,也是超时检查线程定时检查时间间隔
    private final int expirationInterval;

    public ExpiryQueue(int expirationInterval) {
        this.expirationInterval = expirationInterval;
        nextExpirationTime.set(roundToNextInterval(Time.currentElapsedTime()));
    }
    
    private long roundToNextInterval(long time) {
        return (time / expirationInterval + 1) * expirationInterval;
    }
    
    public long getWaitTime() {
        long now = Time.currentElapsedTime();
        long expirationTime = nextExpirationTime.get();
        return now < expirationTime ? (expirationTime - now) : 0L;
    }
    
    public Long update(E elem, int timeout) {
        Long prevExpiryTime = elemMap.get(elem);
        long now = Time.currentElapsedTime();
        Long newExpiryTime = roundToNextInterval(now + timeout);

        if (newExpiryTime.equals(prevExpiryTime)) {
            // No change, so nothing to update
            return null;
        }

        // First add the elem to the new expiry time bucket in expiryMap.
        Set<E> set = expiryMap.get(newExpiryTime);
        if (set == null) {
            // Construct a ConcurrentHashSet using a ConcurrentHashMap
            set = Collections.newSetFromMap(new ConcurrentHashMap<E, Boolean>());
            // Put the new set in the map, but only if another thread hasn't beaten us to it
            Set<E> existingSet = expiryMap.putIfAbsent(newExpiryTime, set);
            if (existingSet != null) {
                set = existingSet;
            }
        }
        set.add(elem);

        // Map the elem to the new expiry time. If a different previous mapping was present, clean up the previous expiry bucket.
        prevExpiryTime = elemMap.put(elem, newExpiryTime);
        if (prevExpiryTime != null && !newExpiryTime.equals(prevExpiryTime)) {
            Set<E> prevSet = expiryMap.get(prevExpiryTime);
            if (prevSet != null) {
                prevSet.remove(elem);
            }
        }
        return newExpiryTime;
    }
    
    public Set<E> poll() {
        long now = Time.currentElapsedTime();
        long expirationTime = nextExpirationTime.get();
        if (now < expirationTime) {
            return Collections.emptySet();
        }

        Set<E> set = null;
        long newExpirationTime = expirationTime + expirationInterval;
        //设置最近的会话过期时间点
        if (nextExpirationTime.compareAndSet(expirationTime, newExpirationTime)) {
            set = expiryMap.remove(expirationTime);
        }
        if (set == null) {
            return Collections.emptySet();
        }
        return set;
    }
    
    public Long remove(E elem) {
        Long expiryTime = elemMap.remove(elem);
        if (expiryTime != null) {
            Set<E> set = expiryMap.get(expiryTime);
            if (set != null) {
                set.remove(elem);
            }
        }
        return expiryTime;
    }
    ...
}

(2)会话激活

一.检查该会话是否已经被关闭

二.计算该会话新的过期时间点newExpiryTime

三.将该会话添加到新的过期时间点对应的bucket中

四.将该会话从旧的过期时间点对应的bucket中移除

为了保持客户端会话的有效性,客户端要不断发送PING请求进行心跳检测。服务端要不断接收客户端的这个心跳检测,并重新激活对应的客户端会话。这个重新激活会话的过程由SessionTracker的touchSession()方法实现。

服务端处理PING请求的主要流程如下:

复制代码
public class NIOServerCnxnFactory extends ServerCnxnFactory {
    private ExpiryQueue<NIOServerCnxn> cnxnExpiryQueue;
    ...
    public void start() {
        stopped = false;
        if (workerPool == null) {
            workerPool = new WorkerService("NIOWorker", numWorkerThreads, false);
        }
        for (SelectorThread thread : selectorThreads) {
            if (thread.getState() == Thread.State.NEW) {
                thread.start();
            }
        }
        if (acceptThread.getState() == Thread.State.NEW) {
            acceptThread.start();
        }
        if (expirerThread.getState() == Thread.State.NEW) {
            expirerThread.start();
        }
    }
    
    class SelectorThread extends AbstractSelectThread {
        @Override
        public void run() {
            ...
            while (!stopped) {
                select();
                ...
            }
            ...
        }
        
        private void select() {
            selector.select();
            Set<SelectionKey> selected = selector.selectedKeys();
            ArrayList<SelectionKey> selectedList = new ArrayList<SelectionKey>(selected);
            Collections.shuffle(selectedList);
            Iterator<SelectionKey> selectedKeys = selectedList.iterator();
            while (!stopped && selectedKeys.hasNext()) {
                SelectionKey key = selectedKeys.next();
                selected.remove(key);
                ...
                if (key.isReadable() || key.isWritable()) {
                    //服务端从客户端读数据(读取请求) + 服务端向客户端写数据(发送响应)
                    handleIO(key);
                }
                ...
            }
        }
        
        private void handleIO(SelectionKey key) {
            IOWorkRequest workRequest = new IOWorkRequest(this, key);
            NIOServerCnxn cnxn = (NIOServerCnxn) key.attachment();
            cnxn.disableSelectable();
            key.interestOps(0);
            //激活连接:添加连接到连接过期队列
            touchCnxn(cnxn);
            //通过工作线程池来处理请求
            workerPool.schedule(workRequest);
        }
        ...
    }
    
    public void touchCnxn(NIOServerCnxn cnxn) {
        cnxnExpiryQueue.update(cnxn, cnxn.getSessionTimeout());
    }
    ...
}

public class WorkerService {
    ...
    public void schedule(WorkRequest workRequest) {
        schedule(workRequest, 0);
    }
    
    public void schedule(WorkRequest workRequest, long id) {
        ScheduledWorkRequest scheduledWorkRequest = new ScheduledWorkRequest(workRequest);
        int size = workers.size();
        if (size > 0) {
            int workerNum = ((int) (id % size) + size) % size;
            ExecutorService worker = workers.get(workerNum);
            worker.execute(scheduledWorkRequest);
        } else {
            scheduledWorkRequest.run();
        }
    }
    
    private class ScheduledWorkRequest implements Runnable {
        private final WorkRequest workRequest;
        
        ScheduledWorkRequest(WorkRequest workRequest) {
            this.workRequest = workRequest;
        }
        
        @Override
        public void run() {
            ...
            workRequest.doWork();
        }
    }
    ...
}

public class NIOServerCnxnFactory extends ServerCnxnFactory {
    private class IOWorkRequest extends WorkerService.WorkRequest {
        private final NIOServerCnxn cnxn;
        public void doWork() throws InterruptedException {
            ...
            if (key.isReadable() || key.isWritable()) {
                cnxn.doIO(key);
                ...
            }
            ...
        }
        ...
    }
    ...
}

public class NIOServerCnxn extends ServerCnxn {
    private final ZooKeeperServer zkServer;
    
    void doIO(SelectionKey k) throws InterruptedException {
        ...
        if (k.isReadable()) {
            ...
            readPayload();
            ...
        }
        ...
    }
    
    private void readPayload() throws IOException, InterruptedException {
        ...
        readRequest();
        ...
    }
    
    private void readRequest() throws IOException {
        //处理输入流
        zkServer.processPacket(this, incomingBuffer);
    }
    ...
}

public class ZooKeeperServer implements SessionExpirer, ServerStats.Provider {
    ...
    public void processPacket(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOException {
        InputStream bais = new ByteBufferInputStream(incomingBuffer);
        BinaryInputArchive bia = BinaryInputArchive.getArchive(bais);
        RequestHeader h = new RequestHeader();
        h.deserialize(bia, "header");
        incomingBuffer = incomingBuffer.slice();
        ...
        Request si = new Request(cnxn, cnxn.getSessionId(), h.getXid(), h.getType(), incomingBuffer, cnxn.getAuthInfo());
        ...
        submitRequest(si);
        ...
    }
    
    public void submitRequest(Request si) {
        ...
        //激活会话
        touch(si.cnxn);
        firstProcessor.processRequest(si);
        ...
    }
    
    void touch(ServerCnxn cnxn) throws MissingSessionException {
        if (cnxn == null) {
            return;
        }
        long id = cnxn.getSessionId();
        int to = cnxn.getSessionTimeout();
        //激活会话
        if (!sessionTracker.touchSession(id, to)) {
            throw new MissingSessionException("No session with sessionid 0x" + Long.toHexString(id) + " exists, probably expired and removed");
        }
    }
    ...
}

由于ZooKeeperServer的submitRequest()方法会调用touch()方法激活会话,所以只要客户端有请求发送到服务端,服务端就会进行一次会话激活。

执行SessionTracker的touchSession()方法进行会话激活的主要流程如下:

一.检查该会话是否已经被关闭

如果该会话已经被关闭,则返回,不用激活会话。

二.计算该会话新的过期时间点newExpiryTime

调用ExpiryQueue的roundToNextInterval()方法计算会话新的过期时间点。通过总时间除以间隔时间然后向上取整再乘以间隔时间来计算新的过期时间点。

三.将该会话添加到新的过期时间点对应的bucket中

从过期队列expiryMap获取新的过期时间点对应的bucket,然后添加该会话到新的过期时间点对应的bucket中。

四.将该会话从旧的过期时间点对应的bucket中移除

从elemMap中获取该会话旧的过期时间点,然后将该会话从旧的过期时间点对应的bucket中移除。

复制代码
public class SessionTrackerImpl extends ZooKeeperCriticalThread implements SessionTracker {
    ...
    ConcurrentHashMap<Long, SessionImpl> sessionsById;//根据会话ID来管理具体的会话实体
    
    synchronized public boolean touchSession(long sessionId, int timeout) {
        SessionImpl s = sessionsById.get(sessionId);
        if (s == null) {
            logTraceTouchInvalidSession(sessionId, timeout);
            return false;
        }
        //1.检查会话是否已经被关闭
        if (s.isClosing()) {
            logTraceTouchClosingSession(sessionId, timeout);
            return false;
        }
        //激活会话
        updateSessionExpiry(s, timeout);
        return true;
    }
    
    private void updateSessionExpiry(SessionImpl s, int timeout) {
        logTraceTouchSession(s.sessionId, timeout, "");
        //激活会话
        sessionExpiryQueue.update(s, timeout);
    }
    ...
}

public class ExpiryQueue<E> {
    //存放会话对象SessionImpl及其对应的最近的过期时间点
    private final ConcurrentHashMap<E, Long> elemMap = new ConcurrentHashMap<E, Long>();
    //存放过期队列,bucket可以理解为一个Set<SessionImpl>会话对象集合
    private final ConcurrentHashMap<Long, Set<E>> expiryMap = new ConcurrentHashMap<Long, Set<E>>();
    //最近的一批会话的过期时间点
    private final AtomicLong nextExpirationTime = new AtomicLong();
    //将会话划分到一个个bucket的时间间隔,也是超时检查线程的定时检查时间间隔
    private final int expirationInterval;
    ...
    private long roundToNextInterval(long time) {
        //通过向上取整来进行计算新的过期时间点
        return (time / expirationInterval + 1) * expirationInterval;
    }
    ...
    public Long update(E elem, int timeout) {
        Long prevExpiryTime = elemMap.get(elem);
        long now = Time.currentElapsedTime();
        //2.计算该会话新的过期时间点newExpiryTime
        Long newExpiryTime = roundToNextInterval(now + timeout);


        if (newExpiryTime.equals(prevExpiryTime)) {
            // No change, so nothing to update
            return null;
        }
      
        //3.从过期队列expiryMap获取新的过期时间点对应的bucket
        //First add the elem to the new expiry time bucket in expiryMap.
        Set<E> set = expiryMap.get(newExpiryTime);
        if (set == null) {
            // Construct a ConcurrentHashSet using a ConcurrentHashMap
            set = Collections.newSetFromMap(new ConcurrentHashMap<E, Boolean>());
            // Put the new set in the map, but only if another thread hasn't beaten us to it
            Set<E> existingSet = expiryMap.putIfAbsent(newExpiryTime, set);
            if (existingSet != null) {
                set = existingSet;
            }
        }
        //将会话添加到新的过期时间点对应的bucket中
        set.add(elem);
      
        //4.从elemMap中获取该会话旧的过期时间点
        //Map the elem to the new expiry time. If a different previous mapping was present, clean up the previous expiry bucket.
        prevExpiryTime = elemMap.put(elem, newExpiryTime);
        //然后将该会话从旧的过期时间点对应的bucket中移除
        if (prevExpiryTime != null && !newExpiryTime.equals(prevExpiryTime)) {
            Set<E> prevSet = expiryMap.get(prevExpiryTime);
            if (prevSet != null) {
                prevSet.remove(elem);
            }
        }
        return newExpiryTime;
    }
    ...
}

(3)会话超时检查

SessionTracker中会有一个线程专门进行会话超时检查,该线程会依次对bucket会话桶中剩下的会话进行清理,超时检查线程的定时检查时间间隔其实就是expirationInterval。

当一个会话被激活时,SessionTracker会将其从上一个bucket会话桶迁移到下一个bucket会话桶。所以超时检查线程的任务就是检查bucket会话桶中没被迁移的会话。

超时检查线程是如何进行定时检查的:

由于会话分桶策略会将expirationInterval的倍数作为会话最近过期时间点,所以超时检查线程只要在expirationInterval倍数的时间点进行检查即可。这样既提高了效率,而且由于是批量清理,因此性能也非常好。这也是zk要通过分桶策略来管理客户端会话的最主要原因。一个zk集群的客户端会话可能会非常多,逐个依次检查会非常耗费时间。

复制代码
public class SessionTrackerImpl extends ZooKeeperCriticalThread implements SessionTracker {
    private final ExpiryQueue<SessionImpl> sessionExpiryQueue;//会话过期队列
    private final SessionExpirer expirer;
    ...
    //超时检查线程
    @Override
    public void run() {
        try {
            while (running) {
                //获取会话过期队列中最近的过期时间和当前时间之差
                long waitTime = sessionExpiryQueue.getWaitTime();
                if (waitTime > 0) {
                    //时间未到则进行睡眠
                    Thread.sleep(waitTime);
                    continue;
                }

                for (SessionImpl s : sessionExpiryQueue.poll()) {
                    //设置过期的会话状态为已关闭
                    setSessionClosing(s.sessionId);
                    //对会话进行过期处理
                    expirer.expire(s);
                }
            }
        } catch (InterruptedException e) {
            handleException(this.getName(), e);
        }
        LOG.info("SessionTrackerImpl exited loop!");
    }
    ...
}

public class ExpiryQueue<E> {
    private final AtomicLong nextExpirationTime = new AtomicLong();
    ...
    public long getWaitTime() {
        //当前时间
        long now = Time.currentElapsedTime();
        //获取最近的过期时间点
        long expirationTime = nextExpirationTime.get();
        return now < expirationTime ? (expirationTime - now) : 0L;
    }
    
    public Set<E> poll() {
        long now = Time.currentElapsedTime();
        //获取最近的过期时间点
        long expirationTime = nextExpirationTime.get();
        if (now < expirationTime) {
            return Collections.emptySet();
        }
        Set<E> set = null;
        //根据expirationInterval计算最新的最近过期时间点
        long newExpirationTime = expirationTime + expirationInterval;
        //重置bucket桶中最近的过期时间点
        if (nextExpirationTime.compareAndSet(expirationTime, newExpirationTime)) {
            //移出过期队列
            set = expiryMap.remove(expirationTime);
        }
        if (set == null) {
            return Collections.emptySet();
        }
        return set;
    }
    ...
}

(4)会话清理

当SessionTracker的会话超时检查线程遍历出一些已经过期的会话时,就要进行会话清理了,会话清理的步骤如下:

一.标记会话状态为已关闭

二.发起关闭会话请求

三.收集临时节点

四.添加临时节点的删除请求到事务变更队列

五.删除临时节点

六.移除会话

七.关闭NIOServerCnxn

一.标记会话状态为已关闭

SessionTracker的setSessionClosing()方法会标记会话状态为已关闭,这是因为整个会话清理过程需要一段时间,为了保证在会话清理期间不再处理来自该会话对应的客户端的请求,SessionTracker会首先将该会话的isClosing属性标记为true。

二.发起关闭会话请求

ZooKeeperServer的expire()方法和close()方法会发起关闭会话请求,为了使对该会话的关闭操作在整个服务端集群中都生效,zk使用提交"关闭会话"请求的方式,将请求交给PrepRequestProcessor处理。

三.收集临时节点

PrepRequestProcessor的pRequest2Txn()方法会收集需要清理的临时节点。在zk中,一旦某个会话失效,那么和该会话相关的临时节点也要被清除掉。因此需要首先将服务器上所有和该会话相关的临时节点找出来。

zk的内存数据库会为每个会话都保存一份由该会话维护的临时节点集合。因此在会话清理阶段,只需根据当前即将关闭的会话的sessionID,便可以从zk的内存数据库中获取到该会话的临时节点列表。

四.添加临时节点的删除请求到事务变更队列

将临时节点的删除请求添加到事务变更队列outstandingChanges中。完成该会话相关的临时节点收集后,zk会将这些临时节点逐个转换成节点删除请求,添加到事务变更队列中。

五.删除临时节点

FinalRequestProcessor的processRequest()方法触发删除临时节点。当收集完所有需要删除的临时节点,以及创建了对应的节点删除请求后,便会在FinalRequestProcessor的processRequest()方法中,通过调用ZooKeeperServer的processTxn()方法,调用到ZKDatabase的processTxn()方法,最后调用DataTree的killSession()方法,从而最终删除内存数据库中该会话的所有临时节点。

六.移除会话

在FinalRequestProcessor的processRequest()方法中,会通过调用ZooKeeperServer的processTxn()方法,调用到SessionTracker的removeSession()方法将会话从SessionTracker移除。即从sessionsById、sessionsWithTimeout、sessionExpiryQueue中移除会话。

七.关闭NIOServerCnxn

在FinalRequestProcessor的processRequest()方法中,最后会调用FinalRequestProcessor的closeSession()方法,从NIOServerCnxnFactory的sessionMap中将该会话进行移除。

复制代码
public class SessionTrackerImpl extends ZooKeeperCriticalThread implements SessionTracker {
    private final ExpiryQueue<SessionImpl> sessionExpiryQueue;//会话过期队列
    private final SessionExpirer expirer;
    ...
    //超时检查线程
    @Override
    public void run() {
        while (running) {
            //获取会话过期队列中最近的过期时间和当前时间之差
            long waitTime = sessionExpiryQueue.getWaitTime();
            if (waitTime > 0) {
                //时间未到则进行睡眠
                Thread.sleep(waitTime);
                continue;
            }
            for (SessionImpl s : sessionExpiryQueue.poll()) {
                //1.设置过期的会话状态为已关闭
                setSessionClosing(s.sessionId);
                //2.对会话进行过期处理,ZooKeeperServer实现了SessionExpirer接口
                expirer.expire(s);
            }
        }
    }
    
    synchronized public void setSessionClosing(long sessionId) {
        SessionImpl s = sessionsById.get(sessionId);
        s.isClosing = true;
    }
    
    //6.移除会话
    synchronized public void removeSession(long sessionId) {
        SessionImpl s = sessionsById.remove(sessionId);
        sessionsWithTimeout.remove(sessionId);
        sessionExpiryQueue.remove(s);
    }
    ...
}

public class ZooKeeperServer implements SessionExpirer, ServerStats.Provider {
    public synchronized void startup() {
        if (sessionTracker == null) {
            createSessionTracker();//创建会话管理器
        }
        startSessionTracker();//启动会话管理器的超时检查线程
        setupRequestProcessors();//初始化请求处理链
        registerJMX();
        setState(State.RUNNING);
        notifyAll();
    }
    
    protected void setupRequestProcessors() {
        RequestProcessor finalProcessor = new FinalRequestProcessor(this);
        RequestProcessor syncProcessor = new SyncRequestProcessor(this, finalProcessor);
        ((SyncRequestProcessor)syncProcessor).start();
        firstProcessor = new PrepRequestProcessor(this, syncProcessor);
        ((PrepRequestProcessor)firstProcessor).start();
    }
    ...
    public void expire(Session session) {
        long sessionId = session.getSessionId();
        //2.发起关闭会话请求
        close(sessionId);
    }
    
    private void close(long sessionId) {
        Request si = new Request(null, sessionId, 0, OpCode.closeSession, null, null);
        setLocalSessionFlag(si);
        //2.以提交"关闭会话"请求的方式,发起关闭会话请求
        submitRequest(si);
    }
    
    public void submitRequest(Request si) {
        ...
        touch(si.cnxn);
        //2.首先由PrepRequestProcessor请求处理器的processRequest方法进行处理
        firstProcessor.processRequest(si);
        ...
    }
    
    public ProcessTxnResult processTxn(Request request) {
        return processTxn(request, request.getHdr(), request.getTxn());
    }
    
    private ProcessTxnResult processTxn(Request request, TxnHeader hdr, Record txn) {
        ...
        //5.ZKDatabase.processTxn方法会根据opCode.closeSession来删除临时节点
        rc = getZKDatabase().processTxn(hdr, txn);
        ...
        if (opCode == OpCode.createSession) {
            ...
        } else if (opCode == OpCode.closeSession) {
            //6.移除会话
            sessionTracker.removeSession(sessionId);
        }
        return rc;
    }
    ...
}

public class PrepRequestProcessor extends ZooKeeperCriticalThread implements RequestProcessor {
    LinkedBlockingQueue<Request> submittedRequests = new LinkedBlockingQueue<Request>();
    private final RequestProcessor nextProcessor;
    ZooKeeperServer zks;
    
    public PrepRequestProcessor(ZooKeeperServer zks, RequestProcessor nextProcessor) {
        super("ProcessThread(sid:" + zks.getServerId() + " cport:" + zks.getClientPort() + "):", zks.getZooKeeperServerListener());
        this.nextProcessor = nextProcessor;
        this.zks = zks;
    }
    ...
    public void processRequest(Request request) {
        submittedRequests.add(request);
    }
    
    @Override
    public void run() {
        while (true) {
            Request request = submittedRequests.take();
            pRequest(request);
        }
    }
    
    protected void pRequest(Request request) throws RequestProcessorException {
        ...
        case OpCode.closeSession:
            pRequest2Txn(request.type, zks.getNextZxid(), request, null, true);
            break;
        ...
        //交给下一个请求处理器处理
        nextProcessor.processRequest(request);
    }
    
    protected void pRequest2Txn(int type, long zxid, Request request, Record record, boolean deserialize) {
        //将请求标记为事务请求
        request.setHdr(new TxnHeader(request.sessionId, request.cxid, zxid, Time.currentWallTime(), type));
        ...
        case OpCode.closeSession:
            //3.收集需要清理的临时节点
            Set<String> es = zks.getZKDatabase().getEphemerals(request.sessionId);
            synchronized (zks.outstandingChanges) {
                ...
                for (String path2Delete : es) {
                    //4.将临时节点的删除请求添加到事务变更队列outstandingChanges中
                    addChangeRecord(new ChangeRecord(request.getHdr().getZxid(), path2Delete, null, 0, null));
                }
                zks.sessionTracker.setSessionClosing(request.sessionId);
            }
            break;
        ...
    }
    
    private void addChangeRecord(ChangeRecord c) {
        //4.将临时节点的删除请求添加到事务变更队列outstandingChanges中
        synchronized (zks.outstandingChanges) {
            zks.outstandingChanges.add(c);
            zks.outstandingChangesForPath.put(c.path, c);
        }
    }
    ...
}

public class FinalRequestProcessor implements RequestProcessor {
    ZooKeeperServer zks;
    
    public void processRequest(Request request) {
        ...
        //5.删除临时节点 + 6.移除会话
        rc = zks.processTxn(request);
        ...
        if (request.type == OpCode.closeSession && connClosedByClient(request)) {
            //7.关闭NIOServerCnxn
            if (closeSession(zks.serverCnxnFactory, request.sessionId) ||
                closeSession(zks.secureServerCnxnFactory, request.sessionId)) {
                return;
            }
        }
        ...
    }
    
    private boolean closeSession(ServerCnxnFactory serverCnxnFactory, long sessionId) {
        if (serverCnxnFactory == null) {
            return false;
        }
        //7.关闭NIOServerCnxn
        return serverCnxnFactory.closeSession(sessionId);
    }
    ...
}

public class NIOServerCnxnFactory extends ServerCnxnFactory {
    private final ConcurrentHashMap<Long, NIOServerCnxn> sessionMap = new ConcurrentHashMap<Long, NIOServerCnxn>();
    ...
    public void addSession(long sessionId, NIOServerCnxn cnxn) {
        sessionMap.put(sessionId, cnxn);
    }
    
    @Override
    public boolean closeSession(long sessionId) {
        NIOServerCnxn cnxn = sessionMap.remove(sessionId);
        if (cnxn != null) {
            cnxn.close();
            return true;
        }
        return false;
    }
    ...
}
相关推荐
东阳马生架构2 小时前
zk源码—7.ZAB协议和数据存储
zookeeper
lilye664 小时前
程序化广告行业(78/89):多因素交织下的行业剖析与展望
数据库·zookeeper·nosql
东阳马生架构1 天前
zk源码—6.Leader选举的实现原理
zookeeper
东阳马生架构3 天前
zk源码—5.请求的处理过程
zookeeper
东阳马生架构3 天前
zk源码—5.请求的处理过程二
zookeeper
东阳马生架构3 天前
zk源码—5.请求的处理过程一
zookeeper
小李独爱秋3 天前
Zookeeper的作用详解
分布式·zookeeper·云原生