|
9.2 RootServer实现机制
& }! e- }5 `9 ARootServer是OceanBase集群对外的窗口,客户端通过RootServer获取集群中其他
7 f" S8 {5 _0 e模块的信息。RootServer实现的功能包括:
: K& _4 R t' u7 A7 S●管理集群中的所有ChunkServer,处理ChunkServer上下线;3 g1 ^( v" }# I
●管理集群中的UpdateServer,实现UpdateServer选主;
& D/ W" M- B3 ^! I●管理集群中子表数据分布,发起子表复制、迁移以及合并等操作;9 H. x6 }7 O. l* ?. Y( L
●与ChunkServer保持心跳,接受ChunkServer汇报,处理子表分裂;3 Y+ z# A. G0 x J( S1 n
●接受UpdateServer汇报的大版本冻结消息,通知ChunkServer执行定期合并;
0 {: h/ t+ a% u2 T. z: X●实现主备RootServer,数据强同步,支持主RootServer宕机自动切换。
" u4 f, i0 v+ K4 }) ]# w* n! }9.2.1 数据结构
' J$ |' K) a1 m/ QRootServer的中心数据结构为一张存储了子表数据分布的有序表格,称为# h3 e4 }8 x' U5 d* l# B; z
RootTable。每个子表存储的信息包括:子表主键范围、子表各个副本所在/ l/ W4 K' Y6 I h6 U7 W
ChunkServer的编号、子表各个副本的数据行数、占用的磁盘空间、CRC校验值以及
6 u9 o5 T: m/ p/ D5 l1 C基线数据版本。
8 e2 `8 I; h" o) z0 c% O3 hRootTable是一个读多写少的数据结构,除了ChunkServer汇报、RootServer发起子" S9 X2 ~7 s7 |. u- N
表复制、迁移以及合并等操作需要修改RootTable外,其他操作都只需要从RootTable
" O! {, N1 \1 B8 A# b中读取某个子表所在的ChunkServer。因此,OceanBase设计时考虑以写时复制的方式( K, O1 A5 P6 f9 C' B- y5 ?
实现该结构,另外,考虑到RootTable修改特别少,实现时没有采用支持写时复制的
& L. C+ ]- H2 e6 nB+树或者跳跃表(Skip List),而是采用相对更加简单的有序数组,以减少工作量。) x6 d; b* ~& o9 {' Q+ L+ ]
往RootTable增加子表信息的操作步骤如下:
G( s* W0 n% N. H) A% s$ F1)拷贝当前服务的RootTable为新的RootTable;
& _7 V' f5 `" w& W2 h* B. H2)将子表信息追加到新的RootTable,并对新的RootTable重新排序;" s; @0 ?3 T3 n/ C7 u+ r
3)原子地修改指针使得当前服务的RootTable指向新的RootTable。# N+ _; R h C3 s% Y' e
ChunkServer一次汇报一批子表(默认一批包含1024个),如果每个子表修改都- L' W9 N) }+ C9 ^9 s1 n* ?) {
需要拷贝整个RootTable并重新排序,性能上显然无法接受。RootServer实现时做了一
$ O* }" Q/ u/ H2 f些优化:拷贝当前服务的RootTable为新的RootTable后,将ChunkServer汇报的一批子
: }- B4 B1 }/ D$ p9 C表一次性追加到新的RootTable中并重新排序,最后再原子地切换当前服务的
# w/ K E4 L" P9 ?# F6 YRootTable为新的RootTable。采用批处理优化后,RootTable的性能基本满足需求,8 w0 G& h5 A3 s4 R
OceanBase单个集群支持的子表个数最大达到几百万个。当然,这种实现方式并不优5 h5 o% s4 U9 {
雅,我们后续将改造RootTable的实现方式。
5 K! m6 [2 _" O1 ]" g8 ?! oChunkServer汇报的子表信息可能和RootTable中记录的不同,比如发生了子表分% Z) q; ^ B1 S! u+ B! s- l
裂。此时,RootServer需要根据汇报的tablet信息更新RootTable。% M: L/ Q4 M2 D3 \( X5 A
如图9-2所示,假设原来的RootTable包含四个子表:r1(min,10]、r2(10,0 m9 z4 o# q$ C/ g" X+ M9 O9 T
100]、r3(100,1000]、r4(1000,max]、ChunkServer汇报的子表列表为:t1(10," p3 v! ~* f, z( y1 o3 I. ` f' J
50]、t2(50,100]、t3(100,1000],表示r2发生了tablet分裂,那么,RootServer会* {# e: y6 t( ~$ T% \1 R) t
将RootTable修改为:r1(min,10]、r2(10,50]、r3(50,100]、r4(100,1000]、
+ {4 j) l0 }, G5 q) ^$ R) wr5(1000,max]。4 ]. d. L3 {* D- `6 y( I
图 9-2 RootTable修改) f) {- J0 N: x0 b% }# C' ~6 q; _
RootServer中还有一个管理所有ChunkServer信息的数组,称为ChunkServer-
! k9 v0 r, o" J) x! i" x6 S8 n6 N: SManager。数组中的每个元素代表一台ChunkServer,存储的信息包括:机器状态(已" L \7 m+ u$ y9 Q2 k
下线、正在服务、正在汇报、汇报完成,等等)、启动后注册时间、上次心跳时
( Q" A! l; S' e7 Y间、磁盘相关信息、负载均衡相关信息。OceanBase刚上线时依据每台ChunkServer磁+ P. _( C7 P( k/ t7 B! C" s8 s
盘占用信息执行负载均衡,目的是为了尽可能确保每台ChunkServer占用差不多的磁
2 N) P( e7 @5 C0 t9 [, q' `( w3 o盘空间。上线运行一段时间后发现这种方式效果并不好,目前的方式为按照每个表
7 A* f. _0 y8 G% v# N0 q6 T% e, H格的子表个数执行负载均衡,目的是尽可能保证对于每个表格、每台ChunkServer上) o, O; d. P8 R% ~2 G
的子表个数大致相同。- e' e" s5 n! x3 X9 @
9.2.2 子表复制与负载均衡6 j7 Y' b9 ?% `, e3 e
RootServer中有两种操作都可能触发子表迁移:子表复制(rereplication)以及负
: ~; L5 |6 U+ q载均衡(rebalance)。当某些ChunkServer下线超过一段时间后,为了防止数据丢
1 m% r! Y; Y" M: ?失,需要拷贝副本数小于阀值的子表,另外,系统也需要定期执行负载均衡,将子2 F$ c5 Z1 u3 N6 e, ]
表从负载较高的机器迁移到负载较低的机器。8 I7 _$ C3 ?! p8 r( T' l
每台ChunkServer记录了子表迁移相关信息,包括:ChunkServer上子表的个数以
+ m4 c: X' s7 r2 q; E5 Q及所有子表的大小总和,正在迁入的子表个数、正在迁出的子表个数以及子表迁移
/ I- B! h4 s* H2 {' G$ a0 ]* A* K任务列表。RootServer包含一个专门的线程定期执行子表复制与负载均衡任务,步骤
. Q& |% v( ]# r- O如下:
* a# `! x4 n. ^, K' G4 @ |, ?7 p1)子表复制:扫描RootTable中的子表,如果某个子表的副本数小于阀值,选取
+ F0 N+ p$ H' i2 O某台包含子表副本的ChunkServer为迁移源,另外一台符合要求的ChunkServer为迁移
( t' H u9 ?' k2 v$ c4 H目的地,生成子表迁移任务。迁移目的地需要符合一些条件,比如,不包含待迁移
" ?$ }+ V: O9 j子表,服务的子表个数小于平均个数减去可容忍个数(默认值为10),正在进行的
: r( w1 i9 b' W2 b迁移任务不超过阀值等。
9 T5 b- {0 H/ \3 v# t2)负载均衡:扫描RootTable中的子表,如果某台ChunkServer包含的某个表格的
8 w/ j7 L( t; k- c9 q4 ^' a! W子表个数超过平均个数以及可容忍个数(默认值为10)之和,以这台ChunkServer为
- p0 H* x( }. `5 M迁移源,并选择一台符合要求的ChunkServer,生成子表迁移任务。; c) w+ {( q! v. G6 m& R
子表复制以及负载均衡生成的子表迁移任务并不会立即执行,而是会加入到迁9 S1 `7 ?; E) d F. }, W
移源的迁移任务列表中,RootServer还有一个后台线程会扫描所有的ChunkServer,接3 ^. l& [: B* Q$ w! G& g$ ^4 K
着执行每台ChunkServer的迁移任务列表中保存的迁移任务。子表迁移时限制了每台
. P; p$ s+ \2 F- c2 p, tChunkServer同时进行的最大迁入和迁出任务数,从而防止一台新的ChunkServer刚上
' @0 C3 U& _" Z线时,迁入大量子表而负载过高。' d( H3 d [! B! A% ~6 g+ {
例9-1 某OceanBase集群包含4台ChunkServer:ChunkServer1(包含子表A1、
9 \% t6 | l% p2 `. g$ Z4 gA2、A3),ChunkServer2(包含子表A3、A4),ChunkServer3(包含子表A2),
) @+ Q& `- I7 y' u* rChunkServer4(包含子表A4)。
# H9 u& \" h: e1 L6 C" W# Y假设子表副本数配置为2,最多能够容忍的不均衡子表的个数为0。RootServer后$ q9 K# C& j6 |% T: D7 G" \
台线程首先执行子表复制,发现子表A1只有一个副本,于是,将ChunkServer1作为迁
7 [# W" \: T- D8 f5 J/ X( ~ c, I移源,选择某台ChunkServer(假设为ChunkServer3)作为迁移目的,生成迁移任务<
5 A2 r8 X% Y! y7 xChunkServer1,ChunkServer3,A1>。接着,执行负载均衡,发现ChunkServer1包含3
' m) N+ Z' o) m% u个子表,超过平均值(平均值为2),而ChunkServer4包含的子表个数小于平均值,
' |: e3 s$ Q6 _+ Z# W7 S( q, [于是,将ChunkServer1作为迁移源,ChunkServer4作为迁移目的,选择某个子表(假8 A. }0 u* ]7 j; ^: w
设为A2),生成迁移任务<ChunkServer1,ChunkServer4,A2>。如果迁移成功,A2
. B; C/ @7 W2 f, T$ ]# P将包含3个副本,可以通知ChunkServer1删除上面的A2副本。最后,tablet分布情况% q& l+ t8 k) V" t/ P
为:ChunkServer1(包含tablet A1、A3),ChunkServer2(包含tablet A3、A4), U$ ~2 p7 ^1 a8 ~$ ~9 q
ChunkServer3(包含tablet A1、A2),ChunkServer4(包含tablet A2、A4),每个
) l3 M7 E8 y# A$ |0 utablet包含2个副本,且平均分布在4台ChunkServer上。& p- z8 J7 b2 J, V9 R2 V- n `
9.2.3 子表分裂与合并7 T$ U/ w8 n$ g: [" h/ @
子表分裂由ChunkServer在定期合并过程中执行,由于每个子表包含多个副本,3 g* i: b$ F0 I6 U: e" z8 U
且分布在多台ChunkServer上,如何确保多个副本之间的分裂点保持一致成为问题的7 m( X; d7 k( A0 i- k' i3 Y
关键。OceanBase采用了一种比较直接的做法:每台ChunkServer使用相同的分裂规
7 ]+ w1 O! E9 ` N( s) d0 {" q+ D则。由于每个子表的不同副本之间的基线数据完全一致,且定期合并过程中冻结的1 ?- w+ p. z3 k/ W8 k
增量数据也完全相同,只要分裂规则一致,分裂后的子表主键范围也保证相同。
) e6 X+ P* N7 ?$ S3 \/ tOceanBase曾经有一个线上版本的分裂规则如下:只要定期合并过程中产生的数: i; g: `( J! q6 r
据量超过256MB,就生成一个新的子表。假设定期合并产生的数据量为257MB,那
9 S4 k# v! F5 \2 n么最后将分裂为两个子表,其中,前一个子表(记为r1)的数据量为256MB,后一& l2 t( `+ ^& }$ J
个子表(记为r2)的数据量为1MB。接着,r1接受新的修改,数据量很快又超过
; Q1 d5 h' d! k( W0 C256MB,于是,又分裂为两个子表。系统运行一段时间后,充斥着大量数据量很少2 R4 I" ]6 _. `' O: g7 D( E I
的子表。
; }! P( [$ R P( |, a" n# i为了解决分裂产生小子表的问题,需要确保分裂以后的每个子表数据量大致相
$ y; n3 s9 \ P9 L, f. H同。OceanBase对每个子表记录了两个元数据:数据行数row_count以及子表大小
3 p: v# v0 O b& \' n' q& n(occupy_size)。根据这两个值,可以计算出每行数据的平均大小,即:5 ^4 {; t s! J' b6 q0 r) P/ A0 [
occupy_size/row_count。' H- H2 r# B' V! U% `
根据数据行平均大小,可以计算出分裂后的子表行数,从而得到分裂点。
; e; F; X9 I0 L3 x- C- C子表合并相对更加麻烦,步骤如下:
' w( X- o" \, \/ i: G+ A1)合并准备:RootServer选择若干个主键范围连续的小子表;8 v; v( f/ [' S; j* m
2)子表迁移:将待合并的若干个小子表迁移到相同的ChunkServer机器;
; m9 M1 [% f5 k/ t5 |3)子表合并:往ChunkServer机器发送子表合并命令,生成合并后的子表范围。
* F, P6 i. x6 K& s6 P例9-2 某OceanBase集群中有3台ChunkServer:ChunkServer1(包含子表A1、7 T0 D/ ^4 k0 V% V9 N0 `+ E$ z
A3),ChunkServer2(包含子表A2、A3),ChunkServer3(包含子表A1、A2),其
( }0 `- I9 B6 C" V0 w0 I9 G中,A1和A2分别为10MB,A3为256MB。RootServer扫描RootTable后发现A1和A2满足
$ K( y9 l; `6 \' h) m0 g子表合并条件,首先发起子表迁移,假设将A1迁移到ChunkServer2,使得A1和A2在9 p# i. c; r+ P* E
相同的ChunkServer上,接着分别向ChunkServer2和ChunkServer3发起子表合并命令。
: X0 Q6 `2 p$ ?6 n d( B1 E7 r* _9 M+ B% T子表合并完成以后,子表分布情况为:ChunkServer1(包含子表A3),
* V! J8 T \! ?! hChunkServer2(包含子表A4(A1,A2),A3),ChunkServer3(包含子表A4(A1," I3 G# _& V( o) \7 U- d! U
A2)),其中,A4是子表A1和A2合并后的结果。& W, o. M% P- p& ^8 V; }1 n
每个子表包含多个副本,只要某一个副本合并成功,OceanBase就认为子表合并( L- O8 |6 D3 v7 k0 Y8 d6 P
成功,其他合并失败的子表将通过垃圾回收机制删除掉。( Q) v- G/ Z5 E; z
9.2.4 UpdateServer选主
0 U* z5 G3 `$ n为了确保一致性,RootServer需要确保每个集群中只有一台UpdateServer提供写% o. w6 q# u. }: a1 i) i
服务,这个UpdateServer称为主UpdateServer。
5 } Z2 ?' I |4 m( R/ ~RootServer通过租约(Lease)机制实现UpdateServer选主。主UpdateServer必须持/ e7 C; {! u+ A# r v
有RootServer的租约才能提供写服务,租约的有效期一般为3~5秒。正常情况下,
2 P. g- o" W# X$ j" _% O: hRootServer会定期给主UpdateServer发送命令,延长租约的有效期。如果主
/ ]! F( q0 X3 g% lUpdateServer出现异常,RootServer等待主UpdateServer的租约过期后才能选择其他的
1 o5 a9 E/ `+ g& H% E( h2 TUpdateServer为主UpdateServer继续提供写服务。+ y& i b2 [* ]' y6 j+ q
RootServer可能需要频繁升级,升级过程中UpdateServer的租约将很快过期,系/ H5 n1 x5 T- P
统也会因此停服务。为了解决这个问题,RootServer设计了优雅退出的机制,即" g' j1 W) l% T9 K. q
RootServer退出之前给UpdateServer发送一个有效期超长的租约(比如半小时),承, k, k1 r* P- w" N) H( G
诺这段时间不进行主UpdateServer选举,用于RootServer升级。代码如下:, t2 y% `; {: _2 z9 v! V
enum ObUpsStatus0 Q3 E" X x! U1 j
{6 j9 a) b: F: p$ |; R2 |" Z1 w
UPS_STAT_OFFLINE=0,//UpdateServer已下线
* I6 ^+ k I9 W0 R9 {UPS_STAT_NOTSYNC=1,//UpdateServer为备机且与主UpdateServer不同步
$ S( D9 T+ a/ w" t( bUPS_STAT_SYNC=2,//UpdateServer为备机且与主UpdateServer同步4 _# _3 V, E; m1 `" ~0 o; `
UPS_STAT_MASTER=3,//UpdateServer为主机
" E1 F, F. w. {3 `! u% l6 ~};) A8 J9 ~; Z% f: |% u. M
//RootServer中记录UpdateServer信息的结构
" O @4 Q, Q/ [0 X9 Eclass ObUps
/ P# \$ w9 e/ q{
. M0 O6 Z/ t- S3 mObServer addr_;//UpdateServer地址
: J+ }7 h' ?0 s' K" n$ n0 Tint32_t inner_port_;//UpdateServer内部端口
9 O0 v5 d$ B& d9 N$ v# W4 X) ^int64_t log_seq_num_;//UpdateServer的日志号' s) s' J2 y- _2 M
int64_t lease_;//UpdateServer的租约' Z$ j" f/ b9 |5 Y% W
ObUpsStatus stat_;//UpdateServer状态- z- B0 V* p6 r5 W. T1 x
};- c7 W8 @* `/ Q
class ObUpsManager& v! y- I, p: |5 c* @
{7 E. S. F: Q9 g8 f( {: `, A
public: z; l6 c2 R% ~' P$ L) ^
//UpdateServer向RootServer注册+ m; U0 a# M* V! o
int register_ups(const ObServer&addr,int32_t inner_port,int64_t! @" J) N h: ?4 b- s
log_seq_num,int64_t lease,const char*server_version);0 ]) V9 s, Y+ ?* `3 f
//检查所有UpdateServer的租约,RootServer内部有专门的线程会定时调用该函数7 g' H' x% x* P! K. x& K0 N
int check_lease();
& e9 h; z7 x7 w' Q# q6 g//RootServer给UpdateServer发送租约( S9 W* x5 m6 _# H" f( |+ }
int grant_lease();7 F7 V$ _& P5 o3 ~& y1 w
//RootServer给UpdateServer发送超长租约/ P1 o. V4 i& N
int grant_eternal_lease();
|& z5 M( x; {private:
; {6 i) ]$ r* _# [% l, vObUps ups_array_[MAX_UPS_COUNT];7 H* s0 d& A3 S4 E' ^
int32_t ups_master_idx_;* p( a! S$ W6 [, E& F0 C
};
7 \4 i; O1 J' ?5 ^8 ~RootServer模块中有一个ObUpsManager类,它包含一个数组ups_array_,其中的' N U& G J9 k8 l+ z7 Z6 C2 s
每个元素表示一个UpdateServer,ups_master_idx_表示主UpdateServer在数组里的下标。
, k/ e7 m: a; H8 D8 YObUps结构记录了UpdateServer的信息,包括UpdateServer的地址(addr_)以及内部" r6 ]2 V) y. w. C7 x# |5 E
端口(inner_port_),UpdateServer的状态(stat_,分为
) Y+ u* J' z8 g* |UPS_STAT_OFFLINE/UPS_STAT_NOTSYNC/UPS_STAT_SYNC/UPS_STAT_MASTER/ |2 C4 E" d" G# ^& E4 u
这四种),UpdateServer的日志号(log_seq_num_)以及租约(lease_)。1 p/ g' J, |6 ]1 p, n H9 L# C
UpdateServer首先通过register_ups向RootServer注册,将它的信息告知
/ n5 j' D# p H* BRootServer。一段时间之后,RootServer会从所有注册的UpdateServer中选取一台日志
) ]7 R* K( X. B2 \; A/ @号最大的作为主UpdateServer。ObUpsManager类中还有一个check_lease函数,由
/ g; z; @/ g9 ARootServer内部线程定时调用,如果发现UpdateServer的租约快要过期,则会通过" J4 f- ?$ Q& c7 L: X
grant_lease给UpdateServer延长租约。如果发现主UpdateServer的租约已经失效,则会
2 q C- a2 w- I8 h( K0 s从所有Update-Server中选择一个日志号最大的UpdateServer作为新的主UpdateServer。$ I( k8 u5 h e/ w, w
另外,Root-Server还可以通过grant_eternal_lease给UpdateServer发送超长租约。/ L8 A' g5 S- E- i: m5 `1 f
9.2.5 RootServer主备3 n- S: D) V( ?( {. ~
每个集群一般部署一主一备两台RootServer,主备之间数据强同步,即所有的操
/ u. U% x/ G+ Y: U作都需要首先同步到备机,接着修改主机,最后才能返回操作成功。$ ~6 L- f3 A6 N( D9 E
RootServer主备之间需要同步的数据包括:RootTable中记录的子表分布信息、
, l0 C0 V, b: t; e o* u: x3 ?; h- GChunkServerManager中记录的ChunkServer机器变化信息以及UpdateServer机器信息。- V# [2 `4 i$ T. I k) g
子表复制、负载均衡、合并、分裂以及ChunkServer/UpdateServer上下线等操作都会
) b$ ^) m6 G, j/ r" w' V引起RootServer内部数据变化,这些变化都将以操作日志的形式同步到备# [0 Q3 v( s' H1 y8 \
RootServer。备RootServer实时回放这些操作日志,从而与主RootServer保持同步。
% s$ i: L! J b/ POceanBase中的其他模块,比如ChunkServer/UpdateServer,以及客户端通过$ m& I4 x% a7 \: v- u
VIP(Virtual IP)访问RootServer,正常情况下,VIP总是指向主RootServer。当主0 Y( M j0 i3 b0 ], W! Z! Y+ i+ N
RootServer出现故障时,部署在主备RootServer上的Linux HA(heartbeat,心跳),软* c9 o( Q& V1 [ z2 M1 l( O* G
件能够检测到,并将VIP漂移到备RootServer。Linux HA软件的核心包含两个部分:
+ A. p! ^% R) u! E7 l心跳检测部分和资源接管部分,心跳检测部分通过网络链接或者串口线进行,主备
0 e _: [( v- G* K) DRootServer上的心跳软件相互发送报文来告诉对方自己当前的状态。如果在指定的时
; f* H3 u: V; [) F间内未收到对方发送的报文,那么就认为对方失败,这时需启动资源接管模块来接+ ~! l- X3 Q# o1 q; T
管运行在对方主机上的资源,这里的资源就是VIP。备RootServer后台线程能够检测
& `! b5 ?6 K; g7 v' {到VIP漂移到自身,于是自动切换为主机提供服务。
- H3 T; W l6 y! P0 v( l5 z6 m% r% H" A# o6 R7 [4 c' @" W5 I& Q
- E" `$ W2 X. ]6 @* V
|
|