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