话说关于 ZooKeeper 方面,面试有什么可问的啊?​ 原创 idea Java知音 2020-11-13 09:33

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
云原生网关 MSE Higress,422元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 话说关于 ZooKeeper 方面,面试有什么可问的啊?​原创 idea Java知音 2020-11-13 09:33

最近小林去面试的时候发现经常会倒在zk的相关知识点面试上边,一直都以为这只是一个普通的注册中心,最近花了些心思进行知识整理,却想不到这里面包含了这么多知识点。


zookeeper的基本概念


zookeeper是一款开源的分布式协调服务框架,为分布式环境提供了一致性服务的功能,常见应用场景有:发布订阅,主动通知,文件管理,集群管理,分布式锁等功能。zk在设计的时候满足了cp两要素,即一致性和分区容错性。


zookeeper的设计理念


这里我参考以往的一些经验,将其简单囊括为了以下几点:


一致性


如下图所示,所有的客户端一旦连接到了集群环境中,不论访问的zk是leader角色还是follower角色,每个zk节点的数据都是相同的。假设某一时刻,zk的某个节点数据被修改了,那么此时必须要将每个节点的数据都做同步之后才能继续提供外界读取节点的功能。


image.png


有头


在集群环境中,一定会有一个leader的角色充当集群领头。一旦leader挂了,就会重新选举新的机器当选leader。


数据树


zk内部存储数据是采用了树状结构,这一点有些类似于文件系统的设计,每个树状节点底下存放的子节点可以是有序排列的状态 , 如下图所示:


image.png


zookeeper内部的相关配置信息


常规配置文件内容:


使用的zk版本为3.4.14版本


# zookeeper时间配置中的基本单位 (毫秒)
tickTime=2000
# 允许follower初始化连接到leader最大时长,它表示tickTime时间倍数 即:initLimit*tickTime 
initLimit=10
# 允许follower与leader数据同步最大时长,它表示tickTime时间倍数 
syncLimit=5
# 存放节点数据的位置
dataDir=/Users/linhao/env/zookeeper/zk-data
# 端口号
clientPort=2181
# 最大并发连接数
#maxClientCnxns=60
#
# 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
#日志地址
dataLogDir=/Users/linhao/env/zookeeper/zk-log
# 保存的数据快照数量,之外的将会被清除
autopurge.snapRetainCount=3
#自动触发清除任务时间间隔,小时为单位。默认为0,表示不自动清除。
autopurge.purgeInterval=1


内部指令整理


启动zookeeper脚本


sh ./zkServer.sh start


连接服务端指令


sh ./zkCli.sh


节点的增删改查


ls /  查询所有根节点
ls /idea 查询idea节点下边的内容
delete /idea 删除idea节点
delete /idea/node-1 删除idea节点下边的node-1子节点
create /idea/node-2 "v1" 创建idea/node-2节点 并且写入v1值
get /idea/node-1 获取相关节点


节点类型


持久化节点


默认创建的节点就是持久化类型


create  /node ""


临时节点


在zookeeper3.4版本里面,创建临时节点的时候可能会有以下异常:


Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
 at org.apache.zookeeper.ZooKeeperMain.processZKCmd(ZooKeeperMain.java:707)


正确的执行命令为:


create -e /temp ""


持久化顺序节点


create -s /seq-node ""


多次执行该指令之后可以看到以下内容:


[zk: localhost:2181(CONNECTED) 4] ls /
[dubbo, seq-node0000000010, ietty, zookeeper, idea]
[zk: localhost:2181(CONNECTED) 5] create -s /seq-node ""
Created /seq-node0000000011
[zk: localhost:2181(CONNECTED) 6] create -s /seq-node ""
Created /seq-node0000000012
[zk: localhost:2181(CONNECTED) 7] create -s /seq-node ""
Created /seq-node0000000013
[zk: localhost:2181(CONNECTED) 8] create -s /seq-node ""
Created /seq-node0000000014


此时节点的下方已经创建了多个顺序节点数据


临时顺序节点


create -e -s /seq-temp-node ""


查看节点信息


stat命令可以查看节点底下的各种属性


