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