|
! N. d! F" ?. O9 Z<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">一.概述</span></strong></blockquote>. m B8 e( W" H6 I; ^3 k5 l& V
<p> ZooKeeper 是什么?</p>
a9 Y% `5 Z5 [, s/ ]<ul>) S/ h3 V% T& v$ G& s! Y
<li>是一个开源的<span style="color: rgba(51, 204, 204, 1)">分布式协调服务</span>。使用分布式系统就无法避免对节点管理的问题(需要实时感知节点的状态、对节点进行统一管理等等),而由于这些问题处理起来可能相对麻烦和提高了系统的复杂性,ZooKeeper作为一个能够<span style="color: rgba(51, 204, 204, 1)">通用</span>解决这些问题的中间件就应运而生了。</li>' R5 D2 w; d. W1 Q& l! v9 ]
<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>
" Z x m; v" N8 l o<li>实现原理:zookeeper=<span style="color: rgba(51, 204, 204, 1)">文件系统</span>+<span style="color: rgba(51, 204, 204, 1)">通知机制</span>。</li>3 {# Z4 t* X# a+ J( h- l4 v2 m) b" I
</ul>* R) k$ E9 l) Z8 c9 g
<p>Zookeeper的作用(应用场景)?</p>
" ]) }0 X' I7 B; r<ul>1 x. N5 [) L# @) ^
<li><span style="color: rgba(51, 204, 204, 1)">统一配置管理</span>:比如现在有A.yml,B.yml,C.yml配置文件,里面有一些公共的配置,但是如果后期对这些公共的配置进行修改,就需要修改每一个文件,还要重启服务器。比较麻烦,现在将这些公共配置信息放到ZK中,修改ZK的信息,会通知A,B,C配置文件。多方便</li>9 x# T! g0 J* I2 L
<li><span style="color: rgba(51, 204, 204, 1)">统一命名服务</span>:这个的理解其实跟<span style="color: rgba(51, 204, 204, 1)">域名</span>一样,在某一个节点下放一些ip地址,我现在只需要访问ZK的一个Znode节点就可以获取这些ip地址。</li>
6 b( a- ]+ V6 O<li><span style="color: rgba(51, 204, 204, 1)">同一集群管理</span>:分布式集群中状态的监控和管理,使用Zookeeper来存储。</li>, ]0 P( z& T$ O' a- C" G* q$ u8 ]
<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调用。
& r; W: w7 w$ P& _<ul>
. O# B7 ?$ k' ^<li><span style="color: rgba(51, 204, 204, 1)">服务节点动态上下线:<span style="color: rgba(0, 0, 0, 1)">如何提供者宕机,就会删除在ZK的节点,然后ZK通知给消费者。</span></span></li>
1 r# w0 P) d( `" ?<li><span style="color: rgba(51, 204, 204, 1)">软负载均衡</span></li># ~+ N* B+ K9 X4 J
<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>2 G4 X0 F* [, w2 a1 L
</ul>0 E: r; ~& x' Y6 e6 m& [! E4 W
</li>% e" X( {& L# b8 z9 G
<li><span style="color: rgba(51, 204, 204, 1)">分布式锁</span>(后续出文章讲)</li>
4 O" V& ]7 Z0 a! M* N+ \ [</ul>- |' M. P8 O) u# r% _" _8 E P
<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">二.原理</span></strong></blockquote>
2 J* T/ x& q* q<p>之所以能做上述功能,主要是归功于ZK的<span style="color: rgba(51, 204, 204, 1)">文件系统</span>和<span style="color: rgba(51, 204, 204, 1)">通知机制</span>。下面我们来分析这两个机制</p>2 K( ]; B5 `/ X3 ]
<hr>
9 v1 V* a) T( N. L<p> 文件系统:</p>+ h; W2 ~7 V8 T6 w) W" m
<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>9 }9 M1 S/ r8 l/ M
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211170746939-2004306213.png" ></p>/ |3 q9 ~1 g; v
<p> Znode节点主要有4中类型:</p>
0 l5 U$ }/ T' Q+ ]3 p9 q<ul>4 ^& f6 K1 R) c( E3 Q( g5 p6 `0 l( T
<li><span style="color: rgba(51, 204, 204, 1)">临时目录节点</span>:客户端与Zookeeper断开连接后,该节点被删除</li>
, j- O; P5 H- l% q6 G! a" Q6 M<li><span style="color: rgba(51, 204, 204, 1)">临时顺序编号目录节点</span>:基本特性同临时节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。</li>) G4 b, ~9 P- M7 M( m
<li><span style="color: rgba(51, 204, 204, 1)">持久化目录节点</span>:客户端与Zookeeper断开连接后,该节点依旧存在</li>& x; U; u! J, H+ Z
<li><span style="color: rgba(51, 204, 204, 1)">持久化顺序编号目录节点</span>:基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。</li>; h5 d! _* n/ Z& S6 E: J8 i
</ul>
$ i7 V9 c2 w6 ]/ [: ~5 W<hr>
x9 c& U4 o5 p<p> 通知机制 (监听机制)</p>! a( b; h% [+ S& l1 n5 H0 f( [7 k
<p>Zookeeper可以提供分布式数据的<span style="color: rgba(51, 204, 204, 1)">发布/订阅</span>功能,依赖的就是Wather监听机制。</p>& z2 h' a! D4 g' X" ^0 x# Y
<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>0 ]) J4 t9 U: O. f1 r1 S
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211172333942-1239203073.png" ></p>) g; t- i# @; s* R4 }1 U
<ol>
+ R& d) n/ g- U' } x<li>客户端向服务端注册Wather监听</li>
, C0 w4 q5 n+ m! i0 W: s3 p; Z<li>保存Wather对象到客户端本地的WatherManager中</li>; c. q6 R6 b9 Y C6 ]
<li>服务端Wather事件触发后,客户端收到服务端通知,从WatherManager(watcher管理器)中取出对应Wather对象执行回调逻辑</li>
7 U) o) T4 B; s$ f4 f/ Z! B</ol>/ j, \; F$ t6 y: B
<p> 主要监听2方面内容:</p>& D3 e3 h; T9 ?0 E4 L( a
<ul class="list-paddingleft-2">
- Y* m% ~# b; V<li>
; M' S# ^$ r3 g- q. Y<p>监听Znode节点的<span style="color: rgba(51, 204, 204, 1)">数据变化:<span style="color: rgba(0, 0, 0, 1)">就是那个节点信息更新了。</span></span></p>" ~9 q- ^- e9 G9 R( A, r1 v7 R
</li>3 T4 \, L0 R; W: M9 N% u
<li>+ H: o: i# A$ w4 q( X0 F; M% H- a
<p>监听子节点的<span style="color: rgba(51, 204, 204, 1)">增减变化<span style="color: rgba(0, 0, 0, 1)">:就是增加了一个Znode或者删除了一个Znode。</span></span></p>* E) V, \9 K1 }
</li>+ ^+ s0 ]" q3 b0 K6 ~1 }. x: m
</ul>
W# t% v2 i4 N* k3 ~. W8 W- Z( W<p><span style="color: rgba(0, 0, 0, 1)">几个特性:</span></p>
/ |& G8 l& p' K4 k2 u' F1 B( V<ul>! u5 a4 l& A) v4 I. D
<li>一次性:一旦一个Wather触发之后,Zookeeper就会将它从存储中移除</li>' @! m) T" _# i2 y
<li>客户端串行:客户端的Wather回调处理是串行同步的过程,不要因为一个Wather的逻辑阻塞整个客户端</li>2 s$ X0 s$ l x4 v( E) ^, W; U
<li>轻量:Wather通知的单位是WathedEvent,只<span style="color: rgba(51, 204, 204, 1)">包含通知状态、事件类型和节点路径,不包含具体的事件内容</span>,具体的时间内容需要客户端主动去重新获取数据</li>
" O) t0 N9 o3 J7 c8 W! e* O R</ul>
8 a% m, V) S/ \# t2 e<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">三.ZK集群(相关概念)</span></strong></blockquote>6 w5 b: [( w$ a: D! p2 u8 C4 r
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211182203890-1695256509.png" ></p>
4 [1 I' ^' E" {- @* g<ul>9 \0 ]& ^# C/ P8 R" u
<li>Leader:负责写数据。(写数据都有事务)</li>
, c: d+ Y P) p3 ]/ x" Q" }1 Q<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>
* x7 W8 a2 @* N8 \( b& H<li><span style="color: rgba(51, 204, 204, 1)"><span style="color: rgba(0, 0, 0, 1)">Observer:只负责读。</span></span></li>
0 q1 f4 e8 x9 V# O& I6 ^" [) C" a* o% ^4 Q- u* d1 }
</ul>3 G! w; B6 E! W1 r M( t: l
<hr>
9 X$ m* B) Z; \' `* _2 {/ L<p>从上面的角色种,我们可以总结ZK节点的工作状态(服务状态)</p>
) ?7 k% ]: W8 {& C0 c3 q4 J9 ]<ul>" b$ } X$ J! _5 d+ r- V
<li>LOOKING:寻 找 Leader 状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。</li>! {3 O5 ~7 y1 w
<li>FOLLOWING:跟随者状态。表明当前服务器角色是 Follower。</li>$ v' I B+ u. T4 F5 O& D
<li>LEADING:领导者状态。表明当前服务器角色是 Leader。</li>8 G9 f/ I, s9 Y, _) E5 M1 q/ J
<li>OBSERVING:观察者状态。表明当前服务器角色是 Observer。</li>
" B8 }7 C1 `/ f* y; j4 o0 }% i2 N% [# a. J; q
</ul>
5 w& l+ {, _5 f. Q<hr>! A( `' K3 F1 K" x2 S; B$ ?
<p>其他概念:</p>, ^' Z- H+ V1 l
<ul>" V) G; l& }" e* E T
<li>zxid:<span style="color: rgba(51, 204, 204, 1)">全局事务ID</span>,分为两部分:; y3 F0 A' o. r
<ul>$ k) r1 t# B$ a2 O! t1 K
<li>纪元(epoch)部分:epoch代表当前集群所属的哪个leader,leader的选举就类似一个朝代的更替,你前朝的剑不能斩本朝的官,用epoch代表当前命令的有效性。</li># P- t8 q+ T3 m: A! s
<li>计数器(counter)部分,是一个<span style="color: rgba(51, 204, 204, 1)">全局有序</span>的数字,是一个递增的数字。</li>& u& ?, U5 L0 h9 K) ~& Z+ e! |( D# X* Q
* F0 \& j4 S7 b
& P8 ~* x# P0 R( k
</ul>
5 o4 Z8 e1 I8 J( u7 Y( c: m( K! X
9 n% ]8 M4 s/ L8 c* R</li>* S8 v) d& ?# a4 V
4 Y4 a% Q( T, s* L
" q8 l5 S9 x m' M& [</ul>1 i% o8 C" u7 O, z+ o( ]8 u
<hr>
4 X" z( X7 K ]* z+ p; x<p>写数据原理:</p>
% V5 l+ z3 }( {) Z/ l: r+ a<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211214106019-937037786.png" ></p>0 R" n2 b( P7 ^* y+ ]7 A
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211214136079-1875911582.png" ></p>5 o5 p1 X1 S+ U- q* A0 I
<ul>+ q* J1 P0 R" E: Y- y; d1 V
<li>写给leader,leader再通知其他节点 </li>
2 h u& V" \8 F F<li>写给follower,follower没有写的权限,交给leader写,leader再通知。 </li>
2 O' v0 ~" r, t. X/ @<li><span style="color: rgba(51, 204, 204, 1)">半数机制</span>:比如上图,zookeeper在通知其他节点写的时候,达到半数就通知客户端写完成。 不需要全部写完成。所以集群的数量一般是奇数。</li>7 D @+ y' m4 t) a1 d2 i
+ l" u( F9 X, h" a9 |% m& F2 `! V# p" j6 N" n
</ul>
6 g& z; `, Z5 ]" [<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">三.ZK集群(原理)</span></strong></blockquote>
+ D3 q, ?# G1 s* l# ]0 s! R7 h: ]<p> 上面我们知道集群的基本概念,那么也会引出很多问题:ZK怎么保证数据一致性?Leader宕机了如何进行选举?选举后数据如何同步?</p>+ l% {. f. T' g9 [
<hr>0 t" ]4 n# X" t* X* p
<p> ZK怎么保证数据一致性?</p>
8 [3 A' j6 V, ]) U: Q7 I- O<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>
; }" n8 W8 t3 ~& V+ T0 N' h<p> 一般我们正常是消息广播:</p>
: B2 c4 |) B" @/ W8 B5 P% ?<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211205808867-321051219.png" ></p>( Y( [% T [# V2 o
<ul>, A% ^7 C. y! m% Q$ ?# X6 ]( f
<li>第一阶段:<span style="color: rgba(51, 204, 204, 1)">广播事务阶段</span>:对应图上的1,2
& I o% ?! G% ^$ S% f$ x<ul>) _$ A( Y4 Z# f' k) \
<li>Leader收到请求之后,将它转换为一个proposal提议,并且为每个提议分配一个事务ID:zxid,然后把提议放入到一个FIFO的队列中,按照FIFO的策略发送给所有的Follower。</li>0 U5 j" z: F! _2 c7 y* z5 p
<li>Follower收到提议之后,以事务日志的形式写入到本地磁盘中,写入成功后返回ACK给Leader</li>* f3 O5 I! T+ |5 P v
! d" Z- K: q2 j+ y
, g4 l; p- `# l5 z3 l. [* m7 ?0 B
- i3 V2 E% e. U- i' I; M$ V8 ?, a/ T0 W, l/ i, N4 q+ z; y, `
1 L$ z8 b4 }8 I7 y3 y. h
# b- Z5 s% Y& t
</ul>
* j/ ~3 }% a; q& i0 ^2 I7 O, l: j- O
- x! U# ]% P7 u j3 ]+ ~! Q
7 v: h. ~# a; X! ]2 S
( {( n* w2 A5 n$ p8 O _9 T" [
2 v0 y1 W; B- c8 V7 s. K6 \8 d
7 I, F! B2 `3 ?5 W0 X</li> ~% }$ W4 x3 Q I
<li>第二阶段:<span style="color: rgba(51, 204, 204, 1)">广播提交操作</span>:对应图上的38 a# \1 C* L6 {* c& }! G6 ]
<ul>
3 F& w- w% i1 [% z<li>Leader在收到超过半数的Follower的ACK之后,即可认为数据写入成功,就会发送commit命令给Follower告诉他们可以提交proposal了。</li>
1 K! Q1 J5 w& r/ x1 |- [1 i
4 x0 x, |4 I8 m& ~( i2 Y9 i& R1 I: J% p/ _/ e% m I# C) f, s
1 T R0 B. u& X' _: h; T3 x6 u% q; S' M ~
- }5 Y+ E7 d6 }6 U4 @
$ m. H, H8 v$ ~; N2 C3 z. C</ul>) v0 H+ }/ }% f% P2 x
- M8 o, q' R( H) O$ s
6 s8 M- T* W% S8 l, |
9 ^% L) T t/ G* E- K! J. b0 \
& h8 x6 L5 _/ Z- |1 j1 p8 }
) _0 w. |" W5 H& V- o* X) h4 t
- j5 M' n0 p7 b8 _, r: j</li>* g/ _) ]# n( j* E: ~
; h, l$ C. B( \ Q6 r* ]2 C0 `$ H+ }; C+ v+ l
% W: a+ q' a. H0 P. w2 D
4 L; U5 v0 Z; b1 C( P5 f' m7 }8 F
6 T7 i: J) H& q# [- `+ ~, J$ m* k5 L) f |9 J, c! Y# E
</ul>
- ]; U; X7 }! v# v<hr>+ v! s7 c4 I6 ^5 G
<p>Leader宕机了如何进行选举?</p>
3 a7 P% _2 T" q9 u' I4 a3 ~" ]<p>这就得使用ZAB的第二种模式,崩溃恢复模式:</p>
l; D( w. g. }* C6 G<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211211246367-43062481.png" ></p>6 o2 r' P5 v- C- a+ f6 a
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211211725764-329743928.png" ></p> W( I( v3 P: w2 f+ Q
<hr>
/ s/ u- z+ C2 d, b' x<p>选举后数据如何同步?</p>+ Q2 x7 @* F+ Y
<p data-tool="mdnice编辑器">那实际上Zookeeper在选举之后,Follower和Observer(统称为Learner)就会去向Leader注册,然后就会开始数据同步的过程。</p>
0 l# W3 e I2 w& U<p data-tool="mdnice编辑器">数据同步包含3个主要值和4种形式。</p>4 m( s, @1 i9 c. N) ]$ \
<ul>
6 o; L, c0 c) D1 y+ Z2 o1 k5 x<li data-tool="mdnice编辑器">PeerLastZxid:Learner服务器最后处理的ZXID</li>0 |. K/ V8 a c+ K, W$ p. z
<li data-tool="mdnice编辑器">minCommittedLog:Leader提议缓存队列中最小ZXID</li># c* Q- i7 H/ i1 L0 B4 w
<li data-tool="mdnice编辑器">maxCommittedLog:Leader提议缓存队列中最大ZXID</li>
! [! I; z% |- a3 q6 q; V ]) Q/ ]( Y4 m6 r* H$ i- d& _
) o6 Z6 g+ Q8 f6 ]( C) l
/ o6 r/ s1 p% |- U4 ?2 z2 B; c
5 K2 d6 H+ J; v
! D' K+ b' O+ r( t) i! l/ W1 m' n: [' g2 R! e1 j: f) X* k
</ul>8 @" I% g" O5 O
<p>同步策略:</p>
/ J% r& d: _+ f: ~% L<ul>; g- J7 z6 J ~+ }# F, H
<li><span style="color: rgba(51, 204, 204, 1)">直接差异化同步</span> (DIFF同步):如果PeerLastZxid在minCommittedLog和maxCommittedLog之间,那么则说明Learner服务器还没有完全同步最新的数据。<ol>
$ A) V+ H7 Y4 p" i+ p& {7 Z+ R5 ^<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>
+ D- ^( p4 B' Y<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>
% h; P6 B, Y% N# v' l! l) Y' 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>$ u+ K5 j; `; Z3 ~' S" r/ z$ @
! L3 B7 C3 s7 F! W, |. L. ^5 A9 X$ ]! e0 n0 Y
/ |8 ^. e3 r X0 d, _7 d
7 F7 C& L) ]+ w! B! J</ol></li># I/ Z% r" m: G/ d" x! o8 f
<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>
" t! z" J( Y! J! I! A8 T<ul>" s& }4 Q7 g, r7 X7 W3 Y: a
<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>
! x2 N, F% a0 A<li style="text-align: justify">$ l, `- s9 b2 [7 G& J0 ^
<p data-tool="mdnice编辑器">A要进行事务回滚,相当于抛弃这条数据,并且回滚到最接近于PeerLastZxid的事务,对于A来说,也就是PeerLastZxid=3。流程和DIFF一致,只是会先发送一个TRUNC命令,然后再执行差异化DIFF同步。</p>8 m. d$ a8 x/ W7 ~# [
9 R$ U6 Y" v' O6 Z0 x9 B: Q1 G& R
1 e! }, P+ a$ c, N+ L: k
7 B; T$ `7 `' |4 X0 u6 O
$ q; k$ S: e, J! c, q</li>, O: y( v( l: L) Q/ y* t
# ^, ~9 x* U* F4 y# m C/ _: G. _! r g, m5 r& Z. R7 M/ }6 P
/ ~ H" J. P3 T5 R I
( t6 }: Z0 a6 _, p0 T6 X, ^</ul>
: G) E9 s' O `- G, r" M5 n* E+ q/ F( c% s3 P
" _1 m9 i; C4 p7 E9 Q2 v5 o
& `7 u! g0 m/ v6 _. }0 {7 P& U M3 s, h7 L( @. K
</li>
. h, r$ b- s# f<li><span style="color: rgba(51, 204, 204, 1)">仅回滚同步</span>(TRUNC同步):
; Q! G, U, r5 l<ul>
! l- G: d/ U' H/ v! @: ~<li data-tool="mdnice编辑器">针对PeerLastZxid大于maxCommittedLog的场景,流程和上述一致,事务将会被回滚到maxCommittedLog的记录。</li>
4 m& b. M! x7 {- r<li data-tool="mdnice编辑器">这个其实就更简单了,也就是你可以认为TRUNC+DIFF中的例子,新的Leader B没有处理提议,所以B中minCommittedLog=1,maxCommittedLog=3。</li>
( X* ^5 k: D% X3 R" r<li data-tool="mdnice编辑器">所以A的PeerLastZxid=4就会大于maxCommittedLog了,也就是A只需要回滚就行了,不需要执行差异化同步DIFF了。</li>
( p9 [3 a- k+ f
# U- f" t) n9 C. l9 J) U0 _5 ^
6 o6 E- f8 P+ k( q" s& S7 Z0 q& E$ V( N' y# k. P/ L7 I. k9 O
& \: Z: p5 k1 C7 M4 P) C1 y
</ul>
' ?4 A' H, M' {) F1 p) Z8 f
2 r. L6 [# ]1 b; T+ F( j( k# J5 t4 }( I/ m
3 Y* p5 S( g2 ~) P, L) r1 L1 ^6 W
</li>
2 [' s2 D7 O, q$ J6 t<li><span style="color: rgba(51, 204, 204, 1)">全量同步</span> (SNAP同步):! W* n7 e9 T' T9 K
<ul>1 |( K! o: D9 O. I
<li>
. ?- }$ `" A2 y<p data-tool="mdnice编辑器">适用于两个场景:</p>
9 U* Q2 q! L1 p/ K2 h<ol class="list-paddingleft-2" data-tool="mdnice编辑器">
" _; Z8 A# u+ r: i- R<li>PeerLastZxid小于minCommittedLog</li>
. q( E1 h; B. R& D<li>Leader服务器上没有提议缓存队列,并且PeerLastZxid不等于Leader的最大ZXID</li>2 Q. S5 v$ `+ f. {$ z( Q
: ^$ f9 p8 o5 b+ Q3 b0 x
% n7 Y2 ^# ^! \/ Y. Z0 i8 `. n+ o
6 y2 d8 x8 U2 e1 T2 z3 t
' _# A$ G! Z: n# e</ol></li>
3 O# x, C$ Y: G9 q<li>这两种场景下,Leader将会发送SNAP命令,把全量的数据都发送给Learner进行同步。</li>
7 x8 [$ P4 {7 u* p5 G" n% ^0 m' n' Q1 }+ x' X8 l9 `) ^+ ^' N
( w, o; C H5 Z h" j, D4 c4 T; [$ S6 j- g5 w
- R, f" k. U, X: }, P) S1 d
</ul># {% p. ?3 R$ @- i2 d% E, u3 ]
7 h& G8 b! ~. x! b4 y2 b/ r
# E* E4 P7 A9 b- p3 q( k# i
/ D! d, w" |/ `8 z
8 B- Y2 `% p0 c) ^* S* b# u</li>
9 N8 o' t( B# t3 w; J
3 F, n( N0 f( I G8 T( t/ y' ]& V, M) P
9 z$ m) I, o' h5 ?) i! Z3 @& @# J2 B+ k: P% Y0 b' s' T4 H& P
% g1 A: m) |8 ]& d
</ul>) F, ~; e. G# V" s3 z# B" `
<hr>
6 n5 c2 o+ r0 F- K<p data-tool="mdnice编辑器">有可能会出现数据不一致的问题吗?</p>
P2 D- `! J$ D<p data-tool="mdnice编辑器">还是会存在的,我们可以分成3个场景来描述这个问题。</p>9 y! O$ G/ p* Q! f/ F: y
<ul>
5 }1 @# e \5 m<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">查询不一致</span><strong><strong>:</strong></strong>( Y" [# w8 @7 E7 a& f" U
<ul>
& m8 f& H+ ]# r<li data-tool="mdnice编辑器">因为Zookeeper是过半成功即代表成功,假设我们有5个节点,如果123节点写入成功,如果这时候请求访问到4或者5节点,那么有可能读取不到数据,因为可能数据还没有同步到4、5节点中,也可以认为这算是数据不一致的问题。</li>% J6 l& S3 u) R9 N! a5 e' E! ?* ^# s' u
<li data-tool="mdnice编辑器">解决方案可以在读取前使用sync命令。</li>
. ?9 \. o _- V {- d) X
: r& l, F$ y5 @
, m( l: o5 E2 \7 n+ B7 F+ h</ul>
: g0 ] V. g: Z; q7 P6 i6 _ b
. @5 Z7 s0 E7 k5 G* o; _1 X6 _2 |1 a# H5 w& o: H2 M% @1 P/ \" O
</li>* q# M' b3 H5 B: J' I) h* Y5 D9 R' u
<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">leader未发送proposal宕机</span><strong>:</strong>
+ r. L% r. S$ N0 c<ul># V# c _- `! `* n3 ?
<li data-tool="mdnice编辑器">% y4 C* _/ h/ W; U
<p data-tool="mdnice编辑器">这也就是数据同步说过的问题。leader刚生成一个proposal,还没有来得及发送出去,此时leader宕机,重新选举之后作为follower,但是新的leader没有这个proposal。</p>
" }4 j; M7 V" }& p! I( n) C
) N$ A5 q( x W, L1 f P2 g& M9 P/ \- _( U/ L
</li>* e' Z6 |, _2 |! o
<li data-tool="mdnice编辑器">1 D' b( Z* k# ]- S* E
<p data-tool="mdnice编辑器">这种场景下的日志将会被丢弃。</p>) a! X j: Y4 F- m/ x
2 V3 z0 k& v6 p- ]' r
" N) L' [$ N+ B) m8 z' n
</li>
, m: f( c6 m/ N- s/ d8 g, v2 @# O! a. B5 j7 q* O1 q1 _) k" `
6 K; s' s( H1 S% Z& l</ul>
2 E' g/ z# _, N, z+ a5 E: f, l+ V: ]4 t3 G1 @
# u( J5 @' e/ l. h/ H$ }: H</li>
. _7 D6 u6 t3 H9 I: N<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">leader发送proposal成功,发送commit前宕机</span><strong>:</strong>6 @- _+ b2 L: u) w
<ul># k3 r; M4 C0 j* |9 x
<li data-tool="mdnice编辑器">如果发送proposal成功了,但是在将要发送commit命令前宕机了,如果重新进行选举,还是会选择zxid最大的节点作为leader,因此,这个日志并不会被丢弃,会在选举出leader之后重新同步到其他节点当中。<strong><br></strong></li>
+ @6 l+ M& N+ V9 }! U5 M) U+ y2 ^0 W/ _, x7 f
% V! L+ E) v! D3 }/ _: h/ O
</ul>9 e# V$ ], {) {( |
6 e# l" M) T8 s% C) q7 N* P
' Q/ O S# u: @. R; H</li>1 V% R7 ]( b6 ~) E8 \1 O
; F1 x& U) a/ d$ h2 K' I& `4 I
$ r4 p M+ e3 G</ul>
1 l7 M! Z0 g$ Z* E. K<blockquote><span style="color: rgba(0, 0, 0, 1)"><strong>四.ZK其他小问题</strong></span></blockquote>
, P" I- h* l9 b5 z+ Q3 h# k- Z/ Z3 T<p>zookeeper 是如何保证事务的顺序一致性的?</p>2 p/ _" o' X5 X" ^ u, m; Y
<ul>& L5 A+ d8 l3 v. U/ i w% _
<li>使用<span style="color: rgba(51, 204, 204, 1)">zxid</span>来保证顺序性。</li>$ D( i% P, X d: P R' U/ b$ X
# k# Q, ^6 |! ?5 O& J2 |" r5 ^/ E& q5 _6 ~2 J+ ?6 P; f- D
</ul>6 p, e/ S' Q" l* Y/ P5 @) l# n
<hr>
3 a+ X/ U4 H* U<p>集群最少要几台机器,集群规则是怎样的?集群中有 3 台服务器,其中一个节点宕机,这个时候 Zookeeper 还可以使用吗?</p>
' A( X- N, ~" j2 m( B<ul>* _# Z5 ?* k3 V6 M, j
<li>集群规则为 <span style="color: rgba(51, 204, 204, 1)">2N+1</span> (奇数)台,N>0,即 3 台。可以继续使用,单数服务器只要没超过一半的服务器宕机就可以继续使用。</li>
! _" u" C& O: I. c. a1 ?/ j( f% g* z6 _" `6 D/ D5 [5 w( Y; w
4 m( `9 G- E% X+ {# C$ K</ul>
2 t7 Q5 |. b, y) p9 D<hr>
. M/ D9 ~7 ~0 E; v/ V8 B<p>说几个 zookeeper 常用的命令:</p>
" f G% S9 w6 j( ~" }# D<ul>& _& c, E. S3 }4 u. X/ q: V2 d
<li>ls path:查看当前 znode 的子节点</li>
* m& v# ~; M V. b, Z; ~2 b+ x<li>get path:获取节点的值</li>
* B" P& d% d2 j8 ?7 s" [: [1 D7 r& W<li>set:设置节点的值</li>3 g" A& m6 E+ |
<li> create,delete:创建/删除节点</li>& x, |( i9 y, n
" [" n" i+ `5 s% V& g0 w
7 A J& [5 [! t0 C0 U* E$ B# [) R U
</ul>
. O& d$ ?! l8 _) t: d<hr>
5 v/ w0 P4 T2 b; H<p>会话Session:</p>) v# v; S" I/ G! `4 n
<ul> K$ }8 e. g. H% U
<li>会话自然就是指Zookeeper客户端和服务端之间的通信,他们使用TCP长连接的方式保持通信,通常,肯定会有<span style="color: rgba(51, 204, 204, 1)">心跳检测</span>的机制,同时他可以接受来自服务器的Watch事件通知。</li>& |0 Z# `) H0 s6 \: e" Q8 Z0 Q; }
9 x* h+ q! j% W8 y# w0 C9 f
$ w1 i: f2 E; w0 I- P! Q
</ul># i( q* i! _6 B3 [. [" Y* }- V8 P
<p> </p>7 ^% z* S# d( l; i T0 [. L
<p>寄语:<span style="color: rgba(51, 204, 204, 1)">平静的湖面酝酿不出精悍的水手,安逸的环境创造不出时代的伟人</span></p>
- V1 F' _$ Z9 ~0 r! F9 `" [ |
|