[zk: localhost:2181(CONNECTED) 12] stat /idea
//创建节点的事务id
cZxid = 0x1bd6
//创建节点的时间
ctime = Sun Nov 08 22:50:57 CST 2020
//修改节点的事务id
mZxid = 0x1bd6
//最后修改时间
mtime = Sun Nov 08 22:50:57 CST 2020
//子节点变更的事务ID
pZxid = 0x1bdb
//子节点的变更次数
cversion = 4
dataVersion = 0
//权限版本
aclVersion = 0
ephemeralOwner = 0x0
//数据长度
dataLength = 12
//第一层子节点的数目 不包含子子节点
numChildren = 2


acl权限设置


ACL全称为Access Control List(访问控制列表),用于控制资源的访问权限。


ZooKeeper使用ACL来控制对其znode的防问。基于scheme:id:permission的方式进行权限控制。scheme表示授权模式、id模式对应值、permission即具体的增删改权限位。

scheme认证模型


方案 描述
world 开放模式,world表示全世界都可以访问(这是默认设置)
ip ip模式,限定客户端IP防问
auth 用户密码认证模式,只有在会话中添加了认证才可以防问
digest 与auth类似,区别在于auth用明文密码,而digest 用sha-1+base64加密后的密码。在实际使用中digest 更常见。


permission权限位


权限位 权限 描述
c CREATE 可以创建子节点
d DELETE 可以删除子节点(仅下一级节点)
r READ 可以读取节点数据及显示子节点列表
w WRITE 可以设置节点数据
a ADMIN 可以设置节点访问控制列表权限


acl 相关命令


命令 使用方式 描述
getAcl getAcl 读取ACL权限
setAcl setAcl 设置ACL权限
addauth addauth 添加认证用户


world权限示例语法:setAcl world:anyone:<权限位> 注:world模式中anyone是唯一的值,表示所有人


查看默认节点权限:


#创建一个节点
create -e /testAcl
#查看节点权限
getAcl /testAcl
#返回的默认权限表示 ,所有人拥有所有权限。
'world,'anyone: cdrwa


修改默认权限为 读写


#设置为rw权限 
setAcl /testAcl world:anyone:rw
# 可以正常读
get /testAcl
# 无法正常创建子节点
create -e /testAcl/t "hi"
# 返回没有权限的异常
Authentication is not valid : /testAcl/t


zookeeper集群


至少要有三台。


这两种场景下会有选举发生:


  1. 服务节点初始化
  2. 或者半数以上的节点无法和leader进行连接建立


集群里面的zk机器均具有读和写的功能。


zk集群的选举


在讲解选举之前,我们需要先了解一下什么是zxid。


在zk的节点数据中,每次发生数据变动都会有一个流水id做递增的记录,这个id我们称之为zxid,不同机器的zxid可能会有所不同,越大代表当前的数据越新。


实际上每个zk节点都有两个用于记录更新的id,分别是czxid和mzxid。通过名称的缩写可以翻译为:


  • czxid:创建节点时候的xid。
  • mzxid:修改节点数据时候的xid。


投票整体思路


第一轮投票全部投给自己


第二轮投票给zxid比自己大的相邻节点 如果得票超过半数,选举结束。


假设现在有3台机器参与选举,分别是1,2,3号机器,启动顺序是1,2,3

第一轮投票:


1号机器投票给到1自己


2号机器投票给到2自己


3号机器投票给到3自己


第二轮:


1号机器的id< 2号机器的id <3号机器的id


1号机器投票给到2号机器,此时2号获取选票大于总机器数目的一半,所以2号成为leader


2号投票给到3号机器,由于此时2号机器已经是leader了,所以3号机器依然只能和1号机器一起保持为follower角色。


所以经过一轮选举之后,2号机当选leader


如何保证主从节点的数据状态同步


ZAB协议


在zk集群中,保证各个server端的数据一致性其实是依靠了一个叫做zab的基础协议。zab协议的全称是zookeeper atomic broadcast zk原子广播协议,可以理解为是一种用于保证分布式环境下事务一致性的一种协议。


在zk的集群环境中,是由一台主机器用于接受集群的请求指令(暂时称之为leader),然后通过leader将指令复制传递给各个follower角色进行数据复制。


如何判断写请求提交成功


当leader将数据成功写入超过半数以上的节点的时候就算是写请求提交成功了。写请求过程中又有哪些细节呢?各位读者不要担心,在下边会有详细讲解。


事务提交的一致性


假设有两个写请求a和b。


a请求:创建一个 /t1 的节点


b请求:创建一个 /t1/a 的节点


