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