分布式相关知识点

289 阅读31分钟

分布式系统

分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。

举例

分布式文件系统: 出名的有 Hadoop 的HDFS ,还有 google的 GFS , 淘宝的 TFS 等

分布式缓存系统:memcache , hbase , mongdb 等

分布式数据库:MySQL , Mariadb, PostgreSQL 等

分布式和集群的区别

(1) 分布式是指一个系统拆分成多个模块, 每个模块在不同的机器上

(2) 集群是对于一个模块, 创建多个实例, 每个实例是一样的, 同样部署在不同的机器上

分布式系统的问题

(1) 一旦系统是分布式的了, 就代表拆分成了不同的模块, 不同模块之间肯定有数据同步的问题, 业务必然会这样, 例如我在模块A修改了一个值, 然后模块B也要修改一个值, 那么如何保证A 和 B一起修改成功, 即分布式事务?

(2) 例如是个分布式文件系统, 我修改了一个节点中的值, 其它节点中的值, 应该同样被修改, 那么如何保证, 其它节点的数据也被修改了呢?

CAP

CAP 定理(CAP theorem)指出对于一个分布式系统来说,当设计读写操作时,只能能同时满足以下三点中的两个:

  • 一致性(Consistence) : 所有节点访问同一份最新的数据副本
  • 可用性(Availability): 非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应)。
  • 分区容错性(Partition tolerance) : 分布式系统出现网络分区的时候,仍然能够对外提供服务。

Partition tolerance 分区容错

就是容错性, 就是允许分布式系统中的一些节点, 在某些时候, 是断开连接, 无法正常访问的.

一个分布式系统里面,节点组成的网络本来应该是连通的。然而可能因为一些故障,使得有些节点之间不连通了,整个网络就分成了几块区域。数据就散布在了这些不连通的区域中。这就叫分区。

当你一个数据项只在一个节点中保存,那么分区出现后,和这个节点不连通的部分就访问不到这个数据了。这时分区就是无法容忍的。

提高分区容忍性的办法就是一个数据项复制到多个节点上,那么出现分区之后,这一数据项就可能分布到各个区里。容忍性就提高了。

Availability 可用性

分布式系统对外的入口中, 非故障的节点必须对请求作出响应.

