文章目录

  • 一、前言
  • 二、Zookeeper三个角色
    • 2.1 ZAB定义 + ZAB作用 + Zxid
    • 2.2 Zookeeper集群中的三个角色(重点:leader follower observer)
      • 2.2.1 三个角色:leader follower observer
      • 2.2.2 三个角色:选举阶段 + 广播阶段
      • 2.2.3 observer存在的意义 + leader存在的意义 + follower存在的意义
    • 2.3 Zookeeper四种状态 + ZAB四种状态(绑在一起看,就是一个选主过程)
      • 2.3.1 Zookeeper集群的服务状态:四种
      • 2.3.2 ZAB四种状态
      • 2.3.3 ZAB协议四个阶段(整个选主过程只与followers和leader有关,observer没有选举权)
      • 2.3.4 ZAB协议的JAVA 实现(FLE-发现阶段和同步合并为Recovery Phase(恢复阶段))
  • 三、Zookeeper选举过程(基于ZAB协议)when how how
    • 3.1 when何时,leader选举的两个时机
    • 3.2 how理论上,leader选举的三种选举规则
    • 3.3 how实践上,leader选举的选举流程:三个步骤
  • 四、Zookeeper的广播过程(读写操作)
    • 4.1 ZAB协议有序性 why what how
    • 4.2 基础知识:两阶段提交(为zookeeper阶段准备)
    • 4.3 zookeeper一个写请求的两阶段提交流程(和理论上的二阶段提交有点不同,有一半follower ack就好)
  • 五、四个状态流转(zookeeper四种服务状态 + ZAB四种步骤状态)
  • 六、面试金手指
    • 6.1 起手式,ZAB + Zookeeper 基础
      • 6.1.1 ZAB定义 + ZAB作用 + Zxid
      • 6.1.2 Zookeeper集群中的三个角色(重点:leader follower observer)
        • 6.1.2.1 三个角色:leader follower observer
        • 6.1.2.2 三个角色:选举阶段 + 广播阶段
        • 6.1.2.3 observer存在的意义 + leader存在的意义 + follower存在的意义
    • 6.2 Zookeeper的选举过程
    • 6.3 Zookeeper的广播过程
      • 6.3.1 ZAB协议有序性
      • 6.3.2 基础知识:两阶段提交(为zookeeper阶段准备)
      • 6.3.3 一个写请求的两阶段提交流程
    • 6.4 Zookeepr四状态 + ZAB四状态
  • 七、尾声

一、前言

二、Zookeeper三个角色

2.1 ZAB定义 + ZAB作用 + Zxid

ZAB定义:ZAB 英文全称为 Zookeeper Atomic Broadcast,译为 Zookeeper 原子广播协议;

ZAB作用:当ZooKeeper leader节点宕机时,用于崩溃恢复的原子广播协议,它保证了zookeeper集群的 数据一致性 和 命令全局有序性。

注意两个关键字:数据一致性 和 命令全局有序性,下面介绍,当leader节点宕机的时候,是怎样保证数据一致性和 命令全全局有序性的。

ZXID(64bit) = epoch(32bit) + counter(32bit)

Zxid定义:Zxid是一个long型(64位)整数,分为两部分:纪元(epoch)部分和计数器(counter)部分,是一个全局有序的数字。

epoch定义:epoch表示当前集群所属的哪个leader,leader的选举就类似一个朝代的更替,你前朝的剑不能斩本朝的官,用epoch代表当前命令的有效性。

counter定义:counter是一个递增的数字,表示该节点所提交的事务的最大id,越大表示数据越完整。

第一,事务编号Zxid

在 ZAB ( ZooKeeper Atomic Broadcast , ZooKeeper 原子消息广播协议) 协议的事务编号 Zxid 设计中,Zxid(Transaction id)类似于 RDBMS 中的事务 ID,用于标识一次更新操作的Proposal(提议)ID。为了保证顺序性,该 zxid 必须单调递增。

如何保证顺序性呢?Zxid 是一个 64 位的数字,其中低 32 位是一个简单的单调递增的计数器,针对客户端每一个事务请求,计数器加 1;高 32 位则代表 Leader 周期 epoch 的编号,每个当选产生一个新的 Leader 服务器,就会从这个 Leader 服务器上取出其本地日志中最大事务的 ZXID,并从中读取epoch 值,然后加 1,以此作为新的 epoch,并将低 32 位从 0 开始计数。

第二,epoch

epoch:可以理解为当前集群所处的年代或者周期,每个 leader 就像皇帝,都有自己的年号,所以每次改朝换代,leader 变更之后,都会在前一个年代的基础上加 1。这样就算旧的 leader 崩溃恢复之后,也没有人听他的了,因为 follower 只听从当前年代的 leader 的命令。

第三,ZAB协议两种模式

Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 Server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 Server 具有相同的系统状态。

2.2 Zookeeper集群中的三个角色(重点:leader follower observer)

2.2.1 三个角色:leader follower observer

Zookeeper 直译为动物园管理员,是一个集群管理工具,一般用于大数据集群管理,也可以管理其他集群,如Solr索引库集群。

Zookeeper 是一个分布式协调服务,可用于服务发现,分布式锁,分布式领导选举,配置管理等。Zookeeper 提供了一个类似于 Linux 文件系统的树形结构(可认为是轻量级的内存文件系统,但只适合存少量信息,完全不适合存储大量文件或者大文件),同时提供了对于每个节点的监控与通知机制。

Zookeeper 集群是一个基于主从复制的高可用集群,其架构如图所示:

每个服务器承担如下三种角色中的一种:Leader Follower Observer。

Leader

1、维持心跳:一个 Zookeeper 集群同一时间只会有一个实际工作的 Leader,它会发起并维护与各 Follwer 及 Observer 间的心跳(就是上图中的ping 和 ack 交互)。

2、写操作过半数写入成功才提交:所有的写操作必须要通过 Leader 完成再由 Leader 将写操作广播给其它服务器。只要有超过半数节点(不包括 observer 节点)写入成功,该写请求就会被提交(类 2PC 协议)。

Follower

1、维持心跳:一个 Zookeeper 集群可能同时存在多个 Follower,它会响应 Leader 的心跳。

2、读请求自己处理,写请求转发给Leader:Follower 可直接处理并返回客户端的读请求,同时会将写请求转发给 Leader 处理。

