|
|
5 ^0 d1 K, K# X* Q; q<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">一.概述</span></strong></blockquote>9 G6 v7 }: [/ G3 }
<p> ZooKeeper 是什么?</p>9 I a9 J6 B: V
<ul>+ y" W+ ^& H, I9 O& p
<li>是一个开源的<span style="color: rgba(51, 204, 204, 1)">分布式协调服务</span>。使用分布式系统就无法避免对节点管理的问题(需要实时感知节点的状态、对节点进行统一管理等等),而由于这些问题处理起来可能相对麻烦和提高了系统的复杂性,ZooKeeper作为一个能够<span style="color: rgba(51, 204, 204, 1)">通用</span>解决这些问题的中间件就应运而生了。</li>
) G) ~4 X C: U* `/ K7 C5 [<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>; j: H2 x" B4 J7 r" f
<li>实现原理:zookeeper=<span style="color: rgba(51, 204, 204, 1)">文件系统</span>+<span style="color: rgba(51, 204, 204, 1)">通知机制</span>。</li>8 h: f! G" v4 _6 a: b$ I- P
</ul>; C$ x0 q* `1 \! i- V
<p>Zookeeper的作用(应用场景)?</p>
( E1 @; q# Z8 ]! E; \<ul>9 h, r5 N$ `- G7 r9 o9 Z q* b
<li><span style="color: rgba(51, 204, 204, 1)">统一配置管理</span>:比如现在有A.yml,B.yml,C.yml配置文件,里面有一些公共的配置,但是如果后期对这些公共的配置进行修改,就需要修改每一个文件,还要重启服务器。比较麻烦,现在将这些公共配置信息放到ZK中,修改ZK的信息,会通知A,B,C配置文件。多方便</li>
- K; G+ \4 i/ M# w$ F% E4 m<li><span style="color: rgba(51, 204, 204, 1)">统一命名服务</span>:这个的理解其实跟<span style="color: rgba(51, 204, 204, 1)">域名</span>一样,在某一个节点下放一些ip地址,我现在只需要访问ZK的一个Znode节点就可以获取这些ip地址。</li>
2 q9 M& i& L; X* s3 G<li><span style="color: rgba(51, 204, 204, 1)">同一集群管理</span>:分布式集群中状态的监控和管理,使用Zookeeper来存储。</li>
# Z0 t4 u' M% X x, ~<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 \! h' g$ K( J' y6 A- T<ul>' T5 A6 |) }2 R( M/ X7 ]$ Z/ w& B7 [
<li><span style="color: rgba(51, 204, 204, 1)">服务节点动态上下线:<span style="color: rgba(0, 0, 0, 1)">如何提供者宕机,就会删除在ZK的节点,然后ZK通知给消费者。</span></span></li>
& c! F( l- Y8 n( T! w3 G<li><span style="color: rgba(51, 204, 204, 1)">软负载均衡</span></li>* `, ~9 E* c- w% ~
<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>
- z- y! G/ D) s9 f9 @</ul>/ y6 N9 U* M. E2 ?* M
</li>
' p, ^7 z0 f! G- C3 Z" C<li><span style="color: rgba(51, 204, 204, 1)">分布式锁</span>(后续出文章讲)</li>
/ ]8 M1 X6 s7 s1 N</ul>
# f0 X& ?9 D8 e6 C! n4 m<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">二.原理</span></strong></blockquote>
$ _1 T& `6 S+ L<p>之所以能做上述功能,主要是归功于ZK的<span style="color: rgba(51, 204, 204, 1)">文件系统</span>和<span style="color: rgba(51, 204, 204, 1)">通知机制</span>。下面我们来分析这两个机制</p>
/ \$ @' L! a* J<hr>
) C! K( v. q( |4 a<p> 文件系统:</p>
' i4 q: \# }( t2 w; X, F<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>
8 J# }0 F* W1 c% d5 L<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211170746939-2004306213.png" ></p>
5 M! R) a& _. r7 Z [' J& Z1 P% R) h<p> Znode节点主要有4中类型:</p>
8 `$ }9 J8 @& [- q<ul>
4 L2 \7 c0 `2 P& i: Z. H! n<li><span style="color: rgba(51, 204, 204, 1)">临时目录节点</span>:客户端与Zookeeper断开连接后,该节点被删除</li>
9 H( ]$ e4 r1 S7 j+ q. A+ l<li><span style="color: rgba(51, 204, 204, 1)">临时顺序编号目录节点</span>:基本特性同临时节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。</li>
8 w. N0 w+ @+ ~& J! Q<li><span style="color: rgba(51, 204, 204, 1)">持久化目录节点</span>:客户端与Zookeeper断开连接后,该节点依旧存在</li>
6 M! h( H8 d* j) y! c$ r0 h& }) X<li><span style="color: rgba(51, 204, 204, 1)">持久化顺序编号目录节点</span>:基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。</li>
6 Q) v4 U7 E2 K</ul>
+ y+ v9 o8 v! P" e& m<hr>/ r( ^- _: q3 Q( W) o* ] s5 n
<p> 通知机制 (监听机制)</p>
; _2 U' O# T! p2 G( @5 e$ ]<p>Zookeeper可以提供分布式数据的<span style="color: rgba(51, 204, 204, 1)">发布/订阅</span>功能,依赖的就是Wather监听机制。</p>
; _6 r+ e: Z, g+ D6 F+ [, X<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>1 W% G5 Q" p5 k8 {+ ]+ i# p; u' _. m
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211172333942-1239203073.png" ></p>
7 \! O1 t. h( y% T4 N5 y2 |<ol>
$ B. i7 G. l6 F# Z% s6 x/ X8 v' E<li>客户端向服务端注册Wather监听</li>
3 X4 ?* p( S, Z+ h( D9 i* c% h<li>保存Wather对象到客户端本地的WatherManager中</li>
$ ?1 |9 T* q) B* h- w) ?<li>服务端Wather事件触发后,客户端收到服务端通知,从WatherManager(watcher管理器)中取出对应Wather对象执行回调逻辑</li>1 \9 g5 J* w, u' N! {
</ol>
2 Y6 z/ M$ n+ `: k9 N+ a: G<p> 主要监听2方面内容:</p>& G2 h- r, R* f; l6 h* ^
<ul class="list-paddingleft-2">$ o( B* B# C' H! Q/ T
<li>
* W/ E* i1 ]; z$ \) ~<p>监听Znode节点的<span style="color: rgba(51, 204, 204, 1)">数据变化:<span style="color: rgba(0, 0, 0, 1)">就是那个节点信息更新了。</span></span></p>- L5 w, V* j0 t- U. ]* Z, |( H
</li>
: T3 |2 @, Z4 m( I/ e% ]. _; g# v9 u4 Z<li>$ m2 f2 T/ E5 S. }! ^: l7 e
<p>监听子节点的<span style="color: rgba(51, 204, 204, 1)">增减变化<span style="color: rgba(0, 0, 0, 1)">:就是增加了一个Znode或者删除了一个Znode。</span></span></p>
$ B: V+ U+ \$ i& f, K3 V' I</li>1 [" r/ f2 b$ P/ `' \& ]: P
</ul>( c/ u6 G8 J% N# N) v
<p><span style="color: rgba(0, 0, 0, 1)">几个特性:</span></p>) h6 B ^5 \1 E
<ul>/ s/ u H: E9 p' c$ ~
<li>一次性:一旦一个Wather触发之后,Zookeeper就会将它从存储中移除</li>. D- v3 `$ @' a! `( @ B
<li>客户端串行:客户端的Wather回调处理是串行同步的过程,不要因为一个Wather的逻辑阻塞整个客户端</li>
$ U4 S, ~: C2 {<li>轻量:Wather通知的单位是WathedEvent,只<span style="color: rgba(51, 204, 204, 1)">包含通知状态、事件类型和节点路径,不包含具体的事件内容</span>,具体的时间内容需要客户端主动去重新获取数据</li>; e& G8 E/ o+ ?" `* ~- C
</ul>
- \; ?$ U6 l4 S! g: ~2 X9 n<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">三.ZK集群(相关概念)</span></strong></blockquote>
# b; W1 l* E3 ]3 W<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211182203890-1695256509.png" ></p>" R; \& }" {/ c6 c6 j* D
<ul>
* x- y/ V+ {3 z<li>Leader:负责写数据。(写数据都有事务)</li>
& l3 V1 ?. s2 ?<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>% v( V8 A4 Z4 @8 H
<li><span style="color: rgba(51, 204, 204, 1)"><span style="color: rgba(0, 0, 0, 1)">Observer:只负责读。</span></span></li>
2 E+ f$ ~$ J9 z* e+ f6 W6 x( p9 y! W: Z; E
</ul>
! p; a% z0 r5 _<hr>' w# C5 j- `5 @/ U
<p>从上面的角色种,我们可以总结ZK节点的工作状态(服务状态)</p>
5 ^* E- @8 F: c. @* {2 _<ul>
, W: _4 b7 ^: C2 i; q: [/ F<li>LOOKING:寻 找 Leader 状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。</li>
0 V9 F) \ @2 `( o* Z: N<li>FOLLOWING:跟随者状态。表明当前服务器角色是 Follower。</li>
1 Y- \; E, ]' X) m+ c6 ?& U) A<li>LEADING:领导者状态。表明当前服务器角色是 Leader。</li>
1 E; t& v) j: y1 A) C<li>OBSERVING:观察者状态。表明当前服务器角色是 Observer。</li>5 W) ?7 N& u( q& f+ H- {+ L# `
! Y' P! e0 n. U) O
</ul>: R4 [! R) s( r- W' |) @2 q
<hr>
1 \# U% `) Q4 X/ W% r<p>其他概念:</p>
0 \" H& w- J3 f) a- _# V<ul>* u" G$ a. g7 `1 T2 A) p
<li>zxid:<span style="color: rgba(51, 204, 204, 1)">全局事务ID</span>,分为两部分:, ^0 r/ d6 _+ `: [; G' ~$ l
<ul>
0 [/ t W: K, u: t<li>纪元(epoch)部分:epoch代表当前集群所属的哪个leader,leader的选举就类似一个朝代的更替,你前朝的剑不能斩本朝的官,用epoch代表当前命令的有效性。</li>
* _! X! T7 {0 C% w$ D/ w<li>计数器(counter)部分,是一个<span style="color: rgba(51, 204, 204, 1)">全局有序</span>的数字,是一个递增的数字。</li>; D! d0 _. l& F$ A! y5 p# ?
5 X; k: F8 d( m& t& m
9 E" G7 P5 J9 z* s2 u</ul>
' {) s5 D& s$ q
. B) W' u2 h) V0 A9 ~' M% z- v0 x H; u: E7 d4 S
</li>
: T' X9 c7 B: O$ O# X5 b, Q' T" o8 o+ z. B
5 ~" S, ?* J9 M* Y. @8 f</ul>) c& t7 Z" R$ M. \/ f
<hr>
7 Y; ?0 E# j; [7 ?7 |<p>写数据原理:</p>0 i: t6 t1 z" s- E0 a* r( {; w( A
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211214106019-937037786.png" ></p>1 q! T H' g4 D. n" D! }
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211214136079-1875911582.png" ></p>
7 A" s1 t: @6 T( r3 x<ul>/ R I, O) T. P/ L" ?
<li>写给leader,leader再通知其他节点 </li>
9 H$ t/ U% \6 X$ p8 Q<li>写给follower,follower没有写的权限,交给leader写,leader再通知。 </li>
( y" W a3 G L x<li><span style="color: rgba(51, 204, 204, 1)">半数机制</span>:比如上图,zookeeper在通知其他节点写的时候,达到半数就通知客户端写完成。 不需要全部写完成。所以集群的数量一般是奇数。</li># U1 D6 O# M; v) h( n
^" n" P* ~1 ^, S3 P- D
2 u- W4 O; o8 `# S$ v</ul>: [# J5 I' V [0 w* d6 \" L) {/ Y+ m
<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">三.ZK集群(原理)</span></strong></blockquote>
% B( X: a$ ~$ E* M<p> 上面我们知道集群的基本概念,那么也会引出很多问题:ZK怎么保证数据一致性?Leader宕机了如何进行选举?选举后数据如何同步?</p>
6 G% r4 \% a. x0 r" L" ^) Y2 W1 c$ w<hr>' J' E4 U8 V9 m8 [
<p> ZK怎么保证数据一致性?</p>
- J6 m p/ y/ W( X9 r<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>
) b0 S5 Y, }+ Y2 ]; Q<p> 一般我们正常是消息广播:</p>
* q4 t- a. n4 f( b<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211205808867-321051219.png" ></p>- d% R# F! [3 G" V
<ul>5 ^7 _: X6 J) R9 N2 i! h
<li>第一阶段:<span style="color: rgba(51, 204, 204, 1)">广播事务阶段</span>:对应图上的1,22 ?6 t' }' Y7 Z
<ul>
+ h" L0 X7 B* `, r% l7 X<li>Leader收到请求之后,将它转换为一个proposal提议,并且为每个提议分配一个事务ID:zxid,然后把提议放入到一个FIFO的队列中,按照FIFO的策略发送给所有的Follower。</li>
9 L/ ]) @4 w& v<li>Follower收到提议之后,以事务日志的形式写入到本地磁盘中,写入成功后返回ACK给Leader</li>; s8 v1 b- n* X
+ e. A {/ Q. I% c$ m
5 i" a- T- ?4 H6 P" m
- d- s& L; V0 L
6 J0 E. y! `0 F8 B4 R* c) I3 R0 M
1 w) x& e6 B; i( ~
S9 E8 @% N0 [$ Q, y+ N5 {( h</ul>5 G/ L+ p7 t3 v
" O/ i: H* t* l3 k! J3 O& f
9 q, g4 T/ L3 M8 V
3 w) w3 d7 q% V2 q
2 [) H4 o0 _, E4 q9 \
( @/ |6 L4 s4 ~- K; z2 C- k' S
2 x, W1 j# z2 L$ _" @" |6 Y0 c</li>
8 u# b) {+ L H; T9 O* z<li>第二阶段:<span style="color: rgba(51, 204, 204, 1)">广播提交操作</span>:对应图上的3/ {1 A5 O. {7 X* i0 H o% m
<ul>
- T9 A6 S1 n2 ]. _! u, P1 a: a# V<li>Leader在收到超过半数的Follower的ACK之后,即可认为数据写入成功,就会发送commit命令给Follower告诉他们可以提交proposal了。</li>; c T0 L( w) P
2 _ R D: x/ u+ y V
0 \$ }8 d2 I) N) Q& J8 I2 p
/ Y; I: |; h, @; t" w* r7 T+ }9 s2 N+ U
9 Q7 X! G5 ^3 U' A" T- N9 }
9 B( Z. z, w/ b- ]; G</ul>
" L: n$ `+ E" }0 f4 x, _
! R1 L3 ?+ s. l; B7 o. ?! k# E3 B* D' d( z! b
& m: u- z( F' G7 K
' X" C* k! d' ?3 N. s% O- z6 M* a. e+ C! g/ u5 y
8 `8 |1 S ?' d( z; ^
</li>
: t! L( u1 N8 r2 Q9 b$ Y! p/ f5 W
! p4 d ~: O5 b" ~/ d! X3 V
& }8 N; M$ z M/ m7 f# \0 I5 ^ U M% [$ T6 _
1 A- Z7 [* A. ]; @: p2 Y/ }
; v7 m/ _( d% C0 I0 }% H8 D1 c! t6 k' G, d: D( ^
</ul>2 _5 P v* D4 |9 I: J7 K
<hr># g: c" c+ x( b8 `4 ^
<p>Leader宕机了如何进行选举?</p>
1 t' U: E* i( K; h; K, I3 B<p>这就得使用ZAB的第二种模式,崩溃恢复模式:</p>
% K2 F" ~- l, {4 u t2 R<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211211246367-43062481.png" ></p>
) N% e+ P% ~: r* X: o- x7 R<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211211725764-329743928.png" ></p>. A, e$ R( N6 e
<hr>
; P2 t" O! b: h. u<p>选举后数据如何同步?</p>
4 c3 F2 g! p" v4 F<p data-tool="mdnice编辑器">那实际上Zookeeper在选举之后,Follower和Observer(统称为Learner)就会去向Leader注册,然后就会开始数据同步的过程。</p>
( T0 q. D8 ?6 j$ T' v0 M. a8 y% j9 [( z<p data-tool="mdnice编辑器">数据同步包含3个主要值和4种形式。</p>
, z4 O9 u& m2 s" H( N<ul>
; e* f$ {/ I9 e<li data-tool="mdnice编辑器">PeerLastZxid:Learner服务器最后处理的ZXID</li>
/ ]; w; X f9 s& S! W2 W% y4 p9 O M<li data-tool="mdnice编辑器">minCommittedLog:Leader提议缓存队列中最小ZXID</li>
\0 S, A' j0 C2 i<li data-tool="mdnice编辑器">maxCommittedLog:Leader提议缓存队列中最大ZXID</li>
7 d" r$ r8 T4 E0 O
* R2 t) W! j% r3 a5 M5 H: H
7 v0 r4 y) i$ i2 v1 P" J& U5 O: p2 p' x Z
1 R) \& `- w; r: B- H/ B/ U$ p* u- s( v" }
; O: @4 V" v% k$ ?6 j, l
</ul>
9 @2 P) b! n% [% @<p>同步策略:</p>
7 { E. U& V& d+ T<ul>9 O/ J4 F1 u' {% q0 L
<li><span style="color: rgba(51, 204, 204, 1)">直接差异化同步</span> (DIFF同步):如果PeerLastZxid在minCommittedLog和maxCommittedLog之间,那么则说明Learner服务器还没有完全同步最新的数据。<ol>
7 v/ d9 E+ {* B6 ]) z$ K<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>0 ^1 n. H, n. v# X5 \$ U4 S' M4 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">发送完成之后发送一个NEWLEADER命令给Learner,同时Learner返回ACK表示已经完成了同步</li>( X7 C9 G5 Y$ O, I
<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>
. `. n* e; a# z1 a4 k# V( G; w9 l; g- Z3 d4 f7 \
- p. i0 z6 A! R% W7 {, ^6 w @+ q
, o3 L. @, U, A9 P. S
" I+ u) i) J/ ]# ] Y</ol></li>; M" ^, J# J! `; H, e/ i
<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>
+ |& i1 N# F6 o4 `6 o<ul>$ s+ v% ~+ c# X3 ]
<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>
; e, }( S4 o3 z$ r- }8 `, E<li style="text-align: justify">
0 V6 c1 [2 Y0 @5 T% d' }<p data-tool="mdnice编辑器">A要进行事务回滚,相当于抛弃这条数据,并且回滚到最接近于PeerLastZxid的事务,对于A来说,也就是PeerLastZxid=3。流程和DIFF一致,只是会先发送一个TRUNC命令,然后再执行差异化DIFF同步。</p>' M4 o7 u" C1 f
+ h, n* F& t) z; X4 ? F
/ [5 i; ^6 G3 w i( }, g/ t$ r1 d) V
8 Z" c5 _- ~: `7 g6 h( J</li>' v( }5 z& F: N3 e+ j
4 ]* r7 Y6 y( O$ K2 h2 v3 L* G7 x1 o/ {; R% t' ]5 b7 }8 Z
8 o9 q$ W1 B6 ?5 S2 M
+ p2 H3 i1 R5 C7 ]. b: Z( `2 J</ul>1 `7 N( x# e" }7 N2 L
5 B% H2 k/ P: a. r" \" D
5 u& j: ~# @- [; {
/ B! a8 ^+ i% R/ Q5 ^5 z1 }7 o- _; t |' W9 I7 v: `
</li>4 U( V9 F8 T+ `$ z
<li><span style="color: rgba(51, 204, 204, 1)">仅回滚同步</span>(TRUNC同步):9 S8 X% U. d8 V! ^- C3 ?$ n( C2 p5 A
<ul>- s# O: \) A% Z3 L; @
<li data-tool="mdnice编辑器">针对PeerLastZxid大于maxCommittedLog的场景,流程和上述一致,事务将会被回滚到maxCommittedLog的记录。</li>
# [; J: ?4 a M' I0 z<li data-tool="mdnice编辑器">这个其实就更简单了,也就是你可以认为TRUNC+DIFF中的例子,新的Leader B没有处理提议,所以B中minCommittedLog=1,maxCommittedLog=3。</li>" f( I- `1 ~! Z
<li data-tool="mdnice编辑器">所以A的PeerLastZxid=4就会大于maxCommittedLog了,也就是A只需要回滚就行了,不需要执行差异化同步DIFF了。</li>
8 l7 T- A9 n; @7 b9 S; z, r# `0 m5 l. {
% ?# G2 ~! P3 n3 A( K8 s8 f: G) d& @7 b
" w! Q3 c' f4 ]
</ul>5 P* z! _) e& g4 g: Y# f& v" o; P
$ A1 J5 d3 f k9 h
- M: p% d4 M+ o
" z6 I6 w) J0 z. ]7 U& l3 V
5 `' r$ r2 W- M</li>+ |/ C' _4 [( _6 ^/ B- f: s" U# {
<li><span style="color: rgba(51, 204, 204, 1)">全量同步</span> (SNAP同步):
8 H6 w& `1 ?8 r. C# {5 X<ul>
. v$ |+ T$ T* D+ x5 q1 {<li>$ M+ _3 i# ?# P
<p data-tool="mdnice编辑器">适用于两个场景:</p>
( Y/ e: J" g$ U/ i* u<ol class="list-paddingleft-2" data-tool="mdnice编辑器">8 R- U: F5 Y8 U+ M& R. [
<li>PeerLastZxid小于minCommittedLog</li>% i2 G- b, E7 m E
<li>Leader服务器上没有提议缓存队列,并且PeerLastZxid不等于Leader的最大ZXID</li>
. c" A. ~0 S' s6 A }$ P9 }% {( f* V( ~3 [9 B* b
8 f/ H# g3 s1 O( Q. g
$ _: y/ N$ [4 U8 X, C, ~: E+ U- ^/ X- u! O9 _# |) X
</ol></li>/ Y6 r) O7 J p( Y
<li>这两种场景下,Leader将会发送SNAP命令,把全量的数据都发送给Learner进行同步。</li> y( z" g' p) w
+ D* U r$ t" S8 C5 f
% l4 H3 i6 w& Y+ V2 z9 p) O
1 M) Z, W' h4 U p+ E6 ]2 |* X
, G' {. Y1 w& y9 K- E0 _</ul>. y6 @9 r3 T4 H8 q G$ a
* n. c' j1 ]& I; e) f. ^7 B6 B8 `& s6 Q2 L; l% S1 V/ e* }: ~4 G7 s
: |3 u) D. E/ `. |& \. _ V
$ S f, Q; P% ~0 y1 i$ F</li>8 U- l2 Y! T! J
2 {; I; k; E' r7 G, Y' x) o3 Y$ W9 Y# M8 [$ E3 I
9 r+ F( B5 n3 o1 K8 F
9 o8 B, C4 n, b* H& U0 o</ul>* y# o1 h4 s+ _% R* {. y3 W
<hr>! [( c+ R& b4 w( y) V) Z9 f: Q/ l- t( H
<p data-tool="mdnice编辑器">有可能会出现数据不一致的问题吗?</p>4 O& {+ P' S- E5 A& R% C9 N
<p data-tool="mdnice编辑器">还是会存在的,我们可以分成3个场景来描述这个问题。</p>
1 p% P9 T- [1 |<ul>
* ~1 \$ x" c: [" k% M<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">查询不一致</span><strong><strong>:</strong></strong>- ^: X3 o5 G, O7 L% f: u
<ul>. w6 n% X7 E$ _
<li data-tool="mdnice编辑器">因为Zookeeper是过半成功即代表成功,假设我们有5个节点,如果123节点写入成功,如果这时候请求访问到4或者5节点,那么有可能读取不到数据,因为可能数据还没有同步到4、5节点中,也可以认为这算是数据不一致的问题。</li>7 Z, I1 X" E( t4 W3 s6 F0 Y6 L
<li data-tool="mdnice编辑器">解决方案可以在读取前使用sync命令。</li>- z3 X8 M! k8 E
( C* l) o2 R4 A8 t( e
; g/ P3 e( C2 X
</ul>
D& O) \' K) Z3 m" p4 u8 H1 `- A8 L
7 A" h8 o1 H/ V! o# z
</li>6 \/ j" X2 W, Q
<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">leader未发送proposal宕机</span><strong>:</strong>
6 M4 [8 G5 l9 t9 U3 ~<ul>- Y T# \; y0 p, U' w4 q1 ?2 X5 y
<li data-tool="mdnice编辑器">5 x/ _8 V- F+ w+ F3 p% N* b* R: g
<p data-tool="mdnice编辑器">这也就是数据同步说过的问题。leader刚生成一个proposal,还没有来得及发送出去,此时leader宕机,重新选举之后作为follower,但是新的leader没有这个proposal。</p>, {7 z( b4 N5 O5 m% s
2 g- j( {& h) P# a
' ]+ P, L% v, u8 N</li># a' m. Z& x7 r/ u" i- y* A# p
<li data-tool="mdnice编辑器">
" X( X& b4 Z7 J' [* r<p data-tool="mdnice编辑器">这种场景下的日志将会被丢弃。</p>. P7 p0 Q0 \+ p
' ^ u2 i+ W$ R u
9 c$ Z' y( \' [/ l
</li># u6 I2 N0 r6 E
3 ~' {8 V$ W% B- {7 }* ? V" Z8 k
& i. F& S& v( R8 x
</ul>6 N$ _ u0 A( e1 |# ^7 B8 }4 T
9 U* R* _% e5 N! R6 f! |% k% Y* p( X; ^: [9 r2 G
</li>) C1 e; e% z7 ?
<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">leader发送proposal成功,发送commit前宕机</span><strong>:</strong>
# q) e' R+ x: x+ n m6 N" ]<ul>
8 ^ W/ G( ~; ]6 w& C<li data-tool="mdnice编辑器">如果发送proposal成功了,但是在将要发送commit命令前宕机了,如果重新进行选举,还是会选择zxid最大的节点作为leader,因此,这个日志并不会被丢弃,会在选举出leader之后重新同步到其他节点当中。<strong><br></strong></li>
( Y& T/ q1 O+ R0 i6 \, a& ]' |. ]9 U/ \6 w2 J% F
9 ^2 U- D5 E+ g" Z; t M8 s( }
</ul>; d1 i _; F; W8 f
2 V2 {& p/ Z! B" V {' j! b% V
, Z' n' e, e+ s1 ?
</li>0 B- a# `3 g1 y
, } }7 f7 {; t% {
- d8 J7 g! h4 R6 `! y/ ]</ul>
0 H ^ h0 K ]2 y<blockquote><span style="color: rgba(0, 0, 0, 1)"><strong>四.ZK其他小问题</strong></span></blockquote>
# D$ {+ F& c/ I* y' g( g<p>zookeeper 是如何保证事务的顺序一致性的?</p>& S8 ^, @- L* X: n
<ul>, t8 `) `9 ?' m% e) h7 \
<li>使用<span style="color: rgba(51, 204, 204, 1)">zxid</span>来保证顺序性。</li>
3 M9 `! `5 J* e0 e% q9 f& I m X! z' c7 W
' S7 M4 b) S, U$ S$ o& _. j( u</ul>/ H" \/ ?* W! e0 D
<hr>
1 @) f* w6 G4 {2 G+ K: }4 S<p>集群最少要几台机器,集群规则是怎样的?集群中有 3 台服务器,其中一个节点宕机,这个时候 Zookeeper 还可以使用吗?</p>, l$ o6 B: W3 F/ L+ X: y$ T
<ul>
! J# N q6 L% d( p" s3 f" s<li>集群规则为 <span style="color: rgba(51, 204, 204, 1)">2N+1</span> (奇数)台,N>0,即 3 台。可以继续使用,单数服务器只要没超过一半的服务器宕机就可以继续使用。</li>
* o+ f6 d4 S6 I, ~6 y: E
; K3 H3 h R/ c6 `8 B
9 ^9 x1 J6 f+ g* i/ R8 t4 ?</ul> W' H% F" w' f% o# I) ?
<hr>
6 u! ?& h0 k& }9 j7 r! H- O! j<p>说几个 zookeeper 常用的命令:</p>
4 n7 k8 |2 j% K+ a; z7 i/ [<ul>
) v! w1 n. p# y# h<li>ls path:查看当前 znode 的子节点</li>
6 {! x B. l5 l! f<li>get path:获取节点的值</li>
8 e5 l6 e8 F. L/ `1 B<li>set:设置节点的值</li>( \7 c( k; u6 Q1 \3 J( @& d
<li> create,delete:创建/删除节点</li>3 N* ~4 D0 o! |9 S' @) r' z9 x
& K/ q9 c, A1 S+ P- P' Z v+ h5 j6 G, ^- m8 m
</ul>
5 \; D/ A- b# B, _+ p<hr>7 Z- z) G! c/ O
<p>会话Session:</p>
) ]' _7 F" z8 s4 J+ K3 f3 d8 J3 b<ul>
- W' \8 S7 E, g3 d<li>会话自然就是指Zookeeper客户端和服务端之间的通信,他们使用TCP长连接的方式保持通信,通常,肯定会有<span style="color: rgba(51, 204, 204, 1)">心跳检测</span>的机制,同时他可以接受来自服务器的Watch事件通知。</li>
7 `) s Q7 C& {- h3 j5 V! _" o5 ? d
) O- u0 t9 ]% D6 E' R
& y$ d! u% s. Y& P, U: V</ul>6 j9 c- h) P% U1 o7 ^7 A% T
<p> </p>1 m. P) T/ P- q' [3 r4 R( `
<p>寄语:<span style="color: rgba(51, 204, 204, 1)">平静的湖面酝酿不出精悍的水手,安逸的环境创造不出时代的伟人</span></p>
. Y: ] D, A$ j |
|