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