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