【分布式-4】zookeeper

news/2024/5/19 22:54:36/文章来源:https://blog.csdn.net/growing_duck/article/details/129717274

一:基本概念

角色:

  • Leader:提供读写
  • follower:提供读操作,参与Leader选举和过半写成功策略。
  • observer: 只提供读,通常情况下,通过client端连接zk集群性能都不错,但是如果client端量很大,增加集群节点(或者有节点挂掉),那么过半策略会因为server增加网络消耗等负担,集群性能会下降。。。Observer的使用,只提供读,那么就不会有差异了。 所以Observer的使用会增加集群性能

session会话

指客户端启动时,与zk服务端建立的tcp连接。

通过这个连接,客户端能够⼼跳检测与服务器保持有效的会话, 也能够向Zookeeper服务器发送请求并接受响应,还能够通过该连接接受来⾃服务器的Watch事件通知

数据节点Znode:存储数据 ,据模型是⼀棵树 ,由斜杠(/)进⾏分割的路径,就是⼀个Znode,例如/app/path1。

  • 临时节点Ephemeral:客户端会话断开后消失
  • 持久节点Persistent:需要主动删除,否则创建后一直存在
  • 顺序节点Sequential:分持久顺序节点和临时顺序节点,在以上两种节点的最后生成一个数字表示顺序

版本

在Znode节点数据中,有个Stat数据结构,记录了三个version

  • version:当前节点版本
  • avesion:当前节点ACL版本-权限控制机制
  • cversion:子节点版本

watcher:

事件监听,注册在节点上。 节点变更时通知相关客户端

zxid-事务id

zk的事务⼀般包括数据节点创建与删除、数据节点内容更新等操作。 在znode中有两部分是数据:业务数据和节点状态信息

  • czxid:当前节点创建的事务id
  • ctime:当前节点创建的时间
  • mzxid:当前节点最后一次被修改的事务id
  • mtime:当前节点最后一次被修改的时间
  • 还有一些版本号,子节点的信息和数量等

授权模式ACL:

授权模式表示客户端操作某个znode节点应该有什么样的权限,可以分三方面理解:

  • 权限模式-Scheme: 用哪一种类型的权限来进行限制
    • IP模式:对ip进行控制的方式
    • Digest模式:最常用的的验证方式,用户:密码
    • World模式:最开放的一种模式,一种特殊的Digest,授权对象只有一个anyone,代表登录到服务器的所有客户端都能对该节点执行某种权限
    • Auth模式:使用已经认证过的用户进行认证,那前提肯定是需要有这么一个用户存在。
  • 授权对象-ID: 具体授权的目标
    • 比如IP模式下,对象就是ip;    Digest模式下,就是 用户名:密码;  World模式下,就是固定的 anyone; Auth模式下,就是一个已经认证过的用户。
  • 权限-Permission:具体的权限,增删改查等
    • CREATE(C):数据节点的创建权限,允许授权对象在该数据节点下创建⼦节点。

    • DELETE(D): ⼦节点的删除权限,允许授权对象删除该数据节点的⼦节点。

    • READ(R):数据节点的读取权限,允许授权对象访问该数据节点并读取其数据内容或⼦节点列表等。

    • WRITE(W):数据节点的更新权限,允许授权对象对该数据节点进⾏更新操作。 ·

    • ADMIN(A):数据节点的管理权限,允许授权对象对该数据节点进⾏ ACL 相关的设置操作。

具体设置节点的权限,就是设置Scheme:ID:Permission的形式。 具体示例参照:

Zookeeper 节点权限控制ACL详解_zk acl_渔夫数据库笔记的博客-CSDN博客

二:zk使用

下载安装这里就不说了,zk的操作主要就是操作节点Znode。

命令行操作

连接zk:

./zkcli.sh 连接本地的zookeeper服务器

./zkCli.sh -server ip:port 连接指定的服务器

创建节点:

create [-s][-e] path data acl

其中,-s或-e分别代表顺序或临时节点,若不指定,则创建持久节点;data是存储的数据;acl⽤来进⾏权限控制(可以不指定)。

 如:create -s /zk-test 123

