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