单机zookeeper处理过程

接上文单机Zookeeper服务端启动过程

这篇文章主要是讲解单机版zk接收到命令后服务端的处理流程

1.上文提到启动过程会调用startup方法org.apache.zookeeper.server.NIOServerCnxnFactory#startup

public synchronized void startup() {
		//session跟踪器
        if (sessionTracker == null) {
            createSessionTracker();
        }
        startSessionTracker();
        //初始化RequestProcessor调用链
        setupRequestProcessors();
        //限流很重要
        startRequestThrottler();
        registerJMX();
        startJvmPauseMonitor();
        registerMetrics();
        //更新zk为运行中状态
        setState(State.RUNNING);
        requestPathMetricsCollector.start();
        localSessionEnabled = sessionTracker.isLocalSessionsEnabled();
		//唤醒前面等待的线程处理请求
        notifyAll();
    }

startRequestThrottler()该方法内启动RequestThrottler线程,该线程从submittedRequests队列获取请求,并判断正在处理请求的数量是否小于maxRequests如果超过就停止处理请求,如果通过那么通过zks.submitRequestNow(request)方法调用下文的三大处理器。
2.org.apache.zookeeper.server.ZooKeeperServer#setupRequestProcessors初始化调用链

protected void setupRequestProcessors() {
        // PrepRequestProcessor线程-->SyncRequestProcessor线程-->FinalRequestProcessor
        RequestProcessor finalProcessor = new FinalRequestProcessor(this);
        RequestProcessor syncProcessor = new SyncRequestProcessor(this, finalProcessor);
        ((SyncRequestProcessor) syncProcessor).start();
        firstProcessor = new PrepRequestProcessor(this, syncProcessor);
        ((PrepRequestProcessor) firstProcessor).start();
    }

其中PrepRequestProcessor与SyncRequestProcessor都为线程,也就是说这两个处理器都是异步进行FinalRequestProcessor是在SyncRequestProcessor处理完由它调用。下面分析这三个处理器都做了那些事情。
3.PrepRequestProcessor处理器,主要方法org.apache.zookeeper.server.PrepRequestProcessor#processRequest仅有一行关键代码submittedRequests.add(request);仅把request添加到队列实现解耦,下面看看该队列由谁去消费。
4.上面提到了三大处理器而且前两个处理器都是线程,那么先来分析PrepRequestProcessor的run()方法,代码比较长就不贴出来了,大部分都是switch case操作针对create,delete,acl,multi等分别构造相应的request给后面的处理器处理。比如我们以create命令作为切入点,该处理器先把队列的request转化为record并做了一些常规校验,以及修改parent一些信息注入版本加一,子节点数量加一,生成CreateMode(节点类型)等,注意这里会生成两条record信息,一条是父record,另一条是本record,想想create -s /parent/child1会发生什么吧就很好理解了。最后把生成的两条record加入队列。再有就是run()方法最后一步调用了nextProcessor.processRequest(request)调用下一个处理器SyncRequestProcessor。
5.org.apache.zookeeper.server.PrepRequestProcessor#addChangeRecord

//父子record分别调用此方法把生成的ChangeRecord加入队列
 protected void addChangeRecord(ChangeRecord c) {
        synchronized (zks.outstandingChanges) {
            zks.outstandingChanges.add(c);
            zks.outstandingChangesForPath.put(c.path, c);
            ServerMetrics.getMetrics().OUTSTANDING_CHANGES_QUEUED.add(1);
        }
    }

6.org.apache.zookeeper.server.SyncRequestProcessor#processRequest是由PrepRequestProcessor调用过来的

public void processRequest(final Request request) {
        Objects.requireNonNull(request, "Request cannot be null");
        request.syncQueueStartTime = Time.currentElapsedTime();
        //又是入队列,在FinalRequestProcessor会用到该队列
        queuedRequests.add(request);
        ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUED.add(1);
    }

