【ZooKeeper】zookeeper源码9-ZooKeeper读写流程源码分析

ZooKeeper选举和状态同步结束之后的服务启动

在Leader的lead()方法的最后,即Leader完成了和集群过半Follower的同步之后,就会调用startZkServer()来启动必要的服务,主要包括:
SessionTracker
RequestProcessor
更新Leader ZooKeeperServer的状态

Leader.lead(){
	startZkServer(){
		zk.startup(){
			//ZooKeeperServer启动
			super.startup(){
				startupWithServerState(State.RUNNING);
			}
			//ZK Container ZNode定时清除任务
			if(containerManager != null){
				containerManager.start();
			}
		}
	}
}

同理,Follower也是一样的,在完成了和Leader的状态同步之后,也就是接收到Leader发送过来的NEWLEADER消息的时候,先拍摄快照,然后调用zk.startupWithoutServing()来启动Follower必要的一些基础服务,包括:
SessionTracker
RequestProcessor
更新Leader ZooKeeperServer的状态

Leader.syncWithLeader(long newLeaderZxid){
	//创建SessionTacker
	zk.createSessionTracker();
	//启动一些服务
	zk.startupWithoutServing(){
		startupWithoutServerState(State.INITIAL);
	}
}

不管Leader还是Follower,最后都执行startupWithServerState(State state),具体实现:

ZooKeeperServer.startupWithServerState(State state){
	//创建和启动SessionTracker 重要
	if(sessionTracker == null){
		createSessionTracker();
	}
	startSessionTracker();
	
	//初始化RequestProcessors 重要
	setupRequestProcessors();

	//其他各项基础服务
	startRequestThrottler();
	registerJMX();
	startJvmPauseMonitor();
	registerMetrics();

	//更新状态为RUNNING
	setState(state);

	//解除其他线程的阻塞
	notifyAll();
}

ZooKeeper SessionTracker启动和工作机制

需求背景:现在每个客户端链接到ZK服务器的时候,在服务器内部,都会创建一个Session来管理这个链接
如果链接断开,删除这个session
如果链接超时,删除这个session(在规定的超时时间前,没有任何动作)
会遇到什么问题?大量的session管理,ZK提供的方案:桶管理机制(Session管理,Connection管理)

在Leader启动的时候,Leader会创建LeaderSessionTracker,在Follower启动的时候,内部会创建一个LearnerSessionTracker。SessionTracker的内部都有globalSessionTracker和localSessionTracker之分,都通过SessionTrackerImpl和ExpiryQueue来完成Session管理。
SessionTrackerImpl的定义:时间轮

public class SessionTrackerImpl extends ZooKeeperCriticalThread implements SessionTracker {
	// 存储 session 信息,key 是 sessionID, value 是 Session 对象
	protected final ConcurrentHashMap<Long,SessionImpl> sessionById = new ConcurrentHashMap<Long,SessionImpl>();
	// SessionTrackerImpl 本身是一个线程
	// 注意这个数据结构,这是理解 Session 和 Connection 超时管理的关键,是一个类似于 时间轮 的数据结构设计
	// 将所有要进行管理的 Session 或者 Connection 加入到不同时刻对应的某个队列中,ZK 只会每隔一段时间,针对一个队列,进行全部过期操作处理
	private final ExpiryQueue<SessionImpl> sessionExpiryQueue;
	// 存储 session 的超时时间信息,key 是 sessionID, value 是 超时时间
	private final ConcurrentMap<Long,Integer> sessionWithTimeout;

	// SessionTrackerImpl 本身是一个线程
	public void run() {
		try {
			while(running) {
				// TODO 注释: 获取下一个队列的超时时间,等待
				long waitTime = sessionExpiryQueue.getWaitTime();
				if(waitTime > 0) {
					Thread.sleep(waitTime);
					continue;
				}
				// TODO 注释: 执行 expire
				for(SessionImpl s : sessionExpiryQueue.poll()) {
					ServerMetrics.getMetrics().STALE_SESSIONS_EXPIRED.add(1);
					setSessionClosing(s.sessionId);
					expirer.expires(s);
				}
			}
		} catch(InterruptedException e) {
            handleException(this.getName(), e);
        }
        LOG.info("SessionTrackerImpl exited loop!");
	}