读取节点:

ls path   查看指定节点的所有下一级子节点 

get path   查看指定节点的内容和属性

更新节点:

set path data [version]    更新指定节点的内容 。 version指基于哪个数据版本号更新,一般不传,更新后dataVersion自动+1。  如果指定,就必须指定为最新的版本号,否则更新失败。基于此特性可以做乐观锁。

删除节点:

delete path [version]   删除指定节点。 同上可以指定版本号删除,如果版本号与最新的不匹配则删除失败,防止删除时别人已更新过数据。

如果节点存在子节点,则删除失败,需要先删除子节点。   也可以使用递归删除命令 deleteall path

客户端操作

java中使用客户端,通常有三种:

zookeeper原生api:不好用,有很多问题;

ZkClient客户端: 基于原生api封装,比较好用;

Curator客户端: 也是基于原生api封装,功能比ZkClient更强,对很多应用场景的支持更完善(如分布式锁,分布式队列,leader选举,缓存机制等), 推荐使用。

Curator:

引入依赖包:

<!-- zookeeper -->
<dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.4.10</version>
</dependency><!-- curator-framework -->
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>4.2.0</version>
</dependency>
<!-- curator-recipes -->
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>4.2.0</version>
</dependency>

Curator创建客户端是通过CuratorFrameworkFactory⼯⼚类来实现的,它有两个方法:

public static CuratorFramework newClient(String connectString, RetryPolicy
retryPolicy);public static CuratorFramework newClient(String connectString, int
sessionTimeoutMs, int connectionTimeoutMs, RetryPolicy retryPolicy);

connectString: 服务器ip:端口(多个用逗号分隔)。

retryPolicy: 重试策略,是一个接口,默认提供了以下实现,分别是ExponentialBackoffRetry(基于backoff的重连策略)、RetryNTimes(重连N次策略)、RetryForever(永远重试策略)。当然,用户可以自己实现。 

connectionTimeoutMs:   连接超时时间,默认15s(建立连接的最大时间,超过后连接建立失败)。

sessionTimeoutMs: 会话超时时间,默认60s(连接建立后会有心跳机制检查会话是否正常)

启动: 

RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181",
5000,1000,retryPolicy);
client.start();

