|
9.2 RootServer实现机制% y4 i: a. W6 D: P) s4 x2 F t
RootServer是OceanBase集群对外的窗口,客户端通过RootServer获取集群中其他3 _% T* k# Q. L# U
模块的信息。RootServer实现的功能包括:
3 Y: ~# J; I4 k●管理集群中的所有ChunkServer,处理ChunkServer上下线;# ~8 ?( ^0 p5 n$ a* H! j2 H
●管理集群中的UpdateServer,实现UpdateServer选主;
) R5 s( g) \ `8 S" q●管理集群中子表数据分布,发起子表复制、迁移以及合并等操作;
& ?7 [6 R: d. z& a1 a$ v●与ChunkServer保持心跳,接受ChunkServer汇报,处理子表分裂;
, k1 H4 {# j# o4 v4 [4 v/ b●接受UpdateServer汇报的大版本冻结消息,通知ChunkServer执行定期合并;
3 p% K, S% x; P. J/ v6 R●实现主备RootServer,数据强同步,支持主RootServer宕机自动切换。
# A. e. H5 d+ _) a7 F" m' y" j9.2.1 数据结构
% K0 ?0 F5 W) @# L4 URootServer的中心数据结构为一张存储了子表数据分布的有序表格,称为/ d9 s7 L; H6 X- A' j4 j
RootTable。每个子表存储的信息包括:子表主键范围、子表各个副本所在# D/ S5 s1 a; e* n
ChunkServer的编号、子表各个副本的数据行数、占用的磁盘空间、CRC校验值以及
* s* o; y0 M+ L& h9 M0 e8 H N" u. R基线数据版本。
1 W" g) i% G6 p$ g/ a+ P1 T& WRootTable是一个读多写少的数据结构,除了ChunkServer汇报、RootServer发起子0 J0 q5 G, V" G' O& Z
表复制、迁移以及合并等操作需要修改RootTable外,其他操作都只需要从RootTable; x, ]* ~% b0 A% J: @1 l/ t( a
中读取某个子表所在的ChunkServer。因此,OceanBase设计时考虑以写时复制的方式+ C0 K! a1 T" w4 a; b; p H' E
实现该结构,另外,考虑到RootTable修改特别少,实现时没有采用支持写时复制的7 Y4 \8 r) y& F0 r. b! F
B+树或者跳跃表(Skip List),而是采用相对更加简单的有序数组,以减少工作量。+ j. _: }" h' D7 r( c
往RootTable增加子表信息的操作步骤如下:
' O4 O: g' d2 S) _% v" {1)拷贝当前服务的RootTable为新的RootTable;
) H7 \- F1 B& y. U, R$ N& X2)将子表信息追加到新的RootTable,并对新的RootTable重新排序;
2 Q% q, G1 o! l3)原子地修改指针使得当前服务的RootTable指向新的RootTable。- s8 w9 v0 R) {; S: {$ l( h
ChunkServer一次汇报一批子表(默认一批包含1024个),如果每个子表修改都5 d( ^, K' u1 f6 O( x6 T- G# B
需要拷贝整个RootTable并重新排序,性能上显然无法接受。RootServer实现时做了一
9 U Y. @7 `" d/ G些优化:拷贝当前服务的RootTable为新的RootTable后,将ChunkServer汇报的一批子. N) Y, k( [7 Z
表一次性追加到新的RootTable中并重新排序,最后再原子地切换当前服务的
% J* X: ^: I% F7 s. Q/ e! cRootTable为新的RootTable。采用批处理优化后,RootTable的性能基本满足需求,9 r: P5 n0 u; f
OceanBase单个集群支持的子表个数最大达到几百万个。当然,这种实现方式并不优7 L. d" ~1 d3 _1 U* r8 O/ j
雅,我们后续将改造RootTable的实现方式。8 H8 e- y/ G1 w
ChunkServer汇报的子表信息可能和RootTable中记录的不同,比如发生了子表分
& |9 j" C! T/ Z3 V8 n* r; z裂。此时,RootServer需要根据汇报的tablet信息更新RootTable。( A# ?- A+ K J* Y, }
如图9-2所示,假设原来的RootTable包含四个子表:r1(min,10]、r2(10,
- a6 W/ A: V8 d) }7 H7 x x0 y" d100]、r3(100,1000]、r4(1000,max]、ChunkServer汇报的子表列表为:t1(10,; o1 b& T% D3 b3 ^
50]、t2(50,100]、t3(100,1000],表示r2发生了tablet分裂,那么,RootServer会! B3 F2 Q* d# V5 v5 k; h- u. V
将RootTable修改为:r1(min,10]、r2(10,50]、r3(50,100]、r4(100,1000]、
3 W) i4 ]5 x0 e! gr5(1000,max]。
3 G: p: e2 D- p2 i c# A' {% a0 O图 9-2 RootTable修改
) f0 D# r; | A9 b8 b0 d# l. l. h8 R* WRootServer中还有一个管理所有ChunkServer信息的数组,称为ChunkServer-9 C$ k# A6 ?+ P& y8 M3 r X9 _$ p
Manager。数组中的每个元素代表一台ChunkServer,存储的信息包括:机器状态(已. \2 z& R2 S G+ N2 Z b: U2 |
下线、正在服务、正在汇报、汇报完成,等等)、启动后注册时间、上次心跳时9 k0 W: l9 _9 ^: B0 {6 f! \0 e. i
间、磁盘相关信息、负载均衡相关信息。OceanBase刚上线时依据每台ChunkServer磁
2 G) p5 Q8 E1 _! F) `盘占用信息执行负载均衡,目的是为了尽可能确保每台ChunkServer占用差不多的磁# t* t# O5 r* O4 [- O
盘空间。上线运行一段时间后发现这种方式效果并不好,目前的方式为按照每个表/ K1 C4 e7 l8 a" C2 _
格的子表个数执行负载均衡,目的是尽可能保证对于每个表格、每台ChunkServer上
1 ?- S% W2 }6 F$ v的子表个数大致相同。
2 V! N" f! l8 `9.2.2 子表复制与负载均衡
8 k1 ]# c. P8 k6 vRootServer中有两种操作都可能触发子表迁移:子表复制(rereplication)以及负
9 `$ A) ?. k2 o- v$ F3 ]载均衡(rebalance)。当某些ChunkServer下线超过一段时间后,为了防止数据丢
' p! t) S O" O, g6 H! J失,需要拷贝副本数小于阀值的子表,另外,系统也需要定期执行负载均衡,将子5 Y9 o0 b7 {. C9 B0 T# j1 e4 [
表从负载较高的机器迁移到负载较低的机器。% n( E; {5 b4 V a: I
每台ChunkServer记录了子表迁移相关信息,包括:ChunkServer上子表的个数以/ M* Z5 K( [* t$ |4 |* E
及所有子表的大小总和,正在迁入的子表个数、正在迁出的子表个数以及子表迁移8 I6 X) n3 `; j) ~5 ~
任务列表。RootServer包含一个专门的线程定期执行子表复制与负载均衡任务,步骤
& @1 c n" }8 |5 c. b2 k0 Q6 b如下:
# g4 [5 D. R+ \9 ]& }* R$ {$ r1)子表复制:扫描RootTable中的子表,如果某个子表的副本数小于阀值,选取
) O1 t% ?+ B( o I/ {. j某台包含子表副本的ChunkServer为迁移源,另外一台符合要求的ChunkServer为迁移
. }6 K- N2 ~* p4 C a目的地,生成子表迁移任务。迁移目的地需要符合一些条件,比如,不包含待迁移
& Q- d0 [$ X6 Z7 V/ q子表,服务的子表个数小于平均个数减去可容忍个数(默认值为10),正在进行的* q7 M; ~; j- D8 k
迁移任务不超过阀值等。$ f- Q* B" J- Y9 O# n1 h. @: t: R
2)负载均衡:扫描RootTable中的子表,如果某台ChunkServer包含的某个表格的
. [1 ~5 L3 \' i ?子表个数超过平均个数以及可容忍个数(默认值为10)之和,以这台ChunkServer为
( [" G- E: _9 R1 K; s( M {迁移源,并选择一台符合要求的ChunkServer,生成子表迁移任务。
% w3 l8 X6 L1 H1 k& q- @4 m子表复制以及负载均衡生成的子表迁移任务并不会立即执行,而是会加入到迁
1 g# L& o* Q& b2 Q: p4 P& g移源的迁移任务列表中,RootServer还有一个后台线程会扫描所有的ChunkServer,接4 {! e! Y1 Q! O
着执行每台ChunkServer的迁移任务列表中保存的迁移任务。子表迁移时限制了每台+ p. N) F9 j' k: Z0 _0 c" K6 a
ChunkServer同时进行的最大迁入和迁出任务数,从而防止一台新的ChunkServer刚上, n6 `9 X! D: d& m% E- Y- V* J
线时,迁入大量子表而负载过高。( C$ V; s5 b8 J( r% R
例9-1 某OceanBase集群包含4台ChunkServer:ChunkServer1(包含子表A1、
6 N, L. O' h/ dA2、A3),ChunkServer2(包含子表A3、A4),ChunkServer3(包含子表A2),: u2 L$ T2 I8 O @) k5 C1 ~, k) H
ChunkServer4(包含子表A4)。/ R! E& s* m1 b) \0 C1 E5 }9 e
假设子表副本数配置为2,最多能够容忍的不均衡子表的个数为0。RootServer后
' s0 e3 f( h& R( c c台线程首先执行子表复制,发现子表A1只有一个副本,于是,将ChunkServer1作为迁
# a7 N% C7 n1 e* p/ Z9 {移源,选择某台ChunkServer(假设为ChunkServer3)作为迁移目的,生成迁移任务<
8 N& A! r* R) F- w5 N* t' QChunkServer1,ChunkServer3,A1>。接着,执行负载均衡,发现ChunkServer1包含3' S& l1 X" A3 F& F
个子表,超过平均值(平均值为2),而ChunkServer4包含的子表个数小于平均值,
3 [" }( `+ b+ Z/ |# z" ^于是,将ChunkServer1作为迁移源,ChunkServer4作为迁移目的,选择某个子表(假4 k9 x+ B1 Z) G
设为A2),生成迁移任务<ChunkServer1,ChunkServer4,A2>。如果迁移成功,A2
0 K5 q, |) H ]$ b" }& W将包含3个副本,可以通知ChunkServer1删除上面的A2副本。最后,tablet分布情况
: [% _4 M$ ?, S7 g) h为:ChunkServer1(包含tablet A1、A3),ChunkServer2(包含tablet A3、A4),
, q& n b6 S1 n- O& N8 G; {. rChunkServer3(包含tablet A1、A2),ChunkServer4(包含tablet A2、A4),每个
: O1 R6 P2 O, {9 b1 {& itablet包含2个副本,且平均分布在4台ChunkServer上。) g# ~5 K1 f( R& |8 s/ E4 D# @- z+ s
9.2.3 子表分裂与合并5 \ t; z( w. ^5 Z3 W7 Z. P
子表分裂由ChunkServer在定期合并过程中执行,由于每个子表包含多个副本,2 [3 i6 @" N# a, T+ \' _. k
且分布在多台ChunkServer上,如何确保多个副本之间的分裂点保持一致成为问题的, K5 y6 F, J. V5 F. Q
关键。OceanBase采用了一种比较直接的做法:每台ChunkServer使用相同的分裂规8 B7 v4 N' y1 p9 j- f% ]
则。由于每个子表的不同副本之间的基线数据完全一致,且定期合并过程中冻结的! p. I7 P, t/ V& h8 W. Z E
增量数据也完全相同,只要分裂规则一致,分裂后的子表主键范围也保证相同。& m6 p- _& Y4 f4 `, |
OceanBase曾经有一个线上版本的分裂规则如下:只要定期合并过程中产生的数5 g! y, I1 u5 e' g/ g* B: q
据量超过256MB,就生成一个新的子表。假设定期合并产生的数据量为257MB,那
0 u, X0 Q v3 h& U么最后将分裂为两个子表,其中,前一个子表(记为r1)的数据量为256MB,后一
2 G. T7 U( p- ^个子表(记为r2)的数据量为1MB。接着,r1接受新的修改,数据量很快又超过
$ V9 M" D4 G w; a4 e256MB,于是,又分裂为两个子表。系统运行一段时间后,充斥着大量数据量很少: { ~4 H- u! H1 E i8 q0 ^
的子表。7 v# n) P0 \, \
为了解决分裂产生小子表的问题,需要确保分裂以后的每个子表数据量大致相9 r/ J$ i l/ U8 `
同。OceanBase对每个子表记录了两个元数据:数据行数row_count以及子表大小4 U# M* X' y% n/ D3 Z6 A
(occupy_size)。根据这两个值,可以计算出每行数据的平均大小,即:
- R% M! o7 L: ? u. l8 ~occupy_size/row_count。/ S8 A6 _. Q/ L9 Z% m$ q! O
根据数据行平均大小,可以计算出分裂后的子表行数,从而得到分裂点。2 G6 S4 f T+ |. y" ?8 V4 ^$ {/ o5 h
子表合并相对更加麻烦,步骤如下:
5 a3 T; u4 ~1 ]; P* H; [; D1)合并准备:RootServer选择若干个主键范围连续的小子表;
5 d# @2 P2 y# Y; Z1 q" ?2)子表迁移:将待合并的若干个小子表迁移到相同的ChunkServer机器;" c' {2 r: b- \" W/ ^; s1 T
3)子表合并:往ChunkServer机器发送子表合并命令,生成合并后的子表范围。+ J9 N/ \7 p% X. _6 a
例9-2 某OceanBase集群中有3台ChunkServer:ChunkServer1(包含子表A1、
/ O/ d: M& Y* M9 t( V7 Y; T: T9 mA3),ChunkServer2(包含子表A2、A3),ChunkServer3(包含子表A1、A2),其/ B! K0 G- v) i% y
中,A1和A2分别为10MB,A3为256MB。RootServer扫描RootTable后发现A1和A2满足' A1 C8 t: i2 d" }0 Z& w
子表合并条件,首先发起子表迁移,假设将A1迁移到ChunkServer2,使得A1和A2在
, }$ @+ l5 |0 Z. I D4 ^- b相同的ChunkServer上,接着分别向ChunkServer2和ChunkServer3发起子表合并命令。
6 y0 [/ H `% P$ a子表合并完成以后,子表分布情况为:ChunkServer1(包含子表A3),
1 w; o7 q& F+ `. e9 ` AChunkServer2(包含子表A4(A1,A2),A3),ChunkServer3(包含子表A4(A1,5 H6 z9 c/ r" W, j& ^: P; o6 p. r
A2)),其中,A4是子表A1和A2合并后的结果。
. J# m# c3 F* d3 S0 q! I7 }每个子表包含多个副本,只要某一个副本合并成功,OceanBase就认为子表合并
0 y7 ^% N. a: \) ^& _9 u6 o7 u2 C+ n成功,其他合并失败的子表将通过垃圾回收机制删除掉。5 D' I9 b7 \( q# e
9.2.4 UpdateServer选主
4 Z1 L6 c8 ?* i为了确保一致性,RootServer需要确保每个集群中只有一台UpdateServer提供写9 V, e$ V. ?3 f$ W
服务,这个UpdateServer称为主UpdateServer。
# H2 b8 n6 P/ l3 b5 tRootServer通过租约(Lease)机制实现UpdateServer选主。主UpdateServer必须持; n6 j$ {; F# r3 x3 ~. L
有RootServer的租约才能提供写服务,租约的有效期一般为3~5秒。正常情况下,! e7 K; P" N2 v1 e
RootServer会定期给主UpdateServer发送命令,延长租约的有效期。如果主
+ K: N% p8 N! x0 yUpdateServer出现异常,RootServer等待主UpdateServer的租约过期后才能选择其他的4 b, r1 r" [; ?& X! T" c
UpdateServer为主UpdateServer继续提供写服务。& K6 R- {- v0 [1 r. E) U
RootServer可能需要频繁升级,升级过程中UpdateServer的租约将很快过期,系
- ]* X) i# b8 }3 X/ L统也会因此停服务。为了解决这个问题,RootServer设计了优雅退出的机制,即; B! ]/ b* B# S5 W
RootServer退出之前给UpdateServer发送一个有效期超长的租约(比如半小时),承( j( m/ k0 C% g; T3 j: @" N9 D
诺这段时间不进行主UpdateServer选举,用于RootServer升级。代码如下:! n1 c) R% [. {0 v6 v* W
enum ObUpsStatus
+ [) T0 ?: q" ]0 e% W. K) D{/ ?* m5 U& K& T9 M/ o
UPS_STAT_OFFLINE=0,//UpdateServer已下线5 e, I2 X7 ]( ?* X
UPS_STAT_NOTSYNC=1,//UpdateServer为备机且与主UpdateServer不同步0 F: }8 q W; ]2 u+ s2 m$ _8 v
UPS_STAT_SYNC=2,//UpdateServer为备机且与主UpdateServer同步& f/ \* n% O0 }7 d! R
UPS_STAT_MASTER=3,//UpdateServer为主机
6 s, z6 u, x) ]# s6 @! z0 h8 w7 r};; K1 ~! @1 x* z$ S; ]/ C" X/ ^
//RootServer中记录UpdateServer信息的结构
0 H8 |3 H/ U% w% |" Q7 Pclass ObUps
3 K9 ?% u, c5 o6 M; C3 ]) ~" o{& r$ i, S* U0 N
ObServer addr_;//UpdateServer地址
# m% L' m' K: V6 kint32_t inner_port_;//UpdateServer内部端口( J: T h6 y5 Z! q) l; J3 T: m! K# y
int64_t log_seq_num_;//UpdateServer的日志号; S8 y) w- P; M. s4 A8 A1 b
int64_t lease_;//UpdateServer的租约4 ]9 J' ^3 }* ]7 Q4 U
ObUpsStatus stat_;//UpdateServer状态' o3 |2 {( ]5 i8 @/ s+ i/ U$ S$ r
};
+ t2 x3 q, }* A% {class ObUpsManager. L4 h# s3 _) N. \' F# \
{! C, |1 x% [9 G& n J' T/ h
public:
* d! ~2 G5 g9 j- O//UpdateServer向RootServer注册
9 o/ x, X6 _& u- Y, J% h% fint register_ups(const ObServer&addr,int32_t inner_port,int64_t5 {' o% c# j0 ]7 I5 y2 l! q$ {& ^8 z, Q
log_seq_num,int64_t lease,const char*server_version);
/ F2 b/ Y8 Q( Z+ H* }; F//检查所有UpdateServer的租约,RootServer内部有专门的线程会定时调用该函数3 |$ J4 p& o0 t4 _( C8 `
int check_lease();
5 H/ [' T* G$ t& v( A' o- r$ _//RootServer给UpdateServer发送租约! Q+ _" C2 M4 q! `
int grant_lease();: }3 n+ V: O$ v$ U9 Y
//RootServer给UpdateServer发送超长租约- G2 V0 z) c D$ k
int grant_eternal_lease();- ]% I& `0 R8 a) G3 R+ ]
private:& p8 f& `7 m5 Q3 s; Q
ObUps ups_array_[MAX_UPS_COUNT];- D$ b, |& X9 E+ O: }
int32_t ups_master_idx_;2 f7 k5 e* z s
};% m3 F. v8 U7 {" R) V. w
RootServer模块中有一个ObUpsManager类,它包含一个数组ups_array_,其中的
. M6 p3 u- @4 }: E! ?每个元素表示一个UpdateServer,ups_master_idx_表示主UpdateServer在数组里的下标。* d0 |" b6 |( Q8 ?
ObUps结构记录了UpdateServer的信息,包括UpdateServer的地址(addr_)以及内部% N+ M0 V4 Q) [8 |2 T7 `
端口(inner_port_),UpdateServer的状态(stat_,分为& |* G0 v. Q3 s+ t" d: h' p
UPS_STAT_OFFLINE/UPS_STAT_NOTSYNC/UPS_STAT_SYNC/UPS_STAT_MASTER
" ^6 k0 ~/ Z# w$ s2 ]2 G. e这四种),UpdateServer的日志号(log_seq_num_)以及租约(lease_)。
" [+ [# V$ k+ t( s# FUpdateServer首先通过register_ups向RootServer注册,将它的信息告知- r' |( D1 R2 |3 j# [8 |7 y
RootServer。一段时间之后,RootServer会从所有注册的UpdateServer中选取一台日志
3 b+ d8 ?4 }; r A. n9 ?号最大的作为主UpdateServer。ObUpsManager类中还有一个check_lease函数,由0 X7 Q% _! [; D& _
RootServer内部线程定时调用,如果发现UpdateServer的租约快要过期,则会通过/ }" [% [9 { w: H9 {) _
grant_lease给UpdateServer延长租约。如果发现主UpdateServer的租约已经失效,则会
4 E4 Z# @7 X( u- ] {8 ]* `" F从所有Update-Server中选择一个日志号最大的UpdateServer作为新的主UpdateServer。
9 K- I {- N" q/ o另外,Root-Server还可以通过grant_eternal_lease给UpdateServer发送超长租约。
* L7 K& c0 M' _% U% A2 w! N9.2.5 RootServer主备
/ e4 q8 s9 n) j3 {! Z7 {9 e每个集群一般部署一主一备两台RootServer,主备之间数据强同步,即所有的操& u. e5 b# A. ?( l! y; M9 n
作都需要首先同步到备机,接着修改主机,最后才能返回操作成功。$ `* h% R4 O0 d: B" E0 N7 @
RootServer主备之间需要同步的数据包括:RootTable中记录的子表分布信息、
* {, H: A8 [& Z; y! IChunkServerManager中记录的ChunkServer机器变化信息以及UpdateServer机器信息。
! a9 m& }2 d( ^0 t子表复制、负载均衡、合并、分裂以及ChunkServer/UpdateServer上下线等操作都会
( K+ I& ^" M- f引起RootServer内部数据变化,这些变化都将以操作日志的形式同步到备8 J! ?+ X! I/ v: t
RootServer。备RootServer实时回放这些操作日志,从而与主RootServer保持同步。
N' I4 e: }, S+ iOceanBase中的其他模块,比如ChunkServer/UpdateServer,以及客户端通过
) o; K( p6 q* i. HVIP(Virtual IP)访问RootServer,正常情况下,VIP总是指向主RootServer。当主
O6 I Z( X. B' f6 K( RRootServer出现故障时,部署在主备RootServer上的Linux HA(heartbeat,心跳),软0 m2 ]$ S. @ {$ a! ~
件能够检测到,并将VIP漂移到备RootServer。Linux HA软件的核心包含两个部分:& ?; N) W: G) R7 j( Q' B& o
心跳检测部分和资源接管部分,心跳检测部分通过网络链接或者串口线进行,主备
( m) i" Q5 ?; u, }* p4 z! E( pRootServer上的心跳软件相互发送报文来告诉对方自己当前的状态。如果在指定的时
4 T" O6 k- `2 V5 o8 C间内未收到对方发送的报文,那么就认为对方失败,这时需启动资源接管模块来接
- c4 R& B3 O/ e" s- E0 [) A2 u管运行在对方主机上的资源,这里的资源就是VIP。备RootServer后台线程能够检测& a; ?1 }* q# V8 e+ K
到VIP漂移到自身,于是自动切换为主机提供服务。
7 s1 L; ?- V+ S/ I7 B
+ ?, S$ u$ g1 N0 ^0 a* c$ P: _" |9 p" _/ ]
|
|