DDIA-分布式系统服务
事务
脏写
行级锁
脏读
语句级MVCC
不可重复读
事务级MVCC
更新丢失
for update
写倾斜
可串行化
幻读
索引区间锁

分布式系统的挑战
面向容错进行设计是对分布式系统软件的基本要求。基于不可靠的组件构建可靠系统 是工程领域并不罕见的思想,如:
纠错码能够容忍信道中偶尔一两个比特的误传。
IP 层不可靠,但 TCP 层却基于 IP 层提供了相对可靠的传输保证。
异步的网络
所有机器不共享资源(如内存、磁盘),通信的唯一途径就是网络。互联网和数据中心(多是以太网)的内部网络多是异步封包网络(asynchronous packet networks)。反正有各种可能性导致你在超时时间内收不到回应,而你不知道对方情况如何。
故障检测
如果你想确定某个请求确实成功了,只能在应用层进行显式确认。在某些特定的场景下,你可以通过一些旁路信号,来获取一些信息:
操作系统通知。如果你能触达服务所在机器,但发现没有进程在监听预期端口(比如对应服务进程挂了),操作系统会通过发送 RST 或 FIN 包来关闭 TCP 连接。但是如果对端节点在处理你的请求时整个宕机了,就很难得知你请求的具体处理进度。
daemon 脚本通知。可以通过一些 守护脚本,在本机服务进程死掉之后,主动通知其他节点。来避免其他节点通过发送请求超时来判断此节点的服务进程不可用。当然这前提是,服务进程挂了,但所在节点没挂。
数据链路层面。如果你是管理员,并且能访问到你数据中心的网络交换机,可以在数据链路层判断远端机器是否宕机。
IP 不可达。如果路由器发现你要发送请求的 IP 地址不可达,它会直接回你一个 ICMP 不可达包。但路由器也并不能真正判断是否该机器不可用了。
网络拥塞与数据包排队
去程网络排队。如果多个节点试图将数据包同时发给一个目的端,则交换机得将他们排队以逐个送达目的端。如果流量进一步增大,超过交换机的处理能力,则其可能会随机进行丢包。
目的机器排队。当数据包到达目的端时,如果目标机器 CPU 负载很高,操作系统会将进来的数据包进行排队,直到有时间片分给他们。
虚拟机排队。在虚拟化环境中,由于多个虚拟机共用物理机,因此经常会整体让出 CPU 一段时间的情况,等待期间是不能处理任何外部请求的,又会进一步给网络请求的排队时延增加变数。
TCP 流控。TCP 流量控制会限制发送方的发送速度,以避免网络过载或者目的端过载(本机排队)。
同步网络
计网学过,电路交换(circuit switching) 是同步的。问题是需要预留带宽,利用率低,且很难应对互联网中无处不在的突发流量(bursty traffic)。
不可靠的时钟
当代的计算机通常支持两类时钟:日历时钟(time-of-day clock)和单调时钟(monotonic clock)。前者常用于物理时间点需求,后者常用于计算时间间隔。
时间同步与精度问题
对于日历时钟来说,由于自身石英钟计时不够精确,为了能够正常使用,需要定时与 NTP 服务器或者其他可信时钟源进行同步。但是校准的 NTP 服务都不是完全可靠的:
单机的硬件时钟都不是很精确,会发生漂移(drift,走的快或者慢)
网络延迟
闰秒 的存在,会导致一分钟可能有 59s 或者 61s,对一些系统不兼容
虚拟机中,其物理时钟是虚拟化出来的,让出内核的等待期存在跳变
时间戳以定序
一种方案是将时钟的读数视为具有置信区间的时间范围。
快照隔离的实现通常需要一个全局自增的事务 ID。在多机协作下,必须要反应因果性(如当事务 B 读到事务 A 写的内容时,事务 B 的事务 ID 就需要比事务 A 大)。我们使用置信区间,确定时间无交集,再进行处理。BOTH earlist < last)
进程停顿问题
使用租约(lease)限定主副本的掌权时间,保证任意时刻只有一个副本可以持有改租约。但是,如果由于时钟跳变导致线程停顿了 15 秒,前面的租约判定失效了。比如:
GC stop the world -> 将GC暂停视为节点临时离线,通知其他节点接管
虚拟机被挂起、在线迁移
操作系统上下文切换、换页、进程暂停信号 -> 实时操作系统(RTOS)
消息的真假?
多数派定义
任何节点都没法独自断言其自身当前状态。大部分分布式算法会基于一个法定人数(quorum),即让所有节点进行投票:任何决策都需要达到法定人数才能生效,以避免对单节点的依赖。
在很多场景下,系统会要求某些东西全局唯一,比如:
每个数据库分片都有唯一的领导者,避免脑裂
只有一个事务或者客户端允许持有某资源的锁,以避免并发写入或者删除
Fencing 令牌

