|
) m/ G# p- x: F% l' F. C<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">一.概述</span></strong></blockquote> _" G# ?9 F& S: k& @' S7 R
<p> ZooKeeper 是什么?</p>
7 K! C4 y6 c0 o1 o9 S/ W2 m& |' C<ul>5 S- x/ a* ^" E! o* X
<li>是一个开源的<span style="color: rgba(51, 204, 204, 1)">分布式协调服务</span>。使用分布式系统就无法避免对节点管理的问题(需要实时感知节点的状态、对节点进行统一管理等等),而由于这些问题处理起来可能相对麻烦和提高了系统的复杂性,ZooKeeper作为一个能够<span style="color: rgba(51, 204, 204, 1)">通用</span>解决这些问题的中间件就应运而生了。</li>
2 F! {( \1 h5 I# }" C& B<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>
3 W0 q7 \' m+ E' a: `) r<li>实现原理:zookeeper=<span style="color: rgba(51, 204, 204, 1)">文件系统</span>+<span style="color: rgba(51, 204, 204, 1)">通知机制</span>。</li>: I9 z( p8 y8 b" O# r
</ul>6 T! Y! _7 I% \6 t- N+ M$ F' ?9 z
<p>Zookeeper的作用(应用场景)?</p>
+ ]* R) O$ z* i' f( W- F<ul>6 B0 k9 `- Q/ K1 t
<li><span style="color: rgba(51, 204, 204, 1)">统一配置管理</span>:比如现在有A.yml,B.yml,C.yml配置文件,里面有一些公共的配置,但是如果后期对这些公共的配置进行修改,就需要修改每一个文件,还要重启服务器。比较麻烦,现在将这些公共配置信息放到ZK中,修改ZK的信息,会通知A,B,C配置文件。多方便</li>8 |: O1 o0 v; f9 z- D
<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 J. D0 n: B$ F; c/ n<li><span style="color: rgba(51, 204, 204, 1)">同一集群管理</span>:分布式集群中状态的监控和管理,使用Zookeeper来存储。</li>5 e* s0 @# d4 t# l9 N
<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调用。+ y) a, \2 }8 X7 @0 w
<ul>8 W8 O/ F2 h& J
<li><span style="color: rgba(51, 204, 204, 1)">服务节点动态上下线:<span style="color: rgba(0, 0, 0, 1)">如何提供者宕机,就会删除在ZK的节点,然后ZK通知给消费者。</span></span></li>
6 O5 M; F% X# b, U" J, I/ y) `<li><span style="color: rgba(51, 204, 204, 1)">软负载均衡</span></li>: D- P/ T7 L* W: K4 k
<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>/ i, K3 \( g' ~+ y' |
</ul>6 m5 ]: I5 K$ D9 @
</li>
* ~! b# U. J: E# q; i5 S<li><span style="color: rgba(51, 204, 204, 1)">分布式锁</span>(后续出文章讲)</li>+ r* q! s s4 a: T+ d6 m0 Z' R
</ul>
9 i- m. j2 P, C8 h- X8 ^<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">二.原理</span></strong></blockquote>
$ T6 C) ?; e/ @+ V4 w" I9 h<p>之所以能做上述功能,主要是归功于ZK的<span style="color: rgba(51, 204, 204, 1)">文件系统</span>和<span style="color: rgba(51, 204, 204, 1)">通知机制</span>。下面我们来分析这两个机制</p>
. G; g" b- e" F% ^" P8 |" y3 C<hr>1 j! v9 z3 f q- n9 s
<p> 文件系统:</p>
1 t! D. T4 n/ 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 l) h( ~- n* f( \2 @9 E( j
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211170746939-2004306213.png" ></p>8 ^; q+ U' o. q
<p> Znode节点主要有4中类型:</p>/ _ k, H$ e _% K& d
<ul>
9 O1 t$ u/ F, i& ?4 J) D<li><span style="color: rgba(51, 204, 204, 1)">临时目录节点</span>:客户端与Zookeeper断开连接后,该节点被删除</li>, q( O. |# F+ w$ H; k) D
<li><span style="color: rgba(51, 204, 204, 1)">临时顺序编号目录节点</span>:基本特性同临时节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。</li>1 f% E/ }2 S7 _( J
<li><span style="color: rgba(51, 204, 204, 1)">持久化目录节点</span>:客户端与Zookeeper断开连接后,该节点依旧存在</li>: ?) v% B4 m- ]' }$ [
<li><span style="color: rgba(51, 204, 204, 1)">持久化顺序编号目录节点</span>:基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。</li>) @9 y1 t( I9 l8 x! G0 r* O
</ul>
# G0 o$ s' ~7 v/ I( A: x# R! @9 l0 M0 f<hr>, w- v1 _' {0 X* I. q4 M
<p> 通知机制 (监听机制)</p>
* h. w! B& s$ w- m/ l, V1 L$ W. u<p>Zookeeper可以提供分布式数据的<span style="color: rgba(51, 204, 204, 1)">发布/订阅</span>功能,依赖的就是Wather监听机制。</p>
" f' d, T& h8 ` X<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>' D7 i t4 R& }$ f; g7 e" Q3 Z! p
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211172333942-1239203073.png" ></p>
q$ ?7 o% s* C0 }* B# j<ol>
7 m( _9 P' d) z; ]1 w( H<li>客户端向服务端注册Wather监听</li>
6 [( Z g# {. K q( ]3 O<li>保存Wather对象到客户端本地的WatherManager中</li>
7 o$ ^& [2 e, d5 i1 h* h: @5 K4 n2 X<li>服务端Wather事件触发后,客户端收到服务端通知,从WatherManager(watcher管理器)中取出对应Wather对象执行回调逻辑</li>
: _7 u$ `; V5 D" c5 K1 h</ol>
3 e4 q% K% O$ \( z, |# l<p> 主要监听2方面内容:</p>2 X" p w% v1 F% T
<ul class="list-paddingleft-2">
# u) I+ f9 B3 h+ Y<li>/ x- o3 g: P9 t. ^3 J
<p>监听Znode节点的<span style="color: rgba(51, 204, 204, 1)">数据变化:<span style="color: rgba(0, 0, 0, 1)">就是那个节点信息更新了。</span></span></p>
Q0 o: K% [' Q</li>
4 X& l: s, Z( c6 y<li>' a- l- }5 G/ K# ~+ w% i
<p>监听子节点的<span style="color: rgba(51, 204, 204, 1)">增减变化<span style="color: rgba(0, 0, 0, 1)">:就是增加了一个Znode或者删除了一个Znode。</span></span></p>
, ?% _; e C- G; D</li>
$ ^, s# B8 R7 y+ z4 Y6 r</ul>
$ Z; O d0 Q5 i( ]9 K<p><span style="color: rgba(0, 0, 0, 1)">几个特性:</span></p>
/ F7 _2 [4 E3 v<ul>& H7 f- t" L0 E% y; `
<li>一次性:一旦一个Wather触发之后,Zookeeper就会将它从存储中移除</li>
1 z+ J' n- q/ @) ?<li>客户端串行:客户端的Wather回调处理是串行同步的过程,不要因为一个Wather的逻辑阻塞整个客户端</li>! I; }5 Z9 C% e, A9 m7 s' `* {
<li>轻量:Wather通知的单位是WathedEvent,只<span style="color: rgba(51, 204, 204, 1)">包含通知状态、事件类型和节点路径,不包含具体的事件内容</span>,具体的时间内容需要客户端主动去重新获取数据</li>; |1 d I/ o K$ t- H! \- M7 G
</ul>7 {- K8 }& s7 i
<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">三.ZK集群(相关概念)</span></strong></blockquote>7 |$ {) ?0 o5 L" F
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211182203890-1695256509.png" ></p>
3 ]4 G" q, v& {( R# B3 _, {<ul>. }5 T4 T' n- n( N% ]! [
<li>Leader:负责写数据。(写数据都有事务)</li>: `& r1 l7 z0 ]2 X! Q* V- n2 W/ 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>& h( z; S% R% I, q0 ^$ N
<li><span style="color: rgba(51, 204, 204, 1)"><span style="color: rgba(0, 0, 0, 1)">Observer:只负责读。</span></span></li>
, j. @3 Z, o; e" e1 y- O* R3 |. J. m3 M, V/ n4 t9 o2 ?
</ul>
! [! {1 M7 m$ c/ t<hr>! T( n/ L6 P- k7 W9 ~% X9 w5 i
<p>从上面的角色种,我们可以总结ZK节点的工作状态(服务状态)</p>' T" u3 [" k' w9 m& O
<ul>
! c6 S! x& a- h% C) w5 O<li>LOOKING:寻 找 Leader 状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。</li>/ C( _4 ]8 Z8 I; r% F
<li>FOLLOWING:跟随者状态。表明当前服务器角色是 Follower。</li>7 L$ t! I4 K, B- ~% a, u
<li>LEADING:领导者状态。表明当前服务器角色是 Leader。</li>- r2 N" i# K3 d6 |* ~' s
<li>OBSERVING:观察者状态。表明当前服务器角色是 Observer。</li>
9 J9 S3 V) y/ V" j1 o& M. ?, _1 G. W$ X* r
</ul>2 C4 Z4 I% U! K9 I( w) l9 O
<hr>; G( ]# s4 {/ }5 C/ j. T5 S; I
<p>其他概念:</p>
% `5 P) t& W' ~<ul>0 ^; ~: m* n& x) s9 R* v& w
<li>zxid:<span style="color: rgba(51, 204, 204, 1)">全局事务ID</span>,分为两部分:
$ P1 g# m; D; T% B; M/ T) e0 n<ul>0 q# Z) [$ a* i/ h
<li>纪元(epoch)部分:epoch代表当前集群所属的哪个leader,leader的选举就类似一个朝代的更替,你前朝的剑不能斩本朝的官,用epoch代表当前命令的有效性。</li>/ J% Y3 ]+ g" l- b5 \
<li>计数器(counter)部分,是一个<span style="color: rgba(51, 204, 204, 1)">全局有序</span>的数字,是一个递增的数字。</li>
" a! b8 Y7 _; G9 L3 O) S0 D% ]! a/ g' j" ^* j7 {
3 p- q' f n& S% u" Y$ w+ `8 n
</ul>/ b) y" E: \9 B- k8 T
' R; q( l7 G7 z# t; C y2 w1 ]; N+ m9 `* [; {
</li>0 A7 E$ m( P7 H: b; h5 B
4 X9 y: b; F! U' D4 H6 F: L) w! p: t
" ~( a8 m+ S& N5 x. ~5 \
</ul>' y9 b+ u" ?. n$ l- M( u
<hr>
: h! K% r$ U; S% S<p>写数据原理:</p>
* A, {2 p" _9 C9 E<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211214106019-937037786.png" ></p>
, w4 v) j6 S/ \6 Z4 C: v# G<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211214136079-1875911582.png" ></p># u" p& k6 h2 G* C$ g8 o* a4 X3 O
<ul>- R' G* j+ O D+ y) X2 \
<li>写给leader,leader再通知其他节点 </li>, _3 z; F3 `/ [
<li>写给follower,follower没有写的权限,交给leader写,leader再通知。 </li>
. H# E. |+ p2 {! o# i( N. i9 O- N<li><span style="color: rgba(51, 204, 204, 1)">半数机制</span>:比如上图,zookeeper在通知其他节点写的时候,达到半数就通知客户端写完成。 不需要全部写完成。所以集群的数量一般是奇数。</li>* o# H3 {/ e; |8 F. n, O
: ~. o6 k' v2 y1 O! {
5 b7 e- E! R- d1 `2 a
</ul>
3 i% M) u. F2 ?. ~) H+ G- i<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">三.ZK集群(原理)</span></strong></blockquote>
+ {$ d7 q/ I4 m; C9 X1 R7 A<p> 上面我们知道集群的基本概念,那么也会引出很多问题:ZK怎么保证数据一致性?Leader宕机了如何进行选举?选举后数据如何同步?</p>9 D+ W) H! A0 Y" _( @
<hr>. v1 g; ?) ^, d
<p> ZK怎么保证数据一致性?</p>
! R0 E" E9 R& i! H- j<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>! l( Z; Z! m6 ^% h4 d6 d8 N3 ?, O3 a- d
<p> 一般我们正常是消息广播:</p>
4 L5 }4 q% E1 i, b% S) V8 T6 e<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211205808867-321051219.png" ></p>. e% y; B) J$ G, Q6 z5 r! K
<ul>
5 `) Z( Q w$ [' s<li>第一阶段:<span style="color: rgba(51, 204, 204, 1)">广播事务阶段</span>:对应图上的1,21 x/ l. s& |0 D, I. V) x# B
<ul>+ j" [6 x3 }4 i
<li>Leader收到请求之后,将它转换为一个proposal提议,并且为每个提议分配一个事务ID:zxid,然后把提议放入到一个FIFO的队列中,按照FIFO的策略发送给所有的Follower。</li>3 h* n/ G- `9 s2 _! `
<li>Follower收到提议之后,以事务日志的形式写入到本地磁盘中,写入成功后返回ACK给Leader</li>) z0 S6 D( ~: K G$ x
. ?4 K/ y- J a, ?& d3 a9 [
" \0 E9 P: }. k5 m
) G( F3 _% w% j$ ?( }9 |
, ]- l% ]* g# w& X- r( S3 S
9 y) R% j3 K$ z5 j6 A3 R( C G; n$ r
</ul>$ r7 C) ]. k7 [! `% [. i" U7 g
& E, a. B y3 w6 }. n6 m
$ s7 Y; V0 h5 H+ Q4 E
! |9 B+ J% Q/ @( g5 u$ J R4 W' B
- {2 G2 q$ |% N* U! ^/ T, o4 d8 b* E
$ ]) K$ j4 M( B1 } o5 U
</li>
2 q$ G( r& }9 Q/ X; N) m<li>第二阶段:<span style="color: rgba(51, 204, 204, 1)">广播提交操作</span>:对应图上的3
0 Z2 b a- M1 \" v) O, b( A<ul>
' ]! G6 k( }0 U- l' J! Q4 j<li>Leader在收到超过半数的Follower的ACK之后,即可认为数据写入成功,就会发送commit命令给Follower告诉他们可以提交proposal了。</li>9 t p1 m9 r9 L' k. g3 H! }2 ^1 x- c
0 K8 J% P" J; a6 {0 q2 X. @2 y1 ^+ f: g6 ]9 j3 q
: ^' Y3 ^- C/ o$ Z+ A+ Q2 o1 {
, H7 d! t; h. P+ D0 s# Z
4 o. p6 J! [% O) a# Z. E) B1 V) s
( |. g1 y" C7 m/ A& _: K3 [</ul>
) E8 s: O2 n9 B
, H8 K" g7 T" t1 f, b- @; ~9 r V! p8 _! T4 n
! I& D5 G7 L! X, `1 H q# C! w7 H0 g
; F; V# y/ B+ p- T* d: B7 [+ |$ v3 v
- u c6 B( p) d7 Y5 j g/ ?: J) N/ L$ }0 L) K8 a! ]( o( _
</li>% {- b. R$ ~. {
) X7 i% c3 H9 N6 \; S: u
' k, F5 |! v- I3 p) O- e; e
) j; i# C7 K: f; L j
6 m8 ]9 F. Q" u* |0 h& t0 p4 O/ W! I# b: R- l' ?! N/ W
+ `) ?2 L3 t; F U+ h# W5 j</ul>! L/ T( u4 w8 M" Y5 s
<hr>
! y( `- N. R& T2 @; _$ z1 ~3 r<p>Leader宕机了如何进行选举?</p>) L5 ^/ B, Q7 U$ y- l% H4 c" d
<p>这就得使用ZAB的第二种模式,崩溃恢复模式:</p>
3 k8 q1 G3 [; _) Z" U<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211211246367-43062481.png" ></p>2 F7 N3 w. T: X; m
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211211725764-329743928.png" ></p>& Q% ^, N1 C& t/ P1 K7 O V0 M7 s
<hr>
) K/ X) {8 q3 g6 j: G( C+ b, M<p>选举后数据如何同步?</p>, A8 q; w, Z6 a9 K3 K: i: F/ d
<p data-tool="mdnice编辑器">那实际上Zookeeper在选举之后,Follower和Observer(统称为Learner)就会去向Leader注册,然后就会开始数据同步的过程。</p>: B. d# s& z, j6 v, ^0 j
<p data-tool="mdnice编辑器">数据同步包含3个主要值和4种形式。</p>
, Z" O8 _3 _( X ]. W: ^' C$ c3 B& G<ul>* s. R5 E% C3 X7 C/ ^/ s0 h
<li data-tool="mdnice编辑器">PeerLastZxid:Learner服务器最后处理的ZXID</li>. k2 R2 P; ?, L
<li data-tool="mdnice编辑器">minCommittedLog:Leader提议缓存队列中最小ZXID</li>
' W2 [8 a" d) t% u( ~! ?2 g<li data-tool="mdnice编辑器">maxCommittedLog:Leader提议缓存队列中最大ZXID</li>
( e: T* x: p% X( ?4 P' E
2 H. H4 Z% L: t
& m2 {9 R" s6 S: ]- E5 ~3 |2 u
5 j _# Z7 y* ?0 u, l
1 W" X, Z* l2 o: T/ M7 k. J u& K+ o- c% k
1 e, e5 S0 v( x( g, z6 J) F" p6 ?
</ul>9 R4 H, D. ?4 H8 i
<p>同步策略:</p>
, y, s( X2 C5 R) b% K: }<ul>& c: x4 R+ B. m
<li><span style="color: rgba(51, 204, 204, 1)">直接差异化同步</span> (DIFF同步):如果PeerLastZxid在minCommittedLog和maxCommittedLog之间,那么则说明Learner服务器还没有完全同步最新的数据。<ol>6 X1 a5 j4 K3 d/ I& l
<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>+ G4 r" S+ s B5 s% Y
<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>
9 B7 B+ d5 S1 F9 w/ i1 L9 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">接着等待集群中过半的Learner响应了ACK之后,就发送一个UPTODATE命令,Learner返回ACK,同步流程结束</li>; y$ z7 O0 ?" m
e& s5 O$ l( @4 K; \3 V& J
* S- L0 s9 K5 F% C% U
) f: K, w5 [# x. ]" T1 x4 r
+ u2 y; J, f5 F4 I3 C) I% L y: V0 c</ol></li>
7 e% C. y& U7 D; Q: r* A* D3 x<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 m2 Y7 w3 A" D<ul>
G2 P2 q4 ?0 D. u) w<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>
$ v- ]# K8 F* g6 c0 U" C- c<li style="text-align: justify">( n; G7 h3 a# v" R7 N
<p data-tool="mdnice编辑器">A要进行事务回滚,相当于抛弃这条数据,并且回滚到最接近于PeerLastZxid的事务,对于A来说,也就是PeerLastZxid=3。流程和DIFF一致,只是会先发送一个TRUNC命令,然后再执行差异化DIFF同步。</p>
# G$ ~6 N# z9 `2 O8 |) K7 _- T
' T% N% V, z/ C. n- i" G
3 P4 S; }. o; M" V3 m5 c. y G {3 k
$ i1 `# a7 |% J- g5 V</li>2 R% a1 W4 Q/ O' I2 Q% K2 D
, J! }+ m9 D/ Z8 d) R( e
1 g" K' V% _. L- O
2 {6 v2 c' ^5 c1 [" I! T9 w6 ~1 a: f4 p1 l. S" j% z" ^- ~. P
</ul>2 s0 n: C* W3 g5 {# d
. X% l8 Q5 I/ u4 E( e8 m8 [. n1 \4 W. u' `
8 o, P! h( I( x1 \* E1 w
* ~/ j3 T6 {: D</li>) D# B- C* y/ k2 c7 @$ j+ \
<li><span style="color: rgba(51, 204, 204, 1)">仅回滚同步</span>(TRUNC同步):6 P2 N$ A: {' Q
<ul>3 i3 ^7 C+ D; G1 F0 i
<li data-tool="mdnice编辑器">针对PeerLastZxid大于maxCommittedLog的场景,流程和上述一致,事务将会被回滚到maxCommittedLog的记录。</li>
8 _+ \( Q2 `% K5 v, l' S<li data-tool="mdnice编辑器">这个其实就更简单了,也就是你可以认为TRUNC+DIFF中的例子,新的Leader B没有处理提议,所以B中minCommittedLog=1,maxCommittedLog=3。</li>
$ R: h$ D( `" F' a7 d( F<li data-tool="mdnice编辑器">所以A的PeerLastZxid=4就会大于maxCommittedLog了,也就是A只需要回滚就行了,不需要执行差异化同步DIFF了。</li>2 r3 v# H1 v) X: G: d( W4 ~+ a
; o! j. H* v- `: L3 I# Y
# N/ I7 p2 h# [: t2 Z' E7 I
3 g3 ` I' o* X* y7 d) X( s l" e9 j* R
</ul>
9 \3 M& f q2 I6 x/ ^2 \6 Z4 V7 ?) Q0 G, f h5 A8 f3 `, D( J, y* A: W
$ }0 \$ y- B8 @) w( p. e! w& v) s1 n J
& `$ y, d6 Q. z. j; D+ \$ ]/ \( u</li>6 m) l' T% y1 y( c( f: |% Z
<li><span style="color: rgba(51, 204, 204, 1)">全量同步</span> (SNAP同步):
) U3 B/ N# j: n, I% Z- a<ul>+ h" |- W1 w% ~3 U) h& d
<li>$ A, y, C, [: r8 u
<p data-tool="mdnice编辑器">适用于两个场景:</p>
. V0 n& i# Y; r! q- M8 u! D<ol class="list-paddingleft-2" data-tool="mdnice编辑器">
' p$ h5 c; h: @$ ~1 b4 {8 a' r r<li>PeerLastZxid小于minCommittedLog</li>
0 V% S$ P9 ^! q7 B<li>Leader服务器上没有提议缓存队列,并且PeerLastZxid不等于Leader的最大ZXID</li>
i$ [5 X& R' h; [, S2 |' g9 Z( q6 y( c3 w
* b% h) O- D0 h( I
- A3 S7 z8 E: G0 C% `0 X; f+ m! Y5 J) y
</ol></li>) y, V9 A, N3 f: T9 {9 p
<li>这两种场景下,Leader将会发送SNAP命令,把全量的数据都发送给Learner进行同步。</li>
( o2 k4 v1 c5 \. j/ c N: {
6 |) L) p) p, I7 F: o& L: x K) D
$ z! Y) g9 D$ e& z. @! Z
* z. {3 D. p: ^% Y7 w</ul>
* y5 `& w0 A9 j S8 V
; P$ x* K4 t3 {3 C% b( S' ?3 X# j- R$ n) m1 u
7 N$ Q+ v Q- v1 y5 i6 h. A
6 T; Y) |# q- R+ C1 m</li>
! t8 G. ]: y4 S b$ D! h5 T& W/ {* A8 {' r9 g
8 G9 O$ n, H7 U, A
) f+ E+ A: w. W Z
$ V& ?7 m/ M% ]' Y
</ul>5 Q0 h/ M8 j7 T. p' G
<hr>) a L5 O. k- N4 p4 Y& i) E
<p data-tool="mdnice编辑器">有可能会出现数据不一致的问题吗?</p>
+ Y9 k. o8 R; @6 h9 @; `- [<p data-tool="mdnice编辑器">还是会存在的,我们可以分成3个场景来描述这个问题。</p>
- n) K7 m" E! |# Q2 P( A0 o' r<ul>
9 ~/ k- F3 k! h, z& K<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">查询不一致</span><strong><strong>:</strong></strong>) _) D1 `6 {/ U7 ~/ G
<ul>
, }4 h4 w4 M! b- {# e: w0 `<li data-tool="mdnice编辑器">因为Zookeeper是过半成功即代表成功,假设我们有5个节点,如果123节点写入成功,如果这时候请求访问到4或者5节点,那么有可能读取不到数据,因为可能数据还没有同步到4、5节点中,也可以认为这算是数据不一致的问题。</li>
# q' i1 t$ c) o& l0 e3 Z<li data-tool="mdnice编辑器">解决方案可以在读取前使用sync命令。</li>
' |5 y4 w, h& ~9 u) ]1 m1 h9 ^# P3 D/ |
9 ^% Y, _+ q! V- @
</ul>
# }6 ]4 I$ y/ e* u1 {7 h9 J4 ^% e. v) \3 l, c; K
7 X [3 P# Q, Z( K* B/ v0 N
</li>
7 g) Y0 e& w# M: p4 B$ P<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">leader未发送proposal宕机</span><strong>:</strong>
' [( ^4 H; M/ ]+ i<ul>0 w" E6 R4 I, D7 s" [( F) ~/ d
<li data-tool="mdnice编辑器">
/ r8 f* o6 D$ J: Q<p data-tool="mdnice编辑器">这也就是数据同步说过的问题。leader刚生成一个proposal,还没有来得及发送出去,此时leader宕机,重新选举之后作为follower,但是新的leader没有这个proposal。</p>
. L- ^* n0 Q4 `4 ?0 L' I# u* T) h. I, h7 z* H% G+ a2 w% G2 [7 ^
' r* ]5 b: B4 j4 |
</li>
$ q2 W4 [0 s K' t( }<li data-tool="mdnice编辑器">7 N1 g7 v2 i0 t+ @3 e7 k
<p data-tool="mdnice编辑器">这种场景下的日志将会被丢弃。</p>
3 q, w5 O: _; U# U( s7 r$ f" T: ]. _' z" J& Q- N
" f t" D$ Q- p1 Q
</li>
- u$ B, Y+ \6 q; W: i3 Q4 E5 l1 ]
# g0 C: L" b$ v& s' Z0 h- r# [5 R
</ul>0 B- W7 o9 |) l, X' k7 s# X
+ Q4 U$ g1 D, d& J0 I5 v9 @8 s* n
</li>
/ \- u9 Z% K2 x3 s<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">leader发送proposal成功,发送commit前宕机</span><strong>:</strong>
& h, z# c, g( {) I I" u; k0 w. ]) d<ul>
l' G" V6 P- o- F: [<li data-tool="mdnice编辑器">如果发送proposal成功了,但是在将要发送commit命令前宕机了,如果重新进行选举,还是会选择zxid最大的节点作为leader,因此,这个日志并不会被丢弃,会在选举出leader之后重新同步到其他节点当中。<strong><br></strong></li>
3 O; M( R; F' U: Z6 m
, X8 L% L. n" T& n6 S, |
{6 E v$ i. Y: b! t</ul>
% R0 h1 F+ y/ Y2 V! w. |: R# z# v: {% m$ M! j( ^
8 K+ b# W# h% Q3 {+ i5 l
</li>, r7 `9 v% d7 y" f5 N
: M1 f- m0 O6 I3 x5 @' g0 F
. e. D; p' r8 D3 p% V/ ~# s</ul>
5 w% o Y8 N# p2 d" P' A<blockquote><span style="color: rgba(0, 0, 0, 1)"><strong>四.ZK其他小问题</strong></span></blockquote>: S* O6 F, x8 l. c3 I
<p>zookeeper 是如何保证事务的顺序一致性的?</p>
" U f* V" w$ e<ul>+ u1 U. L" J/ e- r o
<li>使用<span style="color: rgba(51, 204, 204, 1)">zxid</span>来保证顺序性。</li>9 X. l" F A* T- p$ f3 Y
0 Q8 m; w. Y2 {/ k* }) m& n
- q5 t: o$ o }6 q</ul>/ W! k: i+ i8 {
<hr>
! X R2 u2 x8 m2 C7 J7 y3 A% B<p>集群最少要几台机器,集群规则是怎样的?集群中有 3 台服务器,其中一个节点宕机,这个时候 Zookeeper 还可以使用吗?</p>
, e4 w1 a" R$ Y5 c<ul>+ K' C) x" t' a. c$ D
<li>集群规则为 <span style="color: rgba(51, 204, 204, 1)">2N+1</span> (奇数)台,N>0,即 3 台。可以继续使用,单数服务器只要没超过一半的服务器宕机就可以继续使用。</li>; b! G1 X+ e+ V8 }+ l/ E; x
9 S: W# S& ?1 u# n7 D0 R# s+ o: u# v! h8 z1 W7 ?
</ul>
& q+ u* M- Y+ `% [% s9 z<hr>4 Y- t, ~* B0 x* D- I4 l5 F
<p>说几个 zookeeper 常用的命令:</p>
9 ^0 Q5 y, u ~; Y9 d- Y<ul>
+ ^& M) H' p$ O8 ^' E' _$ E8 y3 H5 G, Z; Q<li>ls path:查看当前 znode 的子节点</li>+ Q w& z* A+ F; s% H
<li>get path:获取节点的值</li>
/ X7 o( u- Y. |5 E" g$ b2 X$ R& }! u; ?<li>set:设置节点的值</li>
* A) }$ } y! u2 }: m5 e<li> create,delete:创建/删除节点</li>8 V- A: i; A! t% n2 s3 V4 S
0 v% n, ?/ G' H2 a
- a% j) I1 n# E$ F& | T6 l
</ul>6 ?! J; l! b6 k- v. o' B# n
<hr>+ J+ J U B" c( |1 Y) j1 U3 W
<p>会话Session:</p>& q0 q! l% U# J: U/ q, `
<ul> Y/ U# ?' B$ ?5 g6 X) L& ^. b
<li>会话自然就是指Zookeeper客户端和服务端之间的通信,他们使用TCP长连接的方式保持通信,通常,肯定会有<span style="color: rgba(51, 204, 204, 1)">心跳检测</span>的机制,同时他可以接受来自服务器的Watch事件通知。</li>
p9 C. w+ K3 m! M; M
3 n, f' e0 ]. [% R2 t' }4 m" p- l* m- _& m" Z5 A
</ul>
8 T- J3 L' r" h! w* X<p> </p>% W4 o! g1 H% k5 L0 J
<p>寄语:<span style="color: rgba(51, 204, 204, 1)">平静的湖面酝酿不出精悍的水手,安逸的环境创造不出时代的伟人</span></p>
2 B7 d% h& s8 o+ T+ D |
|