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