在锁服务每次授予锁或者租约时,会附带给一个防护令牌(fencing token),拒绝携带过期令牌的请求。即,关联了ID的锁服务。
客户端不能独自确定其对资源的独占性。需要在服务端对所有客户端的情况做一个二次核验。(默认:客户端都是老六)
拜占庭错误
系统中的节点有“说谎”(发送任意错误的的或者损坏的信息)的可能性,如区块链。这种行为称为拜占庭故障(Byzantine fault),在具有拜占庭故障的环境中达成共识也被称为拜占庭将军问题(Byzatine Generals Problem)。
这个的解决需要软硬兼施,比较复杂,在此就不展开了。
为软件加上一些对弱谎言(week forms of lying)的简单防护机制仍然很有用,如应用层的校验字段。
系统模型与现实
对于时间的假设,有三种系统模型很常用:
同步模型(synchronous model)。假设网络延迟、进程停顿和时钟错误都是有界的。
半同步模型(partial synchronous)。意思是大部分时间里,网络延迟、进程停顿和时钟漂移都是有界的,只有偶尔,他们会超过界限。这是一种比较真实的模型。任何关于时限的假设都有可能被打破,一旦出现出现异常现象,我们需要做好最坏的打算。
异步模型(Asynchronous model)。算法不能对时间有任何假设,甚至时钟本身都有可能不存在(超时根本没有意义)。
除时间问题,我们还需要对节点故障进行抽象。针对节点,有三种常用系统模型:
宕机停止故障(Crash-stop faults)。节点只会通过崩溃的方式宕机,即某个时刻可能会突然宕机无响应,并且之后永远不会再上线。
宕机恢复故障(Crash-recovery faults)。节点可能会在任意时刻宕机,但在宕机之后某个时刻会重新上线,但恢复所需时间我们是不知道的。我们假设节点的稳定存储中的数据在宕机前后不会丢失,但内存中的数据会丢失。
拜占庭(任意)故障(Byzantine (arbitrary) faults)。我们不能对节点有任何假设,包括宕机和恢复时间,包括善意和恶意。
对于分布式系统算法,如果能够应对该模型下的所有可能出现的情况,并且时刻满足其约束性质,则我们称该算法是正确的。此外有两类不同的属性衡量:
安全性 safety,通俗的可以理解为没有坏事发生(nothing bad happens)
存活性 liveness,可以理解为好的事情最终发生了(something good eventually happens),如最终一致性确实最终一致了。
我们通常会比较关注安全性,在系统模型可能触到的各种情况下,安全性都必须满足。
一致性与共识
共识协议可以让多机对某个确定的操作序列(日志) 达成共识,进而对系统的任意状态达成共识。
事务隔离级别是为了解决并发所引起的竞态条件,分布式一致性是处理由于多副本间延迟和故障所引入的数据同步问题
线性一致性/可线性化
可线性化的基本想法是:让一个系统看起来只有一个数据副本,且所有的操作都是原子的,是单个对象的最新值保证。
如何满足可线性化
Q:读请求时间条与写请求有交集,因此读请求的返回值不确定。我们无从判断在数据库端,读操作和写操作的具体先后关系,因为这些读请求和写请求都是并发的。在写请求持续期间,读客户端可能会看到不断交替的新旧值。
A:线性一致性要求所有操作标记组成序列是永远向前的,一旦我们写入或者读取到某值,所有稍后的读请求都能看到该值(坍缩),直到有人再次将其改写。

线性化的依赖条件
Q:选主为避免脑裂,所有节点都必须同意哪个节点持有相应资源,必须满足可线性化。
A:唯一性约束。可使用共识算法,参考下文。
多通道的时间依赖

