zookeeper知识点

zookeeper

基本知识

定义和原理:

面试题:

zookeeper选举机制:

1 半数机制:集群中有半数以上的服务器存活,则可以继续提供服务
2 zookeeper分leader和follower

监听器原理:

原理详解:
	1 首先有一个main()线程
	2 在main()线程中创建zookeeper客户端,这时就会创建两个线程,一个负责网络通信,另一个负责监听
	3 负责网络通信的线程与zookper服务端连接,将注册监听信息发送给zookeeper服务端,服务端将这个注册信息存储到注册列表中
	4 zookeeper服务端被监听的数据发生改变,获取监听该数据的注册列表,发送数据改变的信息
	5 zookeeper客户端获取zookeeper发来的信息,调用process()方法

节点类型:

1 短暂和持久的 客户端与服务端一旦断开连接,短暂节点则会被删除;持久节点则会一直存在
2 有序或者无序 【在分步式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序】

zookeeper的特点

1 一个领导者leader,多个追随者follower
2 集群中只要有半数以上节点存活,zookeeper就能正常服务
3 全局数据一致--难点【每个server保存的数据都是一样的,都是相同的副本数据,client无论连接到哪个server,数据都是一致的】
4 更新请求顺序进行--重点【每个节点的数据更新是依据更新请求的获取次序进行的】
5 数据更新具有事务性,要么全部成功,要么全部失败--重点【集群中所有节点的数据要么全部更新成功,要么全部失败】
6 实时性,在一定时间范围内,clent能读到最新的数据---重点难点

zookeeper处理工作请求的流程

写数据流程

zookeeper是如何实现数据一致性的?
使用分布式一致性协议ZAB
ZAB的原理:

ZAB原理:

zookeeper一致性协议:
(1)集群在半数以下节点宕机的情况下,能正常对外提供服务;
(2)客户端的写请求全部转交给leader来处理,leader需确保写变更能实时同步给所有follower及observer;
(3)leader宕机或整个集群重启时,需要确保那些已经在leader服务器上提交的事务最终被所有服务器都提交,确保丢弃那些只在leader服务器上被提出的事务,并保证集群能快速恢复到故障前的状态。
Zab协议有两种模式, 崩溃恢复(选主+数据同步)和消息广播(事务操作)。任何时候都需要保证只有一个主进程负责进行事务操作,而如果主进程崩溃了,就需要迅速选举出一个新的主进程。主进程的选举机制与事务操作机制是紧密相关的。下面详细讲解这三个场景的协议规则,从细节去探索ZAB协议的数据一致性原理。

1、选主:leader选举是zk中最重要的技术之一,也是保证分布式数据一致性的关键所在。当集群中的一台服务器处于如下两种情况之一时,就会进入leader选举阶段——服务器初始化启动、服务器运行期间无法与leader保持连接。
选举阶段,集群间互传的消息称为投票,投票Vote主要包括二个维度的信息:ID、ZXID

ID 被推举的leader的服务器ID,集群中的每个zk节点启动前就要配置好这个全局唯一的ID。

ZXID 被推举的leader的事务ID ,该值是从机器DataTree内存中取的,即事务已经在机器上被commit过了。

