|
. }. I2 l( \8 B
<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">一.概述</span></strong></blockquote>8 f/ ^" n( _! ]6 v5 i% u/ h& M
<p> ZooKeeper 是什么?</p>
, P2 b" O: Q! r/ d2 P8 M; K<ul>* E" Q8 A$ s# J
<li>是一个开源的<span style="color: rgba(51, 204, 204, 1)">分布式协调服务</span>。使用分布式系统就无法避免对节点管理的问题(需要实时感知节点的状态、对节点进行统一管理等等),而由于这些问题处理起来可能相对麻烦和提高了系统的复杂性,ZooKeeper作为一个能够<span style="color: rgba(51, 204, 204, 1)">通用</span>解决这些问题的中间件就应运而生了。</li>
! U% e9 l& Z/ \# k6 u; j; o<li>从设计模式角度来理解:是一个基于<span style="color: rgba(51, 204, 204, 1)">观察者模式</span>设计的分布式服务管理框架,它负责<span style="color: rgba(51, 204, 204, 1)">存储</span>和<span style="color: rgba(51, 204, 204, 1)">管理</span>大家都关心的数据,一旦这些数据的状态发生变化,Zookeeper 就 将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应。</li>% s( j3 V4 G# ]/ n/ j( _. T8 o V
<li>实现原理:zookeeper=<span style="color: rgba(51, 204, 204, 1)">文件系统</span>+<span style="color: rgba(51, 204, 204, 1)">通知机制</span>。</li>
! k2 c! p9 p5 b2 O9 l+ F( y+ \</ul>
& B: m$ b6 L" [ x j; b$ P) D4 I* p<p>Zookeeper的作用(应用场景)?</p>
h( M) C5 U* k' ^, q2 V<ul>
: p* J3 P) F" n4 g! R<li><span style="color: rgba(51, 204, 204, 1)">统一配置管理</span>:比如现在有A.yml,B.yml,C.yml配置文件,里面有一些公共的配置,但是如果后期对这些公共的配置进行修改,就需要修改每一个文件,还要重启服务器。比较麻烦,现在将这些公共配置信息放到ZK中,修改ZK的信息,会通知A,B,C配置文件。多方便</li>
% `5 Q7 R% N V- G<li><span style="color: rgba(51, 204, 204, 1)">统一命名服务</span>:这个的理解其实跟<span style="color: rgba(51, 204, 204, 1)">域名</span>一样,在某一个节点下放一些ip地址,我现在只需要访问ZK的一个Znode节点就可以获取这些ip地址。</li>" R: _, ?' ?, S6 G+ A4 J
<li><span style="color: rgba(51, 204, 204, 1)">同一集群管理</span>:分布式集群中状态的监控和管理,使用Zookeeper来存储。</li>
! w! [* F4 p7 d' }$ q<li><span style="color: rgba(51, 204, 204, 1)">分布式协调</span>:这个是我们最常用的,比如把多个<span style="color: rgba(51, 204, 204, 1)">服务提供者</span>的信息放在某个节点上,<span style="color: rgba(51, 204, 204, 1)">服务的消费者</span>就可以通过ZK调用。
: K/ x/ K& m" m9 l, Z<ul>
; D" e$ y6 c5 `* N. X E1 c<li><span style="color: rgba(51, 204, 204, 1)">服务节点动态上下线:<span style="color: rgba(0, 0, 0, 1)">如何提供者宕机,就会删除在ZK的节点,然后ZK通知给消费者。</span></span></li>
- i0 ^, d9 F1 b. I0 k: f) V<li><span style="color: rgba(51, 204, 204, 1)">软负载均衡</span></li>! s" g/ \/ n% j! S/ o3 t9 p
<li><span style="color: rgba(51, 204, 204, 1)">动态选举Maste</span>r:Zookeeper会每次选举最小编号的作为Master,如果Master挂了,自然对应的Znode节点就会删除。然后让<span style="color: rgba(51, 204, 204, 1)">新的最小编号作为Master</span>,这样就可以实现动态选举的功能了。</li>4 w7 b: S' c: ~3 X% k
</ul>7 X5 I: u6 y1 t- R5 A5 x
</li>" ]; k# s5 q2 O" J# J6 N- X
<li><span style="color: rgba(51, 204, 204, 1)">分布式锁</span>(后续出文章讲)</li>; T4 R9 a% n( J1 p) W' b
</ul>
& Z6 h& N0 L; U$ H1 P<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">二.原理</span></strong></blockquote>6 H2 L% W+ E+ D" H9 ^
<p>之所以能做上述功能,主要是归功于ZK的<span style="color: rgba(51, 204, 204, 1)">文件系统</span>和<span style="color: rgba(51, 204, 204, 1)">通知机制</span>。下面我们来分析这两个机制</p>
2 A1 x# W/ F& u3 v4 _. h/ c<hr>2 ]# _1 m9 P( j6 K5 T" R6 `
<p> 文件系统:</p>
6 c* t* ]+ m% H: C8 ~4 M, J: J<p>ZooKeeper的数据结构,跟Unix文件系统非常类似,可以看做是一颗<span style="color: rgba(51, 204, 204, 1)">树</span>,每个节点叫做<span style="color: rgba(51, 204, 204, 1)">Znode</span>。每一个Znode只能存1MB数据。数据只是<span style="color: rgba(51, 204, 204, 1)">配置信息</span>。每一个节点可以通过<span style="color: rgba(51, 204, 204, 1)">路径</span>来标识,结构图如下:</p>
+ q0 D+ \# I/ x N7 S8 g<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211170746939-2004306213.png" ></p>9 G: Y6 m% t# _) U/ U. F
<p> Znode节点主要有4中类型:</p># X S: N( W' c% D
<ul>3 ]" O* ~6 D4 V6 H
<li><span style="color: rgba(51, 204, 204, 1)">临时目录节点</span>:客户端与Zookeeper断开连接后,该节点被删除</li>
4 `# ?4 g# |1 Q; U# V( ~" `3 _<li><span style="color: rgba(51, 204, 204, 1)">临时顺序编号目录节点</span>:基本特性同临时节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。</li>
/ m! ?5 I0 G3 G! R2 q<li><span style="color: rgba(51, 204, 204, 1)">持久化目录节点</span>:客户端与Zookeeper断开连接后,该节点依旧存在</li>+ f: O2 H: J6 u/ f0 r3 ?- O
<li><span style="color: rgba(51, 204, 204, 1)">持久化顺序编号目录节点</span>:基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。</li>
$ L8 B1 D8 S! I' r; l; Q</ul>6 Y! u9 a p* n8 `
<hr>) D- t( R. ]8 J1 Z5 S: [, Q
<p> 通知机制 (监听机制)</p>6 h: n4 F, _0 Z( _7 C+ T
<p>Zookeeper可以提供分布式数据的<span style="color: rgba(51, 204, 204, 1)">发布/订阅</span>功能,依赖的就是Wather监听机制。</p>1 `' }/ u+ }0 c7 Y) i7 [
<p>客户端可以向服务端<span style="color: rgba(51, 204, 204, 1)">注册</span>Wather监听,服务端的指定事件<span style="color: rgba(51, 204, 204, 1)">触发</span>之后,就会向客户端发送一个事件<span style="color: rgba(51, 204, 204, 1)">通知</span>。具体步如下:</p>* J: {: u9 m4 [1 Z/ M
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211172333942-1239203073.png" ></p>7 t: E9 M# I. s
<ol>. o/ P, m8 c3 n3 u% p
<li>客户端向服务端注册Wather监听</li>
0 ]7 e9 X" H' M<li>保存Wather对象到客户端本地的WatherManager中</li>
8 t( a1 E1 p2 u. q3 Z<li>服务端Wather事件触发后,客户端收到服务端通知,从WatherManager(watcher管理器)中取出对应Wather对象执行回调逻辑</li>
) ~5 o t1 T& }3 N# F4 t% l# V</ol>5 u) V4 {" t% ^; b) x- l
<p> 主要监听2方面内容:</p>
# Q% o0 y, Z5 f7 s" H<ul class="list-paddingleft-2">
9 A F' h+ g# ^0 A/ p1 }8 G<li>- i% N2 w) l) B L* u I
<p>监听Znode节点的<span style="color: rgba(51, 204, 204, 1)">数据变化:<span style="color: rgba(0, 0, 0, 1)">就是那个节点信息更新了。</span></span></p># J+ e# w6 ~0 r# [( k
</li>7 k. d: G& P0 P! E
<li>- \8 c @2 z& \+ S8 o
<p>监听子节点的<span style="color: rgba(51, 204, 204, 1)">增减变化<span style="color: rgba(0, 0, 0, 1)">:就是增加了一个Znode或者删除了一个Znode。</span></span></p>
5 I2 s7 }* W/ ^ h S j9 \# O</li>4 e$ S4 i8 U2 R* j
</ul>
5 [! P9 z( R( y/ g/ b# P<p><span style="color: rgba(0, 0, 0, 1)">几个特性:</span></p>! B$ [) R- e- O* p" p
<ul>
" A9 p1 u( m( V! ]<li>一次性:一旦一个Wather触发之后,Zookeeper就会将它从存储中移除</li>
2 R1 A* ]) O4 X<li>客户端串行:客户端的Wather回调处理是串行同步的过程,不要因为一个Wather的逻辑阻塞整个客户端</li>- X [) b) I/ F; N0 @( O5 s: J! `
<li>轻量:Wather通知的单位是WathedEvent,只<span style="color: rgba(51, 204, 204, 1)">包含通知状态、事件类型和节点路径,不包含具体的事件内容</span>,具体的时间内容需要客户端主动去重新获取数据</li>
9 o6 P4 N# B9 C0 r4 ]$ w# ?</ul>
: J% }8 k$ y% g6 ^" F<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">三.ZK集群(相关概念)</span></strong></blockquote>
/ ]$ j+ z" F+ s0 q# O<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211182203890-1695256509.png" ></p>
1 [ `, Z" \8 s* p4 }( R/ W% H0 b<ul>
- W# ]6 q# @" [6 ]<li>Leader:负责写数据。(写数据都有事务)</li>4 i2 i* A! N% e( U
<li>Follower:负责读数据,节点的<span style="color: rgba(51, 204, 204, 1)">选举</span>和<span style="color: rgba(51, 204, 204, 1)">过半写成功<span style="color: rgba(0, 0, 0, 1)">。(读数据没有事务)</span><strong><br></strong></span></li>
& q! a' T& M. P<li><span style="color: rgba(51, 204, 204, 1)"><span style="color: rgba(0, 0, 0, 1)">Observer:只负责读。</span></span></li>
0 ]( V: ^/ c+ G' C8 q4 a! q' s- i# p
</ul>
8 X2 J, z% x, u& p( K% }9 u8 E<hr>% c; \$ {" e0 j8 x$ l5 n
<p>从上面的角色种,我们可以总结ZK节点的工作状态(服务状态)</p>
- D' o/ ^7 B) h& O/ k. I6 U" P<ul>3 B# ]' L$ R9 p
<li>LOOKING:寻 找 Leader 状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。</li>
3 Z% T" ?9 t6 ]5 g5 o<li>FOLLOWING:跟随者状态。表明当前服务器角色是 Follower。</li>9 J% w: O, u3 \9 A! o
<li>LEADING:领导者状态。表明当前服务器角色是 Leader。</li>
0 Y& L! T4 q" {. Y/ ~<li>OBSERVING:观察者状态。表明当前服务器角色是 Observer。</li>+ L ]5 B: i# Q$ X$ E+ l- U" \
1 w* ]) w/ N2 q7 d: l
</ul>
" a- F- |* N) y<hr>
- h$ H+ I, f0 e2 A1 J<p>其他概念:</p>: P) G4 z1 d, T* I4 X* K
<ul>
2 Y$ D; I" {9 C3 n/ Y5 G<li>zxid:<span style="color: rgba(51, 204, 204, 1)">全局事务ID</span>,分为两部分:
# P" H) \% g6 Q1 U# F<ul>2 B2 h. A- a* \
<li>纪元(epoch)部分:epoch代表当前集群所属的哪个leader,leader的选举就类似一个朝代的更替,你前朝的剑不能斩本朝的官,用epoch代表当前命令的有效性。</li>3 N% W8 C# E+ Q* }; m6 `/ K
<li>计数器(counter)部分,是一个<span style="color: rgba(51, 204, 204, 1)">全局有序</span>的数字,是一个递增的数字。</li>
8 U$ }8 W! i2 h: _! {5 y+ u% A9 z4 E& ~8 N
9 Z. {7 i5 d! ~" Q* r0 o1 a6 D% z</ul>5 s! x! j. @1 z7 ^( v
+ `7 _# f0 Z: y. I" r3 F! \
Z% i; I" k9 E! C4 Q9 ]
</li>
O, J" [7 l- d" {# t2 a' l5 f* c
, C% D: r$ x# e3 p) d- d
</ul>
) u" G6 b& W3 K4 T<hr>0 W6 O3 m9 @9 q( [9 o# j! Q0 ~. X' y. x
<p>写数据原理:</p>( q2 R% p/ ^7 m$ U
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211214106019-937037786.png" ></p>
9 p+ T* }3 s. _) q<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211214136079-1875911582.png" ></p>
% T% E+ ?" ~/ m9 x<ul>
* M$ p8 G- E3 H: A1 P<li>写给leader,leader再通知其他节点 </li>
7 R, z( W! G" r% R<li>写给follower,follower没有写的权限,交给leader写,leader再通知。 </li>% {, W( ~+ J& ?' q+ Q) Y( [# _, d
<li><span style="color: rgba(51, 204, 204, 1)">半数机制</span>:比如上图,zookeeper在通知其他节点写的时候,达到半数就通知客户端写完成。 不需要全部写完成。所以集群的数量一般是奇数。</li>
* B* g- s4 o1 c2 u
& _: T: E/ d( x8 R7 Y
8 d: d+ C6 i0 X9 k</ul>% F# u- @0 v' t0 Z* v b
<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">三.ZK集群(原理)</span></strong></blockquote>3 h! p* o$ J0 }3 P6 w9 e
<p> 上面我们知道集群的基本概念,那么也会引出很多问题:ZK怎么保证数据一致性?Leader宕机了如何进行选举?选举后数据如何同步?</p>2 [( y% a* X1 M: `- P5 |, m
<hr>
4 j' _( V( p y8 O+ J<p> ZK怎么保证数据一致性?</p>
6 j5 W' d+ D8 P3 w' x5 I<p>由于ZK只有Leader节点可以写入数据,如果是其他节点收到写入数据的请求,则会将之转发给Leader节点。ZK通过<span style="color: rgba(51, 204, 204, 1)">ZAB协议</span>来实现数据的最终顺序一致性,他是一个类似2PC两阶段提交的过程。ZAB有2种模式:<span style="color: rgba(51, 204, 204, 1)">消息广播</span>,<span style="color: rgba(51, 204, 204, 1)">崩溃恢复</span>(选举)。</p>
/ x k- j/ e! J$ P* F% ^( W<p> 一般我们正常是消息广播:</p>
7 d# ?" e% C5 t- L0 O7 v( M6 I1 L<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211205808867-321051219.png" ></p>) G7 z! m, `+ C5 |
<ul>1 F4 j6 m5 A% N
<li>第一阶段:<span style="color: rgba(51, 204, 204, 1)">广播事务阶段</span>:对应图上的1,2; r3 F+ L- C! r' S
<ul>1 y; N+ G: D7 h, G
<li>Leader收到请求之后,将它转换为一个proposal提议,并且为每个提议分配一个事务ID:zxid,然后把提议放入到一个FIFO的队列中,按照FIFO的策略发送给所有的Follower。</li>& b7 y! t" I! j) I5 u4 L2 f
<li>Follower收到提议之后,以事务日志的形式写入到本地磁盘中,写入成功后返回ACK给Leader</li>
- j7 `; X6 s! ~4 _" ^: t/ Q' p* R0 p2 b+ U# o) A
2 `) x4 f/ Z* o; B
. C1 r0 Z9 Q# C- o: Q1 K
+ C* R% G+ x5 ?& ]$ f2 ~
1 y l& Y0 T. Q9 c
* ~& n8 F* M7 C5 \7 O4 W: `/ x</ul>, D/ U( ^4 r* p J- s3 M+ U. o
7 C4 d3 y( W: ?4 h
! o3 B& m/ y! e% G' @% i, b, ^( }; \; ]$ A; U% y
+ y6 S* B' p0 h% E& l% i
6 U! G2 X' @3 F6 d6 M9 t# H2 z
</li> ^4 E% |- R6 Q: U8 E$ O
<li>第二阶段:<span style="color: rgba(51, 204, 204, 1)">广播提交操作</span>:对应图上的3
( c; c7 t! m6 W1 S! \2 x) @( [<ul>
/ V: \5 D; M' O, g, Y<li>Leader在收到超过半数的Follower的ACK之后,即可认为数据写入成功,就会发送commit命令给Follower告诉他们可以提交proposal了。</li>* r C3 I2 A$ E( @9 n: W4 w+ W
* _# T2 K# Y6 n w
2 q; Q9 Z" m: b( X
3 g. D9 d% `0 y1 f% J- A% i; o- q
3 y) C: w! U* G X+ k! p$ p3 ^3 t
% D. X5 p/ \! z7 @. Z
; W" s4 r1 j5 c0 Y9 f6 ?) M</ul>
9 A/ \/ K' @+ q, Y1 b
; a) n; _% \' m: R+ p
( `( b$ A; Y* K% g1 C& v
. ]6 s# m# z6 d9 B3 Y, y: F( Y0 Z! v7 P0 n6 K/ G
& }9 X# N0 n- K! z6 U2 I6 c9 o! j& P* `$ n3 r. T
</li>
6 J3 @1 @# A# F3 G
I5 V9 p8 ]. v) q( E9 c2 s$ N
# c0 F3 N. `2 t# g! f" k) Z' n$ F) Z
. k% T2 S: Q6 J. d& O$ R, x( C7 @5 R9 C: J- _2 z9 ]
+ x% E0 R n' q</ul>5 R( i. r- |5 i* \* }
<hr>5 k% A m6 @+ Z8 m! u" m
<p>Leader宕机了如何进行选举?</p>
+ w; ?, [7 d3 l: m7 ~<p>这就得使用ZAB的第二种模式,崩溃恢复模式:</p>
* [+ h; H2 ]) |* H<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211211246367-43062481.png" ></p>
4 w' E- F( e) O3 w/ ], z4 e# p @- W<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211211725764-329743928.png" ></p>5 M8 x/ \0 _9 Z+ N; f$ t8 x# G9 P
<hr>6 d* i* ?; J) w
<p>选举后数据如何同步?</p>
: t; v/ f, W% b$ g5 _! i2 d& f<p data-tool="mdnice编辑器">那实际上Zookeeper在选举之后,Follower和Observer(统称为Learner)就会去向Leader注册,然后就会开始数据同步的过程。</p>& l. D& Y+ Z: F6 h/ W
<p data-tool="mdnice编辑器">数据同步包含3个主要值和4种形式。</p>& N- J8 t. B/ n: _3 b, S2 r2 r* x
<ul>
- [ n/ H, F( V. c6 W; \<li data-tool="mdnice编辑器">PeerLastZxid:Learner服务器最后处理的ZXID</li>
# A6 E+ |7 j& O) b, H<li data-tool="mdnice编辑器">minCommittedLog:Leader提议缓存队列中最小ZXID</li># }2 h; n) y5 _# c% |# `4 R
<li data-tool="mdnice编辑器">maxCommittedLog:Leader提议缓存队列中最大ZXID</li>1 y# T" o. K2 k3 ]* `
: v; F, t1 k5 f' t3 M
1 j" K# T: ?( n7 T& M& d
+ {" U8 K1 q. D4 V' k, ~
5 }" i' O( N+ R0 S9 A! U2 @' o8 ~
# l* N' y2 n% j" ]- Q a8 _: A$ X6 R& N5 P4 }
</ul>
: } H U7 q" N<p>同步策略:</p>5 p* R+ z' ^) T9 \2 Z
<ul>% y" j% K* L, m5 g+ ?; z
<li><span style="color: rgba(51, 204, 204, 1)">直接差异化同步</span> (DIFF同步):如果PeerLastZxid在minCommittedLog和maxCommittedLog之间,那么则说明Learner服务器还没有完全同步最新的数据。<ol>; _' z! n+ t; V
<li style="margin-top: 0; margin-right: 0; margin-bottom: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; outline: 0; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important">首先Leader向Learner发送DIFF指令,代表开始差异化同步,然后把差异数据(从PeerLastZxid到maxCommittedLog之间的数据)提议proposal发送给Learner</li>
6 E, l+ M ?; d H) Z" x& U1 S<li style="margin-top: 0; margin-right: 0; margin-bottom: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; outline: 0; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important">发送完成之后发送一个NEWLEADER命令给Learner,同时Learner返回ACK表示已经完成了同步</li>; l% d4 y, c2 [1 r9 X" i$ X
<li style="margin-top: 0; margin-right: 0; margin-bottom: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; outline: 0; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important">接着等待集群中过半的Learner响应了ACK之后,就发送一个UPTODATE命令,Learner返回ACK,同步流程结束</li>) d7 c$ f5 q1 Z
. U- F+ d% F* U$ i, H; o
. u7 Y% G$ I Z8 y& Z. V" K9 t
+ z3 u& B) p1 A" Z; {3 B. u6 [; R
( q0 D8 m. F! K9 w8 P2 `
</ol></li>
7 ~" {8 z R; J( V1 V2 ?5 R<li style="text-align: justify"><span style="color: rgba(51, 204, 204, 1)">先回滚再差异化同步</span>(Trunc+DIFF同步):特殊场景:<span style="font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif"><span style="letter-spacing: 2px">如果Leader刚生成一个proposal,还没有来得及发送出去,此时Leader宕机,重新选举之后作为Follower,但是新的Leader没有这个proposal数据</span><span style="font-size: 16px; letter-spacing: 2px">。</span></span>! ?0 I: c) w* l* S2 V
<ul>1 F1 G) Z( P3 v* ]
<li style="text-align: justify"><span style="font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif"><span style="letter-spacing: 2px">举个栗子:</span></span>假设现在的Leader是A,minCommittedLog=1,maxCommittedLog=3,刚好生成的一个proposal的ZXID=4,然后挂了。重新选举出来的Leader是B,B之后又处理了2个提议,然后minCommittedLog=1,maxCommittedLog=5。这时候A的PeerLastZxid=4,在(1,5)之间。那么这一条只存在于A的提议怎么处理?</li>7 [7 E1 j* Q% |! K
<li style="text-align: justify">
+ g: ^, ?/ ~. ]6 l# D) Z<p data-tool="mdnice编辑器">A要进行事务回滚,相当于抛弃这条数据,并且回滚到最接近于PeerLastZxid的事务,对于A来说,也就是PeerLastZxid=3。流程和DIFF一致,只是会先发送一个TRUNC命令,然后再执行差异化DIFF同步。</p>' Z- {, q2 z4 i) O8 Y0 g- y- I
% v4 |4 N- u- N( k
9 L0 v$ s( v* ?$ \' c/ b) `
% V# a2 h. g; b* k% [7 O5 F' ]. w1 P
</li>
4 c5 Y2 `1 |- j1 j: m0 S7 U) P C4 Y6 Z; y" g
- i3 ~8 E0 }- e N, X
" W; l# T* h* F I! e( o' `- f0 f0 }
</ul>$ |) B6 B" s, ?4 |4 m( M; o$ |+ w+ B' E
& s ?& b5 {: \6 U- h
" c/ U' Y. V" X' m3 J; V7 G6 l' h2 Z7 n
4 W5 O d6 d7 e4 Z: e6 V: D$ p/ D
</li>. f& D5 ]9 o! R* c4 x
<li><span style="color: rgba(51, 204, 204, 1)">仅回滚同步</span>(TRUNC同步):4 }1 k1 p% }/ ?4 g8 F# Z/ ?
<ul>4 O% J2 d9 m7 }8 a$ Z( y
<li data-tool="mdnice编辑器">针对PeerLastZxid大于maxCommittedLog的场景,流程和上述一致,事务将会被回滚到maxCommittedLog的记录。</li>
; p t( d2 J+ G# a" I" S& O4 P<li data-tool="mdnice编辑器">这个其实就更简单了,也就是你可以认为TRUNC+DIFF中的例子,新的Leader B没有处理提议,所以B中minCommittedLog=1,maxCommittedLog=3。</li>6 h7 E) `4 D" F; \ x8 ~
<li data-tool="mdnice编辑器">所以A的PeerLastZxid=4就会大于maxCommittedLog了,也就是A只需要回滚就行了,不需要执行差异化同步DIFF了。</li>
) n9 j' r: i! y8 b6 p, K, X% a" a/ n R1 H9 Z
# S7 ~" Q/ ^& f# w6 @
, G* D- \0 m/ ?3 J' @/ H: z, C6 x- z6 D& a# M3 i) V% i
</ul>
' b2 @. n4 x4 D9 v
8 J% o4 v/ N$ _+ @& v2 x9 b9 W1 s
3 c, G4 q5 ?2 W- g4 I A4 ~
, A5 w; O( b( c. H! N3 E, J
8 N# w8 F9 Z* ^: H" a: [, s</li>) J+ E2 B; Q; f% r) t
<li><span style="color: rgba(51, 204, 204, 1)">全量同步</span> (SNAP同步):
' E: k0 z! P1 k# K# j# p<ul>7 S! v4 I& e# ], f
<li>/ [- F2 @( K6 @: u& y
<p data-tool="mdnice编辑器">适用于两个场景:</p>
3 }$ F: o( ~$ S- k<ol class="list-paddingleft-2" data-tool="mdnice编辑器">3 z7 n0 P/ R& R$ b) W! v9 B
<li>PeerLastZxid小于minCommittedLog</li>& k9 c2 T8 J/ X: C b; j o
<li>Leader服务器上没有提议缓存队列,并且PeerLastZxid不等于Leader的最大ZXID</li>. q; y! h* g1 _5 S6 b, O- @) M2 i
A2 O: ?9 q0 v( \ z- f; o% w6 f8 J7 I% p3 p) L: m1 c) `
& p# \* l) W6 }3 b
# n; X1 z- T; {8 o</ol></li>3 O s" T! H8 T" M
<li>这两种场景下,Leader将会发送SNAP命令,把全量的数据都发送给Learner进行同步。</li>9 m/ i+ N. |1 A, i- B; Y) R. c6 C
! S* N# _ i% e0 d$ H& @, |9 s' p
' }: t' O2 D. [) a' ]! w' W5 e/ L4 q* k z* _ q. {* F7 w, C
. n$ B+ ]$ b/ i$ K+ @" K</ul>; d' L3 [1 ^7 S5 W7 v1 r
6 d8 \ V: f, O% G5 C3 \2 E: o" d
+ J, U9 w3 {- N7 z
6 @- R1 c: R4 d* D7 k7 y
) v4 d% g3 i+ z4 D/ \3 `; Q
</li>
4 t; q/ ]1 j% z* k3 b
1 j3 ~5 t/ P" j
. E" g) T2 K0 Q. x: o1 Z4 A7 T
" L; @1 q' \; a/ K
; x. N; q: ~/ _% N$ J8 @3 Z</ul>: K) d2 g& l U- t
<hr> D* g+ U, ]9 b) |1 ]! a. H5 y
<p data-tool="mdnice编辑器">有可能会出现数据不一致的问题吗?</p>
. t$ ?+ H( }% h8 I7 c% F<p data-tool="mdnice编辑器">还是会存在的,我们可以分成3个场景来描述这个问题。</p>
# `. T' D+ k$ b1 z, q<ul>5 z, W: Y) }( Y, o1 p2 p
<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">查询不一致</span><strong><strong>:</strong></strong>9 V4 C/ \: t+ C t1 J3 [. D4 D
<ul># [7 I: s" U6 D$ ~ y4 k' @, n
<li data-tool="mdnice编辑器">因为Zookeeper是过半成功即代表成功,假设我们有5个节点,如果123节点写入成功,如果这时候请求访问到4或者5节点,那么有可能读取不到数据,因为可能数据还没有同步到4、5节点中,也可以认为这算是数据不一致的问题。</li>6 k9 Y/ _, A0 p
<li data-tool="mdnice编辑器">解决方案可以在读取前使用sync命令。</li>
: v) E# R- P9 \- c$ l; { n) f! N. ^: V( @. p7 R/ K1 J
2 I. I5 E0 A# L4 G) F: D5 n4 b' u- g</ul>
! P1 K3 y$ ?1 _0 d! [& J& K* o
1 g& H& X& N9 k- E3 ~) M, p- ]$ L2 {) x# A; L' K& Z
</li>
5 q9 }7 p. K2 X) s+ B<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">leader未发送proposal宕机</span><strong>:</strong>
9 L; D5 m- ]4 l* Y% U: e<ul>7 K3 v$ l/ L, U* L
<li data-tool="mdnice编辑器">: X/ l9 u% S2 h3 C9 {. I5 }- N
<p data-tool="mdnice编辑器">这也就是数据同步说过的问题。leader刚生成一个proposal,还没有来得及发送出去,此时leader宕机,重新选举之后作为follower,但是新的leader没有这个proposal。</p>) A& d+ Y. B$ q7 W0 t0 q K; x
0 E# K6 Z* l* Y9 d; i
3 q6 L# U* U. J ?$ v6 k
</li>
8 Y' [( W: M/ s# [/ i<li data-tool="mdnice编辑器">( F" _- G& t o/ R* F
<p data-tool="mdnice编辑器">这种场景下的日志将会被丢弃。</p>) _% }4 s3 _/ ]1 `0 a: ~6 {
6 F$ f* d. Y. U. u/ S4 Y3 K+ b `$ ^9 g0 e; A( p
</li>; |2 I ]% y5 x+ v) M6 {
+ {6 c" v0 M1 w+ M `) ]" |
( c5 T" t1 k' L) h h# ]3 o</ul>2 i" @1 ^4 s/ U
, x& o- a8 L0 w) e7 g
& ^( j4 B$ D. T3 d2 G: T0 G7 z: `</li>! q8 T2 q" C i+ w
<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">leader发送proposal成功,发送commit前宕机</span><strong>:</strong>9 }8 P; |; A# m& Y2 M2 o6 B
<ul>
: c) h% ]( |# o8 _! A' c3 H0 d) _& J<li data-tool="mdnice编辑器">如果发送proposal成功了,但是在将要发送commit命令前宕机了,如果重新进行选举,还是会选择zxid最大的节点作为leader,因此,这个日志并不会被丢弃,会在选举出leader之后重新同步到其他节点当中。<strong><br></strong></li>
& X/ u- G4 U8 d+ }( ~4 l2 C. i( O
" G b, h5 u- U/ d" i7 t% R7 T* z
</ul>
6 l% e- ~ c8 D' O9 X; L/ o- M) ~; g" ^1 q' h3 v# ~1 M/ E: e* I
! R' N# G! b1 S$ n, [</li>3 O X& T) G* ~1 b8 a
8 V u5 F5 M+ P' V
1 u( }& U' a5 G</ul>
: ?1 V) M2 C9 R9 C<blockquote><span style="color: rgba(0, 0, 0, 1)"><strong>四.ZK其他小问题</strong></span></blockquote>3 W( c( f5 N f# t) d& w
<p>zookeeper 是如何保证事务的顺序一致性的?</p>& c. Y- N+ x) Q' U
<ul>, f1 L# f- x q: v
<li>使用<span style="color: rgba(51, 204, 204, 1)">zxid</span>来保证顺序性。</li>
# I. x1 W8 x5 j/ E' h/ p0 l: b" f$ u, h! |, b% ]% b
. t: L( X1 q' _5 }3 ?</ul>, u1 J; E. C+ J( C+ _+ {7 m
<hr>
! G& C G7 b& R" k+ A* X& J) [<p>集群最少要几台机器,集群规则是怎样的?集群中有 3 台服务器,其中一个节点宕机,这个时候 Zookeeper 还可以使用吗?</p>) J; I% o, T- x* x9 @, p
<ul>( W5 g @1 u1 b2 ^: O
<li>集群规则为 <span style="color: rgba(51, 204, 204, 1)">2N+1</span> (奇数)台,N>0,即 3 台。可以继续使用,单数服务器只要没超过一半的服务器宕机就可以继续使用。</li># { w3 [9 a( I, K% y! e; G+ }
: Q. E+ K3 I& w. N v
' i7 S: N$ }3 f' Y) f. M2 T
</ul># O( b; L- p, t. d- c
<hr>
$ d# k i+ [9 j- H2 O<p>说几个 zookeeper 常用的命令:</p>
Z6 e* y1 R" o) [7 `<ul>
$ x. h9 Z$ i) x/ y `<li>ls path:查看当前 znode 的子节点</li>0 F. g w( s# V g) N
<li>get path:获取节点的值</li>
# q% }/ B% r- M5 U: ^) G, I1 r<li>set:设置节点的值</li>1 {) T- ` x( D
<li> create,delete:创建/删除节点</li>) n& {0 g% Y! H% V
|: E' N* F& ?* R8 f' X; V
* n/ z6 K2 z* u. S3 D# x</ul>
/ I/ U9 s5 ~& |+ Y L<hr> e( W$ |7 i+ @6 A; a% i
<p>会话Session:</p>
$ ]- E0 X! v' {% N7 g<ul>* ~" R% n# N$ v* @
<li>会话自然就是指Zookeeper客户端和服务端之间的通信,他们使用TCP长连接的方式保持通信,通常,肯定会有<span style="color: rgba(51, 204, 204, 1)">心跳检测</span>的机制,同时他可以接受来自服务器的Watch事件通知。</li>2 D9 e' _5 C' ^
/ O( m- k- R8 K( S8 d
% e2 \. c( n$ j, D6 e6 k# u</ul>& R$ i6 z: J! j1 B- {
<p> </p>
* {8 s4 H6 T# ]4 s6 U" y* ?, }2 [<p>寄语:<span style="color: rgba(51, 204, 204, 1)">平静的湖面酝酿不出精悍的水手,安逸的环境创造不出时代的伟人</span></p>
, T6 N( h3 Q+ V7 W |
|