Q:上图中由于存在分布式存储写请求和消息传递两条时间通道,存在竞争条件,需要线性化的就近性保证。
A:可以参考读写一致性(时间戳)的实现,用复杂性换线性一致性。
实现线性化系统
回顾下第五章的几种多副本模型,逐一考察:
单主模型(Single-leader replication,potentially linearizable) 在一个单主模型的系统中,主副本服务于写请求,其他副本负责备份。如果我们让读取也走主副本,或者使用同步更新从副本的策略,则该系统有可能满足线性一致性。
首先我们得确切知道哪一个是主副本。如果这个自以为是的主节点(delusional leader)继续提供服务,则系统很可能会违反线性一致性。
如果使用异步同步策略,节点宕机可能甚至会丢数据,从而不仅违反线性一致性,也违反了可持久性。
共识算法(Consensus algorithms,linearizable) 有一些共识算法,看起来与单主模型类似,但这些共识协议有一些阻止脑裂和过期副本的手段。由于这些额外细节,共识算法可以实现安全的线性一致性存储。如 Zookeeper 和 etcd
多主模型(Multi-leader replication,not linearizable) 由于可以同时在多个节点上处理写入,并且异步同步写入数据,使用多主模型的系统通常不是线性一致的。由于上述原因,这种系统可能会产生需要手动解决的写入冲突。
无主模型(Leader replication, probably not linearizable) 前面提到,w+r > n 在一些corner case下,并不是强一致的(也取决于你如何定义强一致性)
可线性化的代价

