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