zk 核心概念梳理

介绍zk一些入门概念,重点讲解zab的消息广播以及崩溃恢复,进而衍生出一些拓展的思考以及横向对比

简介:

https://zh.wikipedia.org/wiki/Apache_ZooKeeper#cite_note-1

http://zookeeper.apache.org/doc/current/zookeeperOver.html

CAP中的CP,没有A
CAP:https://zh.wikipedia.org/wiki/CAP%E5%AE%9A%E7%90%86

zk 核心概念梳理_第1张图片
image

最终一致性

全量数据放在内存中,qps级别在10w

概念

节点角色

Leader:仅一台,通过选举完成,提供读写服务
Follower:参与Leader选举以及"过半写成功"策略,提供读服务
Observer:仅提供读服务,不参与选举以及过半写成功

节点状态:

`LOOKING:当前Server不知道leader是谁,正在搜寻`
`LEADING:当前Server即为选举出来的leader`
`FOLLOWING:leader已经选举出来,当前Server与之同步`

会话:

Session是指客户端会话,在讲解客户端会话之前,我们先来了解下客户端连接。在ZooKeeper中,一个客户端连接是指客户端和ZooKeeper服务器之间的TCP长连接。ZooKeeper对外的服务端口默认是2181,客户端启动时,首先会与服务器建立一个TCP连接,从第一次连接建立开始,客户端会话的生命周期也开始了,通过这个连接,客户端能够通过心跳检测和服务器保持有效的会话,也能够向ZooKeeper服务器发送请求并接受响应,同时还能通过该连接接收来自服务器的Watch事件通知。Session的SessionTimeout值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在SessionTimeout规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。

数据:

Zookeeper树结构

Zookeeper提供基于类似于文件系统的目录节点树方式的数据存储,但是在分布式系统中,Zookeeper并不是用来存储数据的,每个节点的数据最大不能超过1MB,它的作用主要是用来维护和监控这些数据的变化的,通过监控这些数据的变化,就可以达到基于数据的集群管理。

zk 核心概念梳理_第2张图片
image

Zookeeper的数据节点称为ZNode,ZNode是Zookeeper中数据的最小单元,每个ZNode都可以保存数据,同时还可以挂载子节点,因此构成了一个层次化的命名空间,称为树。

数据节点

ZooKeeper 节点是有生命周期的,这取决于节点的类型。在 ZooKeeper 中,节点类型可以分为持久节点(PERSISTENT )、临时节点(EPHEMERAL),以及时序节点(SEQUENTIAL ),具体在节点创建过程中,一般是组合使用,可以生成以下 4 种节点类型。

`持久节点(PERSISTENT)`
`持久顺序节点(PERSISTENT_SEQUENTIAL)`
`临时节点(EPHEMERAL)`
`临时顺序节点(EPHEMERAL_SEQUENTIAL)`

|

zk 核心概念梳理_第3张图片
image

上图描述了一个持久顺序节点

节点描述Stat

ZooKeeper命名空间中的每个znode都有一个与之关联的stat结构,类似于Unix/Linux文件系统中文件的stat结构。 znode的stat结构中的字段显示如下,各自的含义如下:

`cZxid:这是导致创建znode更改的事务ID。`
`mZxid:这是最后修改znode更改的事务ID。`
`pZxid:这是用于添加或删除子节点的znode更改的事务ID。`
`ctime:表示从1970-01-01T00:00:00Z开始以毫秒为单位的znode创建时间`
`mtime:表示从1970-01-01T00:00:00Z开始以毫秒为单位的znode最近修改时间。`
`dataVersion:表示对该znode的数据所做的更改次数。`
`cversion:这表示对此znode的子节点进行的更改次数。`
`aclVersion:表示对此znode的ACL进行更改的次数。`
`ephemeralOwner:如果znode是ephemeral类型节点,则这是znode所有者的 session ID。 如果znode不是ephemeral节点,则该字段设置为零。`
`dataLength:这是znode数据字段的长度。`
`numChildren:这表示znode的子节点的数量。`

版本

其中,version,cversion,aversion合称为版本


zk 核心概念梳理_第4张图片
基于乐观锁的写入校验

Client 功能:

`create `
`ls`
`get`
`set`
`delete`
`exists`
zk 核心概念梳理_第5张图片
image
zk 核心概念梳理_第6张图片
image
zk 核心概念梳理_第7张图片
image

watch机制

