【ZooKeeper】zookeeper源码4-集群启动脚本分析

ZooKeeper集群启动脚本分析

哪些源码流程需要我们关注?
1、集群启动
2、崩溃恢复(leader选举)+状态同步
3、读写请求
从哪个地方入手看源码?入口
启动ZooKeeper的时候:
zkServer.sh start
注意:zookeeper中每个节点是单独启动的,启动需要半数成功才行

内部细节:

# TODO 注释: 核心的启动命令4
# TODO 注释: $JAVA = $JAVA_HOME/bin/java
# TODO 注释: $ZOOMAIN = 核心java类 = QuorumPeerMain
# TODO 注释: "$ZOOCFG" = zoo.cfg 的路径 = $ZOOKEEPER_HOME/conf/zoo.cfg
nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}"	\
"-Dzookeeper.log.file=${ZOO_LOG_FILE}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
-XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError='kill -9 %p' \
-cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null &

简化之后:

nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE -Dkey1=value1 -XX:HeapDumpOnOutOfMemoryError $ZOOMAIN "$ZOOCFG"

$JAVA:java命令
$ZOO_DATADIR_AUTOCREATE:参数
-Dkey1=value1:JVM的参数选项
-XX:JVM虚拟机参数
$ZOOMAIN:QuorumPeerMain类
$ZOOCFG:zookeeper配置文件zoo.cfg
通过java命令启动进程(启动QuorumPeerMain类),该类的参数是zoo.cfg

再简化:

java QuorumPeerMain zoo.cfg.path

底层会转调用QuorumPeerMain.main(),这个main方法的第一个参数,就是zoo.cfg的本地路径

ZOOMAIN=“org.apache.zookeeper.server.quorum.QuorumPeerMain”
ZK JVM进程的名称:QuorumPeerMain
QuorumPeer+Main启动QuorumPeer
QuorumPeer代表了一台服务器的全部功能实现
在QuorumPeerMain进程中创建QuorumPeer实例,并启动QuorumPeer实例

ZooKeeper配置文件zoo.cfg

# The number of milliseconds of each tick
tickTime=2000   #间隔时间
# The number of ticks that the initial  synchronization phase can take
initLimit=10    #初始化时间,严格意义上initTime=initLimit*tickTime
# The number of ticks that can pass between  sending a request and getting an acknowledgement
syncLimit=5     #同步时间
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/home/bigdata/data/zkdata   #数据存储目录 根据实际情况修改    快照文件
dataLogDir=/home/bigdata/data/zklog/    #日志存储目录 根据实际情况修改
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60  #最大连接数
# electionAlg=3 = FastLeaderElection
electionAlg=3   #默认选举算法 FastLeaderElection
maxClientCnxns=60
# 服务器的类型: observer/participant(具有选举权的节点:leader  follower)
peerType=observer/participant   #节点类型   participant具有选举和被选举权
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
autopurge.snapRetainCount=3     #保留快照文件有几个
# Purge task interval in hours
# Set to "0" to disable auto purge feature
autopurge.purgeInterval=1   #旧快照清理时间间隔

server.2=bigdata02:2888
server.3=bigdata03:2888:3888
server.4=bigdata04:2888:3888:participant
server.5=bigdata05:2888:3888:observer   #指明节点类型,省去分配类型
类别角色1角色2
是否可以选举Participant(有选举权,包括Leader和Follower)Observer(没有选举权)
按照主从划分LeaderLeaner(包括Follower和Observer)

ZooKeeper QuorumPeerMain启动

