分布式协调-Zookeeper(集群&ZAB&一致性)

分布式协调-Zookeeper(集群&ZAB&一致性)

前面对于zk的一些特性以及如何使用这些特性聊了聊,但是zk作为这样一个重要的中间件,我想瞅瞅他的底层实现原理。并且我们知道每个中间件都必须实现高可用,那我们就有必要去剖析一下他的集群特征。本篇所涉及到的点有:

  • 集群搭建
  • 节点集群角色
  • ZAB协议(崩溃恢复和原子广播)
  • ZK一致性(最终一致性)

集群搭建

准备四个节点(一个leader,一个observer,其他两个follower)

  • 192.168.43.3
  • 192.168.43.4
  • 192.168.43.5
  • 192.168.43.3 【observer】

在zk中的zoo.cfg(这个文件是zoo_samp的副本)中添加:2888端口是数据同步 ,3888是leader选举

  • server.1=192.168.43.3:2888:3888
  • server.2=192.168.43.4:2888:3888
  • server.3=192.168.43.5:2888:3888

创建myid文件在zoo.cfg的dataDir目录下,里面的内容为:第一个节点就写1,第二个节点就写2,第二个节点就写3。这个1、2、3代表的是我们在上一步配置的server.id

开放端口 2181(访问zk的端口)、2888(进行数据同步的端口)、3888(leader选举的端口)

  • firewall-cmd --zone=public --add-port=2181/tcp --permanent   
  • firewall-cmd --zone=public --add-port=2888/tcp --permanent   
  • firewall-cmd --zone=public --add-port=3888/tcp --permanent   

开启zk 【sh zkService.sh start】,然后查看zk状态,两个follower,一个leader节点,至此zk集群搭建成功。

 

 

 

增加一个follower节点:

  • 在此节点的zoo.conf中加上:
  • 【peerType=observer】
  • 和这个节点的ip以及server.id,并且之前的每个节点都需要加上这个节点的ip,就是【server.4=192.168.43.2:2888:3888:observer】
  • 以及他的myid要是4

 Zookeeper集群节点

在分布式架构中任何单节点出现都是不允许的,而多节点就存储节点故障问题,常见的解决方法就是主从集群。一个中心节点对应多个从节点,主节点接受事务请求,从节点进行数据同步(因为当主节点挂了之后,从节点可以顶上去)。

在zk中的节点有:

主节点叫做【leader】: 对事务进行处理,并且当一个数据过来的时候,他需要把数据同步到多个从节点。我们跨节点的数据一致性通常是通过分布式事务来解决,底层是2pc协议,下面会说到。

从节点叫做【follower】进行非事务请求,以及请求的转发。(因为客户端不清楚那个节点是leader,所以可能会把请求发送到follower节点,那follower就把这个请求转发到leader上。)

observer】节点:我们知道增加一个从节点,就会增加投票的开销,那就意味着客户端收到的响应可能会越来越慢。但是我们还是想在读多写少的情况下去缓解我们读节点的压力,所以我们就增加一个observer节点,那他就不参与投票,以及leader选举,也就意味着他永远成为不了leader,只是一个读取数据的节点。

ZAB协议

ZAB协议(Zookeeper Atomic Broadcas)包含两个问题:崩溃恢复和原子广播

崩溃恢复(leader挂了,需要选举新的leader)】:当整个集群启动的时候,或者当leader出现崩溃的时候,zab就会进入恢复模式并选出新的leader,当leader被选举出来的时候,并且集群中有过半的节点和被选举出来的leader完成数据通讯后,zab就会退出这个崩溃回复状态。

  • 【选举leader的前提是满足(后面会对源码进行分析)】:
    • 【已经被提交的消息不能被丢弃】:就是leader确定了集群中有一半已经发送了ack给了自己。然后他把第二阶段的提交发送给follower之后就挂了,那这个消息必须在所有服务器上都执行成功。否则就会出现数据不一致的问题。
    • 【被丢弃的消息不能再次出现】:当leader收到了一个其他节点转发过来的消息后,然后要准备第一阶段提交的时候,他就挂了,那么新的leader上就不需要有这个消息。老的leader重启之后变成follower之后就要把这个消息删除。
  • 【具体做法】:需要先引出两个概念
      • 【epoch】:可以理解为当前集群所处的年代或者周期,每个 leader 就像皇帝,都有自己的年号,所以每次改朝换代,leader 变更之后,都会在前一个年代的基础上加 1。这样就算旧的 leader 崩溃恢复之后,也没有人听他的了,因为 follower 只听从当前年代的 leader 的命令。
      • 【zxid】:因为在数据同步的时候,leader会发送zxid给各个follower。他是一个64位的数字,高32位是epoch,低32位用于递增计数。
    【流程】
        :当集群中的leader服务器出现宕机或者不可用的情况时,
    那么整个集群将无法对外提供服务
      ,而是进入新一轮的Leader选举,服务器运行期间的Leader选举和启动时期的Leader选举基本过程是一致的,都是下面的的这个流程。
      • 当启动的时候,每个节点都把自己的zxid,myid,epoch发送给别的节点。
      • 然后按照规则,比较epoch->zxid->myid。
      • 如果a节点发现b和c的某个节点上的epoch比自己的大,那就把自己的票据变成比自己大的那个票据,然后再次发送。
      • 而节点b和a节点比较发现自己就是最大的,他就不修改,再次把自己的票发送出去。
      • 每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息。这个时候已经超过一半的节点投节点a了,那a就变成了leader

