飞雪团队

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 17361|回复: 0

一文搞懂Zookeeper原理

[复制链接]

9321

主题

9409

帖子

3万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
30293
发表于 2022-2-12 14:35:41 | 显示全部楼层 |阅读模式

$ |4 @1 i* a0 u8 a: {+ b<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">一.概述</span></strong></blockquote>4 Y( k5 N3 C4 H# C6 k* t$ e
<p>&nbsp;ZooKeeper 是什么?</p>
4 W3 \/ E% ~( m* c* j/ E<ul>7 }5 M% r, A+ f; F! w, k
<li>是一个开源的<span style="color: rgba(51, 204, 204, 1)">分布式协调服务</span>。使用分布式系统就无法避免对节点管理的问题(需要实时感知节点的状态、对节点进行统一管理等等),而由于这些问题处理起来可能相对麻烦和提高了系统的复杂性,ZooKeeper作为一个能够<span style="color: rgba(51, 204, 204, 1)">通用</span>解决这些问题的中间件就应运而生了。</li>
" |# E- N* I) f; p<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>5 r( G7 u, x* ?
<li>实现原理:zookeeper=<span style="color: rgba(51, 204, 204, 1)">文件系统</span>+<span style="color: rgba(51, 204, 204, 1)">通知机制</span>。</li>, f5 u+ U3 j" @7 N  C8 |) ~
</ul>
& t; D$ T. \% Y8 x, [) K% X8 B4 l<p>Zookeeper的作用(应用场景)?</p>
% E' m5 }$ [' I5 M0 C& S<ul>9 F. a5 W+ b- j# ?5 k: l; I6 v$ ?
<li><span style="color: rgba(51, 204, 204, 1)">统一配置管理</span>:比如现在有A.yml,B.yml,C.yml配置文件,里面有一些公共的配置,但是如果后期对这些公共的配置进行修改,就需要修改每一个文件,还要重启服务器。比较麻烦,现在将这些公共配置信息放到ZK中,修改ZK的信息,会通知A,B,C配置文件。多方便</li>
% R1 L; w" o1 c( d' ?<li><span style="color: rgba(51, 204, 204, 1)">统一命名服务</span>:这个的理解其实跟<span style="color: rgba(51, 204, 204, 1)">域名</span>一样,在某一个节点下放一些ip地址,我现在只需要访问ZK的一个Znode节点就可以获取这些ip地址。</li>
9 ^! U* F1 q% M' m5 u6 l5 U- q<li><span style="color: rgba(51, 204, 204, 1)">同一集群管理</span>:分布式集群中状态的监控和管理,使用Zookeeper来存储。</li>0 l0 P5 P1 {8 z# j8 v$ O
<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调用。
) G6 Q  `7 r, Y: m, m# M/ b<ul>
% k0 |' G/ B1 n" E<li><span style="color: rgba(51, 204, 204, 1)">服务节点动态上下线:<span style="color: rgba(0, 0, 0, 1)">如何提供者宕机,就会删除在ZK的节点,然后ZK通知给消费者。</span></span></li>
+ s1 f) `+ h  x  {: a<li><span style="color: rgba(51, 204, 204, 1)">软负载均衡</span></li>7 g  J9 U0 j/ B  [' @% \7 L* x# N0 K
<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 M7 B( V0 R9 v" `  c8 u1 W, s</ul>3 F9 A8 G: A' L# z5 Y* B
</li>; F/ C$ Y& E5 O
<li><span style="color: rgba(51, 204, 204, 1)">分布式锁</span>(后续出文章讲)</li>
/ G7 Y- Z6 a/ z+ U</ul>
4 ^1 V: @9 ~* q<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">二.原理</span></strong></blockquote>
3 S/ a& e, l) T. w0 r5 h% |" T! F! M<p>之所以能做上述功能,主要是归功于ZK的<span style="color: rgba(51, 204, 204, 1)">文件系统</span>和<span style="color: rgba(51, 204, 204, 1)">通知机制</span>。下面我们来分析这两个机制</p>
+ K: S. k, t' t% l3 v2 j<hr>( |: r" j( C6 X/ |
<p>&nbsp;文件系统:</p>/ j0 H" r( F* V) ?) v) [
<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>( {9 c; _+ P9 p
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211170746939-2004306213.png" ></p>! d& [6 X7 T; m* Z
<p>&nbsp;Znode节点主要有4中类型:</p>
- ~& \6 s/ |% {/ j2 c7 t0 D<ul>$ @& B/ e6 l) \
<li><span style="color: rgba(51, 204, 204, 1)">临时目录节点</span>:客户端与Zookeeper断开连接后,该节点被删除</li>( X2 W# W1 `& u% W! b* K
<li><span style="color: rgba(51, 204, 204, 1)">临时顺序编号目录节点</span>:基本特性同临时节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。</li>9 }" `$ m; e# e' e- o; Q: t
<li><span style="color: rgba(51, 204, 204, 1)">持久化目录节点</span>:客户端与Zookeeper断开连接后,该节点依旧存在</li>) q. U, H0 N4 q4 Z3 l/ L7 B/ K
<li><span style="color: rgba(51, 204, 204, 1)">持久化顺序编号目录节点</span>:基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。</li>
4 b( T% i- M) _2 {) x</ul>
! z: E1 a8 H6 e- I, s, w+ L<hr>
  I* ^  [) q6 t: b. S<p>&nbsp;通知机制 (监听机制)</p>! a8 x+ Y9 ~' w2 S8 i
<p>Zookeeper可以提供分布式数据的<span style="color: rgba(51, 204, 204, 1)">发布/订阅</span>功能,依赖的就是Wather监听机制。</p>
$ u6 G% w3 g, g" u3 c# `<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 b% p5 a) n. v8 L4 D
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211172333942-1239203073.png" ></p>0 Z4 X. j! M5 w) n
<ol>
: _, F4 `4 F% y: q6 W% v5 z( N<li>客户端向服务端注册Wather监听</li>9 I1 K9 u& k, y' e) O4 J
<li>保存Wather对象到客户端本地的WatherManager中</li>) v4 d( w2 c3 d
<li>服务端Wather事件触发后,客户端收到服务端通知,从WatherManager(watcher管理器)中取出对应Wather对象执行回调逻辑</li>
6 P% ~2 D1 ~: ^! J+ O</ol>
9 _( ?  S4 s7 P2 e  I" Z6 a$ |<p>&nbsp;主要监听2方面内容:</p>2 D1 D$ X- _; f& L7 _5 S
<ul class="list-paddingleft-2">
$ }. @# s; g0 e9 T<li>
4 m) o/ s/ M* i& r9 E* u$ u<p>监听Znode节点的<span style="color: rgba(51, 204, 204, 1)">数据变化:<span style="color: rgba(0, 0, 0, 1)">就是那个节点信息更新了。</span></span></p>6 ?6 a+ R" c  h; D7 T
</li>' d$ C- Q0 Y* a6 ^" `" v( y$ F9 k+ s
<li>
" p3 p6 E, \) n4 ]9 H5 I/ c<p>监听子节点的<span style="color: rgba(51, 204, 204, 1)">增减变化<span style="color: rgba(0, 0, 0, 1)">:就是增加了一个Znode或者删除了一个Znode。</span></span></p>
7 S, r2 Y& A+ s( J$ X  I) \, u</li>
, J  ^/ H3 e; z</ul>
% F' v  X& f9 G<p><span style="color: rgba(0, 0, 0, 1)">几个特性:</span></p>/ d  o8 d' ]  M0 p0 k; S. @, R
<ul>
& }) v4 N: S) A- t- l<li>一次性:一旦一个Wather触发之后,Zookeeper就会将它从存储中移除</li>2 n7 @- l! b4 p7 C1 a. h0 A
<li>客户端串行:客户端的Wather回调处理是串行同步的过程,不要因为一个Wather的逻辑阻塞整个客户端</li>
; z; \- }: U9 l) f& m% W<li>轻量:Wather通知的单位是WathedEvent,只<span style="color: rgba(51, 204, 204, 1)">包含通知状态、事件类型和节点路径,不包含具体的事件内容</span>,具体的时间内容需要客户端主动去重新获取数据</li>6 e; S0 K& ]/ ]$ g" m
</ul>* Q  _$ m! T3 |7 S# z  f% U
<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">三.ZK集群(相关概念)</span></strong></blockquote>! T) N; T2 h  |- g3 Y! v
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211182203890-1695256509.png" ></p>7 n; u  C* g+ P4 o/ |. R4 z
<ul>
9 W, Y3 L9 T  O  I4 k<li>Leader:负责写数据。(写数据都有事务)</li>
) ^; y& O; C+ f& a) Z" I<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>
8 J* R2 [: F+ v1 N2 x" C<li><span style="color: rgba(51, 204, 204, 1)"><span style="color: rgba(0, 0, 0, 1)">Observer:只负责读。</span></span></li>: g6 }" W6 `( w1 m+ @

3 [/ {/ C4 I1 P2 N</ul>
' m5 r" K2 ^. g1 p# _" P) U" R<hr>
/ O9 I* {3 o& d, |' @' V% x<p>从上面的角色种,我们可以总结ZK节点的工作状态(服务状态)</p>- Y2 E4 S& X7 ^1 v; Y
<ul>- `9 y3 L9 V6 p! o
<li>LOOKING:寻 找 Leader 状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。</li>
! `3 s2 X% }: k5 J' g1 y: A: r<li>FOLLOWING:跟随者状态。表明当前服务器角色是 Follower。</li>
$ G# \; Y! ^* o, r/ a<li>LEADING:领导者状态。表明当前服务器角色是 Leader。</li>9 O0 y6 {, m* Y9 v
<li>OBSERVING:观察者状态。表明当前服务器角色是 Observer。</li>
7 U- T/ L$ u+ N9 y0 W& ?. C$ I- Y/ w) |) d+ f- u
</ul>7 f$ [( n) S3 }2 s4 K! u
<hr>
  c+ b' h0 V- L( a: v<p>其他概念:</p>
8 K$ O: }5 Y0 V9 w  [5 E, S" ]<ul>" S0 n! c" m# B( m
<li>zxid:<span style="color: rgba(51, 204, 204, 1)">全局事务ID</span>,分为两部分:
7 e: |1 Y; l5 I2 l; B<ul>* A& z# g( Q. w7 h: S) L
<li>纪元(epoch)部分:epoch代表当前集群所属的哪个leader,leader的选举就类似一个朝代的更替,你前朝的剑不能斩本朝的官,用epoch代表当前命令的有效性。</li>
! y5 d/ Y2 `4 U9 l* p* f. |<li>计数器(counter)部分,是一个<span style="color: rgba(51, 204, 204, 1)">全局有序</span>的数字,是一个递增的数字。</li>- O7 f+ q6 u5 D
! D1 P! G, `1 Z5 @
. l& n  D7 F3 |' W( h1 U. o; c
</ul>- ^- P! F' ~( `' A( T

$ A. ^; Z8 _9 _* A, `8 \; p; q2 y* X, i9 u! x5 i( S0 r
</li>
: D) p8 M5 q5 [- Y$ N5 ]. Q
0 v" [& b! h/ V9 I" {! G5 P9 O8 s% z. T
</ul>6 i: l8 h7 R+ |+ Y% c) ?
<hr>
+ S7 o: R$ U0 Z  t- t<p>写数据原理:</p>
4 c$ P0 e2 \5 E+ p. F<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211214106019-937037786.png" ></p>
# U& |2 K  P: m0 M<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211214136079-1875911582.png" ></p>
* \4 U$ X2 e, B8 w3 w) C5 `8 {<ul>
$ p( I7 F6 e" W' {<li>写给leader,leader再通知其他节点 </li>
+ e( B- _6 F9 {5 M" |7 M5 r<li>写给follower,follower没有写的权限,交给leader写,leader再通知。 </li>% F9 s* S5 M1 J0 u: s0 ~
<li><span style="color: rgba(51, 204, 204, 1)">半数机制</span>:比如上图,zookeeper在通知其他节点写的时候,达到半数就通知客户端写完成。 不需要全部写完成。所以集群的数量一般是奇数。</li>
5 F4 V! Z- ^5 O2 |  P+ L2 w5 k
) k% L" T- o% j0 x
</ul>/ b/ t: b6 h% ^
<blockquote><strong><span style="color: rgba(0, 0, 0, 1)">三.ZK集群(原理)</span></strong></blockquote>
/ t$ a2 @3 H$ f  L2 M<p>&nbsp;上面我们知道集群的基本概念,那么也会引出很多问题:ZK怎么保证数据一致性?Leader宕机了如何进行选举?选举后数据如何同步?</p>) F; e& ?. K& K% e
<hr>
2 y$ f" ~; V9 U: {( r& z, @% c<p>&nbsp;ZK怎么保证数据一致性?</p>+ G& c, W6 n4 }7 z5 ?3 q) g. 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>
4 r7 I4 ^6 S+ a: E. G<p>&nbsp;一般我们正常是消息广播:</p>
9 A7 u2 d, T& N8 B. ?<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211205808867-321051219.png" ></p>$ N9 v$ v7 n) w- M" G" n: D
<ul>! Z# V/ w5 l8 Q& u1 E$ |9 A9 X+ p) R  a
<li>第一阶段:<span style="color: rgba(51, 204, 204, 1)">广播事务阶段</span>:对应图上的1,2
/ ?; ^7 g9 x! p# h  A1 H<ul>
4 k- G$ K; k. H4 c  I; {0 \<li>Leader收到请求之后,将它转换为一个proposal提议,并且为每个提议分配一个事务ID:zxid,然后把提议放入到一个FIFO的队列中,按照FIFO的策略发送给所有的Follower。</li>( E$ e6 H" ~% O3 M9 I
<li>Follower收到提议之后,以事务日志的形式写入到本地磁盘中,写入成功后返回ACK给Leader</li>: n! n; w& n1 b$ {& i& ^+ d$ s
' U4 X$ I, F/ g' T1 l+ ?: ~. _

& x1 [  }+ P# J+ p$ Y. h% M9 E$ b9 L! g
! o- ~2 V: T7 H* V: s* M6 K) P

0 W/ X# h' }$ b# f" w2 a6 @+ A- U4 w
</ul>' Q4 X. {3 }7 n- P1 r* y0 \; a7 S
9 T/ n# C& n3 ]* g) }

2 Z& h# n# _0 E  c3 T3 j( P! V5 C+ E9 a4 c! s4 O$ X: p

/ U6 j3 r. q4 ]) a+ K4 r; Y, p
9 U; b9 Z! ~; V* ]7 [! p9 P- ?& ^! ?: T
</li>
  @: Q: s7 A6 [* _<li>第二阶段:<span style="color: rgba(51, 204, 204, 1)">广播提交操作</span>:对应图上的34 f/ ?) [* {  K+ z
<ul>
( Q* O2 v0 z( M. I<li>Leader在收到超过半数的Follower的ACK之后,即可认为数据写入成功,就会发送commit命令给Follower告诉他们可以提交proposal了。</li>& D' x6 |$ o+ e. T  d- k* Q$ v* p

% z( o; q' q+ U: u& b% u) |
  @2 P- A% ^' }, N2 K" j% Y0 d
, \! `/ ?  n1 v9 ^& g: m" _

. |& Q; m, C/ g3 R8 Z# g/ |- i' Q) P
</ul>: G' z; N* X; U, v  j$ ~' [

$ s) h4 c0 Z  f- Y. L: R' I6 ]* ~" L" x; l

- U* N) H& s8 X) L2 M! ?2 X" [% B' g4 |0 q' S# T3 p1 n

: K3 p4 ~9 {$ W) A: }
5 d* D/ e5 W8 _+ K9 i1 T2 T& k</li>, `( j, w8 {$ d0 M$ ?
" O" ]$ _* I/ Q) l
. Z9 V, [3 v8 I
/ _* E& `9 U7 C0 f/ V3 W

" _# L" r# P' k  C4 `1 V
8 m' n( Q5 q- e9 k# d$ Y7 d% M* C# [
</ul>4 r# d- i# [; i6 H$ e
<hr>; \5 E0 o% v$ E0 q' \" P7 @
<p>Leader宕机了如何进行选举?</p>
3 p1 l4 F8 M% `$ \! D5 f5 E<p>这就得使用ZAB的第二种模式,崩溃恢复模式:</p>( i4 n! p. ^3 \+ E* g8 D0 p# W$ U- v1 \
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211211246367-43062481.png" ></p>+ r. I: m4 F* d& K: {" K8 h0 Z
<p><img src="https://img2022.cnblogs.com/blog/2597186/202202/2597186-20220211211725764-329743928.png" ></p>
# `; h6 k# ]& _, B9 j5 @: q+ D<hr>
* S) U$ M0 A) m8 \( }<p>选举后数据如何同步?</p>
7 s; x& [: b" W- t+ S% Z" ~( _<p data-tool="mdnice编辑器">那实际上Zookeeper在选举之后,Follower和Observer(统称为Learner)就会去向Leader注册,然后就会开始数据同步的过程。</p>
- U  A) u/ s# r$ V# ?<p data-tool="mdnice编辑器">数据同步包含3个主要值和4种形式。</p>
1 [8 k8 o0 V# o6 N4 t4 N; R) m8 o<ul>
" h: h* U+ U9 \4 B9 l<li data-tool="mdnice编辑器">PeerLastZxid:Learner服务器最后处理的ZXID</li>
! h  V8 ]. e& l<li data-tool="mdnice编辑器">minCommittedLog:Leader提议缓存队列中最小ZXID</li>2 X. z9 O8 b; D/ \: a0 r
<li data-tool="mdnice编辑器">maxCommittedLog:Leader提议缓存队列中最大ZXID</li>
( r# q0 j0 Q- r: m
) a1 r/ ^+ y4 l, b! w" s2 U* J) L8 f; {
+ z6 v- c) ~4 j" \% K: r+ c- S' a- E% Q/ P7 l
$ K/ q- }3 b* S) t1 |( X
+ z- `' [3 u. ^: f. Y
1 Q, J$ c" Y' J
</ul>
. b& r$ ?: s4 ]4 Y<p>同步策略:</p>( @1 B$ Y$ z" d
<ul>$ o3 J- Q' q6 z3 h. q2 K
<li><span style="color: rgba(51, 204, 204, 1)">直接差异化同步</span> (DIFF同步):如果PeerLastZxid在minCommittedLog和maxCommittedLog之间,那么则说明Learner服务器还没有完全同步最新的数据。<ol>
+ t9 |- V; w% {0 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>/ _9 j2 x; w. O$ O1 v, `1 y9 H% n3 G
<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>- a" N' n" G. I3 L1 d# g1 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">接着等待集群中过半的Learner响应了ACK之后,就发送一个UPTODATE命令,Learner返回ACK,同步流程结束</li>
# x7 P* r2 ^( c' m, S4 a; z2 J* ]; a% E" h

7 Z. _  s$ ]9 _+ Z: I0 C" Z( \* G$ L, Z+ b% ^: ^

9 d9 Q( C, l- s0 q- E3 O9 r</ol></li>
" e" E5 h2 L9 W6 M- A4 R+ L# Q<li style="text-align: justify"><span style="color: rgba(51, 204, 204, 1)">先回滚再差异化同步</span>(Trunc+DIFF同步):特殊场景:<span style="font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, 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>; c& X' P9 n, q3 _% k+ [& \
<ul>
$ c; e* o: P. _( S<li style="text-align: justify"><span style="font-family: -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, 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>7 U+ f+ E2 L! x, v
<li style="text-align: justify">- D$ Z4 q% r5 t7 x( }
<p data-tool="mdnice编辑器">A要进行事务回滚,相当于抛弃这条数据,并且回滚到最接近于PeerLastZxid的事务,对于A来说,也就是PeerLastZxid=3。流程和DIFF一致,只是会先发送一个TRUNC命令,然后再执行差异化DIFF同步。</p>2 X# S' `7 h4 E2 J# `5 T
, e1 ?( d7 U' n9 e0 ^5 U7 L4 @
/ @' F, [4 J) M) d8 |' c

: B/ l' u5 F4 W. C$ K
2 H( S" j5 R5 F7 I3 w( Y</li>3 N& L  N+ g: E  ~
, X" I. o3 r3 a

- M+ A* C7 n5 J( |2 @
1 j- s7 F" i8 q. I
% X- M4 m! [2 G: M0 @</ul>3 \" ]( X$ n& L! H# c* v

( t9 `3 p! {, E2 Z3 i7 C; E7 a8 f

: j; m8 q! C- E0 j9 A$ p, S9 \8 z' S9 l( E
</li>
- F$ H% I! P& ?; b( e2 n$ e<li><span style="color: rgba(51, 204, 204, 1)">仅回滚同步</span>(TRUNC同步):! U7 m0 g7 a" j) W/ o
<ul>+ N" R& Z! V8 E9 X
<li data-tool="mdnice编辑器">针对PeerLastZxid大于maxCommittedLog的场景,流程和上述一致,事务将会被回滚到maxCommittedLog的记录。</li>
5 @/ _- U! p1 ]! B0 m<li data-tool="mdnice编辑器">这个其实就更简单了,也就是你可以认为TRUNC+DIFF中的例子,新的Leader B没有处理提议,所以B中minCommittedLog=1,maxCommittedLog=3。</li>
8 P3 k9 e! F4 E' Y5 m7 ?<li data-tool="mdnice编辑器">所以A的PeerLastZxid=4就会大于maxCommittedLog了,也就是A只需要回滚就行了,不需要执行差异化同步DIFF了。</li>
9 P& e$ \& V) m! `9 p6 {( A0 l2 N6 p! Q
! u2 U" t! A# h. w

+ N0 o: O. ^2 U2 {0 \' s; v
0 y: s: m& V  f) y3 m' w+ d( |' u</ul>% a& M9 D9 A# `) R

# J% Y' K$ n9 S9 Q* L2 a( R( n: n* P7 O9 f: m0 C

' J3 p1 F" @" j- ?) J# z8 y4 w# R
/ {1 c3 K) t) _; t! i  L</li>
& p) r1 S. v7 V2 t5 ?- z9 T<li><span style="color: rgba(51, 204, 204, 1)">全量同步</span> (SNAP同步):) D% P8 Y! P, U. Q
<ul>! |7 e% g3 ^. A$ B3 q2 v' @+ b
<li>; @0 f8 H  O" z
<p data-tool="mdnice编辑器">适用于两个场景:</p>
) g1 N/ o4 g' u* ?' d8 }# O<ol class="list-paddingleft-2" data-tool="mdnice编辑器">
* b2 W" q! \: S  Y( z( J  t6 J$ t) w<li>PeerLastZxid小于minCommittedLog</li>% {; i' F5 U/ N! m& r
<li>Leader服务器上没有提议缓存队列,并且PeerLastZxid不等于Leader的最大ZXID</li>
; ~8 H; q- @0 G+ e8 D, R9 l$ _5 M: E5 t: l
! ~, P4 ~, n! \$ d* r5 }2 d6 V" Y, q2 e: [. ^8 }
- m% v+ `. M) L9 q- ?. b

0 F3 E6 U9 k! l+ t</ol></li>
" y2 Y' @  Y; e8 |/ j<li>这两种场景下,Leader将会发送SNAP命令,把全量的数据都发送给Learner进行同步。</li>% m! v/ G6 P" v, F; E+ i* k

, ]1 ]7 X0 @. X& {, B/ X
2 L$ ]( ?$ n) T) W) H9 K/ V$ h4 [; ]' u

/ S' a# m  W* {( H& `2 T</ul>
0 k+ u4 D9 I5 U( M- n% Y
8 K7 @2 s5 ~* g& w: K$ X, J5 L9 C5 p1 `7 ]" o
/ n3 c: u9 w, O" o; s

  g! E+ E, B% Y( _</li>8 K. @+ N5 r( n$ c0 N4 N( P
! O! O2 |; H' D6 y

8 \2 g$ z  r6 _1 `
9 Y% c; M/ Q) O! X& F( [; J
$ p' {: p. ]3 [% z. C</ul>- |" X9 j, s: m9 ~% M" p
<hr>
. `% Z  X& u5 R) r- |6 F2 R. }<p data-tool="mdnice编辑器">有可能会出现数据不一致的问题吗?</p>4 y& u2 y/ e' A+ z) }
<p data-tool="mdnice编辑器">还是会存在的,我们可以分成3个场景来描述这个问题。</p>* ~- g7 }: @" A9 r3 K4 N! z6 M4 z
<ul>
6 Y" g- |* o$ A" h<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">查询不一致</span><strong><strong>:</strong></strong>
4 _/ e; D: Z0 c/ k" [( p<ul>" d) A+ i" u. K2 ~# n0 K" r
<li data-tool="mdnice编辑器">因为Zookeeper是过半成功即代表成功,假设我们有5个节点,如果123节点写入成功,如果这时候请求访问到4或者5节点,那么有可能读取不到数据,因为可能数据还没有同步到4、5节点中,也可以认为这算是数据不一致的问题。</li>
1 G2 X8 m% ]3 `3 \, M5 u+ T<li data-tool="mdnice编辑器">解决方案可以在读取前使用sync命令。</li>- r% w  E- I; S8 D  e* Y6 k
8 h; W+ D4 _3 @7 H: X/ ]3 X% [

- U; V) ~4 l7 N! R- q) v8 m/ U</ul>
. K! z% ]* X% D7 L8 n5 t  g' u% P& B& q( q4 D, }
" z; y9 i# s% G# m- Z2 m
</li>: ]* z: ]8 ]. B5 g2 w
<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">leader未发送proposal宕机</span><strong>:</strong>) G: Q- s4 H* z+ T8 I8 w9 T) J9 b
<ul>% _" H2 S( _8 W. U
<li data-tool="mdnice编辑器">+ h) c1 _8 ^& b& S/ ?* ]' d
<p data-tool="mdnice编辑器">这也就是数据同步说过的问题。leader刚生成一个proposal,还没有来得及发送出去,此时leader宕机,重新选举之后作为follower,但是新的leader没有这个proposal。</p>0 |! w! x# Q$ D. ~# X, h9 V, a
8 v+ h' ~/ U3 g5 j( L* ], Q; Q$ S

- Z) [4 x/ k4 M9 n6 x- m/ y</li>
8 G+ f5 _8 {9 {# u3 z1 `" }* K<li data-tool="mdnice编辑器">
6 L8 S/ ^4 q9 Y* \, M( l/ T<p data-tool="mdnice编辑器">这种场景下的日志将会被丢弃。</p>& u1 _* t: {2 r- P9 ?, [
! Y" h' K* g' x, w/ \6 [6 O# ^
: n9 J! M1 N) n* B, n1 P9 |6 M$ |
</li>
( e" v' q( \$ }' p/ F) M, Q6 u$ r$ I; R, Y
! l2 X7 g- x/ R1 ^
</ul>8 i2 r6 F9 E6 n9 ^
: t1 Q* m8 l( f7 ?7 T

1 K( ^7 l5 c( J9 {1 y2 n</li>
' ?1 t1 J4 q) ]/ O7 Q/ _# B<li data-tool="mdnice编辑器"><span style="color: rgba(51, 204, 204, 1)">leader发送proposal成功,发送commit前宕机</span><strong>:</strong>; b/ b6 f2 [: P  s9 E$ K
<ul>
$ J) M: g& Q" j; \<li data-tool="mdnice编辑器">如果发送proposal成功了,但是在将要发送commit命令前宕机了,如果重新进行选举,还是会选择zxid最大的节点作为leader,因此,这个日志并不会被丢弃,会在选举出leader之后重新同步到其他节点当中。<strong><br></strong></li>
& T0 {7 H) _( a" f5 P) w7 O+ i3 O4 k: u) _' t6 H
+ [; n) P6 Y/ Y
</ul>; @" f2 m. u( d, f9 Z

5 D: g  M7 t1 E* ?7 \7 E6 l3 J; r- c0 Q7 D
</li>
( e2 J  c5 b; v% c$ R! x
! u0 A+ ]. A5 Z# M* I6 y7 g! p
. H9 _% P% ?, W4 ^</ul>
6 Z& V. J$ I$ U7 J5 X* n) K<blockquote><span style="color: rgba(0, 0, 0, 1)"><strong>四.ZK其他小问题</strong></span></blockquote>
- V9 |3 Z+ }# m: z7 K: v<p>zookeeper 是如何保证事务的顺序一致性的?</p>
! R$ C: _' F; R* \<ul>3 h) i6 V- r& S( G
<li>使用<span style="color: rgba(51, 204, 204, 1)">zxid</span>来保证顺序性。</li>: V9 K& }! i# m& H' q" ~! j+ C
5 Q6 r3 M1 z0 P. J* \
% b2 S. \3 n$ K5 c4 e) I/ m/ t8 c6 r
</ul>& E" |  D! Y! }1 V+ B% c' S
<hr>
( H0 Y+ u9 ~; a<p>集群最少要几台机器,集群规则是怎样的?集群中有 3 台服务器,其中一个节点宕机,这个时候 Zookeeper 还可以使用吗?</p>
/ a" ]* j( I- s  D' k<ul>
0 T& X; x- ]3 a6 V: ~<li>集群规则为 <span style="color: rgba(51, 204, 204, 1)">2N+1</span>&nbsp;(奇数)台,N&gt;0,即 3 台。可以继续使用,单数服务器只要没超过一半的服务器宕机就可以继续使用。</li>
/ O3 z/ ^: v1 d* u8 P
" ~# U1 ^: ?3 P! R6 V* c. K& e- i( c: d  v2 b
</ul>
. Z1 t# g& k3 `1 u9 Y<hr>
# k0 W# P  D) X4 t- d<p>说几个 zookeeper 常用的命令:</p>
. P5 @5 A* [7 }( B% E<ul>
1 K) k0 H* I9 \1 Z<li>ls path:查看当前 znode 的子节点</li>: g! t- b8 Z6 r4 F1 S
<li>get path:获取节点的值</li>
2 r; v3 E1 C: d1 b  y( v( D<li>set:设置节点的值</li>
* g1 [2 d! e& j# I! }( u<li> create,delete:创建/删除节点</li>
, Z) d) O& C0 C) V( b% M5 ^
" h) }8 B. ~& i6 H$ w& m+ j  p! ?- n( m, f$ H
</ul>
$ A' w* ]2 j, k) P0 _! M$ U" ?<hr>1 K$ L3 ^$ O9 B# R8 ]
<p>会话Session:</p>
" A6 b8 N8 e7 Y<ul>
4 N% r0 J: O7 O9 n$ Z<li>会话自然就是指Zookeeper客户端和服务端之间的通信,他们使用TCP长连接的方式保持通信,通常,肯定会有<span style="color: rgba(51, 204, 204, 1)">心跳检测</span>的机制,同时他可以接受来自服务器的Watch事件通知。</li>
- f# a7 J( W+ a2 J8 x2 A4 E1 o& {/ g7 w: J
# U1 h1 `: I5 V. k: G7 N8 ?
</ul>; U2 K$ `2 F: Q/ X) s
<p>&nbsp;</p>( r/ `" n2 Q$ Q7 l
<p>寄语:<span style="color: rgba(51, 204, 204, 1)">平静的湖面酝酿不出精悍的水手,安逸的环境创造不出时代的伟人</span></p>
( s5 g0 b; o/ t- u2 \* k' |+ _
回复

使用道具 举报

懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|飞雪团队

GMT+8, 2026-10-27 23:24 , Processed in 0.068712 second(s), 21 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表