	public synchronized boolean touchSession(long sessionId,int timeout) {
		SessionImpl s = sessionsById.get(sessionId);

		if(s==null){
			logTraceTouchInvalidSession(sessionId,timeout);
			return false;
		}
		
		if(s.isClosing()) {
			logTraceTouchClosingSession(sessionId,timeout);
			return false;
		}
		
		// 更新该 Session 的超时信息
		// 该方法的内部就是调用 ExpiryQueue 的 update 方法!
		updateSessionExpiry(s,timeou);
		return true;
	}
}

ExpiryQueue的定义:

public class ExpiryQueue<E> {
	// E 是管理对象,比如 Session, value Long 是超时时间
	private final ConcurrentHashMap<E,Long> elemMap = new ConcurrentHashMap<E,Long>();
	// 最核心的数据结构 key = 超时时间, value = 需要进行超时处理的一个集合, 在进行 Session 管理的时候:E = SessionImpl
	private final ConcurrentHashMap<Long,Set<E>> expiryMap = new ConcurrentHashMap<Long,Set<E>>();
	// 桶间隔,默认 10 s,意味着,每隔 10s 执行一个 Set 的过期
	private final int expirationInterval;

	private long roundToNextInterval(long time) {
		return (time / expirationInterval + 1) * expirationInterval;
	}

	// 执行 Session 的 timeout 的更新和换桶操作
	public Long update(E elem, int timeout) {
		// 获取当前 Session elem 的超时时间
		Long prevExpiryTime = elemMap.get(elem);
		//计算 它对应的 session bucket
		long now = Time.currentElapsedTime();
		Long newExpiryTime = roundToNextInterval(now + timeout);
		//如果前后两次 expireTime 的更新依然处于同一个桶,则不做任何操作
		if(newExpiryTime.equals(prevExpiryTime)) {
			return null;
		}

		//根据桶对应的 ExpiryTime 找到 存储会话的 set 集合, 这个 set 集合就是一个所谓的 桶
		//所以其实 Session 管理,就是把所有 Session 分散成多个桶。每隔一段时间,对一个桶的所有 Session 执行过期处理
		Set<E> set = expiryMap.get(newExpriyTime);

		// 如果桶为空,则创建桶,加入到 expiryMap 中
		if(set == null) {
			//新的桶
			set = Collections.newSetFromMap(new ConcurrentHashMap<E,Boolean>());
			//旧的桶
			Set<E> existingSet = expiryMap.putIfAbsent(newExpiryTime,set);
			if(existingSet != null) {
				set = existingSet;
			}
		}
		//将该 Session 加入到新的桶中
		set.add(elem);
		// 更新 elemMap 中的桶信息 即换桶
		prevExpiryTime = elemMap.put(elem,newExpiryTime);
		if(prevExpiryTime != null && !newExpiryTime.equals(prevExpiryTime)) {
			Set<E> prevSet = expiryMap.get(prevExpiryTime);
			if(prevSet != null) {
				//从原来的桶中,移除掉该 session
				prevSet.remove(elem);
			}
		}
		return newExpiryTime;
	}
}

SessionTrackerImpl 线程的意义,在于不停的按照顺序取这个 expiryMap 中的 Set,然后针对这个 Set 执行过期处理。
updateSessionExpiry(s, timeout) 方法是理解 Session 管理的核心。它的作用是,如果某个 Session 活跃了,则会调用 touchSession 方法来更新 Session 的超时时间。

Leader的RequestProcessor初始化

Leader的setupRequestProcessors()方法的核心逻辑:

LeaderZooKeeperServer.java
protected void setupRequestProcessors() {
	//第六个: FinalRequestProcessor
	RequestProcessor finalProcessor = new FinalRequestProcessor(this);
	//第五个: ToBeAppliedRequestProcessor
	RequestProcessor toBeAppliedProcessor = new Leader.ToBeAppliedRequestProcessor(finalProcessor,getLeader());
	//第四个: CommitProcessor
	commitProcessor = new CommitProcessor(toBeAppliedProcessor,Long.toString(getServerId()),false,getZooKeeperServerListener());
	commitProcessor.start();
	//第三个: ProposalRequestProcessor
	//内部初始化 SyncRequestProcessor 和 AckRequestProcessor
	ProposalRequestProcessor proposalProcessor = new ProposalRequestProcessor(this,commitProcessor);
	proposalProcessor.initialize();

	//第二个: PrepRequestProcessor
	prepRequestProcessor = new PrepRequestProcessor(this,proposalProcessor);
	prepRequestProcessor.start();

	//第一个: LeaderRequestProcessor
	firstProcessor = new LeaderRequestProcessor(this,prepRequestProcessor);
	
	//启动删除 Container 节点的一个定时任务
	setupContainerManager();
}

