1. 哪些开源的分布式系统中使用了 zk
- 第一类:分布式 Java 业务系统,分布式电商平台,大部分的 Java 开发的互联网平台,或者是传统架构系统,都是分布式 Java 业务系统,Dubbo、Spring Cloud 把系统拆分成很多的服务或者是子系统,大家协调工作,完成最终的功能。ZooKeeper,用的比较少,分布式锁的功能,而且很多人会选择用 Redis 分布式锁
第二类:开源的分布式系统
- 分布式集群的集中式元数据存储、Master选举实现HA架构、分布式协调和通知
- Dubbo:ZooKeeper 作为注册中心,分布式集群的集中式元数据存储
- HBase:分布式集群的集中式元数据存储
- HDFS:Master 选举实现HA架构
- Kafka:分布式集群的集中式元数据存储,分布式协调和通知
- Canal:分布式集群的集中式元数据存储,Master 选举实现 HA 架构
- 分布式集群的集中式元数据存储、Master选举实现HA架构、分布式协调和通知
第三类:自研的分布式系统
- HDFS,面向的超大文件,切割成一个一个的小块儿,分布式存储在一个大的集群里
- 分布式海量小文件系统:NameNode的HA架构,仿照HDFS的NameNode的HA架构,做主备两个NameNode,进行数据同步,然后自动基于zk进行热切换
- 在很多,如果你自己研发类似的一些分布式系统,都可以考虑,你是否需要一个地方集中式存储分布式集群的元数据?是否需要一个东西辅助你进行Master选举实现HA架构?进行分布式协调通知?如果你在自研分布式系统的时候,有类似的需求,那么就可以考虑引入ZooKeeper来满足你的需求
2. zk 架构设计的特点
- 集群化部署:3~5台机器组成一个集群,每台机器都在内存保存了 zk 的全部数据,机器之间互相通信同步数据,客户端连接任何一台机器都可以。
- 树形结构的数据模型:znode,树形结构,数据模型简单,纯内存保存
- 顺序写:集群中只有一台机器可以写,所有机器都可以读,所有写请求都会分配一个 zk 集群全局的唯一递增编号,zxid,保证各种客户端发起的写请求都是有顺序的
- 数据一致性:任何一台zk机器收到了写请求之后都会同步给其他机器,保证数据的强一致,你连接到任何一台zk机器看到的数据都是一致的
- 高性能:每台 zk 机器都在内存维护数据,所以 zk 集群绝对是高并发高性能的,如果你让zk部署在高配置物理机上,一个3台机器的zk集群抗下每秒几万请求没有问题
- 高可用:哪怕集群中挂掉不超过一半的机器,都能保证可用,数据不会丢失,3台机器可以挂1台,5台机器可以挂2台
- 高并发:高性能决定的,只要基于纯内存数据结构来处理,并发能力是很高的,只有一台机器进行写,但是高配置的物理机,比如16核32G,写入几万QPS,读,所有机器都可以读,3台机器的话,起码可以支撑十几万QPS
3. 客户端和 zk 之间的长连接和回话
- 客户端和zk之间的连接是 TCP 长连接
也就建立了一个会话,就是 session,可以通过心跳感知到会话是否存在,有一个
sessionTimeout
,意思就是如果连接断开了,只要客户端在指定时间内重新连接zk一台机器,就能继续保持 session,否则 session 就超时了4. ZAB 协议
zk 集群的数据同步是用 ZAB 协议,ZooKeeper Atomic Broadcast,就是 ZooKeeper 原子广播协议
4.1 主从同步机制
2PC 和 过半写机制只有 Leader 可以接受写操作,Leader 和 Follower 都可以读
- Leader 收到事务请求,转换为事务 Proposal(提议)同步给所有的 Follower,Follower 接收到提议,放入磁盘,并返回 ack
- 超过半数的 Follower 都说收到事务 proposal 之后,Leader 再给所有的 Follower 发一个 Commit 消息,让所有 Follower 提交一个事务。
4.2 ZAB 选举
如果突然 leader宕机了,会进入恢复模式,重新选举一个 leader,只要过半的机器都承认你是 leader,就可以选举出来一个leader,所以zk很重要的一点是主要宕机的机器数量小于一半,他就可以正常工作。因为主要有过半的机器存活下来,就可以选举新的 leader,新leader重新等待过半follower跟他同步,完了重新进入消息广播模式。
- 集群启动:恢复模式,leader选举(过半机器选举机制) + 数据同步
- 消息写入:消息广播模式,leader 采用 2PC 模式的过半写机制,给 follower 进行同步
4.3 消息广播的顺序性
- 发起一个事务 proposal 之前,leader 会分配一个全局唯一递增的事务 id,
zxid
,通过这个可以严格保证顺序 leader 会为每个 follower 创建一个队列,里面放入要发送给 follower 的事务 proposal,这是保证了一个同步的顺序性
4.4 zk 是强一致性还是最终一致性
强一致性:只要写入一条数据,立马无论从zk哪台机器上都可以立马读到这条数据。你写入时会阻塞,直到 leader 和全部 follower 都进行了 commit 后,才让写入操作返回。此时只要写入成功,无论你从哪个zk机器查询,都是能查到,此为强一致性。
- 最终一致性:写入一条数据,方法返回,告诉你写入成功了,此时有可能你立马去其他zk机器上查是查不到的,需要短暂的同步时间之后,才能查询到。
zk 官方给自己的定义:
**顺序一致性**
因此 zk 是最终一致性的,但是其实他比最终一致性更好一点,因为 leader 一定会保证所有的 proposal 同步到 follower 上都是按照顺序来走的,所以是顺序一致性。
4.5 ZAB 协议下数据不一问题
- 问题1:Leader 收到了过半的 follower的 ack,接着 leader 自己 commit 了,还没来得及发送 commit 给所有 follower 自己就挂了,这个时候相当于 leader 的数据跟所有 follower 是不一致的,你得保证全部 follower 最终都得 commit
- 解决:新选举出来一个leader之后,本身人家会挑选已经收到的事务 zxid 里最大的那个 follower 作为新的 leader,此 follower 的数据量最全
- 问题2:leader 可能会自己收到了一个请求,结果没来得及发送 proposal 给所有 follower 之前就宕机了,此时这个 Leader上 的请求应该是要被丢弃掉的
关于问题2:对于需要丢弃的消息是如何在ZAB协议中进行处理的?
- 每一条事务的zxid是64位的,高32位是 leader 的 epoch,就认为是 leader 的版本吧;低32位才是自增长的 zxid。比如:老leader发送出去的proposal,高32位是1,低32位是11358
- 如果一个 leader 自己刚把一个 proposal 写入本地磁盘日志,就宕机了,没来得及发送给全部的 follower,此时新 leader 选举出来,他会的 epoch 会自增长一位变为 proposal,高32位是2,低32位是继续自增长的 zxid
- 然后老leader恢复了连接到集群是 follower了,此时发现自己比新 leader 多出来一条 proposal,但是自己的 epoch 比新 leader 的 epoch 低了,所以就会丢弃掉这条数据
zk 小集群部署
- 不支持大规模集群部署,一般就三五台机器,因为follower要参与到ZAB的写请求过半ack里去
- 如果你有20个follower,一个写请求出去,要起码等待10台以上的Follower返回ack,才能发送commit,才能告诉你写请求成功了,性能是极差的
- 所以zk的这个ZAB协议就决定了一般其实就是小集群就够了,写请求是无法扩展的,读请求如果量大,可以加observer机器,最终就是适合读多写少的场景