最重要的类QuorumPeer:代表一台服务器
1、一个组件ZKDatabase(即数据库那个模型)
ZKDatabase是ZK数据库,每个QuorumPeer代表一台ZK物理机,每个QuorumPeer内部都有一个ZKDatabase对象,意味着,每个ZK节点都保存了该集群的所有数据
2、JettyAdminServer:WebUI启动的就是AdminServer,默认绑定的端口8080
ZooKeeper从3.5.x版本增加了AdminServer
3、QuorumCnxManager:其中封装的一切组件都是为选举工作的,线程,监听器,队列,这里会启动一个BIO的服务器端,绑定的端口3888,由Listener启动
QuorumCnxManager是一个选举过程中的链接管理器,内部启动了一个Listener,Listener内部启动了一个BIO服务端,它监听3888端口,等待其他客户端发送选举链接请求来建立连接举行选票交换。
4、ServerCnxnFactory:为客户端提供服务的,绑定端口2181
ServerCnxnFactory是一个Runnable实现,默认实现:NIOServerCnxnFactory,内部启动了一个线程,启动了一个NIO服务端,监听2181端口,等待客户端发送链接请求,然后创建一个ServerCnxn,负责完成该客户端的读写请求

专门用来做同步的服务端什么时候启动,当选举结束;当前这个QP成为了leader,会启动一个新的服务端,这个服务端就会绑定2888,进行同步

5、QuorumVerifier:少数服从多数算法,提供一个方法和变量,变量存的是集群总节点个数(总的participant个数),server.2/server.3/server.4是,server.5不是
6、Election:FastLeaderElection:选举算法,FastLeaderElection负责选举的逻辑执行
7、runing,myid,vote:vote存放谁是leader的信息
QuorumPeer内部保存了一些和选举有关的信息,比如myid,vote,Election选举算法实现,从zoo.cfg中解析得到其他的Server的信息
8、Leader/Follower/Observer三个成员变量:每个节点上只有其中一个有值,即每一个节点只能由一个状态
9、Map<Long,QuorumServer> quorumPeers:集群中所有服务器
10、四个内部类SyncMode同步模式(有四种NONE,DIFF,SNAP,TRUNC),ServerState(当前QuorumPeer的状态,也有四种LOOKING,FOLLOWING,LEADING,OBSERVING),LearnerType(PARTICIPANT,OBSERVER),ZabState(ELECTION,DISCOVERY,SYNCHRONIZATION,BROADCAST)

QuorumPeerMain.java

注意QuorumPeer变量被一分为三,存储在 QuorumMaj 的内部(allMembers, votingMembers, observingMembers)

