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