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