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