Zookeeper源码解析:1、启动流程分析

前言

  • 为了方便我们进行Debug跟踪启动流程和查看控制台日志,所以我们要在IDE中启动zk。首先我们创建一个项目引入zk的maven包
		<dependency>
		    <groupId>org.apache.zookeeper</groupId>
		    <artifactId>zookeeper</artifactId>
		    <version>3.4.6</version>
		</dependency>
  • 创建一个main方法,调用QuorumPeerMain的main方法即可启动zk
     public static void main(String[] args) throws IOException {
		/*
		 * path数组放入你们系统中的zk的配置文件,配置文件自行去官网下载一个安装包即可
		 */
	 	String[] path = {"C:\\Users\\Administrator\\Desktop\\zookeeper-3.4.6\\conf\\cluster\\zoo-2182.cfg"};
	 	// 传入path数组,进行启动zk	 	 
		QuorumPeerMain.main(path);    
	} 
  • ZK在Windows中没有提供命令去查看集群的状态,所以我们可以自己在IDE中调用方法去查看集群的状态
	 public static void main(String[] args) throws IOException{
        /*
         * 数组中的值依次为:1、zk的ip地址、
         * 2、zk接收客户端连接的端口(配置文件中的clientPort默认为2181)
         * 3、需要执行命令(因为我们需要查询集群的状态,那么传status即可)
         */ 
        String[] cmd_2181 = {"127.0.0.1","2181","status"};
        FourLetterWordMain.main(cmd_2181);
    }

启动流程核心入口

从上述前言中,我们得知如何在ide中启动zk,所以我们便可知zk的启动入口是类:org.apache.zookeeper.server.quorum.QuorumPeerMain的main方法,所以我们就从这个方法开始。

下面我会把核心的源码复制出来进行讲解,并且会省去一些无关紧要的代码来节省边幅。

类:org.apache.zookeeper.server.quorum.QuorumPeerMain

    public static void main(String[] args) {
       	// 其实main方法什么都没做,主要是调用了本类中的initializeAndRun方法,并且传入args数组
        QuorumPeerMain main = new QuorumPeerMain();
        try {       		
            main.initializeAndRun(args);
        } catch (Exception e) {
        	..........          
        }
        LOG.info("Exiting normally");
        System.exit(0);
    }
    protected void initializeAndRun(String[] args)
        throws ConfigException, IOException
    {
    	// 创建QuorumPeerConfig对象,该类作用很简单就是解析我们之前传入进来的配置文件(zoo.cfg)
        QuorumPeerConfig config = new QuorumPeerConfig();
        if (args.length == 1) {
        	// 解析zoo.cfg
            config.parse(args[0]);
        }
        /*
         * 创建DatadirCleanupManager对象,该类主要是定期清理snapshot快照文件和log事务文件            
         */
        DatadirCleanupManager purgeMgr = new DatadirCleanupManager(config
                .getDataDir(), config.getDataLogDir(), config
                .getSnapRetainCount(), config.getPurgeInterval());
        // 启动清理。默认是不开启
        purgeMgr.start();

		/*
		 * 判断是否为集群模式还是单机模式
		 * 如何判断是否为单机和集群启动,等等解析zoo.cfg文件的时候在简单分析一下
		 */
        if (args.length == 1 && config.servers.size() > 0) {
        	// 集群模式调用这个方法
            runFromConfig(config);
        } else {
            LOG.warn("Either no config or no quorum defined in config, running "
                    + " in standalone mode");
            // there is only server in the quorum -- run as standalone
            // 单机模式调用这个方法
            ZooKeeperServerMain.main(args);
        }
    }

由上面代码可知,main方法其实主要做了3件事

  • 创建QuorumPeerConfig对象,用于解析配置文件(默认zoo.cfg)
  • 创建DatadirCleanupManager,用于定期清理snapshot快照文件和log事务文件,默认不开启,需要在配置文件中进行配置
  • 判断是否为集群模式启动还是单机模式启动,调用对应的方法,这边只讲集群启动。

1、解析配置文件

