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