// 入口方法
QuorumPeerMain.main(args){
    // 核心实现,分三步走
    QuorumPeerMain.initializeAndRun(args) {
        // 第一步;解析配置
        config = new QuorumPeerConfig();
        config.parse(args[0]) {
            Properties cfg = new Properties();
            cfg.load(in);
            parseProperties(cfg);
        }
        // 第二步:启动一个线程(定时任务)来执行关于 old snapshot 的 cleanup
        DatadirCleanupManager purgeMgr = new DatadirCleanupManager(....);
        purgeMgr.start() {
            timer = new Timer("PurgeTask", true);
            TimerTask task = new PurgeTask(dataLogDir, snapDir, snapRetainCount) {
                public void run() {
                    PurgeTxnLog.purge(logsDir, snapsDir, snapRetainCount) {
                        FileTxnSnapLog txnLog = new FileTxnSnapLog(dataDir, snapDir);
                        List<File> snaps = txnLog.findNValidSnapshots(num);
                        int numSnaps = snaps.size();
                        if (numSnaps > 0) {
                            purgeOlderSnapshots(txnLog, snaps.get(numSnaps - 1));
                        }
                    }
                }
            }
            timer.scheduleAtFixedRate(task, 0, TimeUnit.HOURS.toMillis(purgeInterval));
        }
        // 第三步:启动(有两种模式:standalone,集群模式)重点关注集群启动,分两步走
        if (args.length == 1 && config.isDistributed()) {
            runFromConfig(config) {
                // 第一件事:服务端的通信组件 的初始化,但是并未启动(factory = ServerCnxnFactory)
                cnxnFactory = ServerCnxnFactory.createFactory();
                cnxnFactory.configure(config.getClientPortAddress(), config.getMaxClientCnxns(), ...,false){
                    // 启动会话过期线程
                    initMaxCnxns();
                    // 管理 NIOServerCnxn 的超时, 链接超时管理
                    sessionlessCnxnTimeout = Integer.getInteger(ZOOKEEPER_NIO_SESSIONLESS_CNXN_TIMEOUT, 10000);
                    cnxnExpiryQueue = new ExpiryQueue<NIOServerCnxn>(sessionlessCnxnTimeout);
                    expirerThread = new ConnectionExpirerThread();
                    // 启动相应数量的 SelectorThread
                    int numCores = Runtime.getRuntime().availableProcessors();
                    int n = ZOOKEEPER_NIO_NUM_SELECTOR_THREADS;
                    numSelectorThreads = Integer.getInteger(n, Math.max((int) Math.sqrt((float) numCores / 2), 1));
                    numWorkerThreads = Integer.getInteger(ZOOKEEPER_NIO_NUM_WORKER_THREADS, 2 * numCores);
                    for (int i = 0; i < numSelectorThreads; ++i) {
                        selectorThreads.add(new SelectorThread(i));
                    }
                    // 启动一个 ServerSocketChannel
                    this.ss = ServerSocketChannel.open();
                    // 初始化一个 AcceptThread
                    acceptThread = new AcceptThread(ss, addr, selectorThreads);
                }
                // 第二件事:抽象一个zookeeper节点,然后把解析出来的各种参数给配置上,然后启动
                // QuorumPeer 就是所谓的一台服务器的抽象
                quorumPeer = getQuorumPeer() {
                    return new QuorumPeer();
                }
                quorumPeer.setTxnFactory(new FileTxnSnapLog(config.getDataLogDir(), config.getDataDir()));
                quorumPeer.setXXX(...);
                quorumPeer.start() {
                    // 第一件事:把磁盘数据恢复到内存
                    loadDataBase() {
                        zkDb.loadDataBase() {
                            // 冷启动的时候,从磁盘恢复数据到内存
                            snapLog.restore(...., ....){
                                // 从快照恢复
                                snapLog.deserialize(dt, sessions);
                                // 从操作日志恢复
                                fastForwardFromEdits(dt, sessions, listener) {
                                    // 恢复执行一条事务
                                    rocessTransaction(hdr, dt, sessions, itr.getTxn());
                                }
                            }
                        }
                    }
                    // 第二件事:服务端的通信组件工厂的真正启动(cnxnFactory = ServerCnxnFactory)
                    startServerCnxnFactory();
                    // 第三件事:启动 AdminServer
                    adminServer.start();
                    // 第四件事:准备选举的一些必要操作(初始化一些队列和一些线程)
                    startLeaderElection();
                    // 第五件事:启动 JVM 监视器
                    startJvmPauseMonitor();
                    // 第六件事:调用 start() 跳转到 run() 方法。因为 QuorumPeer被封装成 Thread 了
                    super.start() {
                        // 执行选举
                        QuorumPeer.run() {
                            // 选举入口
                            FastLeaderElection.lookForLeader()
                        }
                    }
                }
            }
        }
    }
}

总结

QuorumPeerMain类启动流程总结:

  • 入口:QuorumPeerMain的main方法
    • 解析配置
      • 读取zoo.cfg得到Properties对象,解析这个对象,获取各种配置,设置到QuorumPeerConfig
      • 解析server.开头的各项配置,获取allMembers,votingMembers,observerMembers集合,然后构建QuorumMaj实例
      • 解析myid
    • 启动删除旧快照文件定时任务
    • 启动QuorumPeer
      • 创建NIO服务端相关组件和线程 ServerCnxnFactory.createFactory()
      • 创建QuorumPeer实例,然后把QuorumPeerConfig中的各种配置,设置到QuorumPeer 很多
      • 调用start方法启动 quorumPeer.start()
        • 冷启动数据恢复 loadDataBase()
        • 启动NIO服务端 startServerCnxnFactory()
        • 启动AdminServer即WebUI adminServer.start()
        • startLeaderElection为选举做准备 startLeaderElection()
        • 启动JVM监视器 startJvmPauseMonitor()
        • 启动QuorumPeer线程进入ZAB工作模式:QuorumPeer.run()
          while(running) {
              switch(getPeerState()) {
                 case LOOKING:
                     lookForLeader();
                 case LEADING:
                     lead();
                 case FOLLOWING:
                     followerLeader();
                 case OBSERVING:
                     observerLeader();
              }
          }
          
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值