原子广播(数据同步)】:当集群中已经有过半的Follower节点完成了和Leader状态同步以后,那么整个集群就进入了消息广播模式。这个时候,在Leader节点正常工作时,启动一台新的服务器加入到集群,那这个服务器会直接进入数据恢复模式,和leader节点进行数据同步。

  • 【具体做法】:
    • leader收到事务请求之后,维护一个zxid,并且为每一个follower准备了一个FIFO的队列。
    • 然后将带有zxid的消息发送给所有的follower。
    • follower收到这个消息后,把消息写入磁盘,写入成功后,给leader返回一个成功的ack.
    • leader收到过半节点的ack后,leader发送执行命令给follower,并且在本地执行消息(创建节点)
    • follower收到leader的执行消息后,在自己的本地执行。
    • 至此,数据同步完毕。
    • 这个其实有2pc的影子
    • 2pc:

      • 第一个阶段发起事务提议:
        • leader向所有参与节点发送一个事务内容,比如创建一个节点。然后所有的参与节点会把这个事务写到事务日志中(这个日志并没有提交,这个时候内容是不可见的)。然后发送一个消息给leader。
      • 第二个阶段发起提交或者回滚:
        • 所有节点都返回消息的时候,leader就发起一个commit去让这些参与节点提交事务,这个节点就执行事务,然后新创建的节点就是对客户端可见的。

      然而,在zk中使用的是过半提交(就是如果有3个节点,有两个节点返回成功,那就执行这个事务)

  Zk的一致性

上面说到zk在数据同步的时候是采用过半的策略,那也就是说,当客户端去访问的zk的时候,可能自动路由到某个除了leader之外的节点的话,就会获取不到准确的数据。但是zk官网给的解释说zk是一个【最终一致性】中间件,也就是说他最终的数据会达成一致。

所以zk不保证在每个实例中,两个不同的客户端一定会读取到相同的数据,由于网络延迟等因素,一个客户端可能会在另外一个客户端收到更改通知之前执行更新,如果客户端A和B要读取必须要读取到相同的值,那么client B在读取操作之前执行sync方法。 zooKeeper.sync();

但是!如果一个客户端连接到了zk上的一个最新的数据节点,当这个客户端再次连接zk的时候,zk可以保证这个客户端不会读取到老的数据,这是因为zk中使用zxid进行了处理,流程为:客户端会记录自己已经读取到的最大的zxid,如果客户端重连到zk上的节点发现zk上节点的zxid比自己大,连接会失败。

 常见问题:

上面说了,过半节点返回票据就可以进行数据同步了,那剩下的没有返回票据的节点怎么办?

他会不断的去拿自己的zxid去和leader对比,如果自己的不一样,则进行数据拉取。

他的节点数据存储在哪里?

默认的目录是在/tmp/zookeeper下,该路径可以通过zoo.cfg文件来修改
  • # 内存数据库快照存放地址
  • dataDir=/data/zookeeper-3.4.6/data
  • # 事务日志存储
  • dataLogDir=/data/zookeeper-3.4.6/data/log
在Zab协议中我们知道每当有接收到客户端的事务请求后Leader与Follower都会将把该事务日志存入磁盘日志文件中,该日志文件就是上面配置中的事务日志。其中文件的命名是 log.zxid, 其中zxid表示当前日志文件中开始记录的第一条数据的zxid。

posted @ 2021-11-25 20:29  UpGx  阅读(303)  评论(2编辑  收藏  举报