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