手把手带你撸zookeeper源码-zookeeper的sessionId生成策略和follower调用链初始化

继上篇文章 手把手带你撸zookeeper源码-zookeeper中follower启动的时候会做什么?,分析到了follower启动之后和leader建立了链接,并且把自己本地最新的zxid发送给leader,然后leader根据follower发送过来的zxid来判断如何把最新的数据同步给follower

 

今天继续分析Learner.syncWithLeader()方法中的代码, 首先我们来看下面的一行代码

zk.createSessionTracker();

看字面意思是创建会话跟踪器,zk对象就是我们实例化Follower对象时创建的FollowerZookeeperServer对象,此时调用的createSessionTracker()方法是调用的父类LearnerZookeeperServer中的createSessionTracker()方法, 在上篇文章中有ZookeeperServer类的UML图

@Override
    public void createSessionTracker() {
        sessionTracker = new LearnerSessionTracker(this, getZKDatabase()
                .getSessionWithTimeOuts(), self.getId(),
                getZooKeeperServerListener());
    }

这个地方只是创建了LearnerSessionTracker对象,我们进去看一下创建这个对象时会做什么

public LearnerSessionTracker(SessionExpirer expirer,
            ConcurrentHashMap<Long, Integer> sessionsWithTimeouts, long id,
            ZooKeeperServerListener listener) {
        this.expirer = expirer;
        this.sessionsWithTimeouts = sessionsWithTimeouts;
        this.serverId = id;
        // 初始化sessionId
        nextSessionId = SessionTrackerImpl.initializeNextSession(this.serverId);
        
    }

就是给一些属性赋值,其中我们可以看一下nextSessionId,下一个sessionId,我们知道当客户端和zk集群创建连接交互时,会维护一个session,代表了当前链接,如果session超时回着失效了,需要重新再次建立链接,针对每隔session都会有个sessionId和session对象保持一一对应,这里我们可以看一下sessionId是已什么样的方式生成的

public static long initializeNextSession(long id) {
        long nextSid = 0;
        nextSid = (Time.currentElapsedTime() << 24) >>> 8;
        nextSid =  nextSid | (id <<56);
        return nextSid;
    }

其实就短短四行代码,通过位运算移动来操作的数据的

可以大概分析一下,  Time.currentElapsedTime返回的就是一个long类型的毫秒数,一个long类型时64位

比如当前时间转成64位之后
当前时间为2020-08-09 11:20:20 转成毫秒数之后时 1596943220000,转成64位的long类型如下
0000 0000 0000 0000 0000 0001 0111 0011 1101 0001 0011 1011 1011 1101 0010 0000
向左移动24位
0111 0011 1101 0001 0011 1011 1011 1101 0010 0000 0000 0000 0000 0000 0000 0000
然后向右移动8位,此时为 nextSid
0000 0000 0111 0011 1101 0001 0011 1011 1011 1101 0010 0000 0000 0000 0000 0000 

接下来是 id << 56
id是我们自己设置的myid,一般都是一个比较小的数字,1、2、3、4等等这样的,一个集群有5态机器,一般都是1、2、3、4、5
如果此时id = 5  转成二进制
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0101
向左移动56位,得到数据
0000 0101 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 

然后和nextSid做或操作,或操作是 有1得1,没有1得0,即
0000 0000 0111 0011 1101 0001 0011 1011 1011 1101 0010 0000 0000 0000 0000 0000
0000 0101 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 
得到
0000 0101 0111 0011 1101 0001 0011 1011 1011 1101 0010 0000 0000 0000 0000 0000

为何要这么做呢?

打个比方,如果sessionId只是根据当前服务器的时间来生成,那么如果zk集群中得多台机器同时去了相同得时间呢?所以如果不把myid加入运算就会出现sessionId不唯一性

那么上面得运算就可以得到一个唯一得数字,首先

(Time.currentElapsedTime() << 24) >>> 8

这一行数据就是把最高得8位转成0,而一般myid比较小,然后nextSid | (id <<56) 时得到得就是低位56位是当前时间戳,高8位就是myid,此时得到得数据肯定是一个唯一得值,把当前时间戳和myid都参与到位运算当中去