创建节点:

    private static void createNode(String nodePath, String data) throws Exception { if (StringUtils.isEmpty(nodePath)) { System.out.println("节点【" + nodePath + "】不能为空");return;}//1、对节点是否存在进行判断,否则会报错:【NodeExistsException: KeeperErrorCode = NodeExists for /root】Stat exists = client.checkExists().forPath(nodePath);if (null != exists) { System.out.println("节点【" + nodePath + "】已存在,不能新增");return;} else { System.out.println(StringUtils.join("节点【", nodePath, "】不存在,可以新增节点!"));}//2、创建节点, curator客户端开发提供了Fluent风格的API,是一种流式编码方式,可以不断地点.点.调用api方法//创建永久节点(默认就是持久化的)client.create().forPath(nodePath);//3、也可以手动指定节点的类型为持久化的client.create().withMode(CreateMode.PERSISTENT).forPath(nodePath);//4、如果父节点不存在,创建当前节点的父节点(可以一次性创建多级节点)String node = client.create().creatingParentsIfNeeded().forPath(nodePath);System.out.println(node);//创建节点,并为当前节点赋值内容if (StringUtils.isNotBlank(data)) { //5、创建永久节点,并为当前节点赋值内容client.create().forPath(nodePath, data.getBytes());//6、创建永久有序节点client.create().withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath(nodePath, data.getBytes());//7、创建临时节点client.create().withMode(CreateMode.EPHEMERAL).forPath(nodePath, data.getBytes());}//8、创建临时有序节点client.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(nodePath, data.getBytes());}

更新节点: 

private static void updateNode(String nodePath, String data) throws Exception { //更新节点Stat stat = client.setData().forPath(nodePath, data.getBytes());//指定版本号,更新节点,更新的时候如果指定数据版本的话,那么需要和zookeeper中当前数据的版本要一致,-1表示匹配任何版本//Stat stat = client.setData().withVersion(-1).forPath(nodePath, data.getBytes());System.out.println(stat);//异步设置某个节点数据Stat stat1 = client.setData().inBackground().forPath(nodePath, data.getBytes());System.out.println(stat1.toString());}

获取节点内容:

private static void getNode(String nodePath) throws Exception { //获取某个节点数据byte[] bytes = client.getData().forPath(nodePath);System.out.println(StringUtils.join("节点:【", nodePath, "】,数据:", new String(bytes)));//读取zookeeper的数据,并放到Stat中Stat stat1 = new Stat();byte[] bytes2 = client.getData().storingStatIn(stat1).forPath(nodePath);System.out.println(StringUtils.join("节点:【", nodePath, "】,数据:", new String(bytes2)));System.out.println(stat1);}

查询子节点信息:

private static void getNode(String nodePath) throws Exception { //获取某个节点的所有子节点List<String> stringList = client.getChildren().forPath(nodePath);if (CollectionUtils.isEmpty(stringList)) { return;}//遍历节点stringList.forEach(System.out::println);}

删除节点:

private static void deleteNode(String nodePath) throws Exception { //删除节点//client.delete().forPath(nodePath);//删除节点,即使出现网络故障,zookeeper也可以保证删除该节点//client.delete().guaranteed().forPath(nodePath);//级联删除节点(如果当前节点有子节点,子节点也可以一同删除)client.delete().deletingChildrenIfNeeded().forPath(nodePath);}

三: 应用场景

发布订阅:

zk有push 和 pull两种模式,也就是推 拉。 推:客户端可以在zk某个节点注册监听,有变更时通过wacther事件通知;  拉: 客户端主动去获取节点上的信息。

通常也可以将这两种结合使用,比如把一些信息配置到zk上(如mysql配置信息),客户端启动时主动拉取一次。 当有变更时通过wacther通知,客户端再主动拉取一次。  另外,对于一些不是所有客户端都需要关注的信息,收到通知后,感兴趣的客户端才会去拉取。

命名服务:

在分布式系统中,被命名的实体可以是集群中的机器、提供的服务地址、远程对象等。 比如在dubbo中,就是将接口全路径作为名称注册到zk上,通过调用的接口名就能从zk上找到服务的具体ip地址等(因为有多台服务,通常是一个地址列表),最终通过Netty调用到服务上去。

所以,在分布式系统中,命名要求全局唯一,也就是一个分布式id的概念。可以使用自定义的业务类型+顺序节点的方式:

master选举: 

master说白了,也就相当于要从集群中选出一个节点来做特别的事情。  一些办法是 集群所有节点都往数据库(或redis)插数据,类似分布式锁,成功的节点就作为master。   但是如果master宕机或是异常,集群没法收到通知重新选举。   zk有注册监听的功能,能主动通知状态; 也有分布式锁的功能(所有节点往zk创建同一个节点,只有一个能成功),更适合做此类场景。

分布式锁:

排他锁:

  • 在指定节点/lock下创建临时节点,只有一个节点能成功。  其他客户端需要对/lock做监听等待锁释放。释放也就是客户端删除临时节点,并通过watcher通知。

共享锁:

        共享锁又称读锁,A操作对某个对象上了锁后,其他读操作(B,C)可以执行,但写操作(E,F)必须等对象释放锁后才能执行。zk如何实现呢?

  • 各个客户端执行操作时,在指定节点/lock下创建临时顺序节点,如R-00000001,R-00000002…代表读,W-00000001,W-00000002…代表写。读写交替,就如:R-00000001,W-00000002,R-00000003
  • 创建成功后,当前客户端获取/lock下所有子节点,并对当前/lock节点做监听。
  • 如果当前客户端是读操作,要求比自己小的子节点都是读操作,才算获取锁成功(证明在它之前没有数据变更); 如果是写操作,则自己必须为最小子节点,  否则进入等待。
  • 当某个客户端的操作结束后删除对应的临时顺序节点,也就是释放锁(/lock下子节点发生变化),其他客户端会收到监听。 重复以上步骤获取所有子节点进行判断。

羊群效应: 每一次释放锁都会导致zk发送大量的消息给监听的客户端,而收到通知的客户端又会向zk请求所有的子节点列表来判断自己是否是最小,能否执行业务逻辑。 最终,可能只有最小的那个节点能执行,其他的节点又进入等待,如此反复……

羊群效应改善: 客户端不对/lock做监听,获取全部子节点列表后,如果当前是读,对比自己小的最后一个写节点注册监听; 如果是写,对比自己小的最后一个节点做监听。  收到监听的通知后,即可执行业务(不用再反复判断/lock下所有子节点情况)。

当然了,以上虽然有羊群效应的说法,也是这针对于规模大,量大的情况。 这时要尽量把锁粒度最小化。  如果规模本来就很小,按照常规的共享锁最简单。

分布式队列:

在指定节点/queue下创建临时顺序节点,获取/queue下所有子节点,对自己的前一个节点做监听,收到通知后执行业务逻辑,这样就能按顺序实现队列的FIFO。 共享锁比较像,只是不需要R,W来区分读写。

四: 高级知识点

ZAB协议: 此协议有两个特点,消息广播 和 崩溃恢复。 这是zk中特有的 用来保证集群节点数据一致性的方式。  在zk的架构中,是主备模式的集群,一个leader和多个follower。 leader处理所有的写操作,因此能够很好地处理客户端⼤量的并发请求,再将数据变更广播到所有的follower。  另外,leader可能发生故障,所以需要有崩溃恢复的能力。

消息广播:

  • leader接收client的事务并同步到follower,此过程中会为每个follower分配一个队列,将事务依次放入,并为每一个事务生成全局依次递增的事务id-ZXID。 然后通过TCP协议的网络通信同步到follower。
  • Follower收到队列中的事务后,以事务日志形式写入磁盘并返回ACK确认响应,leader收到半数以上响应后,执行commit,并发送commit命令给所有Follower执行commit。 所以这是一个两阶段提交。

崩溃恢复:

ZAB崩溃恢复需要保证两点:

  1. 丢弃掉原leader未commit的事务
  2. 已在原leader上已经commit的事务(肯定已同步到follower),最终被所有服务器提交。

基于前面两点,崩溃回复就是需要重新选举leader,并保证新选举的leader在所有服务器中是拥有最大ZXID那个节点,那么它的事务执行肯定是最全的(如果ZXID一样,机器id-SID最大的成为leader)。

数据同步:完成选举后,在接收client请求前,需要保证过半follower同步了leader的数据。 然后剩下的follower和新加入的follower继续同步,整体开始对外工作

状态: LOOKING-选举阶段    FOLLOWING-follower和leader保持同步状态    LEADING-当前节点是leader

leader选举:

选举策略:指leader崩溃或与半数以上follower失联,则需要重新选举leader。

选举时间:启动时或崩溃时

过程:

  • 将自己机器的最大事务id和机器id作为投票依据,如(2,0),先投给自己
  • 将依据发给集群其他服务器,接收者会检查投递者是否LOOKING状态判断投递者是否有效
  • 对比ZXID和SID选出leader(如果不是自己,将投票依据改为其他机器的),继续投给其他服务器,每次选举后会统计是否过半,如果是则确定最终结果。
  • leader和follower变更自己的状态,并开始进行数据同步。
  • 选举,在集群启动 或 leader崩溃时都会执行。

五:自定义rpc升级:

在前面章节使用Netty自定义了rpc框架,这里加上zk,就可以模拟dubbo的实现。 

服务端:

就是将所有接口的全限定名,注册为zk的临时节点,不同实例的ip:port为子节点。类似于:

/dubbo/com.java.edu.api.UserService/provider/127.0.0.1:8990,
/dubbo/com.java.edu.api.UserService/provider/127.0.0.1:8991

 

客户端:

通过接口全限定名就能达到具体实例的ip:port,然后通过Netty连接访问。

 如上,在接口代理对象的invoke方法中,RpcClient就没有写死服务端的ip和端口了,而是通过负载均衡策略,在服务端的实例列表中选择一个。   服务端的实例列表,就是在zk中获取的/dubbo/com.java.edu.api.UserService/provider下的子节点列表,也就是所有的ip:port。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_274742.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

旅行商问题的粒子群优化

英文标题&#xff1a;Optimization of Particle Swarms for Travelling Salesman Problem摘要&#xff1a;旅行推销员问题是原子种群优化在本研究中的一个应用。我们已经开发了几种新的技术&#xff0c;旨在用粒子群算法解决TSP问题。此外&#xff0c;我们引入了交换操作和交换…

【k8s】k8s部署mariadb数据库

文章目录前言&#xff1a;一、构建mariadb的dockerfile二、docker build打包并上传到harbor仓库三、编写yaml文件四、使用kubectl apply部署到K8s总结前言&#xff1a; MariaDB数据库管理系统是MySQL的一个分支&#xff0c;主要由开源社区在维护&#xff0c;采用GPL授权许可 M…

5.网络爬虫——Xpath解析

网络爬虫——Xpath解析Xpath简介Xpath解析节点选择路径表达式谓语未知节点Xpath实战演示豆果美食实战获取数据源代码前言&#xff1a; &#x1f4dd;​&#x1f4dd;​此专栏文章是专门针对Python零基础爬虫&#xff0c;欢迎免费订阅&#xff01; &#x1f4dd;​&#x1f4dd;…

Python绘制论文曲线图

1、折线图 plt.plot() 常用的一些参数&#xff1a; 颜色&#xff08;color)&#xff1a; ‘c’ 青红&#xff08;cyan&#xff09; ‘r’ 红色&#xff08;red&#xff09; ‘m’ 品红&#xff08;magente&#xff09; ‘g’ 绿色&#xff08;green&#xff09; ‘y’ 黄色…

什么平台制作表单工具效率高?

目前&#xff0c;低代码开发平台在很多行业中得到了应用和推广。如果需要制作表单工具&#xff0c;还依然用传统的表单工具来制作表单&#xff0c;效率不仅得不到提升&#xff0c;而且办公自动化发展步伐也将缓慢发展。那么&#xff0c;借助什么样的平台可以让制作表单工具更高…

OpenHarmony开发一个App,安装到BearPi-HM Micro开发板

一、前言 建议先阅读我们这篇 OpenHarmony 快速上手 BearPi-HM Micro 一个带显示屏的开发板,这里面详细介绍了Micro开发板如何进行源码编译烧录,以及hap包安装的全过程。 本篇是介绍如何开发一个App,然后安装到BearPi-HM Micro开发板上。 有同学会问,这不是有手就行了吗?…

基于YOLOv5的停车位检测系统(清新UI+深度学习+训练数据集)

摘要&#xff1a;基于YOLOv5的停车位检测系统用于露天停车场车位检测&#xff0c;应用深度学习技术检测停车位是否占用&#xff0c;以辅助停车场对车位进行智能化管理。在介绍算法原理的同时&#xff0c;给出Python的实现代码、训练数据集以及PyQt的UI界面。博文提供了完整的Py…

xss-labs靶场分析及绕过

level1源码&#xff1a;图中框示的为注入点&#xff0c;下同payloadname<script>alert(1)</script>注入结果&#xff1a;原理&#xff1a;由于第一关没有做任何的过滤所以直接注入就行level2源码&#xff1a;payload&#xff1a;keyworda"><script>a…

Vue3学习笔记(1.0)

Vue.js是一套构建用户界面的渐进式框架。 Vue只关注视图层&#xff0c;采用自底向上增量开发的设计。 Vue的目标是通过尽可能简单的API实现响应的数据绑定和组合的视图组件。 <div id"hello-world" class"demo"> {{message}} </div><scr…

Spring WebSocket入门实例、源码解读、STOMP客户端

1 Spring WebSocket入门实例 在浏览器和服务器之间,使用WebSocket发送和接收消息。具体会使用到WebSocket子协议STOMP。 1.1 创建Spring Boot项目,添加WebSocket依赖 后端Spring Boot的WebSocket依赖,前端使用Thymeleaf,所以需要webjars依赖。 pom.xml如下: <!-- 后端W…

buu刷题(4)

目录 [HFCTF2020]BabyUpload [XNUCA2019Qualifier]EasyPHP .htaccess包含文件 第一种方法 第二种方法 [GWCTF 2019]你的名字 [EIS 2019]EzPOP [2020 新春红包题]1 如何绕过后缀名呢 方法一 还有一点就是json格式的数据会被执行吗&#xff0c;实践试试 构造一下试试&am…

【kubernetes云原生】k8s标签选择器使用详解

目录 一、标签选择器来源 二、什么是标签选择器 2.1 标签选择器概述 2.2 标签选择器概述属性 三、标签使用场景 四、标签选择器特点 4.1 基本特点 4.2 核心标签选择器 4.3 补充说明 五、标签选择器常用操作命令 5.1 前置准备 5.2 常用操作命令 5.2.1 查看namespac…

git与gitee结合使用,提交代码,文件到远程仓库

git与gitee结合使用&#xff0c;如何提交文件到远程仓库&#xff0c;以及如何克隆仓库的内容到本地1.安装git1.1 git 安装以后&#xff0c;cmd git --version 说 git 不存在&#xff0c;问题的解决2.配置ssh公钥和创建配置gitee仓库2.1 配置ssh公钥2.2 创建并配置gitee仓库3. 小…

蓝桥杯训练day3

day31.递推&#xff08;1&#xff09;3777. 砖块&#xff08;2&#xff09;95. 费解的开关&#xff08;3&#xff09;1208. 翻硬币2.递归&#xff08;1&#xff09;1497. 树的遍历&#xff08;2&#xff09;97. 约数之和1.递推 &#xff08;1&#xff09;3777. 砖块 思路&…

纯干货:分享一些跨境电商客服经常会用到的话术(含中英文)

纯干货&#xff1a;分享一些跨境电商客服经常会用到的话术&#xff08;含中英文&#xff09;跨境电商同国内的电商平台一样&#xff0c;是将国内的产品卖到国外去赚取信息差&#xff0c;只不过客服面对的是国外的客户&#xff0c;仍旧需要为客户解决一系列服务问题。让前来咨询…

go_admin开源项目笔记

git clone 到本地 然后进入到 go-admin 根目录 执行 &#xff1a;go build 报错如下&#xff1a; 拿第一个详细解读&#xff1a; common\middleware\sentinel.go:4:2: missing go.sum entry for module providing package github.com/alibaba/sentinel-golang/core/system (im…

41.解构赋值

目录 1 数组解构 1.1 基本用法 1.2 交换值 1.3 数组排序 1.4 多解构多余的会变为undefined 1.5 少的就按顺序来 1.6 解构其余的值 1.7 默认解构值 1.8 跳过一些值 1.9 支持多维数组 2 对象解构 2.1 基本用法 2.2 数组套对象 2.3 多级对象 2.4 数…

ideal导入Spring源码详解

前言 踩过很多坑&#xff0c;参考过很多博客&#xff0c;在不懈的坚持下终于迈进了spring源码的门槛 1、环境配置 本博客使用的是 ideal2020.3gradle-6.4.1spring-framework-5.1.xjdk8 1.1安装和配置gradle 1.1.1下载gradle 下载链接 我使用的是gradle-6.4.1-bin.zip这个…

win下pytorch安装—cuda11.7 + cudnn8.4 + pytorch1.13 + tensorRT

安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch安装四、tensorRT8.X安装写在前面 博主这里装的是cuda11.7&#xff0c;最后一步tensorRT运行的时候有个pycuda的安装&#xff0c;它的最新版本只支持到cuda11.6&#xff0c;所以博主最后是又把cuda…

【UML】项目开发流程

以下模型是一个项目从启动到最终部署&#xff0c;逐步细化&#xff08;精化&#xff09;、实现的过程 1、业务用例模型 业务用例模型在项目启动阶段&#xff0c;使用业务用例模型来获取需求&#xff0c;是为了真是业务建立模型&#xff0c;为了和客户达成共识&#xff0c;暂不…