后台架构的演变
以前都是集中式系统架构,随着业务量的爆发,大型单机部署会带来一系列问题:系统大而复杂、发生单点故障引起雪崩、扩展性差等等,分布式系统走上舞台
分布式系统的问题
- 多节点之间的数据一致性如何保证?
- 在并发场景下如何保证任务只被执行一次?
- 一个节点挂掉不能提供服务时如何被集群知晓并由其他节点接替任务?
- 共享资源的安全性和互斥性如何保证?
以上列举了分布式系统中面临的一些挑战,需要一个协调机制来解决分布式集群中的问题,使得开发者更专注于应用本身的逻辑而不是关注分布式系统处理。
分布式协调组件
为解决分布式系统中面临的这些问题,诞生了一些开源组件,这些组件可以在分布式环境下,保证分布式系统的数据一致性和容错性等
组件 | 设计初衷 | 架构 | 数据模型 | CAP | 应用场景 |
---|---|---|---|---|---|
zookeeper | 最初为Hadoop生态系统设计,作为分布式应用程序的协调服务 | 采用主从架构,有一个领导者(Leader)和多个跟随者(Follower),Leader负责事务处理和同步,Follower负责数据读取和选举新Leader。 | 提供类似文件系统的层次化数据模型 | CP模型,遵循ZAB协议 | 广泛应用于配置管理、分布式锁、集群管理、服务发现等 |
etcd | 专为Kubernetes设计的分布式键值存储系统,后来成为云原生生态的重要组件 | 使用Raft协议保证一致性 | 基于键值对存储,支持TTL(Time-To-Live)特性 | CP模型,强调数据的一致性 | 常用于服务发现、配置共享、分布式锁等场景,特别是与Kubernetes紧密集成 |
consul | 提供服务发现与配置管理功能,侧重于易用性和多功能性 | 使用Raft协议保证一致性 | 包含键值存储、服务发现、健康检查等功能,支持丰富的HTTP API | CP模型,强调数据的一致性 | 适合需要服务发现、健康检查、KV存储的微服务架构,尤其是跨数据中心的部署 |
eureka | 由Netflix开发,专为云环境中的服务发现而设计 | 采用AP(可用性优先)模型,更注重高可用性和自我恢复能力,支持客户端缓存和自我保护机制 | 主要用于服务注册和服务发现,不强调数据的一致性,而是通过客户端的重试和容错机制保证服务可用 | AP模型 | 构建高可用的微服务架构,尤其是对服务发现的实时性和容错性要求较高的场景 |
nacos | 阿里开源项目,旨在提供更全面的微服务基础设施支持,包括服务发现、配置管理、服务管理等 | 支持CP和AP两种模式,用户可根据场景选择一致性模型 | 提供丰富的数据结构和配置管理能力,支持服务注册、配置推送、健康检查等 | 默认AP模型,在1.0版本后支持CP模型。 | 适合微服务架构中的服务发现、配置管理、动态配置推送等,特别适合需要同时管理服务和配置的场景 |
一致性问题
CAP理论
设计一个分布式系统必定会遇到一个问题—— 因为分区容忍性(partition tolerance)的存在,就必定要求我们需要在系统可用性(availability)和数据一致性(consistency)中做出权衡 。这就是著名的 CAP 定理。
一个合格的分布式系统,保证P是最基本的
AP
放弃一致性,保证可用性和分区容错性,这种策略适用于对数据实时性要求比较高的系统,如社交网络等。
BASE 理论是对AP 的一个扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。
CP
放弃可用性,追求一致性和分区容错性,zookeeper 其实就是追求的强一致,又比如跨行转账,一次转账请求要等待双方银行系统都完成整个事务才算完成。
那一致性问题,是如何解决的?
分布式一致性问题的解决思路有两种,一种是分布式事务,一种是尽量通过业务流程避免分布式事务。
业务规避要根据业务来具体分析,讲一讲分布式事务
分布式事务
分布式事务实现方案从类型上分为刚性事务和柔性事务。
- 刚性事务:通常无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务(XA协议(2PC、JTA、JTS)、3PC);
- 柔性事务:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务(TCC/FMT、Saga(状态机模式,Aop模式)、本地事务消息、消息事务(半消息)、最多努力通知型事务);
一致性协议和算法
比如 2PC(两阶段提交),3PC(三阶段提交),Paxos 算法等等
所有的一致性协议和算法的必要前提就是安全可靠的消息通道
2PC(二阶段提交)
https://zhuanlan.zhihu.com/p/417294966
https://zhuanlan.zhihu.com/p/368280834
阶段一:
- 事务协调者向各参与者发送 prepare的请求
- 事务参与者接手到请求后,执行事务内容,但不提交
- 失败返回 no,事务协调者直接返回给客户端(事务发起者),事务终止
- 成功后返回 yes,继续
阶段二:
- 事务协调者向各参与者发送 commit的请求
- 事务参与者接手到请求后,提交事务
存在的问题:
- **单点故障:**协调者挂了之后,系统就崩了
- **数据不一致:**在发送commit请求的时候,如果部分参与者因为网络、宕机等原因没有接收到请求,会造成数据不一致
- **阻塞问题:**在参与者执行事务内容不提交之后,要一直等待协调者commit消息,不然提交不了事务,占用着资源不释放,会造成堵塞
3PC (三阶段提交)
三阶段提交,主要优化了几个点
- 在协调节点和事务参与者都引入了超时机制。
- 第一阶段的 prepare 阶段分成了两步,canCommi 和 preCommit。
阶段一:canCommit
协调者向所有参与者发送 CanCommit 请求,参与者收到请求后会判断能否能执行事务,如果可以则返回 yes 响应并进入预备状态,否则返回 NO 。
阶段二:preCommit
协调者将向所有参与者发送 PreCommit 预提交请求,参与者收到预提交请求后,会进行事务的执行操作
阶段二:commit
在commit阶段参与者如未收到协调者发送的提交事务的请求,它会在一定时间内进行事务的提交
paxos算法
ZAB协议
ZAB(Zookeeper Atomic Broadcast) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。ZAB 协议包含两种基本模式,分别是:
原子广播
一旦ZooKeeper集群完成了Leader选举并且过半数的Follower与Leader完成了状态同步,集群就会进入消息广播模式。在此模式下,客户端的所有事务请求都会被发送给Leader,Leader负责对事务进行排序、分配ZXID,并通过两阶段提交的方式广播给所有的Follower。Follower接收到事务后,会先进行预处理(投票同意),一旦Leader收到大多数Follower的同意,就会提交事务,并向客户端返回成功响应,同时告知Follower可以提交事务。这种模式保证了事务的顺序性和一致性,以及高可用性。
崩溃恢复
当系统启动或者leader服务器出现故障等现象时,进入故障恢复模式。将会开启新的一轮选举,选举产生的leader会与过半的follower进行同步,使数据一致。
zookeeper是什么
名字的由来
Zookeeper 最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。
所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。
关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的Pig项目),雅虎的工程师希望给这个项目也取一个动物的名字。
时任研究院的首席科学家 Raghu Ramakrishnan 开玩笑地说:“在这样下去,我们这儿就变成动物园了!”
此话一出,大家纷纷表示就叫动物园管理员吧,因为各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了。
而 Zookeeper 正好要用来进行分布式环境的协调,于是,Zookeeper 的名字也就由此诞生了。
使用情况
zk 是一个开源的分布式协调服务,提供了高可用、高性能、稳定的分布式数据一致性解决方案
zk 将数据保存在内存中,在读多于写的场景中,性能很高,很多顶级的开源项目都用到了 zk,比如:
- Kafka : zk 主要为 Kafka 提供 Broker 和 Topic 的注册以及多个 Partition 的负载均衡等功能。不过,在 Kafka 2.8 之后,引入了基于 Raft 协议的 KRaft 模式,不再依赖 zk,大大简化了 Kafka 的架构。
- Hbase : zk 为 Hbase 提供确保整个集群只有一个 Master 以及保存和提供 regionserver 状态信息(是否在线)等功能。
- Hadoop : zk 为 Namenode 提供高可用支持。
- dubbo : 默认将zookeeper作为注册中心
zk 特点
- 顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
- 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
- 单一系统映像: 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
- 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
- 实时性: 一旦数据发生变更,其他节点会实时感知到。每个客户端的系统视图都是最新的。
- 集群部署:3~5 台(最好奇数台)机器就可以组成一个集群,每台机器都在内存保存了 ZooKeeper 的全部数据,机器之间互相通信同步数据,客户端连接任何一台机器都可以。
- **高可用:**如果某台机器宕机,会保证数据不丢失。集群中挂掉不超过一半的机器,都能保证集群可用。比如 3 台机器可以挂 1 台,5 台机器可以挂 2 台。
核心概念
Data model(数据模型)
ZooKeeper 数据模型采用层次化的多叉树形结构,每个节点上都可以存储数据,这些数据可以是数字、字符串或者是二进制序列。
与文件系统不同,ZooKeeper 数据保存在内存中,这意味着 ZooKeeper 可以实现高吞吐量和低延迟。
强调一下:ZooKeeper 主要是用来协调服务的,而不是用来存储业务数据的,所以不要放比较大的数据在 znode 上,ZooKeeper 给出的每个节点的数据大小上限是 1M 。
znode(数据节点)
每个数据节点在 ZooKeeper 中被称为 znode,老版本都是4种类型,从3.6.2版本开始,有7种类型
类型
- 持久(PERSISTENT)节点:一旦创建就一直存在,即使 ZooKeeper 集群宕机也还在,直到将其删除。
- 临时(EPHEMERAL)节点:临时节点的生命周期是与 客户端会话(session) 绑定的,会话消失则节点消失。并且,临时节点只能做叶子节点 ,不能创建子节点。
- 持久顺序(PERSISTENT_SEQUENTIAL)节点:除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 /node1/app0000000001、/node1/app0000000002 。
- 临时顺序(EPHEMERAL_SEQUENTIAL)节点:除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性
- **容器(CONTAINER)节点:**服务端会定期扫描这些节点,当该节点下面没有子节点时(或其他条件时)服务端会自动删除节点
- **持久TTL (PERSISTENT_WITH_TTL)节点:**当该节点下面没有子节点的话,超过了 TTL 指定时间后就会被自动删除
- **持久顺序TTL (PERSISTENT_WITH_TTL)节点:**当该节点下面没有子节点的话,超过了 TTL 指定时间后就会被自动删除
组成
- stat:状态信息
- data:节点存放的数据的具体内容
如下所示,通过 get 命令来获 根目录下的 dubbo 节点的内容
[zk: 127.0.0.1:2181(CONNECTED) 6] get /dubbo
# 该数据节点关联的数据内容为空
null
# 下面是该数据节点的一些状态信息,其实就是 Stat 对象的格式化输出
cZxid = 0x2
ctime = Tue Nov 27 11:05:34 CST 2018
mZxid = 0x2
mtime = Tue Nov 27 11:05:34 CST 2018
pZxid = 0x3
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1
State字段解释
znode 状态信息 | 解释 |
---|---|
cZxid | create ZXID,节点创建时的事务 id |
ctime | create time,节点的创建时间 |
mZxid | modified ZXID,最近一次更新的事务 id |
mtime | modified time, 最近一次更新时间 |
pZxid | 子节点列表最近一次更新的事务 id,只有子节点列表变更才会更新 pZxid,子节点内容变更不会更新 |
cversion | 子节点版本号,子节点每次更新时值增加 1 |
dataVersion | 数据节点内容版本号,节点创建时为 0,每更新一次节点内容(不管内容有无变化)该版本号的值增加 1 |
aclVersion | 节点的 ACL 版本号,表示该节点 ACL 信息变更次数 |
ephemeralOwner | 创建该临时节点的会话的 sessionId;如果当前节点为持久节点,则 ephemeralOwner=0 |
dataLength | 数据节点内容长度 |
numChildren | 当前节点的子节点个数 |
版本(version)
在前面我们已经提到,对应于每个 znode,zk都会为其维护一个叫作 Stat 的数据结构,Stat 中记录了这个 znode 的三个相关的版本:
- dataVersion:当前 znode 节点的版本号
- cversion:当前 znode 子节点的版本
- aclVersion:当前 znode 的 ACL 的版本。
ACL(权限控制)
特点
- 基于ZNode的权限控制:zk的权限控制粒度到ZNode,这意味着可以为每一个节点独立设置访问控制策略。
- 权限累积:一个ZNode可以有多个ACL条目,对一个用户的最终权限是所有匹配ACL条目的权限的并集。
- 子节点不继承ACL:子节点不会自动继承父节点的ACL设置,父节点和子节点权限互不影响
- 强一致性:zk的ACL变更会像其他数据一样,通过其一致性协议(如ZAB)确保所有副本间的一致性。
组成
scheme(权限模式)
- world:默认方式,所有用户都可无条件访问。
- auth :不使用任何 id,代表任何已认证的用户。
- digest :这也是业务系统中最常用的。用 username:password 字符串来产生一个MD5串,然后该串被用来作为ACL ID。认证是通过明文发送_username:password_ 来进行的,当用在ACL时,表达式为_username:base64_ ,base64是password的SHA1摘要的编码。
- ip : 对指定 ip 进行限制
- sasl:支持SASL认证框架,允许更复杂的认证机制,如Kerberos。
- x509:基于X.509证书的认证,适合需要使用SSL/TLS进行通信的场景
ID(授权对象)
与scheme模式相对应的认证信息
scheme | ID |
---|---|
world | 固定:anyone |
auth | auth:username(要先通过addauth给username授权) |
digest | 基于用户名和密码的认证,使用SHA-1哈希和Base64编码 格式username:Base64(SHA-1(password)) |
ip | ip地址或ip段,比如192.168.0.0/16 |
Permission(权限)
- CREATE : 能创建子节点
- READ:能获取节点数据和列出其子节点
- WRITE : 能设置/更新节点数据
- DELETE : 能删除子节点
- ADMIN : 能设置节点 ACL 的权限
其中CREATE 和 DELETE 这两种权限都是针对 子节点 的权限控制。
实现
的格式为 scheme:ID:Permission
getAcl getAcl
setAcl setAcl
addauth addauth 添加认证用户
# 设置ip地址的方式
setAcl /zk01 ip:192.168.0.0/16:cdwra
# 设置用户名的方式
setAcl /t6 digest:user:6DY5WhzOfGsWQ1XFuIyzxkpwdPo=:crwda
加密通信
ZooKeeper 支持加密通信,以增强数据在传输过程中的安全性,通常采用SSL/TLS协议
Watcher(事件监听器)
Watcher(事件监听器),zk 允许用户在指定节点上注册一些 Watcher,比如可以监听节点增删改等事件,zk 服务端会将事件通知到感兴趣的客户端上去,该机制是 zk 实现分布式协调服务的重要特性。
特点
- 一次性触发(One-time trigger):每个Watcher仅触发一次,当所监视的事件发生时,客户端会接收到通知,之后该Watcher会被自动删除。如果需要持续监控,客户端必须在接收到事件后重新注册Watcher。
- 轻量级:Watcher通知非常简单,只包含事件的基本信息,如事件类型、路径等,不包含事件的具体内容,从而保持高效。
- 异步:Watcher通知是异步发送的,不会阻塞客户端的其他操作。这意味着客户端可以在接收到通知的同时继续执行其他任务。
工作流程
- 注册Watcher:客户端在执行读操作(如getData, exists)或创建、删除、检查子节点列表时,可以同时向ZooKeeper服务器注册一个Watcher。注册时,客户端需指定感兴趣的事件类型。
- 事件触发:当ZooKeeper检测到与Watcher匹配的事件发生时(例如,节点数据变更、节点被删除等),它会将事件封装成一个通知。
- 通知传递:ZooKeeper服务器通过TCP连接将事件通知发送给客户端。注意,通知是异步发送的,且在客户端-服务器连接断开期间不会累积,因此断线期间的事件可能会丢失。
- 客户端处理:客户端接收到通知后,会调用之前注册的Watcher回调函数来处理事件。回调函数执行完毕后,该Watcher即被移除。
- 重新注册:为了持续监听事件,客户端通常会在回调函数中重新注册Watcher,形成一个循环监听的过程。
会话(Session)
会话建立
- 连接初始化:当客户端首次连接到 ZooKeeper 集群时,它会选择一个服务器建立 TCP 连接,并发送一个连接请求(ConnectRequest)。此请求中包含了客户端期望的会话超时时间(SessionTimeout)以及其他配置信息。
- 会话创建:服务器收到请求后,会分配一个唯一的会话ID给客户端,并开始维护这个会话状态。客户端收到确认后,会话正式建立。会话ID和密码(实际上是一个随机数)一起用于后续的认证和会话恢复。
会话保持
- 心跳机制:为了保持会话的有效性,客户端需要周期性地向服务器发送心跳包(心跳检查)。心跳间隔通常远小于会话超时时间的一半,以确保网络延迟或短暂中断不会导致会话失效。
- 会话超时:如果在客户端指定的会话超时时间内没有收到心跳,服务器会认为客户端已断开连接,并标记会话为过期。此时,与该会话相关的临时节点会被自动删除。
会话恢复
- 重连机制:客户端如果因网络问题或服务器故障断开连接,会自动尝试重新连接到集群中的其他服务器。重连成功后,客户端会携带之前的会话ID和密码尝试恢复会话。
- 会话验证:服务器接收到客户端的重连请求后,会验证会话ID和密码。如果验证成功,且在会话超时时间内,服务器会恢复原有的会话状态,包括临时节点等信息,从而实现无缝的会话迁移。
zk架构
运行模式
- 单机模式: 单台机器部署单个zk
- 集群模式: 一般由3台以上的机器组成,每台机器部署一个zk,组成集群
- 伪集群模式: 一台机器上部署3个或以上的zk,形成伪集群
这里主要讲一讲集群模式
集群模式
一个 zk 集群通常由一组机器组成,一般3~5台机器就可以组成一个集群
节点角色
如上图,在 zk 集群中,有 Leader、Follower 和 Observer 三种类型的角色
- Leader:
- zk 集群的主节点,负责处理客户端的事务请求+读请求
- 同步数据到其他的Follower节点
- Follower:
- zk集群中的从节点,负责接收来自Leader节点的同步请求,并将Leader节点的数据复制到自己的本地副本中
- Follower节点可以处理客户端的读请求
- Follower节点也参与Leader选举过程
- Observer:
- Observer的行为在大多数情况下与Follower完全一致,只是不参与选举
- Observer 角色的目的是为了扩展系统,提高读取速度
Client 是 zk 的客户端,请求发起方
节点状态
LOOKING: 节点处于选举状态,正在选举Leader
FOLLOWING: 随从状态,同步 leader 状态,参与投票
OBSERVING: 观察状态,同步 leader 状态,不参与投票
LEADING: 领导者状态
选举过程
集群初始化选举
假设集群当中有 5 个节点,id 分别为 1 到 5,这里 5 个节点按照 id 从小到大顺序启动。
- 首先 server1 启动,发起一次选举,每个节点都有投票权,并且默认都会投给自己。此时 server1 有1 票,但还不够半数以上(3 票),选举无法完成,于是 server1 将状态保持为 LOOKING;
- 然后 server2 启动,再发起一次选举,重新投票。server1 和 server2 仍会把票投给自己,然后再交换选票信息。由于 server1 发现 server2 的 id 比自己大,于是会将自己的票改投给 server2。此时 server1 有 0票,server2 有 2 票,但仍然没有哪个节点拥有超过半数的票,选举无法完成,server1 和 server2 状态都保持为 LOOKING;
- 接下来 server3 启动,再发起一次选举,相信整个过程不需要解释了。老规矩还是先投给自己,再交换选票信息,然后 server1 和 server2 发现自己的 id 都没有 server3 大,于是都会将票改投给 server3。此时 server1 和 server2 的票数为 0,server3 的票数为 3,由于 server3 的票数已超过半数,所以成功当选为 Leader,状态变为 LEADING。而 server1、server2 则成为 Follower,状态改为 FOLLOWING。
- 所以 5个节点,启动 3个之后就能选择出 Leader。然后 server4 又启动了,于是也发起一次选举,并把票投给自己。但 server1、server2、server3 已经不是 LOOKING 状态,所以它们不会更改自己的选票信息,最终结果 server3 仍有 3票,server4 只有 1 11 票。少数服从多数,于是会再将自己的选票交给 server3,成为 Follower,状态改为 FOLLOWING。
- 同理,最后 server5 启动,结果就是 server3 有 4 票,自己只有 1 票。少数服从多数,于是将自己的选票交给 server3,成为 Follower。
运行时选举
leader 暂时断连
server5 认为 server3 挂了之后,便会发起 Leader 选举,呼吁其它追随者进行投票。但是其它追随者发现领导者并没有挂,于是会拒绝 server5 的选举申请,并告知它当前已存在的领导者信息。对于 server5 而言,只需要和已存在的领导者重新建立连接,并进行数据同步即可
leader 真挂了
要重新选举 Leader,而选举规则如下:
- 先比较节点之间的 epoch,epoch 大的直接当选。
- epoch 相同,再比较 zxid,zxid 大的当选。
- epoch 和 zxid 都相同,则比较 sid,sid 大的当选。
持久化机制
autopurge.snapRetainCount=3 # 保留最近的3个快照
dataDir=/var/zk/data # 指定数据快照和事务日志的,所有快照文件都存储在此目录下
autopurge.purgeInterval=1 # 每小时检查一次并清理过期快照和日志
zookeeper.snapSizeLimitInKb=512000 # 设置单个事务日志文件最大为500MB
zk 的持久化机制通过在本地文件系统上存储数据快照和事务日志来实现:
- 数据快照(Snapshots):
- zk 定期将内存中的数据数据快照写入到磁盘,快照文件包含了某一时刻zk服务器内存中数据树的全量状态
- 事务日志(Transaction Logs):
- 每当有事务操作(如数据的增删改)发生时,zk都会将这些操作记录到事务日志中。事务日志采用追加写的方式,保证了写入的高效和原子性。
- 这些日志文件记录了从上一次快照以来的所有状态变更,在zk启动时,它会根据最新的快照和随后的事务日志来重建内存中的数据树,从而恢复到崩溃前的最新状态。
- ZKDatabase:
- zk内部有一个称为ZKDatabase的组件,负责管理这些快照和事务日志,以及它们与内存数据模型之间的同步
- 持久化路径:
- dataDir=/var/zk/data
- 同步与恢复:
- 在ZooKeeper集群中,主节点(Leader)负责接收并顺序执行所有事务请求,然后将这些事务操作广播给所有跟随者(Follower)。
- 跟随者接收到事务日志后,也会执行相同的持久化过程,确保所有节点的数据一致性。
- 当新的服务器加入集群或现有服务器重启时,会从其他节点获取最新的快照和必要的事务日志,完成数据的恢复。
加密通信
zk客户端
zkCli
在zk的bin目录下,有一个自带的zkCli脚本
- 进入客户端 ./zkCli.sh
- help 查看所有命令和使用方式
ZooKeeper
org.apache.zookeeper.ZooKeeper
@SpringBootTest
public class ZookeeperClientTest {
@Autowired
private ZooKeeper zooKeeper;
@Test
void createNodeTest() {
String path = "/zk-01";
byte[] data = "Hello ZooKeeper".getBytes();
// 设置访问控制列表
List<ACL> acl = new ArrayList<>();
Id id = new Id("world", "anyone");
acl.add(new ACL(ZooDefs.Perms.ALL, id));
// 设置创建模式为持久节点
CreateMode createMode = CreateMode.PERSISTENT;
// 设置异步回调接口
AsyncCallback.Create2Callback callback = new AsyncCallback.Create2Callback() {
@Override
public void processResult(int i, String s, Object o, String s1, Stat stat) {
System.out.println("节点创建结果:" + i);
System.out.println("节点创建结果:" + s);
System.out.println("节点创建结果:" + o);
System.out.println("节点创建结果:" + s1);
System.out.println("节点创建结果:" + stat);
}
};
// 设置上下文对象
Object context = new String("上下文对象");
// 设置超时时间
long timeout = 500000;
// 调用create方法
zooKeeper.create(path, data, acl, createMode, callback, context);
}
}
curator
https://zhuanlan.zhihu.com/p/611161550
zk应用场景
数据发布与订阅(配置管理)
ZooKeeper 可以用来集中管理分布式系统的配置信息,允许应用动态获取配置更新,实现配置的统一管理和实时推送。
命名服务
为分布式系统中的服务、任务或者其他实体提供唯一的名称空间,便于查找和定位。
分布式锁
- 创建锁节点:
- 客户端在ZooKeeper中选择一个特定的路径(例如/locks)作为锁的根节点。当需要获取锁时,每个客户端在该根节点下创建一个临时的、有序的子节点。这个子节点的名字会自动加上一个序号后缀,确保了节点创建的顺序性。
- 确定锁拥有者:
- 所有客户端都会检查自己创建的节点是否是所有子节点中序号最小的一个。如果是,则认为该客户端获得了锁;如果不是,客户端需要等待,直到它前面的节点被删除(意味着持有锁的客户端释放了锁)。
- 监听前一个节点:
- 未获得锁的客户端会在其前一个节点(即序号比它小的那个节点)上设置一个Watcher。当这个节点被删除时(意味着锁被释放),ZooKeeper会触发Watcher通知给等待的客户端。
- 获取锁:
- 收到Watcher通知的客户端会再次检查自己是否成为了最小序号的节点,如果是,则获得锁;如果不是(可能是因为同时有其他客户端也在等待并且它的序号更小),则重复步骤3,继续监听前一个节点。
- 释放锁:
- 当持有锁的客户端完成操作后,它会删除自己创建的临时节点。ZooKeeper会自动触发这个节点之后的节点上的Watcher,从而通知下一个等待的客户端可以尝试获取锁。
Master选举
在分布式系统中,有时需要选举出一个或几个节点作为Master角色,负责协调工作、处理写请求或作出关键决策,以保证系统的整体一致性。例如,在Hadoop HDFS中,NameNode就是一个典型的Master角色,负责管理文件系统的命名空间。
服务发现
实现服务的注册与发现,比如dubbo+zk
负载均衡
通过ZooKeeper管理服务实例列表,可以根据负载情况动态分配请求到不同的服务实例。
- 服务注册:
- 服务提供者在启动时,将自己的服务地址(IP、端口等)作为一个临时节点注册到zk的特定目录下。使用临时节点是因为当服务实例关闭时,zk会自动删除该节点,从而自动从服务列表中移除该实例。
- 通常,服务实例会以服务名称作为父节点,实际地址作为子节点,并且可以使用有序节点来跟踪服务实例的注册顺序,这有助于某些排序的负载均衡策略。
- 服务发现:
- 服务消费者(客户端)在需要调用服务时,首先连接到ZooKeeper并查询特定服务名称下的所有子节点,这些子节点就是当前可用的服务实例列表。
- 客户端可以设置Watcher监听服务列表的变更,当有服务实例加入或离开时,ZooKeeper会实时通知客户端,客户端据此更新本地的服务实例列表。
- 负载均衡策略:
- 客户端根据某种负载均衡算法从服务实例列表中选择一个服务实例进行调用。常用的负载均衡策略有:
- 轮询(Round Robin):依次选择每个服务实例,实现请求的均匀分配。
- 随机选择:从服务列表中随机挑选一个实例。
- 哈希一致性(Consistent Hashing):根据请求的某些特征进行哈希计算,确保相同特征的请求尽可能多地被分配到同一个服务实例上。
- 最小连接数:选择当前连接数最少的服务实例,适用于长连接场景。
- 客户端根据某种负载均衡算法从服务实例列表中选择一个服务实例进行调用。常用的负载均衡策略有:
- 重试与容错:
- 如果客户端选择的服务实例不可达或返回错误,可以实施重试策略,如重新选择服务实例或尝试其他负载均衡策略。
- 动态调整:
- 由于ZooKeeper的实时通知机制,客户端可以实时感知服务实例的增减,从而动态调整负载均衡策略,无需人工干预。
分布式队列
可以基于ZooKeeper实现分布式队列,用于消息传递、任务调度等场景。
分布式Barrier和CountDownLatch
用于控制分布式任务的执行顺序,确保一组相关操作要么全部完成,要么都不开始。
元数据/状态信息存储
存储分布式应用的元数据或状态信息
心跳检测与健康监控
监控集群中各个节点的健康状态,通过心跳机制确保服务的正常运行。
安装zookeeper
这里以为伪集群为例,即本地部署3个zk节点
-
下载 tar.gz
地址:[https://archive.apache.org/dist/zookeeper/](https://archive.apache.org/dist/zookeeper/)
-
复制、改名、解压成3个服务
- 在每个服务的conf目录下新建一个zoo.cfg
配置信息只需要修改clientPort、dataDir、dataLogDir三个就行,其他配置保持一致
clientPort是zk服务的端口号,zk01=2181,zk02=2182,zk03=2183
# 通信心跳时间,zookeeper服务器和客户端心跳时间(单位ms)
tickTime=2000
# 集群中的follower与leader之间初始连接时能容忍的心跳数,为10个ticks,即20秒
initLimit=10
# 集群中的follower与leader之间同步状态时能容忍的心跳数,为5个ticks,即10秒
syncLimit=5
# zk服务端监听客户端连接的端口号
clientPort=2181
# 是否跳过访问控制列表(ACL)的检查,这里设置为跳过
skipACL=yes
# 数据目录,用于存储zk的状态和日志文件
dataDir=/Users/banma-2588/app/zookeeper-cluster/zk01/data/
# 日志目录,用于存储事务日志
dataLogDir=/Users/banma-2588/app/zookeeper-cluster/zk01/datalog
# 集群中的一个服务节点。其中,ip地址为127.0.0.1,该节点的选举端口为2880,与其他节点通信的端口为3880
server.1=127.0.0.1:2880:3880
server.2=127.0.0.1:2881:3881
server.3=127.0.0.1:2882:3882
- 在data目录下新建一个myid文件
里面的内容对应的分别是1、2、3
- 启动zk
- 到每个zk的bin目录下,执行./zkServer.sh start
- 全部执行完毕之后,可以查看集群各节点的状态./zkServer.sh status,有1个leader,2个follower,就成功了
大众项目中zk的使用
dubbo+zk
longclaw的zk
xconfig的zk
生产常见问题
- ZooKeeper启动失败:比如配置错误、端口冲突、权限问题或JVM内存设置不当引起的,这里可以看日志
- 会话超时(SESSIONEXPIRED):客户端与ZooKeeper服务器之间的连接断开,导致操作失败。可能是因为网络不稳定、服务器负载过高或sessionTimeout设置不合理。
- 脑裂(Split Brain):网络分区可能导致集群分裂成多个独立部分,每个部分可能尝试选举自己的Leader,违反了一致性原则。
- 数据不一致:虽然罕见,但不恰当的使用、网络问题或软件bug可能导致数据不一致。例如,如果客户端在会话超时前没有收到操作的确认,可能会尝试重新执行操作,导致重复。
- 性能瓶颈:随着数据量和请求量的增长,可能会遇到性能瓶颈,表现为
- 延迟增加或吞吐量下降
- leader压力过大,频繁崩溃选举
- GC较为频繁
- cpu使用率较高
- 日志膨胀与磁盘空间不足
JVM设置、增加节点、优化ZNode结构等。
- ZooKeeper选举问题:Leader选举过程可能因网络延迟、节点故障或配置不当而出现问题,导致选举失败或耗时过长。
- 权限设置不当:ACL设置错误可能导致未授权访问或拒绝合法访问
- 日志和快照管理:日志文件过大或未定期清理快照可能导致磁盘空间耗尽,影响服务稳定性。
- 版本升级问题:ZooKeeper的版本升级过程中,如果不仔细遵循升级指南,可能导致服务中断或数据兼容性问题
缺点或不适合场景
- 负载均衡:尽管zk可以用于实现复杂的负载均衡策略,但对于简单的负载均衡需求,使用zk可能会增加不必要的复杂性和资源消耗
- 大数据存储:ZooKeeper设计为低延迟、高可用、强一致性的服务,设计用于存储元数据而非大量应用数据
- 高并发写:zk设计用于高并发访问,但其优势主要在于读操作,频繁的写操作会导致性能下降并增加网络和系统的负担
- 对延迟敏感的系统:比如多人在线游戏、股票系统等
拷问
- Zookeeper 是什么?
- ZooKeeper 有哪些应用场景?
- 讲一下ZAB协议
- 请描述一下 Zookeeper 的通知机制是什么?
- zk的通知机制是一种基于事件驱动的模型,允许客户端注册Watcher来监控ZooKeeper树上的数据节点(znode)的变化。当被监控的znode发生变动时,ZooKeeper服务端会触发一个Watcher事件,并将这个事件通知给感兴趣的客户端
- Zookeeper 对节点的 watch 监听通知是永久的吗?
- Zookeeper 集群中有哪些角色?
- Zookeeper 集群中是怎样选举leader的?
- Zookeeper 是如何保证事务的顺序一致性的?
- leader接收到客户端的事务请求时,会生成一个提案和一个全局唯一的zxid,且是有顺序的
- leader会将提案和zxid一起广播给所有follower,Follower在接收到提案后,会将其添加到自己的待处理队列中
- 到提交阶段,leader会发送给follower对应的commit,收到commit之后,follower会将提案从队列中拿出来处理
- ZooKeeper 集群中各服务器之间是怎样通信的?
- 建立连接:每个ZooKeeper节点(包括Leader、Follower和Observer)在启动时,会尝试与集群中的其他所有节点建立TCP连接。这些连接是双向的,用于在集群内部传输消息,包括但不限于选举过程中的投票信息、心跳检测、事务请求和响应、以及状态同步等。
- LearnerHandler:在ZooKeeper中,每个服务器会创建一个或多个称为LearnerHandler的实体(具体数量取决于配置和集群规模),这些实体负责维护与其它服务器之间的网络通信。特别是对于Follower和Observer而言,它们通过LearnerHandler与Leader进行数据同步、请求转发以及处理来自Leader的Proposal提议。
- 心跳机制:为了检测集群中各节点的活性,ZooKeeper使用心跳机制。每个服务器定期向其他服务器发送心跳包,这些心跳包不仅用于检测存活状态,还携带了状态更新信息,帮助维护集群的一致性视图。
- ZAB协议:ZooKeeper使用的ZooKeeper Atomic Broadcast (ZAB)协议是其核心通信协议,它确保了事务的顺序性和原子性。在Leader选举和数据同步过程中,ZAB协议指导着消息的广播和确认过程,包括事务提案的提交和应用。
- 选举过程:在Leader选举过程中,所有服务器通过这些TCP连接交换选举相关的消息(例如,选举投票),使用一种基于基本Paxos算法变体的协议来达成共识,选出新的Leader。
- 数据复制:Leader负责将事务日志通过TCP连接复制到所有的Follower和Observer,确保集群中每个节点的数据副本是一致的。Follower和Observer通过心跳机制与Leader保持同步,确认数据的接收和应用。
- 容错与重传:ZooKeeper的通信机制内置了容错能力,例如,如果某个消息在一定时间内没有得到响应,发送方会重新发送该消息,确保消息的可靠传递。
- ZooKeeper 分布式锁怎么实现的?
- 为什么要用临时、有序的节点?
- 了解Zookeeper的系统架构吗?
- 你熟悉Zookeeper节点ZNode和相关属性吗?
- 为什么Zookeeper集群的数目,一般为奇数个?
zk 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 zk 服务器个数大于总数的1/2整个zk才依然可用,假如集群中有 2n 台 ZooKeeper 服务器,那么也就是剩下的服务数必须大于 n,2n 和 2n-1 的容忍度(允许宕机的个数)是一样的,都是 n-1,所以偶数没必要
- 知道Zookeeper监听器的原理吗?
- 说说 Zookeeper 的 CAP 问题上做的取舍?
- Zookeeper脑裂是什么原因导致的?
- leader假死:由于心跳超时(网络原因导致的)认为leader死了,但其实leader还存活着。
- 脑裂:假死会发起新的leader选举,选举出一个新的leader,但旧的leader网络又通了,导致出现了两个leader ,有的客户端连接到老的leader,而有的客户端则连接到新的leader。
- Zookeeper 是如何解决脑裂问题的?
https://www.cnblogs.com/kevingrace/p/12433503.html
zookeeper除了可以采用上面默认的过半方式来避免出现"脑裂",还可以可采用下面的预防措施:
- 添加冗余的心跳线,例如双线条线,尽量减少“裂脑”发生机会。
- 启用磁盘锁。正在服务一方锁住共享磁盘,"裂脑"发生时,让对方完全"抢不走"共享磁盘资源。但使用锁磁盘也会有一个不小的问题,如果占用共享盘的一方不主动"解锁",另一方就永远得不到共享磁盘。现实中假如服务节点突然死机或崩溃,就不可能执行解锁命令。后备节点也就接管不了共享资源和应用服务。于是有人在HA中设计了"智能"锁。即正在服务的一方只在发现心跳线全部断开(察觉不到对端)时才启用磁盘锁。平时就不上锁了。
- 设置仲裁机制。例如设置参考IP(如网关IP),当心跳线完全断开时,2个节点都各自ping一下 参考IP,不通则表明断点就出在本端,不仅"心跳"、还兼对外"服务"的本端网络链路断了,即使启动(或继续)应用服务也没有用了,那就主动放弃竞争,让能够ping通参考IP的一端去起服务。更保险一些,ping不通参考IP的一方干脆就自我重启,以彻底释放有可能还占用着的那些共享资源。
-
Zookeeper选举中投票信息的五元组是什么?
- 服务器ID(SID):每个zk服务器节点都有一个唯一的ID,通常是配置文件中指定的。在选举中,具有较高ID的服务器更有可能成为Leader。
- 选举轮次(Epoch或逻辑时钟,zxid的高32位):每次选举都会有一个选举轮次号,这个数字在每次选举开始时递增,用于区分不同轮次的选举,确保老的投票不会影响到新的选举过程。
- ZXID(事务ID):这是ZooKeeper中事务的唯一标识符,由两部分组成:epoch和计数器。在选举中,具有更大ZXID的服务器表明其数据更新,更适合作为Leader。
- 服务器状态:这可以看作是服务器当前的角色或意图,例如LOOKING(寻找Leader)、LEADING(领导者)、FOLLOWING(跟随者)或OBSERVING(观察者)。在投票信息中,主要是关注哪些服务器被提议为Leader。
- 投票者ID:虽然不直接是投票信息的一部分,但在实际的选举交互中,每个投票都会包含发起投票的服务器ID,以标识投票的来源。
-
讲解一下 ZooKeeper 的持久化机制
-
在Zookeeper中Zxid和Epoch是什么,有什么作用?
-
ZooKeeper 负载均衡和 Nginx 负载均衡有什么区别?
- Nginx 负载均衡:主要用于HTTP(S)请求的负载均衡。Nginx作为反向代理服务器,它可以接收来自客户端的请求,并根据预设的策略(如轮询、最少连接、哈希等)将请求转发给后端的一组Web服务器(upstream servers),实现请求的负载分发。Nginx直接处理客户端的HTTP请求,提供了高性能的七层负载均衡能力。
- ZooKeeper 负载均衡:并非直接提供负载均衡服务,而是作为一个分布式协调服务,用于服务发现和注册。ZooKeeper允许服务实例在启动时向其注册,形成一个服务目录。客户端可以通过ZooKeeper发现可用的服务实例列表,并基于此信息实现自定义的负载均衡策略。ZooKeeper的核心作用是提供分布式环境下的一致性服务,负载均衡是基于其服务发现能力的间接应用。
- 说说Zookeeper中的ACL 权限控制机制
- Zookeeper 有哪几种部署模式?
- ZAB 和 Paxos 算法的联系与区别?
联系:
1. **一致性目标**:两者都旨在解决分布式系统中的一致性问题,确保所有参与节点能够达成一致的状态
2. **领导选举**:ZAB和Paxos都采用了领导选举机制,即系统中会选举出一个领导者(Leader)来协调和管理分布式操作,确保操作的有序执行。
3. **多数同意原则**:在决策过程中,无论是提案的接受还是领导者的选举,都需要大多数节点的同意,以确保即使有节点失败,系统仍能继续正确运行。
区别:
1. **设计目标**:ZAB协议专为构建高可用的分布式数据主备系统设计,如ZooKeeper,它主要关注于数据的复制和主备切换。而Paxos算法的目标更广泛,旨在构建分布式一致性状态机系统,支持任意状态的复制和一致性维护。
2. **应用场景**:ZAB主要应用于那些需要快速故障恢复、支持读写分离的系统中,如服务发现、配置管理等。Paxos则适用于构建更为通用的分布式一致性解决方案,包括数据库复制、分布式文件系统等。
3. **实现方式**:ZAB采用一种简化版的Paxos协议,它在基本的Paxos基础上增加了崩溃恢复的阶段,确保在Leader崩溃后能够快速恢复服务。ZAB协议中包含了广播消息和崩溃恢复两种模式,其中广播消息模式确保数据的有序提交,而崩溃恢复模式则用于选出新的Leader并恢复数据一致性。相比之下,Paxos的实现更加灵活但也更复杂,它需要处理更多的消息类型和故障场景,包括提案的准备、接受和学习阶段。
4. **消息机制**:ZAB协议中,每个消息或事务都包含一个epoch值,用于标记消息所属的领导者周期,这有助于处理领导变更时的事务一致性问题。而Paxos算法中,相类似的概念称为Ballot编号,用来区分不同的提案和避免过时提案的干扰。
总的来说,ZAB可以视为针对特定应用场景(如ZooKeeper)优化过的Paxos变体,它在保证基本一致性的基础上,更加注重于提高系统的可用性和简化恢复流程。而Paxos则是一个理论基础更强、适用范围更广的分布式一致性算法。
- Zookeeper集群支持动态添加机器吗?
- zookeeper会出现数据不一致的问题吗?