LeaderRequestProcessor:Leader调用链开始,这个处理器主要是处理本地session相关的
PrepRequestProcessor:请求预处理器,能够识别出当前客户端请求是否是事务请求,PrepRequestProcessor处理器会对其进行一系列预处理,如创建请求事务头、事务体、会话检查、ACL检查和版本检查等。
ProposalRequestProcessor:事务投票处理。Leader服务器事务处理流程的发起者。接收到非事务请求不做什么处理,会直接将请求转发到CommitProcessor,接收到事务请求,除了将请求转发到CommitProcessor外,还会根据请求类型创建对应的Proposal提议并广播给所有Follower进行投票。另外,它还会将事务请求交付给SyncRequestProcessor进行事务日志的记录。
CommitProcessor:事务提交处理器。对于非事务请求,该处理器会直接将其交付给下一级处理器处理;对于事务请求,其会等待集群内针对Proposal的投票直到该Proposal可被提交,利用CommitProcessor,每个服务器都可以很好的控制对事务请求的顺序处理。
ToBeAppliedRequestProcessor:该处理器有一个toBeApplied队列,用来存储那些已经被CommitProcessor处理过的可被提交的Proposal。其会将这些请求交付给FinalRequestProcessor处理器处理,待其处理完后,再将其从toBeApplied队列中移除。
FinalRequestProcessor:FinalRequestProcessor用来进行客户端请求返回之前的操作,包括创建客户端请求的响应,针对事务请求,该处理还会负责将事务应用到内存数据库中去。
SyncRequestProcessor:事务日志记录处理器。负责将事务持久化到磁盘上。实际上就是将事务数据按顺序追加到事务日志中,同时会触发ZooKeeper进行数据快照。
AckRequestProcessor:负责再SyncRequestProcessor完成事务日志记录后,向Proposal的投票收集器发送ACK反馈,以通知投票收集器当前服务器已经完成了对该Proposal的事务日志记录。

Follower的RequestProcessor初始化

Follower的setupRequestProcessor()方法的核心逻辑:

FollowerZooKeeperServer.java
protected void setupRequestProcessors() {
	//第三个: FinalRequestProcessor
	RequestProcessor finalProcessor = new FinalRequestProcessor(this);
	//第二个: CommitProcessor
	commitProcessor = new CommitProcessor(finalProcessor,Long.toString(getServerId()),true,getZooKeeperServerListener());
	commitProcessor.start();
	//第一个: FollowerRequestProcessor
	firstProcessor = new FollowerRequestProcessor(this,commitProcessor);
	((FollowerRequestProcessor)firstProcessor).start();

	//注释: SyncRequestProcessor
	syncProcessor = new SyncRequestProcessor(this,new SendAckRequestProcessor(getFollower()));
	syncProcessor.start();
}

Follower RequestProcessor详解:
FollowerRequestProcessor:识别当前请求是否是事务请求,若是,那么Follower就会将该请求转发给Leader服务器,Leader服务器是在接收到这个事务请求后,就会将其提交到请求处理链,按照正常事务请求进行处理。
CommitProcessor:同Leader的CommitProcessor
FinalRequestProcessor:同Leader的FinalRequestProcessor
SyncRequestProcessor:同Leader的SyncRequestProcessor
SendAckRequestProcessor:其承担了事务日志记录反馈的角色,在完成事务日志记录后,会向Leader服务器发送ACK消息以表明自身完成了事务日志的记录工作。当Leader服务器接收到足够确认消息来提交这个提议时,Leader就会发送提交事务消息给追随者(同时也会发送INFORM消息给观察者服务器)。当接收到提交事务消息时,追随者就通过CommitRequestProcessor处理器进行处理。