类:org.apache.zookeeper.server.quorum.QuorumPeerConfig

    public void parse(String path) throws ConfigException {
        File configFile = new File(path);
        LOG.info("Reading configuration from: " + configFile);
        try {
            if (!configFile.exists()) {
                throw new IllegalArgumentException(configFile.toString()
                        + " file is missing");
            }
            Properties cfg = new Properties();
            FileInputStream in = new FileInputStream(configFile);
            try {
                cfg.load(in);
            } finally {
                in.close();
            }
            // 上面代码没什么好说的,直接看这个方法      
            parseProperties(cfg);
        } catch (IOException e) {
        ......
        }
    }
    public void parseProperties(Properties zkProp)
    throws IOException, ConfigException {
        int clientPort = 0;
        String clientPortAddress = null;
        for (Entry<Object, Object> entry : zkProp.entrySet()) {
            String key = entry.getKey().toString().trim();
            String value = entry.getValue().toString().trim();
            if (key.equals("dataDir")) {
            	// 快照存储的目录;即snapshot,对应配置文件中的是dataDir
                dataDir = value;
            } else if (key.equals("dataLogDir")) {
            	// 事务日志存储的目录;即log,对应配置文件中的是dataLogDir
                dataLogDir = value;
            //..... 省略了一些判断实在太多,有兴趣可以自己去看
            } else if (key.equals("autopurge.snapRetainCount")) {
            	// 定期清理snapshot文件,需要保留的snapshot文件个数
                snapRetainCount = Integer.parseInt(value);
            } else if (key.equals("autopurge.purgeInterval")) {
            	// 多久启动清理。单位:小时。配置文件中没开启这个值,所以默认不开启定时定期。如有需要,自行开启
                purgeInterval = Integer.parseInt(value);
            } else if (key.startsWith("server.")) {
            	// 这里也就是解析集群配置。如果我们在配置文件中有配置server.id=xxxx就是集群启动
                int dot = key.indexOf('.');
                long sid = Long.parseLong(key.substring(dot + 1));
                String parts[] = value.split(":");
                if ((parts.length != 2) && (parts.length != 3) && (parts.length !=4)) {
                    LOG.error(value
                       + " does not have the form host:port or host:port:port " +
                       " or host:port:port:type");
                }
                InetSocketAddress addr = new InetSocketAddress(parts[0],
                        Integer.parseInt(parts[1]));
                // 下面解析不同的集群配置,有3种情况
                if (parts.length == 2) {
                	//1、eg:    server.1=127.0.0.1:2887
                    servers.put(Long.valueOf(sid), new QuorumServer(sid, addr));
                } else if (parts.length == 3) {
               	    //2、eg:    server.1=127.0.0.1:2887:3887
                    InetSocketAddress electionAddr = new InetSocketAddress(
                            parts[0], Integer.parseInt(parts[2]));
                    servers.put(Long.valueOf(sid), new QuorumServer(sid, addr,
                            electionAddr));
                } else if (parts.length == 4) {
                    //3、eg:    server.1=127.0.0.1:2887:3887:participant
                    InetSocketAddress electionAddr = new InetSocketAddress(
                            parts[0], Integer.parseInt(parts[2]));
                    LearnerType type = LearnerType.PARTICIPANT;
                    if (parts[3].toLowerCase().equals("observer")) {
                        type = LearnerType.OBSERVER;
                        observers.put(Long.valueOf(sid), new QuorumServer(sid, addr,
                                electionAddr,type));
                    } else if (parts[3].toLowerCase().equals("participant")) {
                        type = LearnerType.PARTICIPANT;
                        servers.put(Long.valueOf(sid), new QuorumServer(sid, addr,
                                electionAddr,type));
                    } else {
                        throw new ConfigException("Unrecognised peertype: " + value);
                    }
                }
            } 
        	//.........省略一些代码
        	// 下面是解析myid文件
            File myIdFile = new File(dataDir, "myid");
            if (!myIdFile.exists()) {
                throw new IllegalArgumentException(myIdFile.toString()
                        + " file is missing");
            }
            BufferedReader br = new BufferedReader(new FileReader(myIdFile));
            String myIdString;
            try {
                myIdString = br.readLine();
            } finally {
                br.close();
            }
            try {
                serverId = Long.parseLong(myIdString);
                MDC.put("myid", myIdString);
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException("serverid " + myIdString
                        + " is not a number");
            }
			//.........省略一些代码
        }
    }

2、启动定期清理快照文件和事务log

