|
9.2 RootServer实现机制. D& D* E/ d% F# L2 w
RootServer是OceanBase集群对外的窗口,客户端通过RootServer获取集群中其他
- ]/ Q% d" q# i6 k* }模块的信息。RootServer实现的功能包括:$ }$ C& b0 S6 \
●管理集群中的所有ChunkServer,处理ChunkServer上下线;
' p' [1 p$ R3 _) I* ^, n) o●管理集群中的UpdateServer,实现UpdateServer选主;3 Q8 z+ f# O4 d
●管理集群中子表数据分布,发起子表复制、迁移以及合并等操作;
U5 G! @8 |2 _. z9 T1 a●与ChunkServer保持心跳,接受ChunkServer汇报,处理子表分裂;9 S/ `/ ?( O% y
●接受UpdateServer汇报的大版本冻结消息,通知ChunkServer执行定期合并;% d' T8 i6 d" S
●实现主备RootServer,数据强同步,支持主RootServer宕机自动切换。! f9 R& A) `( j
9.2.1 数据结构
& P& j' ?- i+ ]: p' f( z/ ~3 Z4 `% J6 \RootServer的中心数据结构为一张存储了子表数据分布的有序表格,称为
F) H- V2 h( HRootTable。每个子表存储的信息包括:子表主键范围、子表各个副本所在4 n8 l# f1 b ^3 w- G, d
ChunkServer的编号、子表各个副本的数据行数、占用的磁盘空间、CRC校验值以及
* F C1 d* X( x( \基线数据版本。 w' q$ H, k& L1 }4 I" Q
RootTable是一个读多写少的数据结构,除了ChunkServer汇报、RootServer发起子
5 V% M, W1 N% q p Y B表复制、迁移以及合并等操作需要修改RootTable外,其他操作都只需要从RootTable# Q b/ Y6 `! u! S
中读取某个子表所在的ChunkServer。因此,OceanBase设计时考虑以写时复制的方式- U2 Q7 H% w" p0 v/ U8 x
实现该结构,另外,考虑到RootTable修改特别少,实现时没有采用支持写时复制的: O7 k4 U, I( r% h b3 E* r& y* F
B+树或者跳跃表(Skip List),而是采用相对更加简单的有序数组,以减少工作量。& `6 T7 p. x; }6 Y; s
往RootTable增加子表信息的操作步骤如下:0 V/ {; _$ @8 } J7 d# e9 l; n" ^) w. L- D
1)拷贝当前服务的RootTable为新的RootTable;
- f3 C& P: q; i& [; d2)将子表信息追加到新的RootTable,并对新的RootTable重新排序;. C! K# O1 T! j/ K* x
3)原子地修改指针使得当前服务的RootTable指向新的RootTable。, A3 {4 q3 x3 q) Z0 N* y- \( T# Z
ChunkServer一次汇报一批子表(默认一批包含1024个),如果每个子表修改都- D u2 ~5 ~$ E8 V6 J2 r
需要拷贝整个RootTable并重新排序,性能上显然无法接受。RootServer实现时做了一7 ], M ]/ O/ c- z: W% R
些优化:拷贝当前服务的RootTable为新的RootTable后,将ChunkServer汇报的一批子) `1 X' |* V# }9 |
表一次性追加到新的RootTable中并重新排序,最后再原子地切换当前服务的5 j! g8 `5 q& d( Z
RootTable为新的RootTable。采用批处理优化后,RootTable的性能基本满足需求,
1 f1 e- k/ g4 G3 X! V8 zOceanBase单个集群支持的子表个数最大达到几百万个。当然,这种实现方式并不优# c; b y, W k2 Y
雅,我们后续将改造RootTable的实现方式。
6 {1 ?' h2 B4 r/ l0 v3 t$ ?ChunkServer汇报的子表信息可能和RootTable中记录的不同,比如发生了子表分+ u1 u* `* o; p0 h% Y& N
裂。此时,RootServer需要根据汇报的tablet信息更新RootTable。
$ k$ ^/ L" b% O! l4 S如图9-2所示,假设原来的RootTable包含四个子表:r1(min,10]、r2(10,) z4 f/ e5 ~9 n( ]% u8 k9 D
100]、r3(100,1000]、r4(1000,max]、ChunkServer汇报的子表列表为:t1(10,8 {9 c' v* C( y( e" l `; P' {7 P
50]、t2(50,100]、t3(100,1000],表示r2发生了tablet分裂,那么,RootServer会
3 z' Z4 h# Y2 I5 k: m4 H( L$ }将RootTable修改为:r1(min,10]、r2(10,50]、r3(50,100]、r4(100,1000]、* P" Z/ K) t3 g- D8 h6 T8 f6 V# K
r5(1000,max]。
5 ]6 {1 g* v; Q) g- Y& P+ o图 9-2 RootTable修改) }" I' I3 M0 [. k* y, S+ M
RootServer中还有一个管理所有ChunkServer信息的数组,称为ChunkServer-
8 g0 {2 a# u8 |/ @3 Z* CManager。数组中的每个元素代表一台ChunkServer,存储的信息包括:机器状态(已
" F& j7 |9 f7 a下线、正在服务、正在汇报、汇报完成,等等)、启动后注册时间、上次心跳时2 h6 i( f- [ g8 f: Y2 B
间、磁盘相关信息、负载均衡相关信息。OceanBase刚上线时依据每台ChunkServer磁. a! U2 Z% j% A; B$ R" T3 A
盘占用信息执行负载均衡,目的是为了尽可能确保每台ChunkServer占用差不多的磁
) p9 u' g, ^* ~8 _% v9 _9 r: h1 c盘空间。上线运行一段时间后发现这种方式效果并不好,目前的方式为按照每个表
+ L) P+ b" A+ x4 M6 z格的子表个数执行负载均衡,目的是尽可能保证对于每个表格、每台ChunkServer上
1 ?1 u* i' O/ H- B% Q E# R+ a8 x的子表个数大致相同。
: L, l' ~& @! Z9.2.2 子表复制与负载均衡
5 m; G p. ]7 i( U: jRootServer中有两种操作都可能触发子表迁移:子表复制(rereplication)以及负
; ?" u# T9 F3 P! O. D载均衡(rebalance)。当某些ChunkServer下线超过一段时间后,为了防止数据丢
4 Q$ b5 d. {2 ?失,需要拷贝副本数小于阀值的子表,另外,系统也需要定期执行负载均衡,将子9 f. X2 z! ?- x
表从负载较高的机器迁移到负载较低的机器。
4 H U0 A- h; o( c# K5 M每台ChunkServer记录了子表迁移相关信息,包括:ChunkServer上子表的个数以
9 ^- D! l$ }! {* y; T1 B6 d' K及所有子表的大小总和,正在迁入的子表个数、正在迁出的子表个数以及子表迁移
$ x4 N: `1 S8 Z2 {: r% m5 v任务列表。RootServer包含一个专门的线程定期执行子表复制与负载均衡任务,步骤" M* d+ H& x* b; u3 b, _" V
如下:
5 K4 _# J" E2 h- Z! H! `) s k/ f1)子表复制:扫描RootTable中的子表,如果某个子表的副本数小于阀值,选取
7 l( ~* D$ N* k* k某台包含子表副本的ChunkServer为迁移源,另外一台符合要求的ChunkServer为迁移9 K( ^7 _5 r) Z, _
目的地,生成子表迁移任务。迁移目的地需要符合一些条件,比如,不包含待迁移
( w2 M+ \" }: w1 `! F子表,服务的子表个数小于平均个数减去可容忍个数(默认值为10),正在进行的. ]/ ]8 E# e( N0 Y& P& F0 w4 L
迁移任务不超过阀值等。
* ]6 n) N/ c! d5 ?2)负载均衡:扫描RootTable中的子表,如果某台ChunkServer包含的某个表格的
) w L* a7 G5 y- I& ^# A$ m A子表个数超过平均个数以及可容忍个数(默认值为10)之和,以这台ChunkServer为
/ ~; W% x3 @3 i! r3 m迁移源,并选择一台符合要求的ChunkServer,生成子表迁移任务。 G* k9 T! f3 d( G
子表复制以及负载均衡生成的子表迁移任务并不会立即执行,而是会加入到迁3 d6 Z( e. d2 H3 d
移源的迁移任务列表中,RootServer还有一个后台线程会扫描所有的ChunkServer,接6 s$ M# z7 \$ u# c6 [3 y
着执行每台ChunkServer的迁移任务列表中保存的迁移任务。子表迁移时限制了每台 z! {+ `+ c2 T3 K. E. q9 y! n% R
ChunkServer同时进行的最大迁入和迁出任务数,从而防止一台新的ChunkServer刚上, u( E! Z7 V; I) w6 W; p- X
线时,迁入大量子表而负载过高。
4 y/ s. u' E; Z0 ]) }4 G) C y例9-1 某OceanBase集群包含4台ChunkServer:ChunkServer1(包含子表A1、
" V4 n7 V* |4 g5 _A2、A3),ChunkServer2(包含子表A3、A4),ChunkServer3(包含子表A2),
4 U H, ?9 z4 s! V2 X3 s J8 mChunkServer4(包含子表A4)。
! p/ J' r! z( g假设子表副本数配置为2,最多能够容忍的不均衡子表的个数为0。RootServer后1 R1 R' q1 x$ s+ R" i" [
台线程首先执行子表复制,发现子表A1只有一个副本,于是,将ChunkServer1作为迁! M! V1 D) {$ h. z
移源,选择某台ChunkServer(假设为ChunkServer3)作为迁移目的,生成迁移任务<
; a5 p# | e8 F( j" p- ?ChunkServer1,ChunkServer3,A1>。接着,执行负载均衡,发现ChunkServer1包含3
0 E7 R- o9 y7 R# n3 Z' m0 e; d个子表,超过平均值(平均值为2),而ChunkServer4包含的子表个数小于平均值,
( `1 _9 [% Q* `于是,将ChunkServer1作为迁移源,ChunkServer4作为迁移目的,选择某个子表(假
3 y; W2 x2 [" s* D1 s' ~* c; x$ f设为A2),生成迁移任务<ChunkServer1,ChunkServer4,A2>。如果迁移成功,A2' r. c1 r% _; B) R& H& l
将包含3个副本,可以通知ChunkServer1删除上面的A2副本。最后,tablet分布情况5 Z. v- Z8 W0 S; t8 B
为:ChunkServer1(包含tablet A1、A3),ChunkServer2(包含tablet A3、A4),
' q& b* }* O fChunkServer3(包含tablet A1、A2),ChunkServer4(包含tablet A2、A4),每个" l* P8 Z$ U! d/ o; w9 V
tablet包含2个副本,且平均分布在4台ChunkServer上。! w n8 `7 r8 p. W
9.2.3 子表分裂与合并" c: Q& {* o8 }& L) A
子表分裂由ChunkServer在定期合并过程中执行,由于每个子表包含多个副本,
4 C+ y' G5 X" u/ @9 O0 Q; ^: I且分布在多台ChunkServer上,如何确保多个副本之间的分裂点保持一致成为问题的3 o( n" X! h$ y9 F2 x3 [
关键。OceanBase采用了一种比较直接的做法:每台ChunkServer使用相同的分裂规
J, u& o1 a; j则。由于每个子表的不同副本之间的基线数据完全一致,且定期合并过程中冻结的
) M: I% p+ _+ H! g5 |' Y' U增量数据也完全相同,只要分裂规则一致,分裂后的子表主键范围也保证相同。: d! ]. e, [( C* ~, u6 W! d
OceanBase曾经有一个线上版本的分裂规则如下:只要定期合并过程中产生的数
' }3 ~7 N" ?. w8 ^" M据量超过256MB,就生成一个新的子表。假设定期合并产生的数据量为257MB,那
( N: w4 V* t: M0 m7 X么最后将分裂为两个子表,其中,前一个子表(记为r1)的数据量为256MB,后一! j& p* D4 a" i6 u Q: @
个子表(记为r2)的数据量为1MB。接着,r1接受新的修改,数据量很快又超过. ~. }$ T/ Q8 ~' G: U4 s
256MB,于是,又分裂为两个子表。系统运行一段时间后,充斥着大量数据量很少8 s4 o! C! l! P& X0 ?* P
的子表。0 D( [' T8 W0 ^, r; V5 N. U: c
为了解决分裂产生小子表的问题,需要确保分裂以后的每个子表数据量大致相
# E7 s3 a/ ?! s, s/ w; ?同。OceanBase对每个子表记录了两个元数据:数据行数row_count以及子表大小2 |/ o* H! h. i& {/ B! M1 k% _1 m
(occupy_size)。根据这两个值,可以计算出每行数据的平均大小,即:
( x. p5 C, d8 ^: n: a: O: g7 c4 Goccupy_size/row_count。+ ?! D- z, V* \
根据数据行平均大小,可以计算出分裂后的子表行数,从而得到分裂点。( y% m( `7 P- x+ R9 c
子表合并相对更加麻烦,步骤如下:3 W9 N" n/ k, [* `7 |) M
1)合并准备:RootServer选择若干个主键范围连续的小子表;2 o" U8 g6 A1 X5 `2 I3 \& @& h# G
2)子表迁移:将待合并的若干个小子表迁移到相同的ChunkServer机器;- a" O3 V+ ?% C8 c l3 v0 C$ x! M
3)子表合并:往ChunkServer机器发送子表合并命令,生成合并后的子表范围。
4 m- U; g6 k: ?0 U0 F! e& u7 i例9-2 某OceanBase集群中有3台ChunkServer:ChunkServer1(包含子表A1、
l% T e0 y! ?2 e. HA3),ChunkServer2(包含子表A2、A3),ChunkServer3(包含子表A1、A2),其
, M" X! i0 N. ?7 i中,A1和A2分别为10MB,A3为256MB。RootServer扫描RootTable后发现A1和A2满足
8 E& V" t9 r: ^3 J3 q. W. [子表合并条件,首先发起子表迁移,假设将A1迁移到ChunkServer2,使得A1和A2在3 ]& f) u( n; S3 X+ \% F. J
相同的ChunkServer上,接着分别向ChunkServer2和ChunkServer3发起子表合并命令。
8 u: ]. _+ @3 ]5 i _1 v1 U子表合并完成以后,子表分布情况为:ChunkServer1(包含子表A3),
, ]( H- C! ~5 r0 {( N& R# wChunkServer2(包含子表A4(A1,A2),A3),ChunkServer3(包含子表A4(A1,- ]4 o: s. C: ^/ o) X' n
A2)),其中,A4是子表A1和A2合并后的结果。' K2 J( m; @4 g o4 _
每个子表包含多个副本,只要某一个副本合并成功,OceanBase就认为子表合并8 k1 R7 O* P) E8 o* L5 c D" k$ g
成功,其他合并失败的子表将通过垃圾回收机制删除掉。# T U. p5 b3 B2 H% w1 q
9.2.4 UpdateServer选主: a$ _1 K; k3 W0 R% o
为了确保一致性,RootServer需要确保每个集群中只有一台UpdateServer提供写8 L5 o7 J j* o
服务,这个UpdateServer称为主UpdateServer。) G4 X# h* G4 ?$ p% ~& p
RootServer通过租约(Lease)机制实现UpdateServer选主。主UpdateServer必须持
( l& _3 C! m/ J' p- _有RootServer的租约才能提供写服务,租约的有效期一般为3~5秒。正常情况下,+ R( M/ S; K$ N$ ^8 A K
RootServer会定期给主UpdateServer发送命令,延长租约的有效期。如果主
0 G8 Y, x5 Q6 q4 X4 ZUpdateServer出现异常,RootServer等待主UpdateServer的租约过期后才能选择其他的3 A9 x3 y% `6 T) p: l; [
UpdateServer为主UpdateServer继续提供写服务。! A$ W4 x9 F z8 T: h0 Q _- b4 c
RootServer可能需要频繁升级,升级过程中UpdateServer的租约将很快过期,系+ _/ I3 f) k2 l
统也会因此停服务。为了解决这个问题,RootServer设计了优雅退出的机制,即2 V/ f" D2 p" P v& f C0 n/ J
RootServer退出之前给UpdateServer发送一个有效期超长的租约(比如半小时),承( L; j4 R2 `3 G! m7 t" K: }* L. A7 _
诺这段时间不进行主UpdateServer选举,用于RootServer升级。代码如下:
3 I' [- F" `4 y; z5 G) Z! |enum ObUpsStatus( |+ y4 \ p Q
{
- e# z8 H0 v2 S& sUPS_STAT_OFFLINE=0,//UpdateServer已下线
1 g9 Y: e [; P5 lUPS_STAT_NOTSYNC=1,//UpdateServer为备机且与主UpdateServer不同步5 M \2 g* u$ ~5 q3 b1 L$ o3 j
UPS_STAT_SYNC=2,//UpdateServer为备机且与主UpdateServer同步
- J, r8 y5 s& I" G mUPS_STAT_MASTER=3,//UpdateServer为主机 G# M4 r! t5 s1 R) X# U, T0 d* V4 Y
};2 k: c# ~- @$ j" V* O% \
//RootServer中记录UpdateServer信息的结构
: S0 }$ o8 ?, b) J: q2 ^: F& Fclass ObUps
# [: b q; K: p' q$ V{
" q+ T ], z) CObServer addr_;//UpdateServer地址
) Y# P* a; \: O: C+ K! _int32_t inner_port_;//UpdateServer内部端口7 T2 B2 s9 t) ~# I" Q- v& m# Z8 p
int64_t log_seq_num_;//UpdateServer的日志号, s: K2 G M4 s! f: s/ z5 k" ]
int64_t lease_;//UpdateServer的租约
& y# b' P6 M) lObUpsStatus stat_;//UpdateServer状态# I. J6 ]2 @; M" K- i4 q" T z
};+ \* s) c8 s$ \* t R: |- g7 ~$ d
class ObUpsManager/ i O5 z0 Y& l, h# \! d5 U
{
8 g+ {0 c/ E3 S0 m2 U9 |4 }public:) r6 D, u/ R8 `$ ~
//UpdateServer向RootServer注册1 Z3 e9 {+ [: G2 Q( R Y
int register_ups(const ObServer&addr,int32_t inner_port,int64_t3 {0 {$ @/ ~# ^: y" m( {
log_seq_num,int64_t lease,const char*server_version);0 c' Z% _" K! C' A5 q/ X" S/ s
//检查所有UpdateServer的租约,RootServer内部有专门的线程会定时调用该函数
' R) d/ W( k, k# k8 ?int check_lease();9 b7 d t/ {) T- \8 P8 x: }: f
//RootServer给UpdateServer发送租约
" L/ Y9 E9 _) Sint grant_lease();
, f( k3 j* D5 t9 V# T2 E; _//RootServer给UpdateServer发送超长租约
- D! U& V& P1 Mint grant_eternal_lease();
0 H- i& g0 E) T. ]* bprivate:
8 d R$ P" ]7 @9 o0 e4 N: wObUps ups_array_[MAX_UPS_COUNT];6 x' N9 x( u: F( {5 O
int32_t ups_master_idx_;3 g! @3 G" B% v# _" X# Y
};
5 h! I* ^4 t t, mRootServer模块中有一个ObUpsManager类,它包含一个数组ups_array_,其中的
1 D. v$ u* m$ B Q* t' c8 b每个元素表示一个UpdateServer,ups_master_idx_表示主UpdateServer在数组里的下标。
; t( \- k4 w$ x+ t5 D4 e$ I( M3 jObUps结构记录了UpdateServer的信息,包括UpdateServer的地址(addr_)以及内部; _+ Q6 }6 h% b/ p8 x; y# [) e& C
端口(inner_port_),UpdateServer的状态(stat_,分为6 [+ f9 q$ z/ C5 ~* ^( q6 {
UPS_STAT_OFFLINE/UPS_STAT_NOTSYNC/UPS_STAT_SYNC/UPS_STAT_MASTER
( Z8 n: g# b& m r; C2 m这四种),UpdateServer的日志号(log_seq_num_)以及租约(lease_)。7 c% J0 @' h) c) `4 e- y
UpdateServer首先通过register_ups向RootServer注册,将它的信息告知* {& g/ O# L7 b; \$ b
RootServer。一段时间之后,RootServer会从所有注册的UpdateServer中选取一台日志
7 {$ K* h- u' r) h+ ]0 b: {& c号最大的作为主UpdateServer。ObUpsManager类中还有一个check_lease函数,由
* J2 Q2 ]7 u ^ xRootServer内部线程定时调用,如果发现UpdateServer的租约快要过期,则会通过$ G5 |* y# a9 D. m" R
grant_lease给UpdateServer延长租约。如果发现主UpdateServer的租约已经失效,则会8 Q* }5 b7 a- L- U6 l6 n/ g2 _
从所有Update-Server中选择一个日志号最大的UpdateServer作为新的主UpdateServer。
( Y- a- p8 J2 [. |9 |; R/ a( q6 N另外,Root-Server还可以通过grant_eternal_lease给UpdateServer发送超长租约。2 b: O8 d0 v/ j+ L6 G
9.2.5 RootServer主备
% ? u+ S" L! n7 Z' k每个集群一般部署一主一备两台RootServer,主备之间数据强同步,即所有的操
; ]. K( n5 P$ V4 J8 B作都需要首先同步到备机,接着修改主机,最后才能返回操作成功。
w+ W- o J/ ]RootServer主备之间需要同步的数据包括:RootTable中记录的子表分布信息、
# z6 W% {7 O9 Y N4 M5 h; pChunkServerManager中记录的ChunkServer机器变化信息以及UpdateServer机器信息。
2 { h6 \' F: \子表复制、负载均衡、合并、分裂以及ChunkServer/UpdateServer上下线等操作都会
3 [% ]: p( S6 c引起RootServer内部数据变化,这些变化都将以操作日志的形式同步到备
! n; _8 `! \! Y: t0 P1 iRootServer。备RootServer实时回放这些操作日志,从而与主RootServer保持同步。' n4 @! t! E* p
OceanBase中的其他模块,比如ChunkServer/UpdateServer,以及客户端通过. ~" q3 h: z( c/ M0 R2 x2 q
VIP(Virtual IP)访问RootServer,正常情况下,VIP总是指向主RootServer。当主: P5 a: X1 ^; ]4 K ? Y1 H
RootServer出现故障时,部署在主备RootServer上的Linux HA(heartbeat,心跳),软
; s9 x1 m5 P) V+ _& T件能够检测到,并将VIP漂移到备RootServer。Linux HA软件的核心包含两个部分:
7 Q+ ]% O" |6 h9 i9 v心跳检测部分和资源接管部分,心跳检测部分通过网络链接或者串口线进行,主备
- c W% G) W) U& e" _4 p8 i) ?8 nRootServer上的心跳软件相互发送报文来告诉对方自己当前的状态。如果在指定的时) p- {) y) ?$ G5 ?- d- b' q
间内未收到对方发送的报文,那么就认为对方失败,这时需启动资源接管模块来接9 \2 x% ~) B2 @7 a" {" h3 X
管运行在对方主机上的资源,这里的资源就是VIP。备RootServer后台线程能够检测0 A; _5 L: o( S% \- ^/ m
到VIP漂移到自身,于是自动切换为主机提供服务。
6 }& A7 s7 s H* S6 A& {& _$ g% |3 U. h9 y+ @/ {6 N
9 l# A! S( P, e2 i! O6 v
|
|