3、投票选举Leader:负责在 Leader 处理写请求时对请求进行投票。

Observer

1、维持心跳:一个 Zookeeper 集群可能同时存在多个 Observer,它会响应 Leader 的心跳。

2、读请求自己处理,写请求转发给Leader:Observer 可直接处理并返回客户端的读请求,同时会将写请求转发给 Leader 处理。

3、无投票权:不负责在 Leader 处理写请求时对请求进行投票。

角色与 Follower 类似,但是无投票权。Zookeeper 需保证高可用和强一致性,为了支持更多的客户端,需要增加更多 Server;Server 增多,投票阶段延迟增大,影响性能;引入 Observer,Observer 不参与投票; Observers 接受客户端的连接,并将写请求转发给 leader 节点; 加入更多 Observer 节点,提高伸缩性,同时不影响吞吐率。

Leader(一个,读写,写操作后数据同步到各个节点):同一时间集群总只允许有一个Leader,提供对客户端的读写功能,负责将数据同步至各个节点;
Follower(读写,选主):提供对客户端读功能,写请求则转发给Leader处理,当Leader崩溃失联之后参与Leader选举;
Observer(读写,选主):提供对客户端读功能,写请求则转发给Leader处理,与Follower不同的是但不参与Leader选举。

2.2.2 三个角色:选举阶段 + 广播阶段

zookeeper集群三个角色注意(选举阶段)

关于leader,有且只有一个,没有(启动没有/有了宕机)就选举,有了不需要了。

关于follower,在两种情况的选举下,拥有 投票权(可以贡献一票) 和 被选举权(可能被选举为leader)。

关于observer,在两种情况的选举下,无 投票权(无法贡献一票) 和 被选举权 (不可能被选举为leader)。

所以说,当leader不存在的时候(启动/宕机),是所有的follower来投票,是从follower中选举出一个超过半数follower同意的follower,来成为leader。所以,整个选举过程,和observer 没半毛钱关闭。

zookeeper集群三个角色注意(广播阶段)

关于leader,有且只有一个,用来处理写请求,是zookeeper集群中唯一一个处理写请求的节点,写操作完成后将数据同步到各个节点。

关于follower,在读写操作下,读操作自己处理(不过问leader节点),写请求发给leader,写请求第二阶段所有follower投票过半即可

关于observer,在读写操作下,读操作自己处理(不过问leader节点),写请求发给leader,写请求第二阶段投票没observer什么事。

所以说,由于observer没有 投票权 和 被选举权,选举时的过半数通过和写操作时的过半数ACK 都和它没关系,都是follower和leader的事。

比喻:leader是项目经理(技术攻关写请求)、follower是正式员工(平时干活读请求,投票和被选举)、observer是外包员工(平时干活读请求,无投票、无被选举)

2.2.3 observer存在的意义 + leader存在的意义 + follower存在的意义

问题:observer存在的意义(选举过程中,和observer没半毛钱关系,为什么需要observer)?

标准答案:增加observer节点,在不影响吞吐量的情况下,增加伸缩性;增加follower阶段,在影响吞吐量的情况下,增加伸缩性。

解释:Zookeeper 需保证高可用和强一致性,为了支持更多的客户端,需要增加更多 Server;Server包括两种,follower和observer(其实,leader也是Server,但是只有一个):

follower 增多,投票阶段延迟增大,影响性能(影响吞吐量);引入一种不参与投票的follower,就是observer,因为不参与投票(投票是一种延迟大的内部消耗),所以完全用来处理用户连接; Observers 接受客户端的连接,并将写请求转发给 leader 节点; 加入更多 Observer 节点,提高伸缩性,同时不影响吞吐率。

问题:leader存在的意义(为什么需要一个leader,为什么需要投票选举一个leader,投票是一个延迟大的内部消耗,为什么不去掉)

标准答案:

1、集群server的数据一致性:zookeeper是一个集群,集群是由多个server组成的,多个server要保证数据一致性,这是集群的要求,无法改变。

2、主从结构:然后,要保证多个server的数据一致性,就要灵活处理好 读写请求,就要做 主从结构,主从结构处理的两种方式

(1)主写从读,类似mysql集群读写分离,主库处理写请求(insert|update|delete),从库读(select),然后按时间间隔使用binlog日志将主库中的数据同步到从库,保证读到的数据是刚刚写入的(从库加入时,第一次根据binlog全量复制主库,以后根据binlog增量复制主库数据变化即可);优点:这一种方式,主库和从库,每个数据库都运用起来的,最大化集群吞吐量。

(2)主读写,从仅同步,类似kafka中的,kafka本身就是基于分布式架构的(不用像mysql额外做分布式),partition分区,部署在三个以上的broker中,生产消息和消费消息都在主partition,从partition仅仅同步,当主partition宕机后,才开始操作。

zookeeper使用的时类似mysql集群:
对于写操作:所有的写操作必须要通过 Leader 完成再由 Leader 将写操作广播给其它服务器,只要有超过半数节点(不包括 observer 节点)写入成功,该写请求就会被提交(类 2PC 协议)。
对于读操作:follower和observer 都可以直接处理并返回客户端的读请求,但是都会将 写请求转发给 Leader 处理。

问题:follower存在的意义(为什么需要一个follower)

标准答案:zookeeper 是分布式架构,follower是最基本的节点,具有 投票权 和 被选举权,唯一的leader节点从follower节点产生(observer没有被选举权,不可能变为leader)。至于observer,是没有 投票权 和 被选举权的 follower,增加observer节点,在不影响吞吐量的情况下,增加伸缩性。

2.3 Zookeeper四种状态 + ZAB四种状态(绑在一起看,就是一个选主过程)

2.3.1 Zookeeper集群的服务状态:四种

LOOKING:当节点认为群集中没有Leader,服务器会进入LOOKING状态,目的是为了查找或者选举Leader;

FOLLOWING:follower角色;

LEADING:leader角色;

OBSERVING:observer角色;

可以知道Zookeeper是通过自身的状态来区分自己所属的角色,来执行自己应该的任务。