类:org.apache.zookeeper.server.DatadirCleanupManager

    public void start() {
        if (PurgeTaskStatus.STARTED == purgeTaskStatus) {
            LOG.warn("Purge task is already running.");
            return;
        }
        // Don't schedule the purge task with zero or negative purge interval.
        // 如果配置文件中autopurge.purgeInterval这个值没有开启或者设置小于0,那么定时清理就没有开启
        if (purgeInterval <= 0) {
            LOG.info("Purge task is not scheduled.");
            return;
        }
		
		// 创建一个定时器
        timer = new Timer("PurgeTask", true);
        // 定时清理的逻辑在类PurgeTask中
        TimerTask task = new PurgeTask(dataLogDir, snapDir, snapRetainCount);
        // purgeInterval 小时执行一次
        timer.scheduleAtFixedRate(task, 0, TimeUnit.HOURS.toMillis(purgeInterval));
		
		// 标志状态为已启动
        purgeTaskStatus = PurgeTaskStatus.STARTED;
    }

类:org.apache.zookeeper.server.DatadirCleanupManager.PurgeTask

	@Override
	public void run() {
	    LOG.info("Purge task started.");
	    try {
	        PurgeTxnLog.purge(new File(logsDir), new File(snapsDir), snapRetainCount);
	    } catch (Exception e) {
	        LOG.error("Error occured while purging.", e);
	    }
	    LOG.info("Purge task completed.");
	}

类:org.apache.zookeeper.server.PurgeTxnLog

    public static void purge(File dataDir, File snapDir, int num) throws IOException {
        if (num < 3) {
            throw new IllegalArgumentException("count should be greater than 3");
        }
        FileTxnSnapLog txnLog = new FileTxnSnapLog(dataDir, snapDir);
        Set<File> exc=new HashSet<File>();
        /*
         * 快照(snapshot)文件命名的格式为:snapshot.lastZxid;lastZxid是该快照文件存储的最大的事务id
         * 后面我会写一篇文章详细介绍zk的数据存储,在来讲快照文件和事务日志
         * findNRecentSnapshots:该方法是从存储快照的目录中,首先进行快照文件以文件名的lastZxid降序排序,
         * 找到排在前num的文件,放入list中。
         */
        List<File> snaps = txnLog.findNRecentSnapshots(num);
        // 如果为空,直接返回,说明还没有继续过快照
        if (snaps.size() == 0) 
            return;
        // 取出列表中最后一个快照
        File snapShot = snaps.get(snaps.size() -1);
        // 将刚才list的文件放入set集合中,该set集合的存储的文件是不用被清理的
        for (File f: snaps) {
            exc.add(f);
        }
        /*
         * 从刚刚获取的最后一个快照的文件中解析出zxid。
         */ 
        long zxid = Util.getZxidFromName(snapShot.getName(),"snapshot");
        /*
         * 事务log文件命名:log.zxid;该zxid是这个事务log文件中最小的事务id
         * getSnapshotLogs:找到大于该zxid的事务日志log,并将它们全部加入set集合中。
         * 原因就是小于该zxid的事务已经被全部快照存储到snapshot文件中了,所以这些事务log就没用了可以被清理,
         * 只需要保留大于zxid的事务log即可
         */
        exc.addAll(Arrays.asList(txnLog.getSnapshotLogs(zxid)));

        final Set<File> exclude=exc;
        class MyFileFilter implements FileFilter{
            private final String prefix;
            MyFileFilter(String prefix){
                this.prefix=prefix;
            }
            public boolean accept(File f){
                if(!f.getName().startsWith(prefix) || exclude.contains(f))
                    return false;
                return true;
            }
        }
        // add all non-excluded log files
        /*
         * 找到"log."和"snapshot."开头的文件,并且该文件不在exclude集合中的
         */
        List<File> files=new ArrayList<File>(
                Arrays.asList(txnLog.getDataDir().listFiles(new MyFileFilter("log."))));
        // add all non-excluded snapshot files to the deletion list
        files.addAll(Arrays.asList(txnLog.getSnapDir().listFiles(new MyFileFilter("snapshot."))));
        // remove the old files
        // 清除这些旧文件
        for(File f: files)
        {
            System.out.println("Removing file: "+
                DateFormat.getDateTimeInstance().format(f.lastModified())+
                "\t"+f.getPath());
            if(!f.delete()){
                System.err.println("Failed to remove "+f.getPath());
            }
        }

    }