那么此时,必须要等a请求执行的事务被完全提交了,后边b请求才能被执行。假设a请求没有提交成功,那么此时后边b请求也不会执行成功。


zk集群环境中写入数据过程中发生了什么


客户端首先连接到zk集群环境,然后将需要写入的数据提交给任意一台zk服务器,如果当前服务器是leader,则由leader接受,然后将数据广播给集群的每一台follower机器。如果接受指令的是follower,则会将请求转给leader,再广播给各个follower机器。


广播细节分为两个步骤:


  1. Leader 可以接受客户端新的事务Proposal请求,将新的Proposal请求广播给所有的 Follower。如何确认广播出去的请求,follower确认接收呢?需要借助一个ack的标记信息响应进行反馈。


  1. 假设集群里面有一半的机器都返回了ack信号,那么此时leader就可以再次广播每个机器执行相应事务的commit操作了,并且执行之后需要在本地记录该行为到事务日志中。


崩溃恢复模式和消息广播模式


崩溃模式


zk集群中的leader可能因为网络抖动或者某些异常问题,失去了和其余follower的联系,此时剩余的follower需要重新进行选举。


或者如果集群中只有不到一半的机器能和leader进行通信,那么此时也算是进入到了崩溃模式,需要重新选举。


广播模式


由leader向各个follower发送同步数据的信号,发送各种事务信息和提交信号。


zk节点的数据变动通知特性


当客户端链接到zk服务端的时候,某个节点如果出现了数据变动,之前有监听过这个节点数据的客户端都会接收到相关信号。


可能会有什么问题?


这种主动通知是一种弱最终一致性,而且只会发送一次通知,并不能保证更新的实时准确性。


实践应用场景



网上已经有很多关于基于zk实现分布式锁,dubbo服务注册中心的这些案例,这里我列举了一个自己写的简单案例,扩散各位读者们的一些设计思路。


分布式集群环境的管理


利用了zk里面的有序节点来实现分布式集群环境下的节点管理,设计的思路页比较简单,给每个应用启动的时候加入一个插件,实时将该程序运作的相关性能属性上报到zk的特定节点中,一旦服务下线,节点的数据也会按时消失。


利用zk的临时顺序节点特性就能很好地完成这个功能:


image.png


思路整理:


往zk的一个节点(这里假设是:zk-agent 节点)下创建一批临时有序节点:


zk-agent/server000001 //假设1号机器启动


zk-agent/server000002 //假设2号机器启动


zk-agent/server000003 //假设3号机器启动


每个节点都有相关的写入数据:


{"serverIp":"127.0.0.1","totalCpu":12,"totalMemory":17179869184,"totalMemoryDesc":"16GB","memoryUsage":"58.9%"}


查看的信息的时候只需要通过访问zk注册中心,读取节点下边的数据即可。


当任意一台服务出现异常的时候,相关的服务也需要和zk断开连接,此时节点数据消失。


为什么不用mysql存储服务性能信息?


其实使用mysql,redis等数据库也是可行的,但是必须一点:


1.服务断开之后数据能够及时同步更新


而这一点,在使用zookeeper来落地更加适合,因为zookeeper内部的临时节点与生俱来就支持这一特性。


这里我罗列出自己的一些编码设计,供大家参考:


相关依赖:


<dependencies>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.5.5</version>
    </dependency>
    <dependency>
        <groupId>com.101tec</groupId>
        <artifactId>zkclient</artifactId>
        <version>0.11</version>
    </dependency>
    <dependency>
        <groupId>com.github.oshi</groupId>
        <artifactId>oshi-core</artifactId>
        <version>3.5.0</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.2</version>
    </dependency>


package org.idea.zookeeper.framework.agent;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.I0Itec.zkclient.ZkClient;
import java.lang.instrument.Instrumentation;
import static org.idea.zookeeper.framework.agent.OSOptCommonConstants.*;
/**
 * @author idea
 * @date created in 5:39 下午 2020/11/9
 */