金手指:从 “zookeeper分为选主阶段和广播阶段” 看 zookeeper 的四个状态和 ZAB 的四个状态
对于zookeeper的四个状态,如下:
looking仅在选主阶段出现,没有leader,所有节点都是looking状态
following是zookeeper在广播阶段的follower节点的状态,
leading是zookeeper在广播阶段的leader节点的状态,
observing是zookeeper在广播阶段的observer节点的状态。
对于ZAB的四个状态,实际上选主阶段的四种过程,如下:
ELECTION 选主: 集群进入选举状态,此过程会选出一个节点作为leader角色;
DISCOVERY 发现主确认主:连接上leader,响应leader心跳,并且检测leader的角色是否更改,通过此步骤之后选举出的leader才能执行真正职务;
SYNCHRONIZATION 数据同步:整个集群都确认leader之后,将会把leader的数据同步到各个节点,保证整个集群的数据一致性;
BROADCAST:过渡到广播状态,集群开始对外提供服务。

2.3.2 ZAB四种状态

Zookeeper给ZAB定义了四种状态,与Zookeeper的四种状态对应,下面有图解

ZAB状态作用:反应Zookeeper从选举到对外提供服务的过程中的四个步骤。

ZAB状态定义(枚举定义):ELECTION、DISCOVERY、SYNCHRONIZATION、BROADCAST

public enum ZabState {ELECTION,DISCOVERY,SYNCHRONIZATION,BROADCAST
}

ELECTION: 集群进入选举状态,此过程会选出一个节点作为leader角色;
DISCOVERY:连接上leader,响应leader心跳,并且检测leader的角色是否更改,通过此步骤之后选举出的leader才能执行真正职务;
SYNCHRONIZATION:整个集群都确认leader之后,将会把leader的数据同步到各个节点,保证整个集群的数据一致性;
BROADCAST:过渡到广播状态,集群开始对外提供服务。

2.3.3 ZAB协议四个阶段(整个选主过程只与followers和leader有关,observer没有选举权)

第一阶段,Leader election (选举阶段 - 选出准 Leader ):节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。只有到达 广播阶段(broadcast) 准 leader 才会成为真正的 leader。这一阶段的目的是就是为了选出一个准 leader,然后进入下一个阶段。

第二阶段,Discovery (发现阶段 - 接受提议、生成 epoch 、接受 epoch ):在这个阶段,followers 跟准 leader 进行通信,同步 followers最近接收的事务提议。这个一阶段的主要目的是发现当前大多数节点接收的最新提议,并且准 leader 生成新的 epoch,让 followers 接受,更新它们的 accepted Epoch。

一个 follower 只会连接一个 leader,如果有一个节点 f 认为另一个 follower p 是 leader,f 在尝试连接 p 时会被拒绝,f 被拒绝之后,它(指f)就会进入重新选举阶段,直到连接到它(指f)支持的leader。

第三阶段,Synchronization (同步阶段 - 同步 follower 副本 ):同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。只有当大多数节点都同步完成,准 leader 才会成为真正的 leader。follower 只会接收 zxid 比自己的 lastZxid 大的提议。

第四阶段,Broadcast(广播阶段 -leader 消息广播 ):到了这个阶段,Zookeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。

ZAB 提交事务并不像 2PC 一样需要全部 follower 都 ACK,只需要得到超过半数的节点的 ACK 就可以了。

2.3.4 ZAB协议的JAVA 实现(FLE-发现阶段和同步合并为Recovery Phase(恢复阶段))

协议的 Java 版本实现跟上面的定义有些不同,选举阶段使用的是 Fast Leader Election(FLE),它包含了选举的发现职责。因为 FLE 会选举拥有最新提议历史的节点作为 leader,这样就省去了发现最新提议的步骤。实际的实现将 发现阶段 和 同步合并为 Recovery Phase(恢复阶段)。所以,ZAB 的实现只有三个阶段:Fast Leader Election、Recovery Phase、Broadcast Phase。

三、Zookeeper选举过程(基于ZAB协议)when how how

zookeeper集群中,leader选举基于ZAB协议

zookeeper集群中,只能有一个leader节点,这个leader节点的选举(zookeeper启动时选举leader、leader宕机后选举leader)是基于zab协议来完成的。

zookeeper集群中,leader选举三个问题,如下:

第一,when何时:leader选举什么时候进行?

第二,how理论上,选举规则?

第三,how实践上,选择流程?

3.1 when何时,leader选举的两个时机

选举发生的时机Leader发生选举有两个时机:

1、zookeeper服务启动的时候当整个集群都没有leader节点会进入选举状态,如果leader已经存在就会告诉该节点leader的信息,自己连接上leader,整个集群不用进入选举状态。

2、zookeeper服务运行中,可能会出现各种情况,服务宕机、断电、网络延迟很高的时候leader都不能再对外提供服务了,所有当其他几点通过心跳检测到leader失联之后,集群也会进入选举状态。

3.2 how理论上,leader选举的三种选举规则

接上面,达到两种选举时机之一,开始选举。

zab协议是按照几个比较规则来进行投票的筛选,如果你的票比我更好,就修改自身的投票信息,改投你当leader。

下面代码是zookeeper投票比较规则:

/** We return true if one of the following three cases hold:* 1- New epoch is higher* 2- New epoch is the same as current epoch, but new zxid is higher* 3- New epoch is the same as current epoch, new zxid is the same*  as current zxid, but server id is higher.*/return ((newEpoch > curEpoch)|| ((newEpoch == curEpoch)&& ((newZxid > curZxid)|| ((newZxid == curZxid)&& (newId > curId)))));

对应上面代码的解释(两个节点之间使用比较的方法来选举,三种比较规则):

1、比较epoche纪元(zxid高32bit),如果其他节点的纪元比自己的大,选举epoch大的节点(理由:epoch 表示年代,epoch越大表示数据越新),其对应代码:(newEpoch > curEpoch)

2、比较 zxid, 如果纪元相同,就比较两个节点的zxid的大小,选举 zxid大的节点(理由:zxid 表示节点所提交事务最大的id,zxid越大代表该节点的数据越完整),其对应代码:(newEpoch == curEpoch) && (newZxid > curZxid)

3、比较 serviceId,如果 epoch和zxid都相等,就比较服务的serverId,选举 serviceId大的节点(理由: serviceId 表示机器性能,他是在配置zookeeper集群时确定的,所以我们配置zookeeper集群的时候可以把服务性能更高的集群的serverId设置大些,让性能好的机器担任leader角色)其对应代码 :(newEpoch == curEpoch) && ((newZxid == curZxid) && (newId > curId))