3、集群启动

类:org.apache.zookeeper.server.quorum.QuorumPeerMain

    public void runFromConfig(QuorumPeerConfig config) throws IOException {
		..... 省略一些代码
      try {
      	  /*
      	   * 创建一个连接工厂。该工厂有2中模式分别为Netty、NIO,默认为NIO
      	   * 该工厂作用主要是接收客户端的连接、请求等。
      	   */ 
          ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory();
          cnxnFactory.configure(config.getClientPortAddress(),
                                config.getMaxClientCnxns());
  		  /**
  		   * 创建一个QuorumPeer对象,设置一些必要的值,这里没啥好说的。
  		   * 主要是对刚刚从zoo.cfg中解析出来数据进行设置
  		   */
          quorumPeer = new QuorumPeer();
          quorumPeer.setClientPortAddress(config.getClientPortAddress());
          quorumPeer.setTxnFactory(new FileTxnSnapLog(
                      new File(config.getDataLogDir()),
                      new File(config.getDataDir())));
          quorumPeer.setQuorumPeers(config.getServers());
          quorumPeer.setElectionType(config.getElectionAlg());
          quorumPeer.setMyid(config.getServerId());
          quorumPeer.setTickTime(config.getTickTime());
          quorumPeer.setMinSessionTimeout(config.getMinSessionTimeout());
          quorumPeer.setMaxSessionTimeout(config.getMaxSessionTimeout());
          quorumPeer.setInitLimit(config.getInitLimit());
          quorumPeer.setSyncLimit(config.getSyncLimit());
          quorumPeer.setQuorumVerifier(config.getQuorumVerifier());
          quorumPeer.setCnxnFactory(cnxnFactory);
          quorumPeer.setZKDatabase(new ZKDatabase(quorumPeer.getTxnFactory()));
          quorumPeer.setLearnerType(config.getPeerType());
          quorumPeer.setSyncEnabled(config.getSyncEnabled());
          quorumPeer.setQuorumListenOnAllIPs(config.getQuorumListenOnAllIPs());
  		  
  		  // 启动QuorumPeer,该类继承了Thread,不过它又重写了start方法  		  
          quorumPeer.start();          
          quorumPeer.join();
      } catch (InterruptedException e) {
          // warn, but generally this is ok
          LOG.warn("Quorum Peer interrupted", e);
      }
    }

类:org.apache.zookeeper.server.quorum.QuorumPeer

    public synchronized void start() {
        // 加载zk的内存数据库
        loadDataBase();
        // 启动连接工厂,接收客户端的连接、请求等
        cnxnFactory.start();        
        // 创建选举流程需要的一些必要对象
        startLeaderElection();
        // 因为QuorumPeer类集成了Thread,所以执行run方法(主要是进行选举、数据同步等等)
        super.start();
    }

