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