java自学网VIP

Java自学网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 2627|回复: 0

《大规模分布式存储系统》第9章 分布式存储引擎【9.2】

[复制链接]
  • TA的每日心情
    开心
    2021-5-25 00:00
  • 签到天数: 1917 天

    [LV.Master]出神入化

    2096

    主题

    3754

    帖子

    6万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    66788

    宣传达人突出贡献优秀版主荣誉管理论坛元老

    发表于 2017-3-6 14:37:42 | 显示全部楼层 |阅读模式
    9.2 RootServer实现机制: Z; T- g) h% t" R; j& C  x
    RootServer是OceanBase集群对外的窗口,客户端通过RootServer获取集群中其他
    9 u  y! n3 w: z* Q. Q/ |模块的信息。RootServer实现的功能包括:/ G' y: Q6 B( F( c+ q
    ●管理集群中的所有ChunkServer,处理ChunkServer上下线;/ O% P7 R9 c8 R
    ●管理集群中的UpdateServer,实现UpdateServer选主;
    ; _. g, [5 A7 u- v0 f* G* }6 d●管理集群中子表数据分布,发起子表复制、迁移以及合并等操作;
    % ]2 q9 t$ `% J8 I; }# g9 ]5 A  x●与ChunkServer保持心跳,接受ChunkServer汇报,处理子表分裂;4 J' [/ ?1 F/ j/ v/ N
    ●接受UpdateServer汇报的大版本冻结消息,通知ChunkServer执行定期合并;$ a( H( ]! N; L8 a8 ?$ A, x
    ●实现主备RootServer,数据强同步,支持主RootServer宕机自动切换。" p( r9 z1 J, U; @
    9.2.1 数据结构) i, Z: o% H  g4 e
    RootServer的中心数据结构为一张存储了子表数据分布的有序表格,称为
    ; S8 I  f# v: Z2 Z/ G( n( i! {RootTable。每个子表存储的信息包括:子表主键范围、子表各个副本所在& D6 y* N# I9 j1 Q" p
    ChunkServer的编号、子表各个副本的数据行数、占用的磁盘空间、CRC校验值以及" {1 N. x& \" ^# Q, B
    基线数据版本。: ^  O, f0 v7 ~" i- b. [
    RootTable是一个读多写少的数据结构,除了ChunkServer汇报、RootServer发起子7 c- v& \( x; D, I
    表复制、迁移以及合并等操作需要修改RootTable外,其他操作都只需要从RootTable
    ' D/ T3 b5 y# F# ?7 n; p2 W' Y" `中读取某个子表所在的ChunkServer。因此,OceanBase设计时考虑以写时复制的方式, q. c( K. K/ o
    实现该结构,另外,考虑到RootTable修改特别少,实现时没有采用支持写时复制的
    ' W. ~1 W  y8 @- K& JB+树或者跳跃表(Skip List),而是采用相对更加简单的有序数组,以减少工作量。- P7 |# k4 l- z
    往RootTable增加子表信息的操作步骤如下:' I5 q7 y! U4 R
    1)拷贝当前服务的RootTable为新的RootTable;
    " `2 t& s/ O3 D- D' F! G2)将子表信息追加到新的RootTable,并对新的RootTable重新排序;
    * D* v& ]7 I$ K6 s8 m" W3)原子地修改指针使得当前服务的RootTable指向新的RootTable。
    1 `8 s; i3 y4 E# Y1 _ChunkServer一次汇报一批子表(默认一批包含1024个),如果每个子表修改都
    2 |2 y$ O' M2 }7 }需要拷贝整个RootTable并重新排序,性能上显然无法接受。RootServer实现时做了一
    2 X% |, U0 _  h  v8 m  o  t2 E# A些优化:拷贝当前服务的RootTable为新的RootTable后,将ChunkServer汇报的一批子
    4 P# y1 o% `  t2 I表一次性追加到新的RootTable中并重新排序,最后再原子地切换当前服务的1 A4 U; o) u0 Z4 k/ W& Z* _
    RootTable为新的RootTable。采用批处理优化后,RootTable的性能基本满足需求,
    # y8 M3 m% L( KOceanBase单个集群支持的子表个数最大达到几百万个。当然,这种实现方式并不优
    ; e+ G9 e! E, T/ x雅,我们后续将改造RootTable的实现方式。; S& z! Q, J3 s& @: }' b
    ChunkServer汇报的子表信息可能和RootTable中记录的不同,比如发生了子表分$ r8 h) ^5 l& v+ D: X. p/ @
    裂。此时,RootServer需要根据汇报的tablet信息更新RootTable。4 |- o8 N; I0 w- E
    如图9-2所示,假设原来的RootTable包含四个子表:r1(min,10]、r2(10,* V$ t, g8 x( r: e8 ~4 }  j
    100]、r3(100,1000]、r4(1000,max]、ChunkServer汇报的子表列表为:t1(10,
    ) w$ }+ g) E  C" R  \50]、t2(50,100]、t3(100,1000],表示r2发生了tablet分裂,那么,RootServer会
    % p( L* ~/ T! i将RootTable修改为:r1(min,10]、r2(10,50]、r3(50,100]、r4(100,1000]、( ]# \  `0 e4 r+ x
    r5(1000,max]。
    2 R$ ^7 O/ c$ T/ K6 x图 9-2 RootTable修改
    / H. J4 H% ~, H6 }+ N$ q: e* c- {RootServer中还有一个管理所有ChunkServer信息的数组,称为ChunkServer-3 Q4 `1 E% u+ j; Q; J3 i
    Manager。数组中的每个元素代表一台ChunkServer,存储的信息包括:机器状态(已
    % C# w+ P* A: x0 p1 V下线、正在服务、正在汇报、汇报完成,等等)、启动后注册时间、上次心跳时
    8 h. H1 }+ x  e) n间、磁盘相关信息、负载均衡相关信息。OceanBase刚上线时依据每台ChunkServer磁
    , j; P: \0 K% _1 {- }1 X盘占用信息执行负载均衡,目的是为了尽可能确保每台ChunkServer占用差不多的磁
    # m$ o) \% p$ M% P) b' T. x- u' r盘空间。上线运行一段时间后发现这种方式效果并不好,目前的方式为按照每个表
    6 \8 p$ B9 Z8 `( S& C- r7 K格的子表个数执行负载均衡,目的是尽可能保证对于每个表格、每台ChunkServer上- b. t+ `" A1 `: U' e
    的子表个数大致相同。- q6 N. @) s  m$ X, _0 b
    9.2.2 子表复制与负载均衡
    1 U) l" A. p4 V# IRootServer中有两种操作都可能触发子表迁移:子表复制(rereplication)以及负' h: t3 c+ [4 [. k" Y- [
    载均衡(rebalance)。当某些ChunkServer下线超过一段时间后,为了防止数据丢) D6 n/ j( N6 x$ l( l+ W
    失,需要拷贝副本数小于阀值的子表,另外,系统也需要定期执行负载均衡,将子3 X' M. w: H) t
    表从负载较高的机器迁移到负载较低的机器。% h9 I! ]9 n  }: A! ?
    每台ChunkServer记录了子表迁移相关信息,包括:ChunkServer上子表的个数以) m: Y1 g; t1 T* Q
    及所有子表的大小总和,正在迁入的子表个数、正在迁出的子表个数以及子表迁移
    1 K& m2 T' z( g- H' C; L任务列表。RootServer包含一个专门的线程定期执行子表复制与负载均衡任务,步骤
    + b8 x/ D% x1 ^如下:( B4 k( B2 K+ \
    1)子表复制:扫描RootTable中的子表,如果某个子表的副本数小于阀值,选取+ V$ N7 \0 `" N, e2 U8 y
    某台包含子表副本的ChunkServer为迁移源,另外一台符合要求的ChunkServer为迁移  D0 K7 O0 ?7 i& ~
    目的地,生成子表迁移任务。迁移目的地需要符合一些条件,比如,不包含待迁移- j* r% x# c" @6 a! ~
    子表,服务的子表个数小于平均个数减去可容忍个数(默认值为10),正在进行的
    $ f- _5 u: F8 `8 }迁移任务不超过阀值等。
    . I+ X+ c6 ~' ^: @- y& u" n9 `% r2)负载均衡:扫描RootTable中的子表,如果某台ChunkServer包含的某个表格的' y; A8 P4 }5 E; [
    子表个数超过平均个数以及可容忍个数(默认值为10)之和,以这台ChunkServer为
    0 q1 ^% U, t$ N" S) D+ E, y迁移源,并选择一台符合要求的ChunkServer,生成子表迁移任务。
    ! e4 x, |, c) [: m) [. w. F子表复制以及负载均衡生成的子表迁移任务并不会立即执行,而是会加入到迁
    - f6 d8 E7 c* x) u移源的迁移任务列表中,RootServer还有一个后台线程会扫描所有的ChunkServer,接; F+ [4 E1 o. _; d% a  l% c4 M
    着执行每台ChunkServer的迁移任务列表中保存的迁移任务。子表迁移时限制了每台
    5 \* B7 D% R7 ?" \# d1 rChunkServer同时进行的最大迁入和迁出任务数,从而防止一台新的ChunkServer刚上, D* |0 \6 r2 C, L, Q, P5 D5 o3 N
    线时,迁入大量子表而负载过高。
    " Q6 {( ^1 Q( Q7 v" |7 n0 }1 I例9-1 某OceanBase集群包含4台ChunkServer:ChunkServer1(包含子表A1、/ H6 Z& g7 i# Y$ I2 W3 B4 W
    A2、A3),ChunkServer2(包含子表A3、A4),ChunkServer3(包含子表A2),
    0 g2 W6 M- a6 Y' ]- Y/ r' zChunkServer4(包含子表A4)。
    $ t* N9 F. O% m" }; z$ M# k" m假设子表副本数配置为2,最多能够容忍的不均衡子表的个数为0。RootServer后+ A! A  j5 F) M% p) Q
    台线程首先执行子表复制,发现子表A1只有一个副本,于是,将ChunkServer1作为迁0 r& d' }) u/ c# A
    移源,选择某台ChunkServer(假设为ChunkServer3)作为迁移目的,生成迁移任务<; T) |& s* |$ @2 s
    ChunkServer1,ChunkServer3,A1>。接着,执行负载均衡,发现ChunkServer1包含3. f7 [/ f8 a' V
    个子表,超过平均值(平均值为2),而ChunkServer4包含的子表个数小于平均值,5 v# h* O# Z6 _. f: V
    于是,将ChunkServer1作为迁移源,ChunkServer4作为迁移目的,选择某个子表(假
    ' A' V. y7 @: s9 w& T4 N* W设为A2),生成迁移任务<ChunkServer1,ChunkServer4,A2>。如果迁移成功,A2
    " i6 j8 M( q8 O- ?; e2 n将包含3个副本,可以通知ChunkServer1删除上面的A2副本。最后,tablet分布情况
    ! H8 I) s( x/ r  e* p: P为:ChunkServer1(包含tablet A1、A3),ChunkServer2(包含tablet A3、A4),
    + @* n; }% s4 ]$ ]/ z* lChunkServer3(包含tablet A1、A2),ChunkServer4(包含tablet A2、A4),每个
    5 i  a6 U7 ~# F$ E: }$ ktablet包含2个副本,且平均分布在4台ChunkServer上。7 Y& e2 Z& g& L6 z
    9.2.3 子表分裂与合并$ g1 J5 Q. C6 x& D8 {. b
    子表分裂由ChunkServer在定期合并过程中执行,由于每个子表包含多个副本,
    $ _' z+ m' z: @且分布在多台ChunkServer上,如何确保多个副本之间的分裂点保持一致成为问题的- s, c6 R7 p( \: G# w$ f3 }
    关键。OceanBase采用了一种比较直接的做法:每台ChunkServer使用相同的分裂规$ s. h% L3 l' I5 R& T
    则。由于每个子表的不同副本之间的基线数据完全一致,且定期合并过程中冻结的# l/ ]" ~* C! ~0 j. T* J
    增量数据也完全相同,只要分裂规则一致,分裂后的子表主键范围也保证相同。; g4 U' G, Q1 P8 u; Q( [3 W! Y$ H
    OceanBase曾经有一个线上版本的分裂规则如下:只要定期合并过程中产生的数5 d+ Y6 B1 t9 Z' B$ e4 I
    据量超过256MB,就生成一个新的子表。假设定期合并产生的数据量为257MB,那, v2 Y8 `" d: H" a6 @
    么最后将分裂为两个子表,其中,前一个子表(记为r1)的数据量为256MB,后一
    % F1 S4 k( T) e个子表(记为r2)的数据量为1MB。接着,r1接受新的修改,数据量很快又超过
    4 v6 t; S, }# g' Z& I6 ]256MB,于是,又分裂为两个子表。系统运行一段时间后,充斥着大量数据量很少. a1 B6 N- T- y1 y* W
    的子表。
    * A. L4 {2 B! [. y1 X2 h为了解决分裂产生小子表的问题,需要确保分裂以后的每个子表数据量大致相
    % G2 u5 [) c5 p; |' s: j. Y% z1 A同。OceanBase对每个子表记录了两个元数据:数据行数row_count以及子表大小
    + z. n. ^. u. H4 c(occupy_size)。根据这两个值,可以计算出每行数据的平均大小,即:; l) M: I8 `- t9 E
    occupy_size/row_count。
    2 N: G. Q. j; x根据数据行平均大小,可以计算出分裂后的子表行数,从而得到分裂点。$ u  D/ V4 F+ D3 K) b5 S/ I6 ~4 q
    子表合并相对更加麻烦,步骤如下:
    ( y! A' z4 D/ v4 B' }1)合并准备:RootServer选择若干个主键范围连续的小子表;
    4 c9 X+ b6 }& Y2)子表迁移:将待合并的若干个小子表迁移到相同的ChunkServer机器;% W1 A3 y- T0 g
    3)子表合并:往ChunkServer机器发送子表合并命令,生成合并后的子表范围。, `* E( N( z" y! D8 p3 Y
    例9-2 某OceanBase集群中有3台ChunkServer:ChunkServer1(包含子表A1、
    ( ^4 q9 J7 z/ }6 P! t5 PA3),ChunkServer2(包含子表A2、A3),ChunkServer3(包含子表A1、A2),其
    3 B  f& V/ Q" u8 R/ `; @中,A1和A2分别为10MB,A3为256MB。RootServer扫描RootTable后发现A1和A2满足3 s1 V# s8 T0 r3 v/ Y+ T0 H# {
    子表合并条件,首先发起子表迁移,假设将A1迁移到ChunkServer2,使得A1和A2在% E9 K! Z/ E/ x2 y
    相同的ChunkServer上,接着分别向ChunkServer2和ChunkServer3发起子表合并命令。! Y) n( \1 a1 Q4 r, h
    子表合并完成以后,子表分布情况为:ChunkServer1(包含子表A3),7 U( n2 G4 w  v( o: l1 ~! o% K( j# ~
    ChunkServer2(包含子表A4(A1,A2),A3),ChunkServer3(包含子表A4(A1,2 V, ?6 ]* k+ P" D, y
    A2)),其中,A4是子表A1和A2合并后的结果。# g2 |  R  v- C* P
    每个子表包含多个副本,只要某一个副本合并成功,OceanBase就认为子表合并/ \* d4 P$ A3 e2 u
    成功,其他合并失败的子表将通过垃圾回收机制删除掉。
    ( C; c7 [- M9 ~9.2.4 UpdateServer选主9 }! r) D9 r% T2 s" v' p8 x6 a
    为了确保一致性,RootServer需要确保每个集群中只有一台UpdateServer提供写
    ( ?; Q8 x8 b# x. Q- D! x/ p6 b5 H- D! N服务,这个UpdateServer称为主UpdateServer。. \' R/ _8 E' \
    RootServer通过租约(Lease)机制实现UpdateServer选主。主UpdateServer必须持
    : {/ q, \0 |$ I4 R6 T" g# `1 n4 p有RootServer的租约才能提供写服务,租约的有效期一般为3~5秒。正常情况下,
    ' m3 @4 E! _; [6 s6 S% I. wRootServer会定期给主UpdateServer发送命令,延长租约的有效期。如果主
    7 F4 a  a0 j8 ]( z- eUpdateServer出现异常,RootServer等待主UpdateServer的租约过期后才能选择其他的
    , Z& t0 U( S/ C4 ?+ lUpdateServer为主UpdateServer继续提供写服务。4 E/ R0 I- s  i2 x+ B3 B
    RootServer可能需要频繁升级,升级过程中UpdateServer的租约将很快过期,系
    ( S/ g) \( D/ \* k  t统也会因此停服务。为了解决这个问题,RootServer设计了优雅退出的机制,即
    * \8 n6 B( H# yRootServer退出之前给UpdateServer发送一个有效期超长的租约(比如半小时),承
    " n4 r+ v( R! w5 C. X诺这段时间不进行主UpdateServer选举,用于RootServer升级。代码如下:
    8 a/ ]/ D- {  r0 u& \enum ObUpsStatus
    6 B, [$ p- L2 B% D{
    # k  g, m% E6 N% w& M7 OUPS_STAT_OFFLINE=0,//UpdateServer已下线
    ; K8 h* R8 `7 s% a) YUPS_STAT_NOTSYNC=1,//UpdateServer为备机且与主UpdateServer不同步
    - c( p+ S# |) JUPS_STAT_SYNC=2,//UpdateServer为备机且与主UpdateServer同步; n3 K+ W( v2 q: @# `
    UPS_STAT_MASTER=3,//UpdateServer为主机
    ! T- ]1 H& N. H2 t+ L; @. O2 F};" S6 Q, J, h& J( T  o2 M
    //RootServer中记录UpdateServer信息的结构
    % y( m5 }6 }) Z5 Hclass ObUps' U2 A7 K1 o: L! G
    {
    8 y/ ?0 t6 l2 M. q: `ObServer addr_;//UpdateServer地址) _" b4 s$ L- e, `
    int32_t inner_port_;//UpdateServer内部端口' p; |5 F9 g  e
    int64_t log_seq_num_;//UpdateServer的日志号4 x! o, T5 j5 ~
    int64_t lease_;//UpdateServer的租约
    1 d8 m& s8 j6 N1 s' [ObUpsStatus stat_;//UpdateServer状态+ Y- ~" y' j) v7 M* u7 J
    };! }3 [) A" f8 b0 I( J( }
    class ObUpsManager4 x& m6 a- M; o2 L4 `# q
    {
    " P1 v! i! T5 q" W' @% r1 qpublic:
    8 b( I5 i' _1 H: p  `- }//UpdateServer向RootServer注册
    4 j; V' {  F/ O! Dint register_ups(const ObServer&addr,int32_t inner_port,int64_t
    # ?0 O' Z9 `, @- o7 Vlog_seq_num,int64_t lease,const char*server_version);
    # Z9 ]% \! D' R9 g$ |# c. O//检查所有UpdateServer的租约,RootServer内部有专门的线程会定时调用该函数
    # M5 w, [0 X; c( i/ i' `1 ]int check_lease();$ |  K/ y# R& h+ v; x! p5 t
    //RootServer给UpdateServer发送租约# B! S2 S+ h8 r  X2 y. x. G
    int grant_lease();
    ; c3 a7 z2 g; Y//RootServer给UpdateServer发送超长租约
    * W" `+ q* A! @  d+ l# mint grant_eternal_lease();6 J4 q( A& m4 Z
    private:, J6 p6 N4 k5 m! C
    ObUps ups_array_[MAX_UPS_COUNT];: Q; X5 X. I" d7 P
    int32_t ups_master_idx_;
    " r9 }9 R  g& B( b7 g5 ?; K};- y! y/ u+ d4 b. t" V  I  l
    RootServer模块中有一个ObUpsManager类,它包含一个数组ups_array_,其中的
    $ O/ j( @& q# A* a' M每个元素表示一个UpdateServer,ups_master_idx_表示主UpdateServer在数组里的下标。
    5 A# f8 _7 ?8 OObUps结构记录了UpdateServer的信息,包括UpdateServer的地址(addr_)以及内部3 M7 K4 j# k6 Y% M7 j  x6 N
    端口(inner_port_),UpdateServer的状态(stat_,分为
    / t5 G/ B. X6 I, |) R+ d. \UPS_STAT_OFFLINE/UPS_STAT_NOTSYNC/UPS_STAT_SYNC/UPS_STAT_MASTER
    0 a* f0 @; `& k- E这四种),UpdateServer的日志号(log_seq_num_)以及租约(lease_)。
    3 d- P8 s, [. H! ]UpdateServer首先通过register_ups向RootServer注册,将它的信息告知8 N! s: x: c4 e0 n4 Y4 X; i
    RootServer。一段时间之后,RootServer会从所有注册的UpdateServer中选取一台日志" X7 p& |& E/ ?! i8 U
    号最大的作为主UpdateServer。ObUpsManager类中还有一个check_lease函数,由
    4 o8 h. f) [  b+ ]% O, VRootServer内部线程定时调用,如果发现UpdateServer的租约快要过期,则会通过+ d9 G* q2 O6 {: {% l1 c, x
    grant_lease给UpdateServer延长租约。如果发现主UpdateServer的租约已经失效,则会
    * h! C0 J& n/ g) \' a从所有Update-Server中选择一个日志号最大的UpdateServer作为新的主UpdateServer。
    ( f# D) J$ f7 V/ X1 Q另外,Root-Server还可以通过grant_eternal_lease给UpdateServer发送超长租约。4 x* f; w+ E7 }- ]- x
    9.2.5 RootServer主备
    8 F% H' r6 n; T2 s/ w! \0 Q每个集群一般部署一主一备两台RootServer,主备之间数据强同步,即所有的操
    3 e9 t# Y2 c+ }9 c( p2 V4 ]作都需要首先同步到备机,接着修改主机,最后才能返回操作成功。
    / D8 k& N6 x2 f3 V; J4 ?" |% S7 vRootServer主备之间需要同步的数据包括:RootTable中记录的子表分布信息、6 ^2 Q4 F( j: V! h' S6 O
    ChunkServerManager中记录的ChunkServer机器变化信息以及UpdateServer机器信息。
    / i+ P6 |9 |) O子表复制、负载均衡、合并、分裂以及ChunkServer/UpdateServer上下线等操作都会
    * }0 O0 @1 S$ U; |. h4 ^引起RootServer内部数据变化,这些变化都将以操作日志的形式同步到备
    3 O" N! V& G8 T  ~RootServer。备RootServer实时回放这些操作日志,从而与主RootServer保持同步。
    3 u& \$ [. G$ X% MOceanBase中的其他模块,比如ChunkServer/UpdateServer,以及客户端通过8 A- k. a6 R6 L  ]7 g. _% v
    VIP(Virtual IP)访问RootServer,正常情况下,VIP总是指向主RootServer。当主! Q& ?. U1 r: Q0 P- |* q
    RootServer出现故障时,部署在主备RootServer上的Linux HA(heartbeat,心跳),软. h9 c1 d2 Q8 B7 `5 ~
    件能够检测到,并将VIP漂移到备RootServer。Linux HA软件的核心包含两个部分:; Y8 @2 d  _, T' U
    心跳检测部分和资源接管部分,心跳检测部分通过网络链接或者串口线进行,主备
    ; s4 K/ F5 m  `8 q* a% z7 {% QRootServer上的心跳软件相互发送报文来告诉对方自己当前的状态。如果在指定的时. o- W# }  Z) I! s8 g
    间内未收到对方发送的报文,那么就认为对方失败,这时需启动资源接管模块来接
    / h  v1 c& }; J* ~) L% i% o' K管运行在对方主机上的资源,这里的资源就是VIP。备RootServer后台线程能够检测- p8 c3 C, N' t( ^/ A3 s
    到VIP漂移到自身,于是自动切换为主机提供服务。
    8 q7 O3 m( z/ c7 Q1 h' f6 z+ f% X; x) ]8 |  x2 l. r# m
    6 {/ [+ d9 b# q) M$ ~. Y
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|Archiver|手机版|小黑屋|Java自学网

    GMT+8, 2025-4-1 13:58 , Processed in 0.153082 second(s), 30 queries .

    Powered by Javazx

    Copyright © 2012-2022, Javazx Cloud.

    快速回复 返回顶部 返回列表