3.1 加载zk内存数据库

    private void loadDataBase() {
        File updating = new File(getTxnFactory().getSnapDir(),
                                 UPDATING_EPOCH_FILENAME);
		try {
		    // 核心就是调用了ZKDatabase.loadDataBase()
            zkDb.loadDataBase();

            // 下面是进行一些数据检验
            long lastProcessedZxid = zkDb.getDataTree().lastProcessedZxid;
    		long epochOfZxid = ZxidUtils.getEpochFromZxid(lastProcessedZxid);
            try {
            	currentEpoch = readLongFromFile(CURRENT_EPOCH_FILENAME);
                if (epochOfZxid > currentEpoch && updating.exists()) {
                    LOG.info("{} found. The server was terminated after " +
                             "taking a snapshot but before updating current " +
                             "epoch. Setting current epoch to {}.",
                             UPDATING_EPOCH_FILENAME, epochOfZxid);
                    setCurrentEpoch(epochOfZxid);
                    if (!updating.delete()) {
                        throw new IOException("Failed to delete " +
                                              updating.toString());
                    }
                }
            } catch(FileNotFoundException e) {
            // 省略一些代码..
			}
	}

类:org.apache.zookeeper.server.ZKDatabase;后面会单独抽出一章来详细介绍zk的内存数据存储

    public long loadDataBase() throws IOException {
    	// 创建一个监听器
        PlayBackListener listener=new PlayBackListener(){
            /*
             * 等等解析事务log恢复内存数据的时候,会调用该方法
             */
            public void onTxnLoaded(TxnHeader hdr,Record txn){
                Request r = new Request(null, 0, hdr.getCxid(),hdr.getType(),
                        null, null);
                r.txn = txn;
                r.hdr = hdr;
                r.zxid = hdr.getZxid();
                /*
                 *  将最新的一些事务保存到内存中,作用就是方便leader-follower数据同步的时候,如果follower最新的事务
                 *  是在leader内存中存在的,那么可以采用DIFF(差异化)同步,后面讲数据同步在说。
                 */
                addCommittedProposal(r);
            }
        };
        // 根据快照和事务log进行恢复数据到内存数据库,并且返回最新的zxid
        long zxid = snapLog.restore(dataTree,sessionsWithTimeouts,listener);
        // 标记初始化内存数据库为true
        initialized = true;
        // 返回最新的zxid
        return zxid;
    }

类:org.apache.zookeeper.server.persistence.FileTxnSnapLog。恢复数据到内存数据库由2部分数据组成,分别为快照和事务log

 public long restore(DataTree dt, Map<Long, Integer> sessions, 
            PlayBackListener listener) throws IOException {
     	// 首先从***最新的有效的***快照中恢复数据
        snapLog.deserialize(dt, sessions);        
        FileTxnLog txnLog = new FileTxnLog(dataDir);
        /*
         * 有一部分数据可能在关闭zk或者zk宕机时没有进行快照,而存在事务log列表中。
         * 所以需要根据快照读出来的最新zxid去判断,那么大于这个zxid的事务log列表
         * 即是不存在与快照中,而需要恢复的事务log列表
         */
        TxnIterator itr = txnLog.read(dt.lastProcessedZxid+1);
        long highestZxid = dt.lastProcessedZxid;
        TxnHeader hdr;
        try {
            while (true) {
                // iterator points to 
                // the first valid txn when initialized
                hdr = itr.getHeader();
                if (hdr == null) {
                    //empty logs 
                    return dt.lastProcessedZxid;
                }
                if (hdr.getZxid() < highestZxid && highestZxid != 0) {
                    LOG.error("{}(higestZxid) > {}(next log) for type {}",
                            new Object[] { highestZxid, hdr.getZxid(),
                                    hdr.getType() });
                } else {
                	// 更新最新的zxid
                    highestZxid = hdr.getZxid();
                }
                try {
               		// 执行该事务
                    processTransaction(hdr,dt,sessions, itr.getTxn());
                } catch(KeeperException.NoNodeException e) {
                   throw new IOException("Failed to process transaction type: " +
                         hdr.getType() + " error: " + e.getMessage(), e);
                }
                // 回调之前传进来的监听器
                listener.onTxnLoaded(hdr, itr.getTxn());

 				// 如果全部事务log读取完毕,那么直接break
                if (!itr.next()) 
                    break;
            }
        } finally {
            if (itr != null) {
                itr.close();
            }
        }
        // 返回目前内存数据库中最新的zxid
        return highestZxid;
    }

从快照中恢复数据

类:org.apache.zookeeper.server.persistence.FileSnap

    public long deserialize(DataTree dt, Map<Long, Integer> sessions)
            throws IOException {
        // 首先找到100个以snapshot.开头的快照文件。并且以文件名中的zxid进行降序排序,之前说过快照文件的命名
        List<File> snapList = findNValidSnapshots(100);        
        if (snapList.size() == 0) {
            return -1L;
        }
        File snap = null;
        boolean foundValid = false;
        // 遍历该列表 找到一个最新的合法的快照文件 进行反序列,一个即可,不用全部反序列化
        for (int i = 0; i < snapList.size(); i++) {
            snap = snapList.get(i);
            InputStream snapIS = null;
            CheckedInputStream crcIn = null;
            try {
                LOG.info("Reading snapshot " + snap);
                //  创建输入流读取文件
                snapIS = new BufferedInputStream(new FileInputStream(snap));
                // 这里使用Adler32进行输入流的校验
                crcIn = new CheckedInputStream(snapIS, new Adler32());
                InputArchive ia = BinaryInputArchive.getArchive(crcIn);
                // 反序列化,具体的就不详细说了,自己可以进去看,其实很简单
                deserialize(dt,sessions, ia);
                // 根据输入流获得校验和
                long checkSum = crcIn.getChecksum().getValue();
                // 从文件中读取校验和
                long val = ia.readLong("val");
                // 校验和进行对比。如果不一样,那么说明是非法快照文件,抛出异常,进行下一次循环读取下一个文件
                if (val != checkSum) {
                    throw new IOException("CRC corruption in snapshot :  " + snap);
                }
                // 如果一样,那么说明是合法快照文件,break跳出循环
                foundValid = true;
                break;
            } catch(IOException e) {
                LOG.warn("problem reading snap file " + snap, e);
            } finally {
                if (snapIS != null) 
                    snapIS.close();
                if (crcIn != null) 
                    crcIn.close();
            } 
        }
        if (!foundValid) {
            throw new IOException("Not able to find valid snapshots in " + snapDir);
        }
        // 根据反序列的的快照文件的名称中的zxid,来设置最新的zxid
        dt.lastProcessedZxid = Util.getZxidFromName(snap.getName(), "snapshot");
        // 返回快照文件中读取的最新的zxid
        return dt.lastProcessedZxid;
    }

PlayBackListener 监听器的作用

        PlayBackListener listener=new PlayBackListener(){
            public void onTxnLoaded(TxnHeader hdr,Record txn){
                Request r = new Request(null, 0, hdr.getCxid(),hdr.getType(),
                        null, null);
                r.txn = txn;
                r.hdr = hdr;
                r.zxid = hdr.getZxid();
                addCommittedProposal(r);
            }
        };
	/*
	 * 该方法将提交的事务保存到内存集合中,作用就是方便之后leader-follower数据同步的时候,如果follower最新的事务
     * 是在leader内存中存在的,那么可以采用DIFF(差异化)同步,后面讲数据同步在说。
	 */
    public void addCommittedProposal(Request request) {    	
        WriteLock wl = logLock.writeLock();
        try {
        	// 加写锁
            wl.lock();
            // 如果内存中保存的事务log数量大于定义数量的限制
            if (committedLog.size() > commitLogCount) {
            	// 移除第一个事务log
                committedLog.removeFirst();
                // 重新设置最小事务zxid
                minCommittedLog = committedLog.getFirst().packet.getZxid();
            }
            // 如果该request是第一个事务,那么直接设置最小提交zxid和最大zxid为该request的zxid
            if (committedLog.size() == 0) {
                minCommittedLog = request.zxid;
                maxCommittedLog = request.zxid;
            }
			
			// 构造QuorumPacket对象
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos);
            try {
                request.hdr.serialize(boa, "hdr");
                if (request.txn != null) {
                    request.txn.serialize(boa, "txn");
                }
                baos.close();
            } catch (IOException e) {
                LOG.error("This really should be impossible", e);
            }
            QuorumPacket pp = new QuorumPacket(Leader.PROPOSAL, request.zxid,
                    baos.toByteArray(), null);
            // 构建Proposal对象
            Proposal p = new Proposal();
            p.packet = pp;
            p.request = request;
            // 加入commitLog集合
            committedLog.add(p);
            // 设置最大提交的zxid
            maxCommittedLog = p.packet.getZxid();
        } finally {
            wl.unlock();
        }
    }

