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