ZooKeeper允许客户端向服务端注册感兴趣的Watcher监听,当服务端触发了这个Watcher,那么就会向客户端发送一个时间来实现分布式的通知功能。真正的Watcher回调与业务逻辑执行都在客户端

推拉结合,推:通知,但是不告诉最新值,拉:拉最新值

zk 核心概念梳理_第8张图片
image

特性

| 特性 | 说明 |
| 一次性 | Watcher是一次性的,一旦被触发就会移除,再次使用时需要重新注册 |
| 客户端顺序回调 | Watcher回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态。一个Watcher回调逻辑不应该太多,以免影响别的watcher执行 |
| 轻量级 | WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容; |
| 时效性 | Watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍可接收到通知; |

Watcher事件类型(EventType)

EventType是数据节点(znode)发生变化时对应的通知类型。EventType变化时KeeperState永远处于SyncConnected通知状态下;当KeeperState发生变化时,EventType永远为None。其路径为org.apache.zookeeper.Watcher.Event.EventType,是一个枚举类,枚举属性如下;

| 枚举属性 | 说明 |
| None (-1) | 无 |
| NodeCreated (1) | Watcher监听的数据节点被创建时 |
| NodeDeleted (2) | Watcher监听的数据节点被删除时 |
| NodeDataChanged (3) | Watcher监听的数据节点内容发生变更时(无论内容数据是否变化) |
| NodeChildrenChanged (4) | Watcher监听的数据节点的子节点列表发生变更时 |

Watcher通知状态(KeeperState)

KeeperState是客户端与服务端连接状态发生变化时对应的通知类型。路径为org.apache.zookeeper.Watcher.Event.KeeperState,是一个枚举类,其枚举属性如下;

| 枚举属性 | 说明 |
| Unknown(-1) | 属性过期 |
| Disconnected(0) | 客户端与服务器断开连接时 |
| NoSyncConnected(1) | 属性过期 |
| SyncConnected(3) | 客户端与服务器正常连接时 |
| AuthFailed(4) | 身份认证失败时 |
| ConnectedReadOnly(5) | 3.3.0版本后支持只读模式,一般情况下ZK集群中半数以上服务器正常,zk集群才能正常对外提供服务。该属性的意义在于:若客户端设置了允许只读模式,则当zk集群中只有少于半数的服务器正常时,会返回这个状态给客户端,此时客户端只能处理读请求 |
| SaslAuthenticated(6) | 服务器采用SASL做校验时 |
| Expired(-112) | 会话session失效时 |

zk 核心概念梳理_第9张图片
image

ZAB协议

ZAB协议包括两种基本的模式,分别是崩溃恢复和消息广播。当整个服务框架在启动过程中,或者是当Leader服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB协议就会进入恢复模式并选举产生新的Leader服务器。当选举产生了新的Leader服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致。

当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个服务框架就可以进入消息广播了。当一台同样遵守ZAB协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么新加入的服务器就会自觉的进入数据恢复模式: 找到Leader所在的服务器,与其进行数据同步,然后一起参与到消息广播流程中去。正如上文介绍中所说的,Zookeeper设计成只允许唯一的一个Leader服务器来进行事务请求的处理。Leader服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播协议; 而如果集群中的其他机器接收到客户端的事务请求,那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。

当Leader服务器出现崩溃退出或者机器重启,亦或是集群中已经不存在过半的服务器与该Leader服务器保持正常通行时,那么在重新开始新一轮原子广播事务操作之前,所有进程首先会使用崩溃恢复协议来使彼此达到一个一致的状态,于是整个ZAB流程就会从消息广播模式进入到崩溃恢复模式。

一个机器要成为新的Leader,必须获得过半进程的支持,同时由于每个进程都有可能会崩溃,因此,ZAB协议运行过程中,前后会出现多个Leader,并且每个进程也有可能会多次成为Leader。进入崩溃恢复模式后,只要集群中存在过半的服务器能够彼此进行正常通信,那么就可以产生一个新的Leader并再次进入消息广播模式。举个例子来说,一个由3台机器组成的ZAB服务,通常由1个Leader,2个Follower服务器组成。某一个时刻,假如其中一个Follower服务器挂了,整个ZAB集群是不会中断服务的,这是因为Leader服务器依然能够获得过半机器(包括Leader自己)的支持。

消息广播(5min)

类似于2PC

2pc 3pc

https://zh.wikipedia.org/wiki/%E4%BA%8C%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4