节点进入选举阶段后的大体执行逻辑如下:
(1)设置状态为LOOKING,初始化内部投票Vote (id,zxid) 数据至内存,并将其广播到集群其它节点。节点首次投票都是选举自己作为leader,将自身的服务ID、处理的最近一个事务请求的ZXID(ZXID是从内存数据库里取的,即该节点最近一个完成commit的事务id)及当前状态广播出去。然后进入循环等待及处理其它节点的投票信息的流程中。
(2)循环等待流程中,节点每收到一个外部的Vote信息,都需要将其与自己内存Vote数据进行PK,规则为取ZXID大的,若ZXID相等,则取ID大的那个投票。若外部投票胜选,节点需要将该选票覆盖之前的内存Vote数据,并再次广播出去;同时还要统计是否有过半的赞同者与新的内存投票数据一致,无则继续循环等待新的投票,有则需要判断leader是否在赞同者之中,在则退出循环,选举结束,根据选举结果及各自角色切换状态,leader切换成LEADING、follower切换到FOLLOWING、observer切换到OBSERVING状态。

    算法细节可参照FastLeaderElection.lookForLeader(),主要有三个线程在工作:选举线程(主动调用lookForLeader方法的线程,通过阻塞队列sendqueue及recvqueue与其它两个线程协作)、WorkerReceiver线程(选票接收器,不断获取其它服务器发来的选举消息,筛选后会保存到recvqueue队列中。zk服务器启动时,开始正常工作,不停止)以及WorkerSender线程(选票发送器,会不断地从sendqueue队列中获取待发送的选票,并广播至集群)。WorkerReceiver线程一直在工作,即使当前节点处于LEADING或者FOLLOWING状态,它起到了一个过滤的作用,当前节点为LOOKING时,才会将外部投票信息转交给选举线程处理;如果当前节点处于非LOOKING状态,收到了处于LOOKING状态的节点投票数据(外部节点重启或网络抖动情况下),说明发起投票的节点数据跟集群不一致,这时,当前节点需要向集群广播出最新的内存Vote(id,zxid),落后节点收到该Vote后,会及时注册到leader上,并完成数据同步,跟上集群节奏,提供正常服务。
1
2、选主后的数据同步:选主算法中的zxid是从内存数据库中取的最新事务id,事务操作是分两阶段的(提出阶段和提交阶段),leader生成提议并广播给followers,收到半数以上的ACK后,再广播commit消息,同时将事务操作应用到内存中。follower收到提议后先将事务写到本地事务日志,然后反馈ACK,等接到leader的commit消息时,才会将事务操作应用到内存中。可见,选主只是选出了内存数据是最新的节点,仅仅靠这个是无法保证已经在leader服务器上提交的事务最终被所有服务器都提交。比如leader发起提议P1,并收到半数以上follower关于P1的ACK后,在广播commit消息之前宕机了,选举产生的新leader之前是follower,未收到关于P1的commit消息,内存中是没有P1的数据。而ZAB协议的设计是需要保证选主后,P1是需要应用到集群中的。这块的逻辑是通过选主后的数据同步来弥补。
选主后,节点需要切换状态,leader切换成LEADING状态后的流程如下:
(1)重新加载本地磁盘上的数据快照至内存,并从日志文件中取出快照之后的所有事务操作,逐条应用至内存,并添加到已提交事务缓存commitedProposals。这样能保证日志文件中的事务操作,必定会应用到leader的内存数据库中。
(2)获取learner发送的FOLLOWERINFO/OBSERVERINFO信息,并与自身commitedProposals比对,确定采用哪种同步方式,不同的learner可能采用不同同步方式(DIFF同步、TRUNC+DIFF同步、SNAP同步)。这里是拿learner内存中的zxid与leader内存中的commitedProposals(min、max)比对,如果zxid介于min与max之间,但又不存在于commitedProposals中时,说明该zxid对应的事务需要TRUNC回滚;如果 zxid 介于min与max之间且存在于commitedProposals中,则leader需要将zxid+1~max 间所有事务同步给learner,这些内存缺失数据,很可能是因为leader切换过程中造成commit消息丢失,learner只完成了事务日志写入,未完成提交事务,未应用到内存。
(3)leader主动向所有learner发送同步数据消息,每个learner有自己的发送队列,互不干扰。同步结束时,leader会向learner发送NEWLEADER指令,同时learner会反馈一个ACK。当leader接收到来自learner的ACK消息后,就认为当前learner已经完成了数据同步,同时进入“过半策略”等待阶段。当leader统计到收到了一半已上的ACK时,会向所有已经完成数据同步的learner发送一个UPTODATE指令,用来通知learner集群已经完成了数据同步,可以对外服务了。
细节可参照Leader.lead() 、Follower.followLeader()及LearnerHandler类。

3、事务操作:ZAB协议对于事务操作的处理是一个类似于二阶段提交过程。针对客户端的事务请求,leader服务器会为其生成对应的事务proposal,并将其发送给集群中所有follower机器,然后收集各自的选票,最后进行事务提交。流程如下图。