在使用多主模型的数据库中,在上图情形下,由于向其他数据中心的数据传输是异步的,每个数据中心仍能正常工作,只是由于数据中心间网络的问题,所有数据同步都被排队了起来,待到网络恢复就会重新发出。当然该客户端可以无视该中断,直接从从数据中心的从副本进行读取,但其读到的内容可能是过期的(主副本接受了新的写入),也因此不满足线性一致。如果应用层要求线性一致的读写,则数据中心间的网络中断会造成服务的不可用。
CAP:如果系统不提供线性一致性,就可以对网络故障更加鲁棒。 现代多核CPU上的内存就是非线性化的(每个核有独立的cache和寄存器),CAP理论已不适用于当前的多核-内存一致性模型。放开线性化的原因是性能(内存屏障)而不是容错。有证明,如果想要满足线性化,那么读写请求的响应时间至少要和网络延迟成正比。
保序
因果将顺序施加于事件(event):
先有因,后有果
先有消息发送,然后该消息被收到
先有问题,后有答案
如果一个系统遵循因果约束,则我们称其为因果一致的(causally consistent)。 回顾一些因果例子:
一致前缀读。(观察者的读取来自不同partition,顺序与实际请求先后相反)
多主模型网络延迟导致条目不一致
发生于之前(happened before)是因果性的另一种表现,并发则无因果联系。
读倾斜(不可重复读)
在可串行的快照隔离级别(SSI)下,通过追踪事务间的因果依赖(即读写数据集依赖)来检测写倾斜。
未线性化坍缩:一旦某个读返回新值,之后所有读(不论分区)都必须返回新值
多通道的时间依赖
因果序非全序
全序和偏序的区别也体现在不同强度数据库一致性模型上:
线性一致性(Linearizability):对于任意两个操作,我们总是可以确定其发生的先后关系,满足全序关系,对应事件的时间线
因果一致性(Causality)。如果我们无从判定两个操作的先后关系,则称之为并发关系。换言之,如果两个事件因果相关,则可排序;另外一类操作是并发的,则不可比。也即,因果性定义了一种偏序(partial order)关系。
因果依赖是应用层定义的,系统层较难完全识别,版本向量技术、事务追踪等可以推广为通用的部分解决方案。
线性一致性是因果一致性的充分(implies)条件。从事件拓扑来看,可以认为因果一致性(DAG) ⇒ 线性一致性(经过所有事件点的单向路径)
序列号定序法
如果操作 A 发生在 B 之前,则 A 获取到的序列号比 B 小。并发操作获取到的序列号顺序不确定。(本质上是一种全序) 单主模型:主节点上操作日志的追加顺序确定了一个对所有操作的全序,且满足操作发生的因果关系。主节点可以为每条日志按顺序关联一个全局递增的序列号,副本按此序应用。
非单主模型:因为不同节点上序列号的增长速率很难完全同步、物理时间戳存在多机时钟偏差,不能够很好地捕捉跨节点的操作因果关系。提出一种相对简洁的Lamport 时间戳法(但不是充分必要的,即不能通过两个 Lamport 时间戳的大小来判断其是有因果关系还是并发关系):
Lamport 时间戳定序
每个节点有一个唯一的 id 和一个记录操作计数器,Lamport 时间戳是上述两者组成的二元组:(counter, node ID)。显然是唯一的,可比:具有较大 counter 的时间戳较大,则counter 相同,具有较大 node ID 的时间戳较大。让 Lamport 时间戳能够满足因果一致性的核心点在于:每个节点和客户端都会让 counter 追踪当前所看到(包括本机的和通信的)的最大值。当节点看到请求或者回复中携带的 counter 值比自己大,就会立即用其值设置本地 counter。
但是还有一种case:唯一性约束。如两个客户端创建相同用户名的账户,当你拿到系统中所有的账户创建操作后,你才可以比较他们的时间戳。(只有一方,何谈比较?)也就是说,只有在收集到系统中所有操作之后,才能真正确定所有操作的全序。单个节点并不能立即独自判断该请求成功还是失败。
在此case中,为判断其他节点是否【收到同名账户的创建请求,并且获得了较小的时间戳】,又想考虑高可用(其他节点宕机或者网络故障时,依旧可提供服务)下:这些未知节点的操作可能被插入全序到不同位置,不能确定最终的事件的全序,我不知道能否执行请求。因此仅为所有时间进行全局定序是不够的,你还需要知道该定序何时完成(收敛)。
全序广播
全序广播是一种多个节点间交换消息的协议,在分布式系统中常用于对多条执行事件线进行定全序。它要求系统满足两个安全性质:
可靠交付。如果一个节点收到了消息,则系统内所有相关节点都要收到该消息。
全序交付。每个节点接收到消息的顺序一致。(当收到消息时,其顺序已经确定)
还可以从另外一个角度来理解全序广播——用来写日志(比如复制日志、事务日志或者写前日志):投递消息就像追加日志。由于所有节点都会按照同样的顺序发送消息,则所有节点在读取日志的时候也会得到同样的消息序列。像 Zookeeper 和 etcd 等共识服务都实现了全序广播算法。
全序广播是异步的:系统保证以同样的顺序交付消息,但并不保证消息的交付时刻(即,有的消息接收者间可能存在着滞后)。 与之相对,线性一致性是一种新鲜度保证:读取一定能看到最新成功的写。
我们来看几个例子:
实现 Fencing 令牌 每个上锁请求可以作为消息追加到日志中,依据其追加到日志中的顺序,所有请求可以被自然地编号。由于这个序列号是单调递增的,便可以充当防护令牌。在 Zookeeper 中,这个序列号便是 zxid。
实现线性一致性存储(上面创建账户的例子) 对每一个可能的用户名,我们使用一个支持 CAS 操作的线性寄存器,使用某个用户名创建账户时,
CAS(old=null, new=account-id)。 使用全序广播系统作为日志追加服务,可实现这样的原子CAS寄存器:服务中追加一个带有某用户名的消息条目,表明你想使用该用户名。
(由于全序广播是异步的,需要等待同步)不断读取日志,直到能够读到刚才你追加的消息条目。
检查所有想要使用该用户名的消息,这时你可能会得到多条消息,当且仅当你当初写下的消息在第一条,则你是成功的。
因为异步,尽管该方式能够提供线性化的写入,却不能保证线性化的读取。如果想让读取也变得可线性化,有几种做法:
让读取也走日志,即通过追加消息的方式将读取顺序化,然后当读取请求所在节点收到这条读取日志时才去真正的去读。则消息在日志中的位置定义了整个时间序列中读取真正发生的时间点。(etcd 中的法定读取就是用的类似的做法)
如果日志服务允许查询最新日志的位置,则可以在请求到来时,获取当时最新位置,然后不断查询日志看是否已经跟到最新位置。如果跟到了,就进行读取。(这是 Zookeeper 中 sync() 操作的背后的原理)
可以将读取路由到写入发生的那个节点,或者与写入严格同步的节点,以保证能够读到最新的内容。(这种技术用于链式复制 chain replication 中)
使用线性一致性的存储实现全序广播 实际上我们是可以反向实现上面的case的... 最简单的方法,假设我们有一个整数寄存器,并提供 increment-and-get 原子操作,来实现一个分布式系统的"TCP":
对于每一个发给全序广播系统的消息,使用整数寄存器 increment-and-get 操作关联一个序列号
将消息发送给所有节点(重试任何丢失的消息)。每个节点接收到消息后利用序列号顺序对外交付消息,并通过ACK确认。
和 Lamport 时间戳不同,从线性化的寄存器中获取的数字是连续的,非跳跃的。如某节点交付了消息 4 后,收到了消息 6,但不能立即交付,而需要等待消息 5 的到来。但在 Lamport 时间戳系统中则非如此——这(是否连续)也是全序广播和时间戳顺序的核心不同。
但其实有个老问题,怎么得到一个高可用的线性序列生成器?可以证明,一个线性的 CAS 寄存器和全序广播都等价于共识协议(equivalent to consensus)。
分布式事务与共识协议 🌟
共识问题,就是对所有提案达成一致。在唯一性约束和原子提交服务上是必要的。
原子提交和 2PC
原子性可以防止失败的事务破坏系统,防止系统夹杂成功与失败,对于多对象事务和二级索引的维护非常重要。 事务提交(或中止)的关键点在于磁盘完成日志记录的时刻。
两阶段提交(2PC,two-phase commit)是一种在多个节点上实现原子事务的方法,即确保所有节点要么全部提交,要么全部中止。2PC 引入了一个单机事务中没有的角色:协调者。
当应用想开启一个分布式事务时,它会首先向协调者要一个事务 ID。该事务 ID 是全局唯一的。
应用会使用前述事务 ID 向所有的参与者发起一个单机事务,所有节点会各自完成读写请求,即执行语句但不提交,在此过程中,如果有任何出错(比如节点宕机或者请求超时),协调者或者任意参与者都可以中止事务。 【阶段一:prepare】
当应用层准备好提交事务时,协调者会向所有参与者发送准备提交( prepare) 请求,并在请求中打上事务 ID 标记。如果有请求失败或者超时,则协调者会对所有参与者发送带有该事务 ID 的中止请求。
当参与者收到准备提交请求时,它必须确认该事务能够承诺在任何情况下都能被提交,才能回复“可以” 。此时并没有真正地提交。 【阶段二:commit】
当协调者收到所有参与者准备提交的回复后,当且仅当所有参与者都回复“可以”时,才会提交。协调者需要将该决策写入事务日志,并下刷到磁盘,以保证即使宕机重启,该决策也不会丢失。这被称为提交点(commit point)。
协调者将决策刷入了磁盘后,再将决策(提交或者中止)请求发给所有参与方。如果某个请求失败或者超时,则协调者会对其进行无限重试,直到保证成功执行。如果参与者在此时宕机了,则当重启时也必须进行提交——因为承诺。
当协调者故障(coordinator failure)时,不能单方面的中止事务,参与者只能无奈等待,在提交或者中止事务前不能够释放获取的这些锁,达到一个低可用、不确定的状态,直到等待协调者通过事务日志恢复。
由于 2PC 在等待协调者宕机恢复时系统可能会卡住,因此两阶段提交又称为阻塞式原子提交协议。在假定网络具有有界延迟,请求延迟也是有界的情况下,3PC 似乎更work。(can-commit, pre-commit, do-commit)
分布式事务
存在两种分布式事务:
数据库内部分布式事务 在一些分布式数据中(标配支持多分区和多副本的数据库),支持跨节点的内部分布式事务。
异构的分布式事务 在异构的分布式事务中,所有参与者使用了两种以上的技术栈,需协调多个服务的事务。只有参与系统都支持原子提交时,此分布式事务才是可行的。
XA 事务(eXtended Architecture)是在异构系统间实现两阶段提交的一个标准,定义了一组和事务协调者交互的API 接口。Open Group 组织针对 XA 定义了分布式事务处理模型,也被称为DTP模型。包括三个组件:
AP(Application Program):应用程序,通过定义组成事务的特定操作来定义事务边界。
RM(Resouces Manager):资源管理器,管理共享资源的服务,对应两阶段提交协议中的参与者,如数据库或消息队列服务。
TM(Transaction Manager):事务管理器,管理全局事务,协调事务的提交或者回滚,并协调故障恢复。
应用层以XA API参与服务交互。事务的协调者实现了 XA API,会追踪事务中的所有参与者,在要求参与者准备提交(prepare)后收集其回复,使用本地磁盘上的日志来跟踪每个事务的提交/中止决策。
事务的协调者也是一个“数据库”,因此:
如果单机部署,则存在单点故障。
在宕机重启后,参与者会利用这些日志来推进卡住的参与者。应用层不再是无状态的,违反HTTP的Restful等偏好)
当出现内部故障,分布式事务有级联故障的可能性。
共识算法
共识协议通常被描述为:一个或者多个节点可能会各自提议(propose)一些值,共识协议需要在这些值中间做出唯一的决策(decide)。一个共识协议必须满足以下条件(假设无拜占庭故障):
全局一致性(Uniform agreement) 没有任何两个节点最终做出不同决策。
纯洁性(Integrity) 没有任何节点会做出两次决策(不反复横跳) 【所有节点都要决策出同样的结果,并且一旦做出决策,就不能反悔。】
有效性(Validity) 如果一个节点做出了决策,该决策所对应的值一定来自系统内某个节点的提议。
可终止性(Termination) 任何没有宕机的节点,最终都会给出对某个值的决策。(上面三者是安全性,此为活性)。可终止性是对容错的一种形式化描述。可以证明,任何共识算法都要求多数节点存活,以确保正常运行,满足可终止性。多数派节点可以安全的构成一个法定多数。
全序广播等价于多轮次的共识协议(每个轮次,会使用共识协议对全序广播中的一条消息的全局顺序做出决策):
由于共识协议的全局一致性,所有节点会以同样的顺序投递同样的消息。
由于纯洁性,具有同样 id 的消息不会重复。
由于有效性,消息不会是损坏的,也不会是凭空捏造的。
由于可终止性,消息不会丢失。
循环依赖?:为防止脑裂,需要共识算法选主,共识算法本质上可以描述为全序广播算法,然后全序广播算法又和单主复制一样,然后主从复制又依赖时刻保证选出单个主防止脑裂...
共识算法都在内部需要一个某种形式上的主节点,但提供的是较弱的保证:协议会定义一个任期,并且保证在每一个任期内,主节点是唯一的。
共识算法的局限性
共识算法对于分布式系统是一个划时代的突破:他们能够在不确定的环境里保证安全性(一致性、纯洁性和有效性),在此基础上还能够进行容错(只要大多数节点还活着就能正常运转)。他们还实现了全序广播,因此能够用来实现容错的线性一致的系统。但是:
同步复制损失性能。每次进行决策(更改数据)前都要让多数节点进行投票,意味着这是一个同步复制系统。
动态成员变更复杂。很多共识算法会假定有固定的数目节点参与投票,如果允许集群的节点集随时间推移而发生扩展变动,则扩展版本算法非常难理解。
复杂网络环境性能差。共识系统通常通过超时机制来对故障节点进行检测。在延迟高度抖动的网络中(尤其是多地部署的分布式系统中)会有误判,导致频繁的领导者选举。
成员关系和协调服务
Zookeeper 和 etcd 设计目标为存储小尺度的数据,且只会很低频的改变。系统使用可容错的全序广播算法,将小尺寸的数据被复制到所有节点上。除却共识机制,还有一些协调功能集:
线性化的原子操作(lock) 使用原子的 CAS 操作,可以实现锁。共识协议能够保证,即使随时可能出现节点宕机或者网络故障,操作仍然是原子和线性化的。一个分布式锁通常实现为具有过期时间的“租约”(lease)。
操作的全序保证(zxid) 使用防护令牌机制来防止由于进程停顿而造成的加锁冲突。防护令牌一个在每次获取锁都会单调自增的数值。Zookeeper 通过给每个操作赋予一个全局自增的事务 id(zxid)和一个版本号(cversion)来提供该功能。
故障检测(ephemeral node) 客户端和 ZooKeeper 的服务器间维持着一个长会话,通过周期性的心跳来检测存活。如果心跳停顿超过会话的超时阈值,ZooKeeper 会标记该会话死亡。所有该会话关联的锁在超时都将会被释放(暂态节点 ephemeral nodes 类节点可以将生命周期与会话进行绑定)。
变动通知(watch) 客户端不仅可以读取其他节点创建的锁或者值,也可以直接对这些对象的变化进行监视(watch),使得客户端立即发现其他客户端加入集群(通过这些客户端写入 ZooKeeper 的值)、故障(通过这些客户端注册到 ZooKeeper 中的暂态节点的消失)等。通过订阅,客户端可以避免频繁拉取信息进行比对。
ZooKeeper 及类似服务可以视为成员服务,可以确定当前集群中哪些节点当前时存活的。
最后更新于
这有帮助吗?