以上就是生成sessionId得策略,sessionId是在客户端和zk集群中得某一个zk建立连接时生成得,后面分析客户端和服务端交互得代码中还会分析的

 

outerLoop:
            while (self.isRunning()) {
                readPacket(qp);
                switch(qp.getType()) {
                case Leader.PROPOSAL:
                    PacketInFlight pif = new PacketInFlight();
                    pif.hdr = new TxnHeader();
                    pif.rec = SerializeUtils.deserializeTxn(qp.getData(), pif.hdr);
                    if (pif.hdr.getZxid() != lastQueued + 1) {
                    LOG.warn("Got zxid 0x"
                            + Long.toHexString(pif.hdr.getZxid())
                            + " expected 0x"
                            + Long.toHexString(lastQueued + 1));
                    }
                    lastQueued = pif.hdr.getZxid();
                    packetsNotCommitted.add(pif);
                    break;
                case Leader.COMMIT:
                    if (!writeToTxnLog) {
                        pif = packetsNotCommitted.peekFirst();
                        if (pif.hdr.getZxid() != qp.getZxid()) {
                            LOG.warn("Committing " + qp.getZxid() + ", but next proposal is " + pif.hdr.getZxid());
                        } else {
                            zk.processTxn(pif.hdr, pif.rec);
                            packetsNotCommitted.remove();
                        }
                    } else {
                        packetsCommitted.add(qp.getZxid());
                    }
                    break;
                case Leader.INFORM:
                    /*
                     * Only observer get this type of packet. We treat this
                     * as receiving PROPOSAL and COMMMIT.
                     */
                    PacketInFlight packet = new PacketInFlight();
                    packet.hdr = new TxnHeader();
                    packet.rec = SerializeUtils.deserializeTxn(qp.getData(), packet.hdr);
                    // Log warning message if txn comes out-of-order
                    if (packet.hdr.getZxid() != lastQueued + 1) {
                        LOG.warn("Got zxid 0x"
                                + Long.toHexString(packet.hdr.getZxid())
                                + " expected 0x"
                                + Long.toHexString(lastQueued + 1));
                    }
                    lastQueued = packet.hdr.getZxid();
                    if (!writeToTxnLog) {
                        // Apply to db directly if we haven't taken the snapshot
                        zk.processTxn(packet.hdr, packet.rec);
                    } else {
                        packetsNotCommitted.add(packet);
                        packetsCommitted.add(qp.getZxid());
                    }
                    break;
                case Leader.UPTODATE:   // 标识同步leader数据已经同步完了,可以接收客户端发起的请求了
                    if (isPreZAB1_0) {
                        zk.takeSnapshot();
                        self.setCurrentEpoch(newEpoch);
                    }
                    self.cnxnFactory.setZooKeeperServer(zk);                
                    break outerLoop;    // 跳出循环
                case Leader.NEWLEADER: // Getting NEWLEADER here instead of in discovery 
                    // means this is Zab 1.0
                    // Create updatingEpoch file and remove it after current
                    // epoch is set. QuorumPeer.loadDataBase() uses this file to
                    // detect the case where the server was terminated after
                    // taking a snapshot but before setting the current epoch.
                    File updating = new File(self.getTxnFactory().getSnapDir(),
                                        QuorumPeer.UPDATING_EPOCH_FILENAME);
                    if (!updating.exists() && !updating.createNewFile()) {
                        throw new IOException("Failed to create " +
                                              updating.toString());
                    }
                    if (snapshotNeeded) {
                        zk.takeSnapshot();
                    }
                    self.setCurrentEpoch(newEpoch);
                    if (!updating.delete()) {
                        throw new IOException("Failed to delete " +
                                              updating.toString());
                    }
                    writeToTxnLog = true; //Anything after this needs to go to the transaction log, not applied directly in memory
                    isPreZAB1_0 = false;
                    writePacket(new QuorumPacket(Leader.ACK, newLeaderZxid, null, null), true);
                    break;
                }
            }
        }
        ack.setZxid(ZxidUtils.makeZxid(newEpoch, 0));
        // 给leader发送ack
        writePacket(ack, true);
        sock.setSoTimeout(self.tickTime * self.syncLimit);
        //----------关键点------------初始化调用链-------------
        zk.startup();
        /*
         * Update the election vote here to ensure that all members of the
         * ensemble report the same vote to new servers that start up and
         * send leader election notifications to the ensemble.
         * 
         * @see https://issues.apache.org/jira/browse/ZOOKEEPER-1732
         */
        self.updateElectionVote(newEpoch);

        // We need to log the stuff that came in between the snapshot and the uptodate
        if (zk instanceof FollowerZooKeeperServer) {
            FollowerZooKeeperServer fzk = (FollowerZooKeeperServer)zk;
            for(PacketInFlight p: packetsNotCommitted) {
                fzk.logRequest(p.hdr, p.rec);
            }
            for(Long zxid: packetsCommitted) {
                fzk.commit(zxid);
            }
        } else if (zk instanceof ObserverZooKeeperServer) {
            // Similar to follower, we need to log requests between the snapshot
            // and UPTODATE
            ObserverZooKeeperServer ozk = (ObserverZooKeeperServer) zk;
            for (PacketInFlight p : packetsNotCommitted) {
                Long zxid = packetsCommitted.peekFirst();
                if (p.hdr.getZxid() != zxid) {
                    // log warning message if there is no matching commit
                    // old leader send outstanding proposal to observer
                    LOG.warn("Committing " + Long.toHexString(zxid)
                            + ", but next proposal is "
                            + Long.toHexString(p.hdr.getZxid()));
                    continue;
                }
                packetsCommitted.remove();
                Request request = new Request(null, p.hdr.getClientId(),
                        p.hdr.getCxid(), p.hdr.getType(), null, null);
                request.txn = p.rec;
                request.hdr = p.hdr;
                ozk.commitRequest(request);
            }
        } else {
            // New server type need to handle in-flight packets
            throw new UnsupportedOperationException("Unknown server type");
        }