public class ZkAgent {
    private static ZkClient zkClient;
    private static String nodePath;
    public static void premain(String args, Instrumentation instrumentation) {
        System.out.println("[ZkAgent] begin init ");
        if (args != null) {
            System.out.println(args);
        }
        zkClient = new ZkClient(ZK_SERVER);
        init();
    }
    /**
     * 初始化操作
     */
    public static void init() {
        if (!zkClient.exists(ROOT_PATH)) {
            zkClient.createPersistent(ROOT_PATH, "");
        }
        //当前创建的节点路径
        nodePath = zkClient.createEphemeralSequential(SERVER_PATH, getOsBeanDesStr());
        System.out.println("创建节点:" + nodePath);
        final Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    //守护线程计算相关内存信息
                    updateData(nodePath, getOsBeanDesStr());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
    }
    /**
     * 获取节点的描述信息
     *
     * @return
     */
    public static String getOsBeanDesStr() {
        //获取系统内部相关信息
        OSBean osBean = CPUMonitorUtil.getOSBean();
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.writeValueAsString(osBean);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 更新节点数据信息
     *
     * @param path
     * @param data
     */
    public static void updateData(String path, Object data) {
        if (zkClient.exists(path)) {
            zkClient.writeData(path, data);
        } else {
            zkClient.createEphemeral(path, data);
        }
    }
}


通过结合jdk的Instrumentation技术,在启动程序的时候正好初始化获取操作系统内部的

性能指标。创建一个守护线程往节点定时刷新上报系统性能指标,一旦主进程挂了,守护线程也随之消失,从而实现和zk断开会话,节点消失。


查看zk内部节点的信息我这里暂时只设计了一个简单的命令窗口:


package org.idea.zookeeper.framework.agent;
/**
 * 获取操作系统内部的一些属性信息服务
 *
 * @author linhao
 * @date created in 8:51 下午 2020/11/9
 */
public interface OSInfoService {
    /**
     * 根据opts来查看不同的数据信息
     *
     */
    void doOptionInfo();
}


package org.idea.zookeeper.framework.agent;
import org.I0Itec.zkclient.ZkClient;
import java.util.List;
import java.util.Scanner;
import static org.idea.zookeeper.framework.agent.OSOptCommonConstants.*;
/**
 * @author linhao
 * @date created in 10:13 下午 2020/11/9
 */
public class OSInfoServiceImpl implements OSInfoService {
    private ZkClient zkClient;
    public OSInfoServiceImpl() {
        System.out.println("初始化连接zk[" + ZK_SERVER + "]=====");
        this.zkClient = new ZkClient(ZK_SERVER);
    }
    @Override
    public void doOptionInfo() {
        while (true) {
            System.out.println("请输出执行指令:【】");
            Scanner scanner = new Scanner(System.in);
            int opts = scanner.nextInt();
            if (LIST_ALL == opts) {
                List<String> childrenList = zkClient.getChildren(ROOT_PATH);
                System.out.println(childrenList);
                for (String childPath : childrenList) {
                    Object nodeData = zkClient.readData(ROOT_PATH +"/"+ childPath);
                    System.out.println("【serverInfo】:" + nodeData);
                }
            } else if(EXIT == opts){
                return;
            }
        }
    }
    public static void main(String[] args) {
        OSInfoServiceImpl osInfoServiceOptioner = new OSInfoServiceImpl();
        osInfoServiceOptioner.doOptionInfo();
    }
}


将插件打包成jar包,然后通过注入javaagent指令后启动:


-javaagent:/Users/idea/IdeaProjects/my-github/zookeeper-framework/zookeeper-agent/target/zk-agent.jar


实践结果:


同时启动三个应用


image.png


然后查看监控中心:


image.png


这里只是一个粗糙版本的管理平台,如果大家有兴趣可以根据这种思路再去加入一些更加详细的监控数据,例如:将控制台查看信息转换为通过漂亮美观的web界面来渲染,加入节点的监听功能,动态监听服务的下线和上线等等。

相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
1月前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
120 6
|
28天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
67 14
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
1月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
1月前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
1月前
|
Java 编译器 程序员
Java面试高频题:用最优解法算出2乘以8!
本文探讨了面试中一个看似简单的数学问题——如何高效计算2×8。从直接使用乘法、位运算优化、编译器优化、加法实现到大整数场景下的处理,全面解析了不同方法的原理和适用场景,帮助读者深入理解计算效率优化的重要性。
36 6
|
1月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
70 4
|
1月前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
135 4
|
2月前
|
存储 安全 算法
Java面试题之Java集合面试题 50道(带答案)
这篇文章提供了50道Java集合框架的面试题及其答案,涵盖了集合的基础知识、底层数据结构、不同集合类的特点和用法,以及一些高级主题如并发集合的使用。
125 1
Java面试题之Java集合面试题 50道(带答案)