https://zh.wikipedia.org/wiki/%E4%B8%89%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4

3pc主要解决了2pc的单点故障,并引入超时机制

zk 核心概念梳理_第10张图片
image

ZAB协议与二阶段提交协议不同的是,ZAB协议在二阶段提交过程中,移除了中断逻辑。

ZAB协议在有过半的Follower服务器已经反馈Ack之后就开始提交Proposal了,而不需要等待集群中所有Follower服务器都反馈响应。

关于ZAB在Leader出现单点宕机如何保证事务提交,保证数据一致性,则引入崩溃恢复模式来解决这个问题。

ZAB的消息广播协议是基于具有FIFO(先进先出)特性的TCP协议来进行网络通信,保证消息广播过程中消息的接收与发送的顺序性。

在整个消息广播过程中,Leader服务器会为每一个事务请求的处理步骤:

(1)Leader服务器会为事务请求生成一个全局的的递增事务ID(即ZXID),保证每个消息的因果关系的顺序。

(2)Leader服务器会为该事务生成对应的Proposal,进行广播。

(3)Leader服务器会为每一个Follower服务器都各自分配一个单独的队列,然后将需要广播的事务Proposal依次放入这些队列中去,并根据FIFO策略进行消息发送。

(4)每一个Follower服务器在接收到这个事务Proposal之后,首先以日志形式写入本地磁盘,并且成功写入后反馈给Leader服务器一个Ack响应

(5)当Leader服务器接收超过半数的Follower的Ack响应,Leader自身也会完成对事务的提交。同时就会广播一个Commit消息给所有的Follower服务器以通知进行事务提交。每一个Follower服务器在接收到Commit消息后,也会完成对事务的提交。

Zk的写机制

所有的写的请求,转发给Leader,Leader采取两阶段提交的方式。

  1. 本地生成自增的zxid,生成Proposal日志(持久化)

  2. 广播所有的Follower,并且有单独的线程统计 Ack Proposal的数量

  3. Proposal ack过半之后,广播Commit,并且把这个request丢到各自的CommitProcessor里面处理

  4. Master commit日志,更新lastCommitZxid,apply到内存树中,Ack client操作成功

崩溃恢复

选主机制(发现):

1)Zab 协议需要确保那些**已经在 Leader 服务器上提交(****Commit****)的事务最终被所有的服务器提交**。

2)Zab 协议需要确保**丢弃那些只在 Leader 上被提出而没有被提交的事务**。

zk 核心概念梳理_第11张图片
image

保证P2最终被commit

zk 核心概念梳理_第12张图片
image

保证P3被丢弃

sid, zxid

zxid 64位= epoch(周期) + 事务id(周期内事务id)

在 Zab 的事务编号 zxid 设计中,zxid是一个64位的数字。

其中低32位可以看成一个简单的单增计数器,针对客户端每一个事务请求,Leader 在产生新的 Proposal 事务时,都会对该计数器加1。而高32位则代表了 Leader 周期的 epoch 编号。

epoch 编号可以理解为当前集群所处的年代,或者周期。每次Leader变更之后都会在 epoch 的基础上加1,这样旧的 Leader 崩溃恢复之后,其他Follower 也不会听它的了,因为 Follower 只服从epoch最高的 Leader 命令。

每当选举产生一个新的 Leader ,就会从这个 Leader 服务器上取出本地事务日志中最大编号 Proposal 的 zxid,并从 zxid 中解析得到对应的 epoch 编号,然后再对其加1,之后该编号就作为新的 epoch 值,并将低32位数字归零,由0开始重新生成zxid。

zxid大的优先,zxid相等用sid比较,这样保证偏序关系,保证收敛

zk 核心概念梳理_第13张图片
image

只有超过半数节点Ack了的事务操作,才会被commit,才会最终响应到客户端。所以响应了客户端的操作,不管leader是否挂了,新leader中肯定存了这个日志,否则选举中不会获胜。

未完成半数Ack的事务操作,leader挂了,新leader可能保存这个日志,也可能没有保存这个日志。

通信协议:

jute(不是传统的pb或者thrift)序列化

持久化以及数据同步:

背景:

数据存储,备份,冗余,同步

分类:

FileTxnLog,事务日志

SnapLog,快照

作用:

应用日志

diff同步,trunc+diff同步,trunc同步,snap同步