小结:epoche年代 zxid提交事务id表示数据完整 serviceId计算机性能。

3.3 how实践上,leader选举的选举流程:三个步骤

对于上图的解释(leader选举的):

步骤1:所有节点第一票先选举自己当leader,将投票信息广播出去;

步骤2:从队列中接受投票信息,根据三个比较规则,确定自己选举的leader,然后将更改后的投票信息第二次广播出去

步骤3:判断是否有超过一半的投票选举同一个节点,如果是选举结束根据投票结果设置自己的服务状态,选举结束,否则继续进入投票流程。

步骤1:选举自己并广播出去,每个节点第一个先选自己;
步骤2:接收广播,更新自己的选举的leader,再次广播自己的投票信息,每个节点接受其他 n-1 个节点;
步骤3:是否一个节点过半数,是了就行了,不是跳到步骤2,继续接收 n-1 节点。

问题:为什么zookeeper选举过程设计为一半通过?为什么不是 1/3 投票 或者 2/3 投票?

子问题1:为什么不是1/3而是1/2?

标准答案:为了确保leader的唯一性,一个follower节点获得了1/2票数,就不可能再有一个follower节点或者 1/2 票数,就唯一确定了这个follower为leader;如果设置为1/3,选出两个follower达到 1/3 票数,但是leader只有一个,选谁,还是要继续比较票数吗?太麻烦,还不如设置为 1/2 ,一步到位;同理,设置 1/4,选出 三个 follower大于 1/4,尴尬…

子问题2:为什么不是2/3而是1/2?

标准答案:节约选举成本,投票是一种延迟大的内部消耗,影响吞吐量,这就是为什么要引入 无投票权、无被选举权的 observer的原因,1/2已经能够确定唯一的leader,变为2/3 3/4 只会加大选举的循环次数,影响性能。

问题:为什么zookeeper写操作设计为一半ACK?为什么不是 1/3 ACK 或者 2/3 ACK ?

标准答案:没有什么太大理由,zookeeper就是这么设计,所以说zookeeper数据是非强一致性的,二阶段提交,第二阶段follower ACK数量过半就行(Ack确定和observer无关,无论是哪个节点 leader/follower/observer 收到写操作,都是发送给leader,follower过半ACK,leader就提交)。

关于zookeeper中的投票过半数(包含两层意思):
第一,选举过程中,一半的节点选举了,就好了
第二,广播过程中,两阶段提交,eader收到一半的follower发过来的ACK,就提交了(所以,zookeeper不是强一致性的)

问题:为什么说zookeeper的两阶段提交不是强一致性的?

回答:之前的mysql和oracle基于XA实现的分布式事务,底层两阶段提交是要所有的都ACK,三阶段提交也是要所有的都ACK,这才是强一致性的。

四、Zookeeper的广播过程(读写操作)

zookeeper集群对外提供服务(即读写操作)如何保证各个节点数据的一致性。

4.1 ZAB协议有序性 why what how

为什么需要zab需要保证有序性?why

标准答案:zookeeper的存储结构是类型目录结构,不保证有序性无法定位,会报错。有序性是zab协议必须要保证的一个很重要的属性,因为zookeeper是以类似目录结构的数据结构存储数据的,必须要求命名的有序性。比如一个命名a创建路径为/test,然后命名b创建路径为/test/123,如果不能保证有序性b命名在a之前,b命令会因为父节点不存在而创建失败。

问题2:有序性定义?what

标准答案:zab在广播状态中保证以下特征:

第一,可靠传递: 如果消息m由一台服务器传递,那么它最终将由所有服务器传递。

第二,全局有序: 如果一个消息a在消息b之前被一台服务器交付,那么所有服务器都交付了a和b,并且a先于b。

第三,因果有序: 如果消息a在因果上先于消息b并且二者都被交付,那么a必须排在b之前。

问题3:ZAB保证有序性实现?how

标准答案:zab协议的有序性保证是通过几个方面来体现的,如下:

第一,服务之前用TCP协议进行通讯,保证在网络传输中的有序性;

第二,节点之前都维护了一个FIFO的队列,保证全局有序性;

第三,通过全局递增的zxid保证因果有序性。

4.2 基础知识:两阶段提交(为zookeeper阶段准备)

关于两阶段提交,一共包括三块,分别是:
两阶段提交定义
两阶段提交流程
两阶段提交优缺点

两阶段提交定义:在分布式系统中,每一个机器节点虽然能够明确的知道自己在进行事物操作过程的结果是失败或成功,但却无法直接获取其他分布式节点的操作结果。所以,事物操作需要跨越多个分布式节点的时候,需要引入一个协调者统一调度所有节点的执行逻辑。

两阶段提交详细

阶段一:提交事物请求

1.1 leader发送:leader事物询问

协调者向所有的参与者发送事物内容,询问是否可以执行事物操作,并开始等待各参与者的响应。

1.2 follower/observer操作:follower/observer执行事物

各参与者节点执行事物操作,并将Undo和Redo信息记入事物日志中

1.3 follower/observer响应:各参与者follower/observer向协调者leader反馈事物询问的响应

如果参与者成功执行了事物操作,那么就反馈给协调者Yes响应,表示事物可以执行;如果参与者没有成功执行事物,那么就反馈给协调者No响应,表示事物不可以执行。

阶段二:执行事物请求

在阶段二中协调者会根据参与者的反馈情况来决定最终是否可以进行事物操作,其中有两种可能 Commit/Cancel。假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事物响应(zookeeper是过半就好)。

2.1 Leader发送:Leader发送提交请求

协调者向所有参与者节点发出Commit请求

2.2 follower/observer响应:follower/observer事物提交

参与者接收到Commit请求后,会正式执行事物提交操作,并在完成提交之后释放在整个事物执行期间占有的事物资源。

2.3 follower/observer发送:follower/observer反馈事物提交结果

参与者在完成事物提交之后,向协调者发送Ack消息。

2.4 leader响应:leader完成事物

协调者接收到所有参与者反馈的Ack消息后,完成事物。

二阶段提交优缺点

优点:原理简单,实现方便

缺点:同步阻塞,单点问题,脑裂,数据丢失(四个问题,在分布式事务中讲到过)。

