万有斥力 发表于 2025-4-10 10:50:10

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是指会话最近的过期时间点。

https://i-blog.csdnimg.cn/img_convert/59cbdf859c0e5a2889c4ee62b8b85054.png
对于一个新会话创建完毕后,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;
    }
    ...
}

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: zk源码—4.会话的实现原理二