ZAB协议的二阶段提交过程中,移除了中断逻辑(事务回滚),所有follower服务器要么正常反馈leader提出的事务proposal,要么就抛弃leader服务器。follower收到proposal后的处理很简单,将该proposal写入到事务日志,然后立马反馈ACK给leader,也就是说如果不是网络、内存或磁盘等问题,follower肯定会写入成功,并正常反馈ACK。leader收到过半follower的ACK后,会广播commit消息给所有learner,并将事务应用到内存;learner收到commit消息后会将事务应用到内存。

ZAB协议中多次用到“过半”设计策略 ,该策略是zk在A(可用性)与C(一致性)间做的取舍,也是zk具有高容错特性的本质。相较分布式事务中的2PC(二阶段提交协议)的“全量通过”,ZAB协议可用性更高(牺牲了部分一致性),能在集群半数以下服务宕机时正常对外提供服务。

应用场景

统一命名服务

统一配置管理

统一集群管理

服务器节点动态上下线

软负载均衡

统一命名服务

分布式环境下,需要对服务进行命名管理,便于识别

统一配置管理

需求:

分布式环境下,配置文件同步是非常常见的。1、一般要求一个节点中,所有节点的配置文件是一致的;2、对配置文件修改后,希望能够快速同步到各个节点上。

解决:

在zookeeper中,可以将配置文件管理交由zookeeper实现,具体实现方式如下:

1 可将配置文件信息写入zookeeper上的一个znode
2 每个客户端服务器监听这个zndoe
3 一旦znode中的数据被修改,zookeeper将通知各个客户端服务器

统一集群管理

需求:分布式环境中,实时掌握每个节点的状态是必要的,需要根据每个节点实时状态做出一些调整

解决:

1 可以将节点信息写入到ZooKeeper上的一个ZNode
2 监听这个ZNode可获取它的实时状态变化

服务器动态上下线

需求:客户单需要实时观察服务器的动态上下线变化

解决:

使用zookeeper在服务端启动时创建节点,存储注册信息,并且让客户端监听这些节点

软负载均衡

需求:使用软件的方式实现负载均衡

解决:

在ZooKeeper中记录每台服务器的访问次数,让访问数最少的服务器去处理最新的客户请求

使用docker搭建zookeeper集群

客户端命令行操作

help
ls <path> 现实该节点是否子节点
ls2 <path> 详细现实该节点
create -e -s <nodeName> 创建该节点
get <path> 获取该节点的数据
set path value 设置该节点的数据
stat path 显示该节点的状态
delete path 删除该节点
rmr path 递归删除该节点

使用java代码模拟客户端对zookeeper服务端进行操作

ZookeeperclientApplicationTests.java

package com.hzc.zookeeper;

import org.apache.zookeeper.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;

@SpringBootTest
class ZookeeperclientApplicationTests {

    private String connectString = "127.0.0.1:2181,127.0.0.1:2181,127.0.0.1:2181";
    private int sessionTimeout = 2000;
    private ZooKeeper zooKeeper;

    @Test
    void contextLoads() {
    }

    @BeforeEach
    public void init() throws IOException {
        zooKeeper = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("watcher is watching");
            }
        });
    }

    /**
     * 创建节点
     *
     * @throws InterruptedException
     * @throws KeeperException
     */
    @Test
    public void createNode() throws InterruptedException, KeeperException {
        String s = zooKeeper.create("/myTest", "this is a test data".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        System.out.println(s);
    }

    @Test
    public void getChildrenNode() throws InterruptedException, KeeperException, IOException {
        try {
            List<String> children = zooKeeper.getChildren("/", true);
            assert children != null;
            for (String string : children) {
                System.out.println(string);
            }
        } catch (KeeperException | InterruptedException e) {
            e.printStackTrace();
        }
      //循环监听某个节点
        zooKeeper.addWatch("/newtt", watchedEvent -> System.out.println("监听/newtt节点..."), AddWatchMode.PERSISTENT);
        System.out.println("开始睡眠...");
        System.in.read();
    }
}

服务器动态上下线案例分析

代码实现如下:

HDFS HA高可用

高可用:即使7*24小时不中断服务

完整版详细介绍

https://cloud.tencent.com/developer/article/1704858

posted @ 2021-05-08 18:56  黄子梦  阅读(124)  评论(0编辑  收藏  举报