上面的代码就是从leader中开始接受要同步的数据,并把数据写入本地日志文件和本地内存数据结构中,这块不详细分析了,因为很多是和接下来的分析的代码进行重合的,比如对于2PC的数据如何进行发送proposal、如何发送ACK、如何进行COMMIT的

其中有一个方法也是很重要的,就是每个zk server如何链式去处理proposal、commit等请求的

zk.startup()

zk对象上面说过了,这里不多说了,startup()方法调用的是父类的ZookeeperServer中的startup()

public synchronized void startup() {
        if (sessionTracker == null) {
            createSessionTracker();
        }
        // 启动一个定时任务,清理session
        startSessionTracker();
        // 初始化 leader 、 follower、 observer的调用链
        setupRequestProcessors();

        registerJMX();

        setState(State.RUNNING);
        notifyAll();
    }

这个方法做了两件重要的事情,第一个就是启动一个定时任务检查session,如果有过期的session则会定时把session给剔除掉

session超时这块后面文章再来分析,首先会调用createSessionTracker来创建SessionTrackerImpl对象,然后调用startSessionTracker()方法会开启一个线程,来定时去检查session超时情况,我们先大概知道即可,后面分析客户端的时候再来详细分析

我们先看看setupRequestProcessors()方法,它会调用子类的方法,我们先看看FollowerZookeeperServer

@Override
    protected void setupRequestProcessors() {
        RequestProcessor finalProcessor = new FinalRequestProcessor(this);
        commitProcessor = new CommitProcessor(finalProcessor,
                Long.toString(getServerId()), true,
                getZooKeeperServerListener());
        commitProcessor.start();
        firstProcessor = new FollowerRequestProcessor(this, commitProcessor);
        ((FollowerRequestProcessor) firstProcessor).start();
        // 写proposal事务日志
        syncProcessor = new SyncRequestProcessor(this,
                new SendAckRequestProcessor((Learner)getFollower()));
        syncProcessor.start();
    }

初始化后的代码大概如下:

然后启动了SyncRequestProcessor线程和FollowerRequestProcessor线程,在这两个线程中都是会从queueRequest中取数据,然后经过一次处理之后交给nextProcessor,形成一个调用链路

 

下篇文章我们再看看follower接收到leader发送过来的数据如何进行处理的,然后和上面的processor调用链进行结合起来看看整条数据处理链条都做了什么事

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值