3.2 创建选举流程需要的一些必要对象

类:org.apache.zookeeper.server.quorum.QuorumPeer

    synchronized public void startLeaderElection() {
    	try {
    		// 创建一个对自己的投票对象
    		currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());
    	} catch(IOException e) {
    		RuntimeException re = new RuntimeException(e.getMessage());
    		re.setStackTrace(e.getStackTrace());
    		throw re;
    	}
        for (QuorumServer p : getView().values()) {
            if (p.id == myid) {
                myQuorumAddr = p.addr;
                break;
            }
        }
        if (myQuorumAddr == null) {
            throw new RuntimeException("My id " + myid + " not in the peer list");
        }
        // 如果electionType等于0,创建UDP
        if (electionType == 0) {
            try {
                udpSocket = new DatagramSocket(myQuorumAddr.getPort());
                responder = new ResponderThread();
                responder.start();
            } catch (SocketException e) {
                throw new RuntimeException(e);
            }
        }
        // 根据electionType创建选举算法,如果没有在配置文件中设置electionAlg。那么该electionType默认为3
        this.electionAlg = createElectionAlgorithm(electionType);
    }
    protected Election createElectionAlgorithm(int electionAlgorithm){
        Election le=null;
                
        //TODO: use a factory rather than a switch
        switch (electionAlgorithm) {
        case 0:
            le = new LeaderElection(this);
            break;
        case 1:
            le = new AuthFastLeaderElection(this);
            break;
        case 2:
            le = new AuthFastLeaderElection(this, true);
            break;
        case 3:
        	// 默认electionAlgorithm = 3
        	/*
        	 * 首先创建QuorumCnxManager对象
        	 * 该对象主要作用于管理服务器之间选举的通信        	 
        	 */
            qcm = new QuorumCnxManager(this);
            QuorumCnxManager.Listener listener = qcm.listener;
            if(listener != null){
            	// 开启监听器,监听器用于监听选举端口、接收别的服务器连接
                listener.start();
                // 创建FastLeaderElection选举算法,用于选举,后面单独拿出一节来讲详细的选举流程
                le = new FastLeaderElection(this, qcm);
            } else {
                LOG.error("Null listener when initializing cnx manager");
            }
            break;
        default:
            assert false;
        }
        return le;
    }