*
zk 核心概念梳理_第14张图片
image
`diff同步`
`    peerLastZxid在min和max之间`
`trunc + diff`
`    leader处理事务记录zxid后,发出proposal前挂掉了,也就是follower不知道这一条zxid`
`    follower无视这一条zxid进行新的集群处理之后,老leader再进来了,需要trunc + diff`
`trunc`
`    上述简单版,peerLastZxid>max`
`snap`
`没办法diff或者trunc,比如  peerLastZxid < min` 

实际应用:

数据发布/订阅 , 分布式锁,分布式队列等等
Kafka,solr,Eureka,Dubbo,hadoop,hbase
如何简单用zk弄一个服务发现

Future topic:

ACL
session管理
异常处理
服务器启动
watcher管理

zk 核心概念梳理_第15张图片
image

集群间消息通信 7.7.4
请求处理 7.8
数据与存储,如何进行镜像同步or增量同步 7.9
会话 https://jimmy2angel.github.io/2019/01/12/Zookeeper-Session%E6%9C%BA%E5%88%B6/
https://www.cnblogs.com/GrimMjx/p/10922480.html
Watcher https://www.jianshu.com/p/c68b6b241943

延伸思考:(6min)

如果自己设计一个分布式系统,需要注意什么

CAP,2PC,3PC,分布式事务
选主机制,事务机制,执行顺序
通信协议,消息体定义,消息体格式(req,resp),向前兼容
网络IO,NIO,零拷贝,直接内存,网络流量负载,会话管理
序列化,反序列化机制
持久化日志,日志格式,日志索引,日志截断,日志清理,日志可视化,checksum,刷盘
数据崩溃恢复,增量同步,镜像同步
配置,服务端conf和client conf,心跳,超时等参数
状态机,状态处理,回调
负载均衡,监控
伸缩性,动态扩缩容,单机内存,硬盘大小,支持qps
边界情况,异常处理,机器挂了,网络不通,脑裂

可以结合netty,redis,mysql,kafka横向对比

kafka: HW,ISR,OSR,心跳
Redis: gossip,aof,rdb,线程模型
Mysql: binlog,主从同步,数据冗余
netty: 零拷贝

重点总结:

zab的广播和崩溃恢复

`为什么要有两个状态,广播和崩溃恢复`
`崩溃恢复的时候如何选主 `
`    什么时候会处于崩溃状态   `
`    sid+zxid,保证偏序关系,保证收敛`
`        为什么要zxid,为什么要sid,为什么要收敛`
`    zxid 64位由什么组成,epoch代表什么,事务id代表什么`
`    为什么要过半同意,过半为什么是>而不是>=一半`
`    如何利用日志来恢复数据`
`        diff+trunc+snap`
`        为什么优先用diff,用不了diff就用snap`
`        为什么不能保存全量日志`
`如何消息广播`
`    为什么要类似2pc,一阶段会有什么问题`
`    为什么要记录日志,为什么要两种日志,为什么zk单节点大小不建议超过1M`
`    数据如何冗余`
`    zk是强一致的么,是哪种一致`
`    事务的顺序如何保证的`
`        zk的写操作全部给leader保证`

再回顾最上面的简介

思考题

`leader挂了之后再恢复,它还能成为leader么`
`zk会脑裂么,脑裂还会提供服务么,为什么要奇数台`
`如果leader发出提议之后还没有过半机器ack就挂了,那么下次这个提议是否被新leader应用`
`    不保证,比如abcde中a是leader,b收到了,cde没收到`
`    如果b没挂,bcde网络正常,那么新的b是leader,能应用这个提议`
`    如果b也挂了或者b和cde网络不通,那么新leader的cde中选一个,就不会应用这个提议,b恢复了之后也会要trunc+diff或者snap`
`zk为什么快`
`一个之前挂掉的机器加入一个正常状态的集群需要做什么,能直接向client提供服务吗`

refer:

http://zookeeper.apache.org/doc/current/zookeeperOver.html zk官网

https://juejin.im/post/5b037d5c518825426e024473 zk漫画简介

https://blog.csdn.net/weixin_44778202/article/details/90315015 zk思维导图

《paxos到zk》 核心第四章 第七章

https://www.jianshu.com/p/dc292f72089a 读写机制

https://www.cnblogs.com/sunddenly/p/4138580.html zk一致性原理

http://www.kailing.pub/raft/index.html raft算法达成一致

你可能感兴趣的:(zk 核心概念梳理)