浅析Zookeeper的诞生意义

1,507 阅读9分钟

所谓单体架构,就是将系统的所有功能都打包成一个可发布的应用程序

随着用户的增加和业务的发展,单体架构的弊端也逐渐显露

  • 代码臃肿、难以维护,牵一发而动全身
  • 整体构建部署、时间长、不够灵活,难以快速迭代
  • 高频服务与低频服务占用相同资源,难以均衡
  • 某个服务故障影响所有服务,造成服务中断
  • 技术栈受限,所有模块都必须使用相同的开发语言
  • 产生技术债务,不坏不修,重构难以下手

该如何解决单体架构的以上问题?

为了解决需求的快速变化和性能瓶颈,需要将单体应用中的模块和服务进行拆分,共享配置、独立部署, 每个拆分的系统都是一个独立的系统,他们共同组成一个完整的应用。

以微服务为代表的分布式架构能够很好的满足以上功能,它有以下优点

  • 易于开发、理解和维护
  • 启动速度比单体架构
  • 以服务为粒度进行修改发布,有利于持续集成
  • 故障隔离,一个服务出问题,不会造成整体服务中断
  • 高频服务和低频服务合理分配资源
  • 不受限于某一个技术栈

分布式架构的特点

  • 网络波动:分布式系统的节点部署在不同的机器、设置不同的机房中,网络状况直接影响分布式系统的稳定性
  • 三态: 分布式系统的每次请求和响应,都存在三种状态:成功、失败和超时
  • 网络分区(脑裂):分布式系统里,节点与节点之间网络延迟增大甚至断开导致无法互相通信,造成数据不同步,数据分散在几个区域之中
  • 分布式事务:事务的ACID(原子性、一致性、隔离性、持久性)

分布式系统待解决的痛点

  • 服务独立部署,服务提供方是如何让服务消费方感知服务的存在?
  • 定时任务如何保证只在一个实例上执行?
  • 共享资源如何保证安全、互斥?

Zookeeper的数据模型

  • 节点类型

    • 持久节点:只要创建,不会因为客户端session断开而删除
    • 持久有序节点: 有序的持久节点
    • 临时节点: session断开后,自动删除,不能创建子节点
    • 临时有序节点:有序的临时节点
    • 容器节点: 3.5.5新增,可自动回收的节点类型
  • Watch机制 watch监听,客户端可以对ZNode进行监视,ZNode的事务操作会触发watch事件,向客户端发送通知。Watch事件触发后会自动清楚,必须再次设置监听

  • 数据存储限制 ZK的设计初衷是用来作为协调服务,它虽然可以存储数据,但这并不是它的主要目的,因此他允许保存的数据大小上限是1M

服务注册、发现过程

服务提供方服务上线时,连接到zookeeper,在类文件系统的结构中创建服务节点,并将自己的IP、端口信息发布到临时节点上,保持会话。 服务消费方调用服务时,从zookeeper的节点目录中查找对应的服务,获取临时节点上的IP、端口信息,由客户端负载算法进行服务调用。 服务提供方服务下线时,断开会话,zookeeper上保存服务IP、端口信息的临时节点删除

Zookeeper整体流程图

个人流程图链接: https://www.processon.com/view/link/5ef737115653bb2925b7fc24

Zookeeper的设计思想

ps:有兴趣的朋友可以自行阅读 Nacos的AP模式解析,感受一下两种模式(AP vs CP)的不同

www.processon.com/view/link/6…

个人流程图 Nacos源码剖析-服务注册与发现(临时实例AP模式).jpg

著名的CAP理论

  • 一致性(Consistency),所有节点都保存一份最新数据的副本
  • 可用性(Availability),客户端的每次请求都能得到服务端的成功响应,无论数据是否正确或最新
  • 分区容错性(Partition-tolerance),分布式系统一旦出现了网络分区,就必须在一致性和可用性之间保证二选其一

衍生的BASE理论

  • 基本可用(Basically Available),所有节点都保存一份最新数据的副本
  • 软状态(Soft State),客户端的每次请求都能得到服务端的成功响应,无论数据是否正确或最新
  • 最终一致性(Eventual Consistency),分布式系统一旦出现了网络分区,就必须在一致性和可用性之间保证二选其一

2PC

当一个事务操作需要跨越多个分布式节点的时候,为了保持事务处理的ACID特性,就需要引入一个“协调者”TM(Transaction manager )来统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点被称为AP(Application Program)。TM负责调度AP的行为,并最终决定这些AP是否要把事务真正进行提交;整个事务是分为两个阶段:投票、提交。

阶段一:事务请求(投票)

