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