zookeeper笔记

Zookeeper的数据模型
    
        节点特性 
            临时节点(生命周期
            
            持久化节点
            
            有序节点(递增的序列号)
                
                有序节点的使用场景 有序节点: 全局ID、分布式锁、分布式队列
            
            先有父节点,再有子节点
            
            临时节点下不能存在子节点
            
            同级节点下,节点名字必须是唯一
    
        弱一致性模型
            
        2pc协议( 原子性 )
            
        过半提交
    
    QuorumPeerMain 是zookeeper集群的启动入口类,是用来加载配置启动QuorumPeer线程的
    
    ZooKeeperServerMain 是zookeeper单机的启动入口类
    
    Leader角色 Leader服务器是整个zookeeper集群的核心,主要的工作任务有两项
        1. 事物请求的唯一调度和处理者,保证集群事物处理的顺序性
        
        2. 集群内部各服务器的调度者
    
    Follower角色 Follower角色的主要职责是
        1. 处理客户端非事物请求、转发事物请求给leader服务器
        
        2. 参与事物请求Proposal的投票(需要半数以上服务器通过才能通知leader commit数据; Leader发起的提案,要求Follower投票)
        
        3. 参与Leader选举的投票
    
    Observer角色 Observer是zookeeper3.3开始引入的一个全新的服务器角色,从字面来理解,该角色充当了观察者的角色。
        观察zookeeper集群中的最新状态变化并将这些状态变化同步到observer服务器上。
        
        Observer的工作原理与follower角色基本一致,而它和follower角色唯一的不同在于observer不参与任何形式的投票,包括事物请求Proposal的投票和leader选举的投票。
        
        简单来说,observer服务器只提供非事物请求服务,通常在于不影响集群事物处理能力的前提下提升集群非事物处理的能力

    
    
    
    Leader选举 leader选举存在与两个阶段中,
        一个是服务器启动时的leader选举。 
        
        另一个是运行过程中leader节点宕机导致的leader选举 ;
        
    了解几个重要的参数
        
        服务器ID(myid) 比如有三台服务器,编号分别是1,2,3。编号越大在选择算法中的权重越大。
        
        zxid 事务id 值越大说明数据越新,在选举算法中的权重也越大
        
        
        逻辑时钟(epoch – logicalclock) 或者叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的。
        
            每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断选举状态 
            
            
        LOOKING,竞选状态。
        
        FOLLOWING,随从状态,同步leader状态,参与投票。
        
        OBSERVING,观察状态,同步leader状态,不参与投票。
        
        LEADING,领导者状态。
        
        
        
        
    
        
        
    投票机制
    
        QuorumPeerMain 启动类 
        
        初始化
        
        启动2181的端口(独立的业务服务),监听客户端请求(zkClient)
        
        启动(2888、 3888)这个端口的监听
        
        初始化leader选举(----)
        
        开启leader选举
        
        加载磁盘的数据
        
            MetricsProvider     指标数据
            
            QuorumPeerConfig    加载配置(zoo.cfg)
                
            QuorumPeer 集群节点的信息
            
            ServerCnxnFactory.configure  默认为 NIOServerCnxnFactory ServerCnxnFactory.createFactory()
            
                AcceptThread  用于处理接收客户端的请求
                
                SelectorThread 用来处理selector的读写请求  根据CPU个数除以2 取算数平方根
                
                
            
            quorumPeer.start()
                
                loadDataBase(); //加载数据
                
                startServerCnxnFactory(); //这里来启动 2181 的服务监听. ServerSocketChannel
                
                
                
                startLeaderElection(); //开启leader选举
                    
                    this.electionAlg = createElectionAlgorithm(electionType); 根据electionType 来创建选举算法
                    
                    QuorumCnxManager 管理集群选举和投票相关的操作
                    
                        createElectionAlgorithm
                        
                        QuorumCnxManager.Listener listener = qcm.listener;  监听集群中的票据
                    
                        QuorumCnxManager.Listener.run -> ListenerHandler.run -> acceptConnections
                        
                        client = serverSocket.accept(); 接收请求 最后通过参数 quorumSaslAuthEnabled 判断是异步处理还是同步处理
                        
                            receiveConnectionAsync(client); -> connectionExecutor.execute  进入 QuorumConnectionReceiverThread.run 
                            
                            receiveConnection(client); -> handleConnection

                            殊途同归 最终调用  receiveConnection -> handleConnection 生成两个线程 去进行集群节点的通信
                            
                            SendWorker sw = new SendWorker(sock, sid);  从 queueSendMap 中拿到集群节点的信息
                            
                            RecvWorker rw = new RecvWorker(sock, din, sid, sw); 把通讯返回的节点信息 放入 recvQueue 中 
                            
                            
                            
                        
                    
                    FastLeaderElection fle = new FastLeaderElection(this, qcm);  初始化了FastLeaderElection
                        
                        this.messenger = new Messenger(manager); 初始化两个线程
                            
                        1:this.ws = new WorkerSender(manager);     对应 sendqueue 
                                
                            WorkerSender.run ->    process(m); -> manager.toSend(m.sid, requestBuffer);
                                
                            最终进入到 QuorumCnxManager.toSend  ->    connectOne(sid);
                            
                            connectOne  -> initiateConnectionAsync ->  -> connectionExecutor.execute
                            
                            进入到 QuorumConnectionReqThread.run 方法  -> initiateConnection  ->  startConnection(sock, sid);

                            DataOutputStream 输出流写出数据

                            
                        2:this.wr = new WorkerReceiver(manager);     从 recvqueue 中取出通讯返回来的节点信息
                            
                            把节点信息 放入 queueSendMap 中 让 SendWorker 拿节点信息进行投票选举的通信
                        
                        
                 
                super.start(); //启动线程。进入 QuorumPeer.run 方法
                
                run 方法 while循环 
                    
                    looking -> setCurrentVote(makeLEStrategy().lookForLeader()); 得到vote是一个leader的vote.->当前节点一定会在选举算法中,得到leader之后,重设设置一个状态
                    
                    lookForLeader -> updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());//myid,  zxid , epoch 把自己的投票的proposal设置成自己的信息
                        
                        sendNotifications(); //发送通知
                        
                        while ((self.getPeerState() == ServerState.LOOKING) && (!stop))  只要当前节点的状态是LOOKING,就不断的循环
                        
                        其他节点会同步leader节点的epoch,然后发送过来进行比较,也就是epoch趋于一致,比较myid和zxid,最后完成投票
                        
                
                
                
    
    watch机制    
        
        客户端
        
            1: ClientCnxn 初始化 在 ZooKeeper 类构造方法
            
            2: exists(getChild,addWatch等事件)注册watcher监听
            
            3:ClientCnxn.submitRequest 
                
                调用queuePacket,把请求数据添加到队列  outgoingQueue 队列(LinkedBlockingDeque<ClientCnxn.Packet> outgoingQueue)
                
                通过packet.wait使得当前线程一直阻塞,直到请求完成
        
            4: SendThread.run 在Zookeeper这个对象初始化的时候,启动了一个 SendThread,这个线程会从 outgoingQueue 中获取任务,然后发送到服务端处理
                
                最后执行 clientCnxnSocket.doTransport
        
            5:ClientCnxnSocketNIO.doTransport 调用协议层进行数据传输。
                
                读写请求调用 doIO(pendingQueue, cnxn)
            
            6: ClientCnxnSocketNIO.doIO
                
                找到可以发送的 packet 如果Packet的byteBuffer没有创建,那么就创建
                
                outgoingQueue 从待发送队列中移除  packet
                
                把信息放入 SocketChannel  进行通讯
                
            7: 客户端收到请求后的处理     sendThread.readResponse
            
                    客户端接收请求的处理是在ClientCnxnSocketNIO的doIO中,之前客户端发起请求是写,现在客户端收到请求,则是一个读操作,也就是当客户端收到服务端的数据时会触发一下代码的执行。
                    
                    其中很关键的是sendThread.readResponse(incomingBuffer);来接收服务端的请求。
                    
                    这个方法里面主要的流程如下首先读取header,如果其xid == -2,表明是一个ping的response,return
                    
                    如果xid是 -4 ,表明是一个AuthPacket的response return
                    
                    如果xid是-1,表明是一个notification,此时要继续读取并构造一个enent,通过EventThread.queueEvent发送,return
                    
                    其它情况下:从pendingQueue拿出一个Packet,校验后更新packet信息对于exists请求,返回的xid=1,则进入到其他情况来处理
                    
                SendThread.readResponse 调用 finishPacket
            
            8: finishPacket 通过前面客户端和服务端的交互,可以确定服务端已经成功保存了watcher这个事件,那么受到服务端的确认之后,客户端会把这个watcher保存到本地的事件中。
            
                所以,finishPacket主要功能是把从 Packet 中取出对应的 Watcher 并注册到 ZKWatchManager 中去
                    
                watchRegistration.register 
                    
                    Map<String, Set<Watcher>> watches = getWatches(rc); 把path对应的watcher本地回调保存到一个集合中。 
                        
                        ExistsWatchRegistration.getWatches
                        
                        DataWatchRegistration.getWatches
                        
                        ChildWatchRegistration.getWatches
                    
                    watchers.add(watcher); //把watcher保存到watches集合,此时的watcher对应的就是在exists方法中传入的匿名内部类
                    
                ZkWatchManager ZkWatchManager 是客户端这边用来保存本地节点对应的 watcher 回调的管理类,提供了三种不同的事件管理机制。
                
                eventThread.queuePacket
                    
                    processEvent  进入到 实现 Watcher 接口的实现类 表明节点发生了变化
        
        服务端
            
            1: zookeeper启动的时候,通过下面的代码构建了一个ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory(); 监听 2181 端口
                
                并且,在QuorumPeer.start()->startServerCnxnFactory()->cnxnFactory.start(); 中,启动了一个 acceptThread 线程,这个线程从名字上看,应该是用来处理客户端的来请求
        
            2: AcceptThread.run  在run方法中,调用了select()方法
                
                select方法中,会通过复路器Selector,去进行select操作,获取就绪的连接。其中select这个方法中主要做的事情是遍历所有的就绪连接,进行连接的判断调用 doAccept 方法进行处理
                
            3: doAccept 真正读取客户端的请求
                
                轮询,将当前连接分配给选择器线程 SelectorThread 
                 
                selectorThread.addAcceptedConnection 把当前连接再丢给SelectorThread来处理 添加到接收队列 acceptedQueue,后续会为该连接注册读写事件                
            
            4: SelectorThread.run
                
                1: select(); 处理多路复用   
                    
                    从selector中获取就绪的连接,针对读写事件,调用 handleIO 方法进行处理。             
                
                2: processAcceptedConnections(); 处理连接请求               
                
                3: processInterestOpsUpdateRequests(); 注册一个更新请求
                
            
            5: SelectorThread.run -> select -> handleIO  ->  WorkerService.schedule -> ScheduledWorkRequest.run -> NIOServerCnxnFactory.IOWorkRequest.doWork
                
                通过N个异步化处理过程,最终进入到 ZookeeperServer.processPacket 
                
                调用链路: WorkerService.schedule -> ScheduledWorkRequest.run -> IOWorkRequest.doWork->NIOServerCnxn.doIO->readPayload->readRequest->processPacket
            
            6: ZookeeperServer.processPacket 
                
                根据数据包的类型来处理不同的数据包,对于读写请求
                
            7: ZookeeperServer.submitRequest 将请求添加到 RequestThrottler(限流器)中去处理,它是一个线程,而 submitRequest 方法实际就是把任务添加到阻塞队列。    
            
            8: RequestThrottler.submitRequest
                
                RequestThrottler 本身就是一个线程 进入到 run 方法 在 RequestThrottler 的 run 方法中,会从阻塞队列中取出任务进行处理。
            
            9:ZookeeperServer.submitRequestNow
                
                firstProcessor  这个是一个责任链模式 firstProcessor 的初始化是在 ZookeeperServer.setupRequestProcessor 中完成的
                    
                    firstProcessor = PrepRequestProcessor(SyncRequestProcessor(FinalRequestProcessor))
                
            10: PredRequestProcessor.processRequest    
                
                通过上面的调用链关系以后,firstProcessor.processRequest(si); 会调用到 PrepRequestProcessor 在这个处理器中,又把请求对象提交到了阻塞队列中
                
                PredRequestProcessor  是一个线程,在构建处理器链的时候,就已经启动了这个线程,所以直接进入到 run 方法中。
                    
                    PreRequestProcessor 一般是放在处理链的起始部分的,它对请求做一些预处理
                    
                        1. 检查Session 、2. 检查要操作的节点及其父节点是否存在 、3. 检查客户端是否有权限
                        
                    pRequest 方法比较长,主要逻辑就是根据不同的请求类型实现不同的操作。
                
                SyncRequestProcessor  是一个线程,在构建处理器链的时候,就已经启动了这个线程,所以直接进入到 run 方法中。
                    
                    这个 processor 负责把写request持久化到本地磁盘,为了提高写磁盘的效率,这里使用的是缓冲写,但是会周期性(1000个request)的调用flush操作,flush之后request已经确保写到磁盘了
                    
                    同时他还要维护本机的txnlog和snapshot
                        
                        每隔snapCount/2个request会重新生成一个snapshot并滚动一次txnlog,同时为了避免所有的zookeeper server在同一个时间生成snapshot和滚动日志,
                        
                        这里会再加上一个随机数,snapCount的默认值是10w个request

                FinalRequestProcessor 是一个线程,在构建处理器链的时候,就已经启动了这个线程,所以直接进入到 run 方法中。
                
                    这个是最终的一个处理器,主要负责把已经commit的写操作应用到本机,对于读操作则从本机中读取数据并返回给client
                    
                    ProcessTxnResult rc = zks.processTxn(request); 修改内存中的数据.(DataTree)

                        processTxnInDB() -> getZKDatabase().processTxn() ->  DataTree.processTxn
                    
                    
                    最终在 FinalRequestProcessor.run 找到一个很关键的代码,判断请求的getWatch是否存在,如果存在,则传递cnxn(servercnxn)  //对于exists请求,需要监听data变化事件,添加watcher
                        
                        exists -> Stat stat = zks.getZKDatabase().statNode(path, existsRequest.getWatch() ? cnxn : null);
                    
                    statNode  statNode应该会做两个事情 1:获取指定节点的元数据 、 2:保存针对该节点的事件监听

                        DataNode n = nodes.get(path) 根据path获取节点数据
    
                        dataWatches.addWatch(path, watcher) 如果watcher不为空,则将当前的watcher和path进行绑定
                    
                    WatchManager.addWatch 通过WatchManager来保存指定节点的事件监听,WatchManager维护了两个集合。 watchTable 、watch2Paths
                        
                        private final Map<String, Set<Watcher>> watchTable = new HashMap<>();  表示从节点路径到 watcher 集合的映射
                        
                        private final Map<Watcher, Set<String>> watch2Paths = new HashMap<>(); 表示从 watcher 到所有节点路径集合的映射
                        
                            设置watch的模式    
                            watch 有三种类型,
                                默认为一次性的
                                一种是PERSISTENT、 前者是持久化订阅
                                一种是PERSISTENT_RECURSIVE、STANDARD,后者是持久化递归订阅,所谓递归订阅就是针对监听的节点的子节点的变化都会触发监听,  
                                 watcherModeManager.setWatcherMode(watcher, path, watcherMode);
                    
                    返回处理结果 在 FinalRequestProcessor 的 processRequest 方法中,将处理结果rsp返回给客户端

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值