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