一致性(Consistence

所有节点访问同一份最新的数据副本

只有AP和CP

分布式系统总会出现部分节点网络不可用, 导致网络分区的情况, 因此必须要分区容错性, 如果你数据只保存在一个节点, 一旦该节点失去连接, 那么整个系统就不可用了, 所以是不能接受的.

那么一旦满足了P, A和C就无法同时满足.

因为如果要保持一致性C, 那么在所有节点同步数据的过程中, 服务是不应该提供服务的.

如果要一直满足可用性A, 那么没有办法能够一瞬间完成所有数据的同步.

但是大多数分布式系统, 都会在满足P的前提下, 在A和C之间作出一些折中.

两段式提交

采用两段式提交的分布式集群, 一般存在一个协调者, 协调者负责接收client的命令

(1) 协调者向分布式集群内所有节点发布事务执行的命令, 各节点分别执行, 但是不提交, 执行完毕后, 返回执行结果

(2) 如果所有节点都返回了成功, 那么协调者发送commit命令, 然后返回给client返回成功, 之后所有节点收到commit命令分别提交.

如果有节点一一直没返回成功, 那么等待一段时间后, 协调者向所有节点发布回滚的命令

缺点

  • (1) 执行成功的时间, 受执行最慢的一个节点的速度限制, 可能会很慢
  • (2) 需要所有人都执行成功才能提交
  • (3) 如果协调者发布提交命令, 但是其它节点在接受提交命令的时候, 出现了问题, 并没有真正提交, 就出现了问题
  • (4) 协调者如果挂了, 需要想办法选出新的协调者

三段式提交

如果网络情况正常, 两段式提交就能完成数据一致性的保证, 但是三阶段提交, 多增加一个阶段.

(1) 协调者询问各个节点能否正常执行事务, 节点收到后, 对自己的系统状况进行一个判断, 这个是轻量级的操作, 每个节点不执行任何内容, 所以应该很快

(2) 收到全部节点的应答后(有超时机制), 如果全部成功, 则发布事务执行命令, 节点收到命令后执行事务, 当协调者收到所有节点执行成功的结果后(会有超时限制), 进入下一阶段, 否则回滚

(3) 发布提交命令, 所有节点提交.

缺点

(1) 相当于在两对式提交前, 加了一个轻量级的询问, 能够快速感知节点状态.

拜占庭问题

背景:拜占庭派10支军队去围攻一个强大的敌人,至少6支军队同时进攻才可以取胜,否则不进攻。

前提: 所有好将军都是想进攻的, 如果没有叛徒, 进攻是必胜的.

难题:其中一些将军是叛徒,会发布假消息或者相反的进攻意图。

目的:忠诚将军远程协商最终达成一致。

解决方案就是, 每个将军向别的将军表明自己的意图, 每个将军都能收到所有其它将军的意图, 当收到半数以上的将军表明要进攻时, 忠诚将军就会真的做出进攻, 否则就撤退. 叛徒将军不会根据投票结果行动.

所有忠诚将军都发出1, 表明进攻

所有叛徒都发出1或者0

假设所有将军数量为n, 叛徒数量为m, 当n >= 3 * m + 1的时候, 通过这种互相通知消息的方式, 可以保证所有忠诚将军的行动是一致的, 要么是进攻, 要么是撤退. 即投票的结果在所有将军处的结论是一致(即半数以上的结果是确定的), 只不过忠诚将军会根据投票结果采取行动, 而叛徒不会.

需要注意的一点是,拜占庭问题最后取得的共识有可能是进攻,也有可能是不进攻,最终目的不是为了胜利,而是为了统一行动,不让忠将因为错误信息而牺牲。

paxos算法

就是针对拜占庭问题提出的一种算法.

该算法是一种分布式系统中具有高度容错性保持一致性的算法, Google Chubby的作者Mike Burrows说过,世上只有一种一致性算法,那就是Paxos,所有其他一致性算法都是Paxos算法的不完整版。

有一点需要明确,Paxos的本质是确定值,而不是确定一个“正确”的值,什么意思?Paxos协议只是一个工具,它并不在意值的“正确性“,只在意“一致性”也就是所谓的“共识”。假设一个分布式数据库用Paxos系统来保证一致性,它收到很多个proposals,有些proposals要求a写入“1+1=2”,而有的是要求a写入“1+1=3”,那么Paxos也只能用来保证其系统内的状态机写入了同一个proposal,从而使得让所有节点状态一致(无论写入的是1+1=2”还是“1+1=3”)。

因为paxos在多个值同时提出的时候, 会保证最后所有节点一定是其中的某个值, 这才是该算法的意义, 而并不保证最后的值一定是正确的那个值. 因此paxos算法更像一个选主算法.

算法针对的场景

在常见的分布式系统中,总会发生诸如机器宕机或网络异常(包括消息的延迟、丢失、重复、乱序,还有网络分区)(也就是会发生异常的分布式系统)等情况。Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致。也可以理解成分布式系统中达成状态的一致性。

算法中的名词

Proposer: 提议者, 即提案提出者, 可以是多个, 但是为了解决活锁问题, 通常会从多个提议者中选出一个, 作为真正的提案提出者.

Acceptor: 拥有投票权的人, 也是提案接受者, 主要目的就是协助Proposer完成提案的确定, 可以解决拜占庭问题(n/2的情况). 同时对于达成共识的提案, 存储其数据.

Learners: 不参与提案的过程, 只负责在Acceptor接受到提案确定的请求后, 同步相应的数据

Proposal: 提案 由提案编号 + 提案值组成, 通常写成<N, V> N代表提案编号, V代表提案值(也就是分布式系统期望达成一致的值)

Basic Paxos

my.oschina.net/u/150175/bl…

intheworld.win/2017/10/12/…

假设有一个client发出数据变更的请求, 提议者作为分布式数据存储集群的入口, 有提议者发起提议, 集群中其它机器就是acceptor, 他们之间就一起遵循Paxos算法.

如果只有一个提议者提出一个提议, 朴素Paxos算法其实没必要这么设计.

只有出现多个提议的时候, Paxos算法魅力才能体现.

特点归纳如下

  • 一个或者多个服务器发起提议
  • 系统会在一个被选择的提议值上达成一致
  • 只有一个提议值会被选择

主要流程

假设当前的提案编号是Nx

阶段1: 主要目的就是提议者和拥有投票权的人一起协商 采用哪个提议值(promise的过程) (1) Proposer选择一个提案编号Nx, 然后向半数以上的Acceptor发送编号为Nx的Prepare请求

(a) 提案编号必须是全局自增的, 不管之前提议者是不是这个Proposer, 他们生成订单提案编号必须是全局唯一且自增的.
(b) 只发送半数以上是因为只需要半数的acceptor有响应即可, 因此半数以上, 理论上是足够的, 为什么不发全部, 是因为没必要, 发送请求毕竟也要耗费资源, 至于是半数多多少, 你可以自己设定。

(2) 如果一个Acceptor收到一个编号为Nx的Prepare请求,如果小于它已经响应过的prepare请求的提案编号maxN(注意这里指的响应是阶段1的响应),不回应。 若Nx大于该Acceptor的maxN,那么它就会将它已经处理过的提案(即经过第二阶段accept的提案)的编号中的最大的编号以及提议值f返回. 该过程是promise过程.

同时更新maxN = Nx, 这样如果该接受者再收到比Nx小的提案就不会处理了.

阶段2: 将提议值推广给大家

(1) 如果一个Proposer收到半数以上Acceptor对编号为Nx的响应,那么它就会发送一个针对[Nx,V]提案的Accept请求给所有的Acceptor。注意:V就是收到的响应中编号最大的提案的value(在这里都是同一个提议值),如果响应中不包含任何提案,那么V就由Proposer自己决定。(例如第一轮提案的时候, 提议者将提议值设置成法令的内容)

(2) 如果Acceptor收到一个针对编号为Nx的提案的Accept请求,Nx >= maxN,它就接受该提案。 此时接受者的maxN可能比Nx大的, 例如又有一个提议者发出了提案Ny > Nx的, 并且这个接受者在阶段1响应了Ny, 此时的maxN就是Ny, 是比Nx大的.

当proposer没有收到过半的回应,那么他会重新进入第一阶段,递增提案号,重新提出prepare请求。 (无论是没收到半数prepare请求的回应, 还是半数accept请求的回应)

提议何时结束

Proposer在阶段2中, 向所有acceptor发出accept请求, 然后半数以上的acceptor接受了提案并且响应的话, 那么对于提议者来说此次提议就结束了, 否则再次发起一轮新的提案.

看样子似乎不是所有accept都接受了提案啊

是的, 没有接受到提案的acceptor的数据还是旧的或者没有数据, 这是Paxos算法允许的情况. 正是因为允许集群中有不可用节点的存在, 所以可以有节点没有完成更新, 除了当时不可用的节点, 其它节点一定是完成了更新的

算法acceptor为什么要拒绝低版本的提案的accept请求

因为算法每次的提议值, 都是尽量采取高版本提案的值, 此时如果某个低版本提案的accept请求, 因为网络原因, 较晚到来, 如果接受了的话, 就会造成某个节点数据不一致的情况.

为什么半数以上投票完成即可?

因为是具有高度容错性, 所以不需要全部节点都完成投票

阶段1的意义

保证系统在任一时刻, 只有一个多数派的情况, 因为每个acceptor都会做出promise.

阶段2第一步的意义

一旦有一个提案得到了多数派的任何, 那么你再提出再多的提案, 也不会改变整个集群的节点值, 这也说明了该算法确实是为了让集群迅速的达到某个同步值, 而不是所谓正确的值

为什么阶段2使用的是返回的最大编号的提案值?

假设集群中所有acceptor在一次提案的过程中, 数目是不变的.

首先有个前置条件:如果一个值(v0)被集群多数派accept,假设这个值对应的版本号为n,那么对于集群中其余的那部分少数派来说,他们要么还没有accept过值,要么accept过另一个值v1,但是v1对应的版本号一定是小于n的。

假设少数派中存在已accept的版本号>n的提案,设为nx,那么nx之所以能够被accept,必定因为之前被多数派promise过,设这个promise nx的集合为S0,同时设accept n的acceptors集合为S1,S0和S1都是多数派,产生交集S2。

对于S2里面的acceptors来说,它们既要promise nx 又要 accept n,这是矛盾的、不可能实现的。

所以前置条件成立.

活锁现象

假设有提议者A和B,

A发出提议, 度过阶段1, 正准备阶段2发送accept请求, 此时提议者B发出提议(提案号+1), 大多数acceptor promise了, 然后A提议者的accept请求到达, 不会收到半数以上的应答, 此时B准备进行阶段2, A又重新提出提议(提案号+1), 大多数acceptor promis了, 那么B也不会收到半数以上应答. A和B就轮流提议, 最后都无法让所有节点对某一个值达成一致.

为了解决活锁, 最好是由一个提议者发出提议.

Raft算法

raft是工程上使用较为广泛的强一致性、去中心化、高可用的分布式协议。在这里强调了是在工程上,因为在学术理论界,最耀眼的还是大名鼎鼎的Paxos。

但Paxos是:少数真正理解的人觉得简单,尚未理解的人觉得很难,大多数人都是一知半解。本人也花了很多时间、看了很多材料也没有真正理解。

直到看到raft的论文,两位研究者也提到,他们也花了很长的时间来理解Paxos,他们也觉得很难理解,于是研究出了raft算法。

但是很多细节方面, 是遵循paxos算法的.

raft是一个共识算法(consensus algorithm),所谓共识,就是多个节点对某个事情达成一致的看法,即使是在部分节点故障、网络延时、网络分割的情况下

采用主从复制的方式

选主

角色

  • Leader(领导者):系统只有一个节点处是 Leader,处理所有客户端的请求并同步给 Follower, 读写都由leader处理.
  • Follower(跟随者):只响应其他角色(Leader、Candidate)的请求
  • Candidate(候选者):在选举领导的时候出现

这里的角色就是你的分布式系统中每台机器的角色, 相当于你的代码实现了Raft算法, 和你本身的应用融合到一起.

term 任期 类似于提案号 但是每个term自己维护, 并不全局

  • 一段选举的任期范围(选举开始+正常工作)

  • term 号自动 +1

  • 如果选票均分,则该 term 直接结束,进入下一个 term(即没有得到多数派的任何)

  • Raft 中的「逻辑时钟」,可发现过期信息,规则:

  • 每个节点会存储当前term号,term编号单调递增

  • 节点间通信,交换 term 号

  • (1)节点当前 term 号 < 他人 term 号,更新 term 号

  • (2)节点当前 term 号 > 他人 term 号,拒绝请求

  • (3)Candidate、Leader 发现自己的 term < 他人 term,立即变成 Follower

  • 对一个给定的 term 号,最多选举出一个 Leader, 每个节点在一个term中, 只会投出一票.

选举过程

开始选举的触发过程

(1) 所有节点刚刚启动时, 所有人都是follower.

(1) leader和follower保持心跳, 一旦失去leader的心跳(无论是因为leader挂了, 还是leader和当前follower之间通讯发生故障了)

节点投票的过程

(1) follower节点在经过一个超时时间后, 变成Candidate节点, 每个节点的超时时间是随机的(这是为了防止大家同时要票, 导致票分配不均匀的情况, 大多数情况下, 就只有一个节点, 率先进入Candidate状态)

(2) 假设是A节点变成了Candidate, 将自己的term + 1, 发起投票请求, 号召大家投自己, 请求包含内容如下

  • term:发起者此时的term
  • candidateId:发起者的ID
  • lastLogIndex:当前节点的最新日志(logIndex)的上一条的Index(logIndex-1),理解为下标
  • lastLogTerm:上述日志对应的term

(3) 收到投票请求的节点, 有以下几种情况, 只保证对于一个term只投票一次, 多term就投多次

  • (a) term比自己的小, 返回false
  • (b) term比自己的大, 且lastLogIndex也比自己的大, 那么投票给他, 返回true, 并更新自己的term
  • (c) term和自己的一样, 如果自己已经投过票, 并且就是他, 继续返回true(可能上次投票信息丢失了), 如果投过票, 但是不是他, 不投票(保证一个term只投一次). 如果没投过票, 进行步骤b

(4) 节点A什么情况下改变状态

  • (a) A收到半数以上的的投票返回 A变成leader, 此时广播通知所有其它节点
  • (b) 收到其它节点宣布自己是leader的广播(leader的term必须大于等于自己的term, 否则不认可这个leader), A变成follower, 跟随leader
  • (c) 一定时间后, 依旧没有完成步骤a和b, 此时term + 1, 发起新一轮选举

数据一致性保证

数据复制采取主从复制的方式, 一致性由类似两段式提交保证

阶段1:

(1) Leader 会接收客户端的请求,leader将当前日志的index+1,赋给该log,并append到自己的Logs中

(2) 向所有 Follower 发送附加条目的RPC,让他们复制这个日志条目到自己的日志文件中

阶段2:

(1) Follower尝试将日志写入到自己的文件, 如果成功, 就回复确认

(2) 当leader收到半数以上的follower回复确认, 就将自己的状态机该为已提交, 然后将日志持久化到磁盘, 更新自己的状态机, 表示已经持久化好了, 回复client确认命令. 如果没有收到半数以上的确认, leader下次会以相同的日志索引去让follower复制, 这样就覆盖了上一次的在各个follower节点的复制操作

没有回复确认的follower, leader会不断发送重试命令.

(3) leader向所有follower发送commit命令, 让所有follower将日志持久化到磁盘. follower持久化完毕后, 会修改自己状态机的状态, 表示已经持久化了.

Raft算法能保证一致性吗

Raft算法所有和client的交互都是由leader节点完成的, 因此是可以保证读写强一致的.

如果leader不挂的话, 肯定是强一致的.

如果leader挂了, 选主机制可以选出包含最多日志的节点当leader.保证了仅允许包含了所有已提交日志的candidate赢得选举成为候选人

数据一致性是在什么情况下才保证

当leader回复client确认后, 才保证数据一致性, 如果没回复, 根本就无从谈保证.

数据一致性检查

每次leader节点, 让所有follower节点复制日志的时候, 会检查follower上, leader节点要求复制的日志的索引之前的所有日志是否和leader一样, 如果不一样, 就先复制提交之前的, 这样就保证了(follower节点如果在挂了一段时间后复活的时候, 日志都够追上)

通过以上的机制,Raft 就能保证:

如果两个日志条目有相同的 log index 和 term,则它们的内容一定相同。

如果两个节点中的两个条目有相同的 log index 和 term,则它们之前的所有日志一定相同。

如果leader在还没提交日志的时候就挂了呢.

如果leader A回复了client确认, 此时leader A是已经持久化到磁盘了的, 然后在向follower发送commit之前挂掉了, 此时所有的follower的最新一条日志都没有提交.

新的leader B被选举出来后, 如果自己包含了A那条未提交的数据, 并不会直接尝试帮上一个leader提交, 而是在自己收到新的写请求后, 再去尝试提交上一个leader的提交 (根据数据一致性检查)

概述

该算法因为只有主节点提供读写操作, 因此可以保证强一致性, 所有的follower更像是主的备份, 一旦主失效, 就由备份顶上, 为了强一致性, 而失去了性能

ZAB算法

ZAB 协议全称:Zookeeper Atomic Broadcast(Zookeeper 原子广播协议)

借鉴Paxos算法的一种实现.

角色

leader: 是ZAB算法的核心 只有一个

  • (1) 不会直接接受client的请求.
  • (2) 事务请求(写操作)的唯一调度和处理者,保证集群事务处理的顺序性;集群内部各个服务的调度者。
  • 对于 create,setData,delete 等有写操作的请求,则需要统一转发给 leader 处理,leader 需要决定编号、执行操作,这个过程称为一个事务。
  • (3) leader会按照写请求到来的先后顺序, 按顺序执行写操作.

follower: 接受client请求的角色 有多个

  • (1) 读请求直接自己处理
  • (2) 写请求转发给leader, 等待leader的安排
  • (3) 可以参与选主

Observer: 观察者角色, 接受client请求的角色

  • (1) 读请求直接自己处理
  • (2) 写请求转发给leader, 等待leader的安排
  • (3) 不配参与选主

zxid: 事务id, 每个节点都有一个事务id, 每一次执行写请求后, 该值就会 + 1

epoch: 纪元编号, 使用zxid的高32位

myid: 每个节点都有一个自己的编号, 不可重复

每个leader和folllower通过心跳消息, 来互相了解对方是否存活的.

状态

  • LOOKING:当前Server不知道leader是谁,正在搜寻; 此时该节点不能对外提供服务
  • LEADING:当前Server即为选举出来的leader;
  • FOLLOWING:leader已经选举出来,当前Server与之同步;

选主过程 也叫崩溃恢复模式

只有LOCKING状态的节点才能索要选票, 以及投票

(1) 当所有节点处于初始状态, 或者当leader失去大多数follower的时候, 就进入了崩溃恢复模式

  • (a) 每一个follower失去和leader的心跳后, 都会变成LOCKING状态, 尝试去索要选票, 但是因为此时系统中大多数机器都是正常的, 他无法得到半数以上的选票, 因此会一直在LOCKING状态.
  • (b) 等到半数以上的follower都变成LOCKING状态后, 就会选举出新的leader了. 此时进入崩溃恢复模式, 整个集群不再对外提供服务.
  • (c) 进入崩溃模式后, 之前的那个leader的zxid是会被清零的, 保证他不会被再次选举被leader

(2) 进入崩溃恢复模式后, 所有节点变成LOCKING状态, 然后他们每个人投自己一票, 然后将自己的投票结果广播给所有其它节点(带有Zxid和myid), 每一次选举, Zxid的高32位作为纪元编号都会 + 1

(3) 其它节点收到别人的投票请求后, 和自己心目中的leader按优先级依次比较epoch, zxid 和 myid的大小, 如果比自己心目中的leader大, 那么更改自己的投票结果, 重新广播.

(4) 每个节点会纪录所有收到的投票结果, 一旦发现某个节点得到半数以上的选票, 就会广播通知其它节点, 哪个节点是leader节点.

这个选主流程能够保证每次一定能选出一个leader.

同步过程 类似paxos算法的阶段2

选主完成后, 需要进行数据同步的过程, 会快速的就集群达成某个值的共识

    1. leader等待follower连接;
    1. Follower连接leader,将最大的zxid发送给leader;
    1. leader收到半数以上follower的回复后, Leader 选取最大zxid作为同步值, 让所有follower同步该值
    1. follower节点会根据leader的消息进行回退或者是数据同步操作。

最终目的要保证集群中所有节点的数据副本保持一致。

写数据的过程 广播模式

采用两段式提交的方式

  1. Client向Follwer发出一个写的请求

  2. Follwer把请求发送给Leader

  3. Leader接收到以后, leader就会发起一次proposal(提案), Leader为每一个Follower准备了一个FIFO队列,并把Proposal发送到队列上。

4.follower读取队列中的信息, 并执行, 成功执行就返回某一个提案的ack

5.Leader将结果汇总后发现某个提案收到超过半数的ack了, 先不提交, 看一下之前的提案是不是都已经提交, 如果提交了, 才将当前提案提交, 然后像所有follower发送commit.

6.follower收到commit请求后, 完成自己节点的提交

  1. leader将写的结果返回给follower, flower返回给client.

写数据的强一致性

因为leader节点来单点控制写入, 并且每个followr都有一个队列来保证写入的顺序性, 如果follower对某个提案一直没有写入, 那么之后的信息他一定也没有写入, 就保证了不会跳着写的情况.

读取的顺序一致性

首先, 读取全部都是通过follower读的, 而follower没有保证所有节点的数据是一致的, 只保证大多数节点的数据是一致的, 因此如果客户端连接到不同的follower节点, 看到的数据可能是不一致的, 这是允许的. 但是读取可以保证以下两点

(1) client一旦连接过一次follower, 就会纪录下此时的zxid, 当下一次连接的时候, 只会连接zxid比自己记录的大的follower, 保证你看不到旧的数据, 只会看到更新的.

(2) client连接follower看到的数据虽然可能不是最新的, 但一定是系统之前某个时刻的状态, 而不是一个从未出现过的错误的状态(由写入时的队列保证)

ZooKeeper(就是作为一个单点, 来协调分布式的多实例)

zookeeper可以服务于分布式系统, 当你的系统属于分布式集群的时候, 即一个程序部署在多个服务器上, 那么多个服务器的的状态就需要协调一致.zookeeper就可以实现这种功能. 也就是管理一些分布式的东西.

zookeeper的本质是什么

zookeeper本身是一个服务, 需要找一台机器启动. 它会维护一个具有层次的数据结构(底层使用树结构), 就类似于一个文件操作系统. 可以在这个数据结构中, 创建多个节点, 就相当于你创建文件夹一样, 文件夹就有层次, 有嵌套. zookeeper中每个文件夹, 就叫做Znode, 就等同于文件夹, 同一层次文件夹不能重名(总而言之, 就是一个树形的, 像文件夹系统的一个结构)

如果你启动了这个服务, 本身并没有啥用, 因为没有任何服务(即client)与之关联. 如果想关联, 你就需要在你的服务中创建一个ZooKeeper链接, 这个链接就可以和zookeeper服务建立联系, 你就能获取到zookpeeper中所有节点的情况(例如有哪些节点等, 在zookeeper上创建, 删除节点等.)zookeeper链接就是你和zookeeper之间的桥梁, 所有的功能都是通过这个连接实现的, 一旦这个连接建立, 那么每个服务可以主动去获取zookeeper的信息, 也可以通过这个桥梁收到ZooKeeper服务的通知. 具体的就可以根据想要实现建立不同的连接来实现.

zookeeper连接是可以设置监控的, 你如果在建立连接的时候指明需要监控的ZNode, 然后重写watch()方法, 那么在你监控的节点发生任何变化的时候, zookeeper服务就会将变化通知给你, 你自己决定进行什么操作

zookeeper本质的概括

Zookeeper 从设计模式角度来看,是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper 就将负责通知已经在 Zookeeper 上注册的那些观察者做出相应的反应,从而实现集群中类似 Master/Slave 管理模式.

zookeeper节点类型

zookeeper节点就是zookeeper中的文件夹, 它有多种类型, 不同的功能可以选择不同的节点类型实现

  • (1) PERSISTENT:持久化目录节点,这个目录节点存储的数据不会丢失;
  • (2) PERSISTENT_SEQUENTIAL:顺序自动编号的目录节点,这种目录节点会根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名;
  • (3) EPHEMERAL:临时目录节点,一旦创建这个节点的客户端与服务器端口也就是 session 超时,这种节点会被自动删除;
  • (4) EPHEMERAL_SEQUENTIAL:临时自动编号节点

zookeeper的功能

根据你的需求不同, zookeeper可以为你完成各种服务, 它本身的功能有很多, 但是你在使用时, 根据场景不同, 分别应用了他的一些特性, 逐一介绍

统一命名服务

对于单点服务来说, 你如果需要为某个东西命名(例如数据库某个字段), 你可以保证不重复(有很多种方式可以实现), 但是如果是多台同样的服务, 除非你自己实现广播通信, 否则很难实现命名不重复的情况(很典型的场景就是如果我有10个服务, 分别有自己的10张表, 我希望这10张不同的表, 生成的某个字段不能重复.). 这种情况下, 就必须要一个单点, 来协调各个集群的命名情况

zookeeper就可以完成这个功能, 服务通过和zookeeper建立连接, 创建一个自增的节点, 然后返回给你一个自增的数值, 这样就通过单点创建, 完成了不重复

配置管理

如果想为多个服务实现集中化配置管理, 就是说多台实例某些配置如果想同时修改, 除了修改好重新上线的方式外, 还可以采用zookeeper来同时对配置这些修改.

多台实例只需要维持一个和zookeeper的连接, 监听zookeeper服务某个ZNODE节点的内容, 这里的内容就是那些配置的具体信息, 当你去修改zookeeper服务该节点的内容时, zookeeper就会通知所有监听了该节点的实例, 这样就完成了配置管理

实现分布式锁

单进程里, 多线程之间实现共享锁有很多种方法, 但是多实例实现分布式锁就不太好实现了.

zookeeper作为单点, 很容易就可以实现.

  • (1) 需要获得锁的线程 去zookeeper在某个文件夹AA下创建一个自增名称的文件夹, 会返回一个id
  • (2) 判断id是不是AA里, 文件夹命名最小的那个
  • (3) 如果是, 就代表获得锁了, 执行自己的操作, 然后释放锁的时候将自己那个文件夹删除掉
  • (4) 如果不是, 线程阻塞, 并监听那个AA文件夹的变化, 一旦有变化, 再次进行比较自己创建的id是不是最小的那个

这样就实现了一个按照先到先得的分布式锁.

集群管理

如有多台 Server 组成一个服务集群,那么必须要一个“总管”知道当前集群中每台机器的服务状态,一旦有机器不能提供服务,集群中其它集群必须知道,从而做出调整重新分配服务策略。同样当增加集群的服务能力时,就会增加一台或多台 Server,同样也必须让“总管”知道。

zookeeper可以接受服务创建节点, 每个服务在启动的时候在zookeeper创建一个EPHEMERAL_SEQUENTIAL(临时自动编号节点), 每个服务存活的时候, 和zookeeper的连接就不会中断, 那么它的临时节点就会一直存在, 一旦服务挂了, zookeeper连接就会中断, 它对应的节点在zookeeper也会自动被删除, 这样zookeeper就会通知所有的其他服务. 同样的如果新的服务建立了链接, 也会通知给所有监控了的服务.

zookeeper集群选主后如何标记

成功当选的节点, 在集群中创建一个固定名称的节点, 然后根据节点中的内容指向谁是主机.

zookeeper集群

如果我只启动一个zookeeper服务, 来作为单点提供功能是肯定可以的. 但是嘛, 只启动一个的话, 总归不太安全, 一旦挂了, 所有相关zookeeper的使用就都失效了.

Zookeeper服务自身(注意是Zookeeper服务自身哦, 和client没半毛线的关系)组成一个集群,2n+1个(奇数)服务允许n个失效,集群内一半以上机器可用,Zookeeper就可用。

具体算法

ZAB算法