类:org.apache.zookeeper.server.quorum.QuorumPeer

    public void run() {
		// 省略一些代码

        try {
            /*
             * Main loop
             */
            while (running) {
             	// 这里有个死循环
                switch (getPeerState()) {
                // 根据服务器的状态,执行对应的逻辑
                case LOOKING:
                	// 正在进行选举
                    LOG.info("LOOKING");

                    if (Boolean.getBoolean("readonlymode.enabled")) {
						// 这里是开启只读模式的一些逻辑,暂时省略。。。。
                    } else {
                        try {
                        	/*
                             * 调用选举算法的lookForLeader()方法,触发选举流程。(默认是FastLeaderElection算法)
                             * 选举结束后,将leader选票设置为currentVote
                             */
                            setBCVote(null);
                            setCurrentVote(makeLEStrategy().lookForLeader());
                        } catch (Exception e) {
                            LOG.warn("Unexpected exception", e);
                            setPeerState(ServerState.LOOKING);
                        }
                    }
                    break;
                case OBSERVING:
                	// 选举完毕,Observer角色执行的逻辑
                    try {
                        LOG.info("OBSERVING");
                        setObserver(makeObserver(logFactory));
                        observer.observeLeader();
                    } catch (Exception e) {
                        LOG.warn("Unexpected exception",e );                        
                    } finally {
                        observer.shutdown();
                        setObserver(null);
                        setPeerState(ServerState.LOOKING);
                    }
                    break;
                case FOLLOWING:
               	 // 选举完毕,Follower角色执行的逻辑
                    try {
                        LOG.info("FOLLOWING");                        
                        setFollower(makeFollower(logFactory));
                        // Follower数据同步等等流程,后续说
                        follower.followLeader();
                    } catch (Exception e) {
                        LOG.warn("Unexpected exception",e);
                    } finally {
                        follower.shutdown();
                        setFollower(null);
                        setPeerState(ServerState.LOOKING);
                    }
                    break;
                case LEADING:
                	// 选举完毕,Leader角色执行的逻辑
                    LOG.info("LEADING");
                    try {
                        setLeader(makeLeader(logFactory));
                        // Leader数据同步等等流程,后续说
                        leader.lead();
                        setLeader(null);
                    } catch (Exception e) {
                        LOG.warn("Unexpected exception",e);
                    } finally {
                        if (leader != null) {
                            leader.shutdown("Forcing shutdown");
                            setLeader(null);
                        }
                        setPeerState(ServerState.LOOKING);
                    }
                    break;
                }
            }
        } finally {
            LOG.warn("QuorumPeer main thread exited");
            try {
                MBeanRegistry.getInstance().unregisterAll();
            } catch (Exception e) {
                LOG.warn("Failed to unregister with JMX", e);
            }
            jmxQuorumBean = null;
            jmxLocalPeerBean = null;
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值