ZooKeeper客户端初始化

new ZooKeeper 的时候内部初始化了 ClientCnxn 的客户端通信对象

// 初始化了 ClientCnxn 的客户端通信对象
ZooKeeper zk = new ZooKeeper("bigdata01:2181,bigdata02:2181", 5000, watcher);
// 创建 znode 节点
zk.create();
// 查询 znode 节点数据
zk.getData();

ZooKeeper.java
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher) throws IOException {
    /************************************************
     *  注释:
     *  1、链接地址
     *  2、会话超时时间
     *  3、默认监听器
     */
    this(connectString, sessionTimeout, watcher, false);
}
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, ZKClientConfig conf) throws IOException {
    this(connectString, sessionTimeout, watcher, false, conf);
}
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly,
                 HostProvider aHostProvider) throws IOException {
    this(connectString, sessionTimeout, watcher, canBeReadOnly, aHostProvider, null);
}
// 进入ZooKeeper 构造方法
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly,
                 HostProvider aHostProvider, ZKClientConfig clientConfig) throws IOException {
	LOG.info("Initiating client connection, connectString={} sessionTimeout={} watcher={}", connectString, sessionTimeout, watcher);
	if (clientConfig == null) {
		clientConfig = new ZKClientConfig();
	}
	this.clientConfig = clientConfig;
	
	watcherManager = defaultWatchManager();
	watcherManager.defaultWatcher = watcher;
	ConnectStringParser connectStringParser = new ConnectStringParser(connectString);
	hostProvider = aHostProvider;
	/*************************************************
    * 注释: 创建和 ZK Server 的链接
    *  1、这个方法创建一个链接对象
    *  2、具体其实是创建: ClientCnxnSocketNIO,内部启动了一个 NIO 客户端
    *  cnxn = ClientCnxn 包装了 ClientCnxnSocketNIO
    */
    cnxn = createConnection(connectStringParser.getChrootPath(), hostProvider, sessionTimeout, this, watchManager, getClientCnxnSocket(), canBeReadOnly);
    cnxn.start();
}

//进入createConnection方法
public ClientCnxn createConnection(String chrootPath, HostProvider hostProvider, int sessionTimeout,
								ZooKeeper zooKeeper, ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket,
								boolean canBeReadOnly) throws IOException {
	//真正意义上的客户端
	return new ClientCnxn(chrootPath,hostProvider,sessionTimeout,this,watcherManager,clientCnxnSocket,canBeReadOnly);
}

ClientCnxn.java
//进入ClientCnxn方法
public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper,
	ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket, boolean canBeReadOnly) throws IOException {
	this(chrootPath, hostProvider, sessionTimeout, zooKeeper, watcher, clientCnxnSocket, 0, new byte[16], canBeReadOnly);
}
public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper,
            ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket, long sessionId, byte[] sessionPasswd,
            boolean canBeReadOnly) throws IOException {
    this.zooKeeper = zooKeeper;
    this.watcher = watcher;
    this.sessionId = sessionId;
    this.sessionPasswd = sessionPasswd;
    this.sessionTimeout = sessionTimeout;
    this.hostProvider = hostProvider;
    this.chrootPath = chrootPath;

	connectTimeout = sessionTimeout / hostProvider.size();
	readTimeout = sessionTimeout * 2 / 3;
	readOnly = canBeReadOnly;

	//发起请求的线程(链接请求,写数据请求,读数据请求)
	sendThread = new SendThread(clientCnxnSocket);
	//接收响应,执行处理的(watcher,callback,response)
	eventThread = new EventThread();

	this.clientConfig = zooKeeper.getClientConfig();
	initRequestTimeout();
}
public void start() {
	//先发起链接请求,建立链接之后,再发起正常的读写数据请求
	//SendThread 内部保存了一个 ClientCnxnSocketNIO,相当于一个 NIO 的客户端,
	//负责和 ZooKeeper 的 ServerCnxnFactory 中启动的服务端建立连接,然后负责消费 outgoingQueue 中的消息,执行请求发送。
	sendThread.start();
	// 接收到请求的响应,执行处理的线程
	//EventThread 线程消费 waitingEvents 队列,调用 processEvent(event) 负责处理服务端返回回来的消息,事件,异步回调等。
	eventThread.start();
}