4.3 zookeeper一个写请求的两阶段提交流程(和理论上的二阶段提交有点不同,有一半follower ack就好)

解释上图(zookeeper写请求的两阶段提交):

当收到客户端的写请求的时候会经历以下几个步骤:

步骤1(阶段一,Leader发出):Leader收到客户端的写请求,生成一个事务(Proposal),其中包含了zxid;

步骤2(阶段一,Leader发出):Leader开始广播该事务,需要注意的是所有节点的通讯都是由一个FIFO的队列维护的;

步骤3(阶段一,Leader发出):Follower接受到事务之后,将事务写入本地磁盘,写入成功之后返回Leader一个ACK;

步骤4(阶段二,Leader接收):Leader收到过半的ACK之后,开始提交本事务,并广播事务提交Commit信息

步骤5(阶段二,Leader接收):从节点开始提交本事务。

所以,zookeeper通过二阶段提交来保证集群中数据的一致性,因为只需要收到过半的ACK就可以提交事务,所以zookeeper的数据并不是强一致性。

五、四个状态流转(zookeeper四种服务状态 + ZAB四种步骤状态)

前面介绍了zookeeper服务状态有四种,ZAB状态也有四种。这里就简单介绍一个他们之间的状态流转,更能加深对zab协议在zookeeper工作流程中的作用。

对于上图的解释:

步骤1:服务在启动或者和leader失联之后服务状态转为LOOKING;

步骤2:如果leader不存在选举leader,如果存在直接连接leader,此时zab协议状态为ELECTION;

步骤3:如果有超过半数的投票选择同一台server,则leader选举结束,被选举为leader的server服务状态为LEADING,其他server服务状态为FOLLOWING/OBSERVING;

步骤4:所有server连接上leader,此时zab协议状态为DISCOVERY;

步骤5:leader同步数据给learner,使各个从节点数据和leader保持一致,此时zab协议状态为SYNCHRONIZATION;

步骤6:同步超过一半的server之后,集群对外提供服务,此时zab状态为BROADCAST。

每个 sever 首先给自己投票,然后用自己的选票和其他 sever 选票对比,权重大的胜出,使用权重较大的更新自身选票箱。具体选举过程如下:
1、每个 Server 启动以后都询问其它的 Server 它要投票给谁。对于其他 server 的询问,server 每次根据自己的状态都回复自己推荐的 leader 的 id 和上一次处理事务的 zxid(系统启动时每个 server 都会推荐自己)。
2、收到所有 Server 回复以后,就计算出 zxid 最大的那个 Server,并将这个 Server 相关信息设置成下一次要投票的 Server。
3、计算这过程中获得票数最多的的 server 为获胜者,如果获胜者的票数超过半数,则改server 被选为 leader。否则,继续这个过程,直到 leader 被选举出来。里程碑:选举阶段完成
4、leader 就会开始等待 server 连接。
5、Follower 连接 leader,将最大的 zxid 发送给 leader。
6、Leader 根据 follower 的 zxid 确定同步点。里程碑:发现阶段完成
7、选举阶段完成 Leader 同步后通知 follower 已经成为 uptodate 状态。里程碑:数据同步阶段完成
8、Follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了。里程碑:广播阶段开始

举例:目前有 5 台服务器,每台服务器均没有数据,它们的编号分别是 1,2,3,4,5,按编号依次启动,它们的选举过程如下:

1、服务器 1 启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器 1 的状态一直属于 Looking。

2、服务器 2 启动,给自己投票,同时与之前启动的服务器 1 交换结果,由于服务器 2 的编号大所以服务器 2 胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。

3、服务器 3 启动,给自己投票,同时与之前启动的服务器 1,2 交换信息,由于服务器 3 的编号最大所以服务器 3 胜出,此时投票数正好大于半数,所以服务器 3 成为领导者,服务器1,2 成为小弟。

4、服务器 4 启动,给自己投票,同时与之前启动的服务器 1,2,3 交换信息,尽管服务器 4 的编号大,但之前服务器 3 已经胜出,所以服务器 4 只能成为小弟。

5、服务器 5 启动,后面的逻辑同服务器 4 成为小弟。

Zookeeper整个工作流程步骤如下(选主阶段+广播阶段):
步骤一:Zookeeper 的核心是原子广播,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。Zab 协议有两种模式,它们分别是恢复模式和广播模式。
步骤二:当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 server 的完成了和 leader 的状态同步以后,恢复模式就结束了。
步骤三:状态同步保证了 leader 和 server 具有相同的系统状态。
步骤四:一旦 leader 已经和多数的 follower 进行了状态同步后,他就可以开始广播消息了,即进入广播状态。这时候当一个 server 加入 zookeeper 服务中,它会在恢复模式下启动,发现 leader,并和 leader 进行状态同步。待到同步结束,它也参与消息广播。Zookeeper服务一直维持在 Broadcast 状态,直到 leader 崩溃了或者 leader 失去了大部分的followers 支持。
步骤五:广播模式需要保证 proposal 被按顺序处理,因此 zk 采用了递增的事务 id 号(zxid)来保证。所有的提议(proposal)都在被提出的时候加上了 zxid。
步骤六:实现中 zxid 是一个 64 为的数字,它高 32 位是 epoch 用来标识 leader 关系是否改变,每次一个 leader 被选出来,它都会有一个新的 epoch。低 32 位是个递增计数。
步骤七:当 leader 崩溃或者 leader 失去大多数的 follower,这时候 zk 进入恢复模式,恢复模式需要重新选举出一个新的 leader,让所有的 server 都恢复到一个正确的状态。

六、面试金手指

6.1 起手式,ZAB + Zookeeper 基础

6.1.1 ZAB定义 + ZAB作用 + Zxid

ZAB定义:ZAB 英文全称为 Zookeeper Atomic Broadcast,译为 Zookeeper 原子广播协议;
ZAB作用:当ZooKeeper leader节点宕机时,用于崩溃恢复的原子广播协议,它保证了zookeeper集群的 数据一致性命令全局有序性

ZXID(64bit) = epoch(32bit) + counter(32bit)
Zxid定义:Zxid是一个long型(64位)整数,分为两部分:纪元(epoch)部分和计数器(counter)部分,是一个全局有序的数字。
epoch定义:epoch表示当前集群所属的哪个leader,leader的选举就类似一个朝代的更替,你前朝的剑不能斩本朝的官,用epoch代表当前命令的有效性。
counter定义:counter是一个递增的数字。

