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