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