6.1.2 Zookeeper集群中的三个角色(重点:leader follower observer)

6.1.2.1 三个角色:leader follower observer

Leader:同一时间集群总只允许有一个Leader,提供对客户端的读写功能,负责将数据同步至各个节点;
Follower:提供对客户端读功能,写请求则转发给Leader处理,当Leader崩溃失联之后参与Leader选举;
Observer:与Follower不同的是但不参与Leader选举。

6.1.2.2 三个角色:选举阶段 + 广播阶段

zookeeper集群三个角色注意(选举阶段)
关于leader,有且只有一个,没有(启动没有/有了宕机)就选举,有了不需要了。
关于follower,在两种情况的选举下,拥有 投票权(可以贡献一票) 和 被选举权(可能被选举为leader)。
关于observer,在两种情况的选举下,无 投票权(无法贡献一票) 和 被选举权 (不可能被选举为leader)。
所有说,当leader不存在的时候(启动/宕机),是所有的follower来投票,是从follower中选举出一个超过半数follower同意的follower,来成为leader。所以,整个选举过程,和observer 没半毛钱关闭。

zookeeper集群三个角色注意(广播阶段)
关于leader,有且只有一个,用来处理写请求,是zookeeper集群中唯一一个处理请求的节点。
关于follower,在读写操作下,读操作自己处理(不过问leader节点),写请求发给leader,写请求第二阶段所有follower投票过半即可
关于observer,在读写操作下,读操作自己处理(不过问leader节点),写请求发给leader,写请求第二阶段投票没observer什么事。

所以说,由于observer没有 投票权 和 被选举权,选举时的过半数通过和写操作时的过半数ACK 都和它没关系,都是follower和leader的事。

比喻:leader是项目经理(技术攻关写请求)、follower是正式员工(平时干活读请求,投票和被选举)、observer是外包员工(平时干活读请求,无投票、无被选举)

6.1.2.3 observer存在的意义 + leader存在的意义 + follower存在的意义

问题:observer存在的意义(选举过程中,和observer没半毛钱关系,为什么需要observer)?
标准答案:增加observer节点,在不影响吞吐量的情况下,增加伸缩性;
增加follower阶段,在影响吞吐量的情况下,增加伸缩性。
解释:Zookeeper 需保证高可用和强一致性,为了支持更多的客户端,需要增加更多 Server;
Server包括两种,follower和observer(其实,leader也是Server,但是只有一个)
follower 增多,投票阶段延迟增大,影响性能(影响吞吐量);引入一种不参与投票的follower,就是observer,因为不参与投票(投票是一种延迟大的内部消耗),所以完全用来处理用户连接; Observers 接受客户端的连接,并将写请求转发给 leader 节点; 加入更多 Observer 节点,提高伸缩性,同时不影响吞吐率。

问题:leader存在的意义(为什么需要一个leader,为什么需要投票选举一个leader,投票是一个延迟大的内部消耗,为什么不去掉)
标准答案:
1、集群server的数据一致性:zookeeper是一个集群,集群是由多个server组成的,多个server要保证数据一致性,这是集群的要求,无法改变。
2、主从结构:然后,要保证多个server的数据一致性,就要灵活处理好 读写请求,就要做 主从结构,主从结构处理的两种方式
(1)主写从读,类似mysql集群读写分离,主库处理写请求(insert|update|delete),从库读(select),然后按时间间隔使用binlog日志将主库中的数据同步到从库,保证读到的数据是刚刚写入的(从库加入时,第一次根据binlog全量复制主库,以后根据binlog增量复制主库数据变化即可);优点:这一种方式,主库和从库,每个数据库都运用起来的,最大化集群吞吐量。
(2)主读写,从仅同步,类似kafka中的,kafka本身就是基于分布式架构的(不用像mysql额外做分布式),partition分区,部署在三个以上的broker中,生产消息和消费消息都在主partition,从partition仅仅同步,当主partition宕机后,才开始操作。
zookeeper使用的时类似mysql集群的,
对于写操作:所有的写操作必须要通过 Leader 完成再由 Leader 将写操作广播给其它服务器,只要有超过半数节点(不包括 observer 节点)写入成功,该写请求就会被提交(类 2PC 协议)。
对于读操作:follower和observer 都可以直接处理并返回客户端的读请求,但是都会将 写请求转发给 Leader 处理。

问题:follower存在的意义(为什么需要一个follower)
标准答案:zookeeper 是分布式架构,follower是最基本的节点,具有 投票权 和 被选举权,唯一的leader节点从follower节点产生(observer没有被选举权,不可能变为leader)
至于observer,是没有 投票权 和 被选举权的 follower,增加observer节点,在不影响吞吐量的情况下,增加伸缩性。

6.2 Zookeeper的选举过程

zookeeper集群中,leader选举基于ZAB协议
zookeeper集群中,只能有一个leader节点,这个leader节点的选举(zookeeper启动时选举leader、leader宕机后选举leader)是基于zab协议来完成的。

zookeeper集群中,leader选举三个问题,
第一,when何时:leader选举什么时候进行?
zookeeper启动的时候从follower中选举leader + 原有leader宕机后从follower中选举leader.
第二,how理论上,选举规则
1、比较 epoche纪元(zxid高32bit),如果其他节点的纪元比自己的大,选举 epoch大的节点(理由:epoch 表示年代,epoch越大表示数据越新)代码:(newEpoch > curEpoch);
2、比较 zxid, 如果纪元相同,就比较两个节点的zxid的大小,选举 zxid大的节点(理由:zxid 表示节点所提交事务最大的id,zxid越大代表该节点的数据越完整)代码:(newEpoch == curEpoch) && (newZxid > curZxid);
3、比较 serviceId,如果 epoch和zxid都相等,就比较服务的serverId,选举 serviceId大的节点(理由: serviceId 表示机器性能,他是在配置zookeeper集群时确定的,所以我们配置zookeeper集群的时候可以把服务性能更高的集群的serverId设置大些,让性能好的机器担任leader角色)代码 :(newEpoch == curEpoch) && ((newZxid == curZxid) && (newId > curId))。
第三,how实践上,选举leader流程
步骤1(每个节点第一个先选自己):所有节点第一票先选举自己当leader,将投票信息广播出去;
步骤2(每个节点接受其他 n-1 个节点):从队列中接受投票信息;
步骤3(根据三个比较规则,确定自己选举的leader,然后第二次广播出去):按照三条规则判断是否需要更改投票信息,将更改后的投票信息再次广播出去;
步骤4:(是否一个节点过半数,是了就行了,不是跳到步骤2,继续接收 n-1 节点)判断是否有超过一半的投票选举同一个节点,如果是选举结束根据投票结果设置自己的服务状态,选举结束,否则继续进入投票流程。