ZooKeeper服务端初始化

ZooKeeper集群启动服务的入口类QuorumPeerMain.java

QuorumPeerMain.java
-main
	-main.initializeAndRun(args);
-initializeAndRun
	-runFromConfig(config);
-runFromConfig
		//第一件事:创建。通过反射创建 NIOServerCnxnFactory 实例
	-ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory();
	//第二件事:初始化。启动内部组件,即各种线程
	-cnxnFactory.configure(config.getSecureClientPortAddress(), config.getMaxClientCnxns(), config.getClientPortListenBacklog(), true);
	//进入NIOServerCnxnFactory.java

	//第三件事:启动工作线程。启动 QuorumPeer
	-quorumPeer.start();
	//进入QuorumPeer.java

QuorumPeer.java
-start
	-startServerCnxnFactory();
		-secureCnxnFactory.start();
		//进入NIOServerCnxnFactory.java


NIOServerCnxnFactory.java
-configure
	//
	initMaxCnxns();
	//管理 NIOServerCnxn 的超时
	sessionlessCnxnTimeout = Integer.getInteger(ZOOKEEPER_NIO_SESSIONLESS_CNXN_TIMEOUT,10000);
	//启动一个链接管理线程
	cnxnExpiryQueue = new ExpiryQueue<NIOServerCnxn>(sessionlessCnxnTimeout);
	expirerThread = new ConnectionExpirerThread();
	//获取cpu个数
	int numCores = Runtime.getRuntime().availableProcessors();
	//获取selectorThread的个数
	numSelectorThreads = Integer.getInteger(ZOOKEEPER_NIO_NUM_SELECTOR_THREADS, Math.max((int)Math.sqrt((float)numCores/2),1));
	//获取WorkThread个数
	numWorkerThreads = Integer.getInteger(ZOOKEEPER_NIO_NUM_WORKER_THREADS, 2*numCores);

	//初始化SelectorThread
	for(int i = 0;i<numSelectorThreads;++i) {
		selectorThreads.add(new SelectorThread(i));
	}

	//启动 NIO 服务端 ServerSocketChannel
	this.ss = ServerSocketChannel.open();
	ss.socket().setReuseAddress(true);
	ss.socket().bind(addr);

	//初始化一个 AcceptThread
	acceptThread = new AcceptThrea(ss,addr,selectorThreads);
-start
	//启动WorkThread
	workPool = new WorkerService("NIOWorker", numWorkerThreads, false);
	//进入WorkService.java

	//启动SelectorThread
	-for(SelectorThread thread:selectorThreads) {
		if(thread.getState() == Thread.State.NEW) {
			thread.start();
		}
	}
	//启动AcceptThread
	if(acceptThread.getState() == Thread.State.NEW) {
		acceptThread.start();
	}
	//启动ConnectionExpirerThread
	if(expirerThread.getState() == Thread.State.New) {
		expirerThread.start();
	}
	
WorkService.java
-WorkerService
	-start();

-start
	-workers.add(Executors.newFixedThreadPool(numWorkerThreads,new DaemonThreadFactory(threadNamePrefix)));

	

NIOServerCnxnFactory内部首先启动(一个AcceptThread,多个SelectorThread,一个线程池WorkThread)每次接收到一个客户端链接请求:在服务端会生成一个ServerCnxn的组件,这个对象内部就封装了一个SocketChannel=专门对某个client执行服务的。
在ZooKeeper的客户端ClientCnxn初始化的时候,是由内部的SendThread发起链接请求给服务端建立连接,然后服务端会给当前的客户端生成一个ServerCnxn,一个客户端就会有一个对应的ServerCnxn,相当于ServerCnxn是一一对应的关系。而且当服务端生成好了ServerCnxn之后,还会给当前这个链接创建一个Session,通过Session来管理这个链接。

第一步:创建服务端NIOServerCnxnFactory
第二步:对NIOServerCnxnFactory进行初始化
第三步:启动NIOServerCnxnFactory内部的各种工作线程
AcceptThread负责接收链接请求,建立连接
SelectorThread负责IO读写
WorkService负责请求处理
ConnectionExpirerThread线程负责连接超时管理

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值