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