问题:为什么zookeeper选举过程设计为一半通过?为什么不是 1/3 投票 或者 2/3 投票?
第一,为什么不是1/3而是1/2?标准答案:为了确保leader的唯一性,一个follower节点获得了1/2票数,就不可能再有一个follower节点或者 1/2 票数,就唯一确定了这个follower为leader;如果设置为1/3,选出两个follower达到 1/3 票数,但是leader只有一个,选谁,还是要继续比较票数吗?太麻烦,还不如设置为 1/2 ,一步到位;同理,设置 1/4,选出 三个 follower大于 1/4,尴尬…
第二,为什么不是2/3而是1/2?标准答案:节约选举成本,投票是一种延迟大的内部消耗,影响吞吐量,这就是为什么要引入 无投票权、无被选举权的 observer的原因,1/2已经能够确定唯一的leader,变为2/3 3/4 只会加大选举的循环次数,影响性能。
问题:为什么zookeeper写操作设计为一半ACK?为什么不是 1/3 ACK 或者 2/3 ACK ?
标准答案:没有什么太大理由,zookeeper就是这么设计,所以说zookeeper是非强一致性的,二阶段提交,第二阶段follower ACK数量过半就行(Ack确定和observer无关,无论是哪个节点 leader/follower/observer 收到写操作,都是发送给leader,follower过半ACK,leader就提交)。

关于zookeeper中的投票过半数(包含两层意思):
第一,选举过程中,一半的节点选举了,就好了
第二,广播过程中,两阶段提交,eader收到一半的follower发过来的ACK,就提交了(所以,zookeeper不是强一致性的)

问题:为什么说zookeeper的两阶段提交不是强一致性的?
回答:之前的mysql和oracle基于XA实现的分布式事务,底层两阶段提交是要所有的都ACK,三阶段提交也是要所有的都ACK,这才是强一致性的。

6.3 Zookeeper的广播过程

6.3.1 ZAB协议有序性

有序性定义? + ZAB保证有序性实现?
有序性定义?
标准答案
:zab在广播状态中保证以下特征
第一,可靠传递: 如果消息m由一台服务器传递,那么它最终将由所有服务器传递。
第二,全局有序: 如果一个消息a在消息b之前被一台服务器交付,那么所有服务器都交付了a和b,并且a先于b。
第三,因果有序: 如果消息a在因果上先于消息b并且二者都被交付,那么a必须排在b之前。
ZAB保证有序性实现?
标准答案
:zab协议的有序性保证是通过几个方面来体现的,
第一,服务之前用TCP协议进行通讯,保证在网络传输中的有序性;
第二,节点之前都维护了一个FIFO的队列,保证全局有序性;
第三,通过全局递增的zxid保证因果有序性。

为什么需要zab需要保证有序性?
标准答案
:zookeeper的存储结构是类型目录结构,不保证有序性无法定位,会报错。
有序性是zab协议必须要保证的一个很重要的属性,因为zookeeper是以类似目录结构的数据结构存储数据的,必须要求命名的有序性。比如一个命名a创建路径为/test,然后命名b创建路径为/test/123,如果不能保证有序性b命名在a之前,b命令会因为父节点不存在而创建失败。

6.3.2 基础知识:两阶段提交(为zookeeper阶段准备)

基础知识:两阶段提交(定义 + 流程 + 优缺点)
两阶段提交定义:在分布式系统中,每一个机器节点虽然能够明确的知道自己在进行事物操作过程的结果是失败或成功,但却无法直接获取其他分布式节点的操作结果,所以,事物操作需要跨越多个分布式节点的时候,需要引入一个协调者统一调度所有节点的执行逻辑。
两阶段提交详细
阶段一:提交事物请求
1.1 leader事物询问
协调者向所有的参与者发送事物内容,询问是否可以执行事物操作,并开始等待各参与者的响应。
1.2 follower/observer执行事物
各参与者节点执行事物操作,并将Undo和Redo信息记入事物日志中
1.3 各参与者follower/observer向协调者leader反馈事物询问的响应
如果参与者成功执行了事物操作,那么就反馈给协调者Yes响应,表示事物可以执行;如果参与者没有成功执行事物,那么就反馈给协调者No响应,表示事物不可以执行。
阶段二:执行事物请求
在阶段二中协调者会根据参与者的反馈情况来决定最终是否可以进行事物操作,其中有两种可能 Commit/Cancel。假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事物响应(zookeeper是过半就好)。
2.1 Leader发送提交请求
协调者向所有参与者节点发出Commit请求
2.2 follower/observer事物提交
参与者接收到Commit请求后,会正式执行事物提交操作,并在完成提交之后释放在整个事物执行期间占有的事物资源。
2.3 follower/observer反馈事物提交结果
参与者在完成事物提交之后,向协调者发送Ack消息。
2.4 leader完成事物
协调者接收到所有参与者反馈的Ack消息后,完成事物。
二阶段提交优缺点
优点:原理简单,实现方便
缺点:同步阻塞,单点问题,脑裂,数据丢失(四个问题,在分布式事务中讲到过)。

6.3.3 一个写请求的两阶段提交流程

zookeeper写请求的两阶段提交(当收到客户端的写请求的时候会经历以下几个步骤):
步骤1(阶段一,Leader发出):Leader收到客户端的写请求,生成一个事务(Proposal),其中包含了zxid;
步骤2(阶段一,Leader发出):Leader开始广播该事务,需要注意的是所有节点的通讯都是由一个FIFO的队列维护的;
步骤3(阶段一,Leader发出):Follower接受到事务之后,将事务写入本地磁盘,写入成功之后返回Leader一个ACK;
步骤4(阶段二,Leader接收):Leader收到过半的ACK之后,开始提交本事务,并广播事务提交Commit信息
步骤5(阶段二,Leader接收):从节点开始提交本事务。