prep处理器调用sync处理器把请求扔到了sync处理器的队列(queuedRequests)中
7.SyncRequestProcessor处理器主要负责生成日志与快照文件。类似redis的AOF与RDB文件。
日志文件:为每条修改命令生成日志并持久化到磁盘。
快照文件:对内存生成镜像到磁盘中。
主要分析一下org.apache.zookeeper.server.SyncRequestProcessor#run方法

public void run() {
        try {
            // we do this in an attempt to ensure that not all of the servers
            // in the ensemble take a snapshot at the same time
            resetSnapshotStats();
            lastFlushTime = Time.currentElapsedTime();
            while (true) {
                ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_SIZE.add(queuedRequests.size());
                long pollTime = Math.min(zks.getMaxWriteQueuePollTime(), getRemainingDelay()); 
                //从队列中取request               
                Request si = queuedRequests.poll(pollTime, TimeUnit.MILLISECONDS);
                //说明过了pollTime也没有请求过来,那么不等待了直接把日志提交,先说明下日志提交是两阶段,第一个阶段仅仅写入outputstream中,第二阶段flush时候才持久化到磁盘
                if (si == null) {                    
                    /* We timed out looking for more writes to batch, go ahead and flush immediately */
                    flush();
                   //持久化完日志再从队列里获取请求,如果没有获取到那么阻塞等待
                    si = queuedRequests.take();
                }
				//毒丸消息停止服务端
                if (si == REQUEST_OF_DEATH) {
                    break;
                }

                long startProcessTime = Time.currentElapsedTime();
                //监控指标忽略
                ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_TIME.add(startProcessTime - si.syncQueueStartTime);
                //客户端发来的修改命令(写请求)写入到log文件中,自己跟进去看很简单
                if (zks.getZKDatabase().append(si)) {
                    // 判断是否打快照
                    if (shouldSnapshot()) {
                        resetSnapshotStats();
                        //下次进来会生成新的log
                        zks.getZKDatabase().rollLog();
                        // take a snapshot
                        if (!snapThreadMutex.tryAcquire()) {
                            LOG.warn("Too busy to snap, skipping");
                        } else {
                            //开启单独线程异步打快照DataTree
                            new ZooKeeperThread("Snapshot Thread") {
                                public void run() {
                                    try {
                                        zks.takeSnapshot();
                                    } catch (Exception e) {
                                        LOG.warn("Unexpected exception", e);
                                    } finally {
                                        snapThreadMutex.release();
                                    }
                                }
                            }.start();
                        }
                    }
                } else if (toFlush.isEmpty()) {
				//之前的请求都flush完之后来了一个读请求会进来
				//flush完之后toFlush队列为空,这时候可以调用后面的处理器
                   
                    if (nextProcessor != null) {
                    //调用FinalRequestProcessor
                        nextProcessor.processRequest(si);
                        if (nextProcessor instanceof Flushable) {
                            ((Flushable) nextProcessor).flush();
                        }
                    }
                    continue;
                }
                //读写请求都入队列
                toFlush.add(si);
               //判断是否满足日志提交条件,两阶段的最后一段,提交
                if (shouldFlush()) {
                	//提交log,flush到磁盘
                    flush();
                }

                ServerMetrics.getMetrics().SYNC_PROCESS_TIME.add(Time.currentElapsedTime() - startProcessTime);
            }
        } catch (Throwable t) {
            handleException(this.getName(), t);
        }
        LOG.info("SyncRequestProcessor exited!");
    }

上面代码的思路还是不错的,作为开发人员可以从中受益。
8.最有一个处理器FinalRequestProcessor该处理器主要功能如下

  • 触发watch机制
  • 移除不合格的写请求的ChangeRecord记录,4中提到的record
  • 集群模式下会提交proposal我们讲的单机模式下可以忽略
  • 记录auditlog
  • 给客户端发送response
    有兴趣的读者可以自己看下org.apache.zookeeper.server.FinalRequestProcessor#processRequest
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值