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