所以,zookeeper通过二阶段提交来保证集群中数据的一致性,因为只需要收到过半的ACK就可以提交事务,所以zookeeper的数据并不是强一致性。

6.4 Zookeepr四状态 + ZAB四状态

金手指:从 “zookeeper分为选主阶段和广播阶段” 看 zookeeper 的四个状态和 ZAB 的四个状态
对于zookeeper的四个状态
looking仅在选主阶段出现
following是zookeeper在广播阶段的follower节点的状态,
leading是zookeeper在广播阶段的leader节点的状态,
observing是zookeeper在广播阶段的observer节点的状态。
对于ZAB的四个状态,实际上选主阶段的四种过程
ELECTION: 集群进入选举状态,此过程会选出一个节点作为leader角色;
DISCOVERY:连接上leader,响应leader心跳,并且检测leader的角色是否更改,通过此步骤之后选举出的leader才能执行真正职务;
SYNCHRONIZATION:整个集群都确认leader之后,将会把leader的数据同步到各个节点,保证整个集群的数据一致性;
BROADCAST:过渡到广播状态,集群开始对外提供服务。

七、尾声

zookeeper的ZAB协议,完成了。

天天打码,天天进步!!!

Zookeeper,集群管理之独孤求败相关推荐

  1. solr集群搭建,zookeeper集群管理

    1. 第一步 把solrhome中的配置文件上传到zookeeper集群.使用zookeeper的客户端上传. 客户端命令位置:/root/solr-4.10.3/example/scripts/cl ...

  2. 好程序员大数据技术分享:Zookeeper集群管理与选举

    为什么80%的码农都做不了架构师?>>>    大数据技术的学习,逐渐成为很多程序员的必修课,因为趋势也是因为自己的职业生涯.在各个技术社区分享交流成为很多人学习的方式,今天很荣幸找 ...

  3. 大数据培训:Zookeeper集群管理与选举

    1.集群机器监控 这通常用于那种对集群中机器状态,机器在线率有较高要求的场景,能够快速对集群中机器变化作出响应.这样的场景中,往往有一个监控系统,实时检测集群机器是否存活.过去的做法通常是:监控系统通 ...

  4. java集群_JAVA架构师学习:实践ZooKeeper 应用场景与集群管理,辛勤总结

    ZooKeeper 典型的应用场景 Zookeeper 从设计模式角度来看,是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生 ...

  5. Solr集群搭建,zookeeper集群搭建,Solr分片管理,Solr集群下的DataImport,分词配置。...

    1   什么是SolrCloud SolrCloud(solr 云)是Solr提供的分布式搜索方案,当你需要大规模,容错,分布式索引和检索能力时使用 SolrCloud.当一个系统的索引数据量少的时候 ...

  6. Solr集群搭建,zookeeper集群搭建,Solr分片管理,Solr集群下的DataImport,分词配置。

    1   什么是SolrCloud SolrCloud(solr 云)是Solr提供的分布式搜索方案,当你需要大规模,容错,分布式索引和检索能力时使用 SolrCloud.当一个系统的索引数据量少的时候 ...

  7. 架构设计 | 分布式系统调度,Zookeeper集群化管理

    本文源码:GitHub·点这里 || GitEE·点这里 一.框架简介 1.基础简介 Zookeeper基于观察者模式设计的组件,主要应用于分布式系统架构中的,统一命名服务.统一配置管理.统一集群管理 ...

  8. 启动zookeeper_架构设计 | 分布式系统调度,Zookeeper集群化管理

    一.框架简介 1.基础简介 Zookeeper基于观察者模式设计的组件,主要应用于分布式系统架构中的,统一命名服务.统一配置管理.统一集群管理.服务器节点动态上下线.软负载均衡等场景. 知了一笑:Li ...

  9. 13、Zookeeper 分布式集群管理技术

    1.Zookeeper 简介 Zookeeper 分布式服务框架主要是用来解决分布式应用中经常遇到的一些数据管理问题,提供分布式.高可用性的协调服务能力,在 FusionInsight 集群中主要用途 ...

  10. Zookeeper集群部署和使用

    Zookeeper 由 Apache Hadoop 的 Zookeeper 子项目发展而来,Google Chubby的一个开源实现.它是一个分布式应用程序协调服务,提供的功能包括:配置管理,名字服务 ...

最新文章

  1. linux 安装php 5.5_Linux下yum升级安装PHP 5.5
  2. HDU - 5877 Weak Pair 2016 ACM/ICPC 大连网络赛 J题 dfs+树状数组+离散化
  3. 入门训练 Fibonacci数列-python实现
  4. springboot 应用中静态资源下载
  5. python-windows环境安装
  6. android开源许可证
  7. WebRTC视频编解码器性能评估
  8. maven mirror 优先级
  9. python 格式化工具_推荐一个小而美的 Python 格式化工具
  10. java 读取resources_java读取Resources下文件
  11. Memory Networks论文串烧
  12. srand函数_SUDA-CS考研复试常用函数
  13. 【python】获取http响应
  14. Raki的读paper小记:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
  15. 1.4.1用空间向量研究直线、平面的位置关系教学设计
  16. 等级保护第三级安全扩展要求笔记(2019)
  17. Matplotlib之散点图绘制
  18. ai怎么做盒子效果图_AI教程:打造一个逼真的包装盒
  19. BUCK电感工作模式
  20. 小红书API根据关键词取商品列表,Onebound数据

热门文章

  1. HTML渐变背景不重复,在身体上设置的CSS3渐变背景不会拉伸,而是重复?
  2. linux 搭建技术博客,Linux NTP服务器搭建精讲
  3. 2022低压电工考试题及答案
  4. 2021年G3锅炉水处理模拟考试及G3锅炉水处理考试试题
  5. Excel如何把同类数据合并到同一单元格
  6. 服务器的部署与Web项目的发布
  7. Hi3516开发笔记(四):Hi3516虚拟机编译uboot、kernel、roofts和userdata以及分区表
  8. 基于Java的超市积分管理系统(附:论文 源码 课件)
  9. php如何实时显示弹幕,斗鱼弹幕 PHP 实现
  10. 桌面图标出现混乱,图标文件混乱无法显示怎么修复