1.事务询问:向所有参与者发送事务内容,等待参与者响应 2.执行事务:参与者预执行事务操作,记录Undo和Redo日志 3.反馈事务询问:参与者如果成功执行了事务,反馈yes,否则反馈no

阶段二:事务提交 在这个阶段,协调者会根据各参与者的反馈情况来决定最终是否可以进行事务提交操作,正常情况下包含两种可能 执行事务、中断事务

Zookeeper的架构设计

集群角色

Leader 事务请求的唯一调度协调者,保证全局事务处理顺序性

Follower 处理非事务请求,事务请求转发给Leader,对Leader发起的提议进行投票,参与Leader选举投票

Observer 3.3版本引入的新角色,顾名思义,它只充当观察者角色,不参与任何形式的投票(包括事务提议、Leader选举等),作为集群扩容,提升读性能的节点。

集群节点

通常zookeeper是由2n+1节点组成,能提供服务的节点至少为n+1 从容灾角度考虑,如果允许X个节点宕机,那么这个集群至少由2X+1组成 3个节点的集群,允许挂掉一个节点,5个节点的集群,允许挂掉2个节点 偶数节点并没有比奇数节点更有优势,反而增加了网络开销

一致性协议 Paxos Zab and Raft

ZAB协议

一种支持崩溃恢复的原子广播协议。在ZooKeeper中,主要依赖ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主从模式的系统架构来保持集群中各个副本之间的数据一致性。

ZAB协议包含两种基本模式,分别是

  • 崩溃恢复
  • 原子广播

原子广播的实现过程(事务请求)

Leader接收消息→生成全局唯一自增zxid(保证有序 将带有zxid的消息放入每个Follower的FIFO队列中,作为一个提议发给所有Follower

Follower收到提议后,写入磁盘,成功后返回一个ack 当Leader收到超过半数成功的ack后,leader就会发出提交命令,同时在本地提交该消息

崩溃恢复

在网络环境中,一旦Leader崩溃,或者Leader与过半的Follower失去了通讯(产生网络分区),那么此时的Leader已经失效了,为了保证系统正确运行,会基于ZAB协议选举出新的Leader。

首先思考以下两个场景:

  • 已经被执行的事务请求如何保证不丢失
  • 已经被丢弃的事务提议如何保证不重现

ZAB协议必须满足以上两种场景,才能保证所有 节点的数据同步

zxid的结构

Zookeeper选举算法通过zxid,保证选举出来的Leader拥有集群中最大的事务编号,因此能保证这个新Leader一定具有已经提交的事务请求,新选举出来的Leader,生成的zxid高32位大于旧的Leader,同时旧Leader的低32位消息计数器被清零,旧的Leader恢复后作为Follower角色接入集群,同步新Leader的数据,清空之前未被提交的提议。

Leader选举

何时会进行选举?

  • 启动时
  • Leader宕机时 Zookeeper Server的4种状态:

zk选举流程

Zookeeper的缺陷与其他中间件比较

CP系统和AP系统的区别? AP:ip1和ip10可用,但相对于其他Server,流量不均衡,如果满足最终一致条件,则流量会逐渐达到均衡

CP:ip1和ip10被抛弃

分区容错性

机房3与其他机房通讯断开,成为孤岛,发生网络分区。此时ZK5无法写入,svcB无法重启、 扩容、缩容。导致同机房的svcA无法调用svcB服务

Zookeeper的分区容错是以舍弃可用性为代价的

服务规模

当zookeeper集群达到一定的规模,几千甚至上万,事务请求会由Leader发起提议进行头片,两个因素会造成较大影响:

  • Follower量大,造成数据同步较大的延迟
  • Leader选举时,大量的Follower参与投票尤其在跨机房时,网络问题会被加倍放大,虽然可以通过Observer来缓解,但只能提高读性能

容灾

作为服务发现中间件,当自身宕机时,是否要考虑服务之间调用的可用性?如左图,如果注册中心挂了,那么原来svcA正常调用svcB的链路是否会受影响?

应该是不受影响的,服务链路对注册中心应该是弱依赖,zookeeper的原生客户端并没有容灾实现,需要服务自己处理。

Dubbo对服务ip、port进行缓存,并提供SPI扩展

zk的缺点以及改进

官方api 只提供最基础的实现,在自研或扩展中间件时,是极其不方便的 ZkClient 在官方api的基础上进行封装,解决了如下问题:

  • Session会话超时重连
  • Watch再次注册
  • 简化api的开发

Curator 是Netflix的开源客户端框架,除了ZkClient实现的功能外,提供如下功能:

  • 实现了Fluent风格
  • 基于场景抽象封装(如分布式锁、Master选举、分布式计数器等)
  • 简化了原生的方法、事件

zk与其他中间件对比