java自学网VIP

Java自学网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 2511|回复: 0

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

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

    [LV.Master]出神入化

    2025

    主题

    3683

    帖子

    6万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    66345

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

    发表于 2017-3-6 14:37:42 | 显示全部楼层 |阅读模式
    9.2 RootServer实现机制
    . Y0 c8 L+ L) ~RootServer是OceanBase集群对外的窗口,客户端通过RootServer获取集群中其他  L# z; u" N! T3 A
    模块的信息。RootServer实现的功能包括:( h; C; U/ [; z" j* |
    ●管理集群中的所有ChunkServer,处理ChunkServer上下线;" C/ a0 _. w1 `- @
    ●管理集群中的UpdateServer,实现UpdateServer选主;
    ; y" M" u: E4 x0 j  ]+ i●管理集群中子表数据分布,发起子表复制、迁移以及合并等操作;
    5 A9 W  }8 _- \0 h* ?, [& w! P●与ChunkServer保持心跳,接受ChunkServer汇报,处理子表分裂;6 p* t* Y8 G! S" M% A1 |
    ●接受UpdateServer汇报的大版本冻结消息,通知ChunkServer执行定期合并;
    8 }8 P$ v: c* v; W2 o1 F$ S●实现主备RootServer,数据强同步,支持主RootServer宕机自动切换。
    , ^9 E: Z! R+ ~2 {; Y9.2.1 数据结构# L+ {" X( T+ p- R
    RootServer的中心数据结构为一张存储了子表数据分布的有序表格,称为; Y, N  ?3 {& ?3 k4 @9 `8 c) g
    RootTable。每个子表存储的信息包括:子表主键范围、子表各个副本所在
    - q* m% Q6 l5 ?1 ]( P2 O, q5 YChunkServer的编号、子表各个副本的数据行数、占用的磁盘空间、CRC校验值以及9 h9 c& h2 R) G6 s
    基线数据版本。7 L+ g+ M  M: x/ g
    RootTable是一个读多写少的数据结构,除了ChunkServer汇报、RootServer发起子5 b' K  T+ X4 \7 d8 Z$ K3 x0 W
    表复制、迁移以及合并等操作需要修改RootTable外,其他操作都只需要从RootTable0 x8 H$ p2 N/ ^8 O7 |1 c3 V( e6 ^. R
    中读取某个子表所在的ChunkServer。因此,OceanBase设计时考虑以写时复制的方式* {8 a* i- F4 l% s
    实现该结构,另外,考虑到RootTable修改特别少,实现时没有采用支持写时复制的
    2 X2 l/ a4 Q/ q+ D1 IB+树或者跳跃表(Skip List),而是采用相对更加简单的有序数组,以减少工作量。/ ~4 k8 S/ l* c7 w* k/ s# d% m" S2 ?
    往RootTable增加子表信息的操作步骤如下:
    + R6 ]' C/ V1 r& o: r/ Y1)拷贝当前服务的RootTable为新的RootTable;9 x* z! }) M) }" B) i
    2)将子表信息追加到新的RootTable,并对新的RootTable重新排序;1 \9 ^5 B# d" R. N$ s; x
    3)原子地修改指针使得当前服务的RootTable指向新的RootTable。
    4 N5 ~1 V/ S; T6 {8 C: oChunkServer一次汇报一批子表(默认一批包含1024个),如果每个子表修改都" K* F4 A$ j2 r4 ?
    需要拷贝整个RootTable并重新排序,性能上显然无法接受。RootServer实现时做了一
    ) M9 _; l' k' J! l些优化:拷贝当前服务的RootTable为新的RootTable后,将ChunkServer汇报的一批子, j2 {8 R5 `' @2 E7 c9 \
    表一次性追加到新的RootTable中并重新排序,最后再原子地切换当前服务的
    8 h* n8 u2 b- F6 j' [7 |RootTable为新的RootTable。采用批处理优化后,RootTable的性能基本满足需求,
    ) o  v4 O  o6 A' @$ x6 w7 rOceanBase单个集群支持的子表个数最大达到几百万个。当然,这种实现方式并不优
    7 B' A. e& v* p+ Z雅,我们后续将改造RootTable的实现方式。
    - J, j2 k8 \/ k% nChunkServer汇报的子表信息可能和RootTable中记录的不同,比如发生了子表分0 r$ i7 n' `( e# `- c( c/ r8 g
    裂。此时,RootServer需要根据汇报的tablet信息更新RootTable。2 w$ D9 w8 B% z' n* x
    如图9-2所示,假设原来的RootTable包含四个子表:r1(min,10]、r2(10,
    * {: o# i2 @4 z2 ]100]、r3(100,1000]、r4(1000,max]、ChunkServer汇报的子表列表为:t1(10,
    : |1 ^- U/ X! E3 S2 Z6 k! V  x8 u0 e50]、t2(50,100]、t3(100,1000],表示r2发生了tablet分裂,那么,RootServer会/ e/ Y" g. ]9 c+ k1 {+ s
    将RootTable修改为:r1(min,10]、r2(10,50]、r3(50,100]、r4(100,1000]、+ `8 Y! m% Q" }6 k* y
    r5(1000,max]。
    * h2 b( V* t  p* U* M* v. u图 9-2 RootTable修改4 t4 B% ~) K, K7 J$ }, `2 B
    RootServer中还有一个管理所有ChunkServer信息的数组,称为ChunkServer-5 _. E; h* X; T; A: u
    Manager。数组中的每个元素代表一台ChunkServer,存储的信息包括:机器状态(已! g/ {& n6 v+ ]
    下线、正在服务、正在汇报、汇报完成,等等)、启动后注册时间、上次心跳时3 j/ c9 L5 \6 S0 ]! ^- J
    间、磁盘相关信息、负载均衡相关信息。OceanBase刚上线时依据每台ChunkServer磁, j& y* ?' b* i: U2 _
    盘占用信息执行负载均衡,目的是为了尽可能确保每台ChunkServer占用差不多的磁
    $ X9 {, G9 l! i7 S  o3 F7 j盘空间。上线运行一段时间后发现这种方式效果并不好,目前的方式为按照每个表
    " T* R/ t" K* z+ {* O' A格的子表个数执行负载均衡,目的是尽可能保证对于每个表格、每台ChunkServer上3 V0 \( _+ F. Z. d0 M8 Y1 [8 e
    的子表个数大致相同。
    - o; Y  e5 k# }$ T, E3 {9.2.2 子表复制与负载均衡
    8 n6 z. \% ]6 B* R% kRootServer中有两种操作都可能触发子表迁移:子表复制(rereplication)以及负
    ( f) t; p( q( J载均衡(rebalance)。当某些ChunkServer下线超过一段时间后,为了防止数据丢# [/ x. j9 b4 T+ ^+ |
    失,需要拷贝副本数小于阀值的子表,另外,系统也需要定期执行负载均衡,将子
    7 w( B  Q! ?& i2 u* ?6 t, q0 m6 x表从负载较高的机器迁移到负载较低的机器。' H; p3 U9 I7 i8 Q
    每台ChunkServer记录了子表迁移相关信息,包括:ChunkServer上子表的个数以
    ; c3 A! o) @+ m. u0 j及所有子表的大小总和,正在迁入的子表个数、正在迁出的子表个数以及子表迁移! l5 y+ t$ @2 V
    任务列表。RootServer包含一个专门的线程定期执行子表复制与负载均衡任务,步骤2 I- \2 `# j  h, M; L2 e2 l
    如下:
    1 |% p. D! k/ S1)子表复制:扫描RootTable中的子表,如果某个子表的副本数小于阀值,选取
    ! n/ _+ O5 D7 u' X某台包含子表副本的ChunkServer为迁移源,另外一台符合要求的ChunkServer为迁移
    ) A; L0 X7 x6 ~5 ^目的地,生成子表迁移任务。迁移目的地需要符合一些条件,比如,不包含待迁移# S' f6 G6 `, H1 @2 H
    子表,服务的子表个数小于平均个数减去可容忍个数(默认值为10),正在进行的
    2 _# d) N* s% a/ ?; z迁移任务不超过阀值等。
    2 l0 S; Q$ _$ n. s2)负载均衡:扫描RootTable中的子表,如果某台ChunkServer包含的某个表格的
    ) y6 K0 `, d/ Q  [) j子表个数超过平均个数以及可容忍个数(默认值为10)之和,以这台ChunkServer为
    6 ~' T# |  r2 z) Z. ^迁移源,并选择一台符合要求的ChunkServer,生成子表迁移任务。% w: f. w7 P; {9 N, o9 v
    子表复制以及负载均衡生成的子表迁移任务并不会立即执行,而是会加入到迁, `" k  `, z3 b* j- L
    移源的迁移任务列表中,RootServer还有一个后台线程会扫描所有的ChunkServer,接
    9 b7 U: k7 d% {* ~% G着执行每台ChunkServer的迁移任务列表中保存的迁移任务。子表迁移时限制了每台
    / n* w+ L$ Z# d- n* y* t9 Z1 rChunkServer同时进行的最大迁入和迁出任务数,从而防止一台新的ChunkServer刚上
    4 C7 ]) G3 P: E% ]线时,迁入大量子表而负载过高。# y1 O' f$ B' V! Y1 p) c2 y
    例9-1 某OceanBase集群包含4台ChunkServer:ChunkServer1(包含子表A1、( d  B3 M- Y7 {" s" `' s
    A2、A3),ChunkServer2(包含子表A3、A4),ChunkServer3(包含子表A2),
    5 M9 i7 R) `/ sChunkServer4(包含子表A4)。0 b$ O4 U( S1 ^- c8 J
    假设子表副本数配置为2,最多能够容忍的不均衡子表的个数为0。RootServer后2 q0 i( d$ e7 Q7 E7 `! {
    台线程首先执行子表复制,发现子表A1只有一个副本,于是,将ChunkServer1作为迁; w# A% O" T5 V! |& Y' d
    移源,选择某台ChunkServer(假设为ChunkServer3)作为迁移目的,生成迁移任务<; T3 A/ }4 G$ L% U+ s/ p( z
    ChunkServer1,ChunkServer3,A1>。接着,执行负载均衡,发现ChunkServer1包含3
    $ Y$ i3 g' ]$ |' }: {. t: x; B* v个子表,超过平均值(平均值为2),而ChunkServer4包含的子表个数小于平均值,0 m3 E" A- ^) O4 W$ p2 Q3 b5 v
    于是,将ChunkServer1作为迁移源,ChunkServer4作为迁移目的,选择某个子表(假
    + T6 ]9 @7 j' }4 r设为A2),生成迁移任务<ChunkServer1,ChunkServer4,A2>。如果迁移成功,A2/ J" `5 @5 d" H* i; T; ?
    将包含3个副本,可以通知ChunkServer1删除上面的A2副本。最后,tablet分布情况
    1 H- n  q) a  ~1 k6 E  b为:ChunkServer1(包含tablet A1、A3),ChunkServer2(包含tablet A3、A4),, ~4 F# g( F+ s$ B8 E5 e1 Z
    ChunkServer3(包含tablet A1、A2),ChunkServer4(包含tablet A2、A4),每个
    % T# p2 s% m; ?( C2 f+ E" ntablet包含2个副本,且平均分布在4台ChunkServer上。
    4 w" p; g# b1 q- o( {. Z9.2.3 子表分裂与合并
    - E$ `0 r& I8 S3 {3 K/ X$ v3 G3 H0 u2 E子表分裂由ChunkServer在定期合并过程中执行,由于每个子表包含多个副本,# _9 X- K1 A/ k  k" {' Q: |
    且分布在多台ChunkServer上,如何确保多个副本之间的分裂点保持一致成为问题的
    " A0 s3 m* f; b' Q/ }3 h2 \关键。OceanBase采用了一种比较直接的做法:每台ChunkServer使用相同的分裂规
    + M- X2 r" N; W+ ~" @, g则。由于每个子表的不同副本之间的基线数据完全一致,且定期合并过程中冻结的- [1 h, o; P0 r1 q
    增量数据也完全相同,只要分裂规则一致,分裂后的子表主键范围也保证相同。' P% |9 f: V8 @+ ?- {
    OceanBase曾经有一个线上版本的分裂规则如下:只要定期合并过程中产生的数+ H& x) X  s# L( P
    据量超过256MB,就生成一个新的子表。假设定期合并产生的数据量为257MB,那
    ( ~) @# {4 {9 P: T么最后将分裂为两个子表,其中,前一个子表(记为r1)的数据量为256MB,后一5 N) P6 ^5 u. Z2 @
    个子表(记为r2)的数据量为1MB。接着,r1接受新的修改,数据量很快又超过
    : K" v$ I, W! j2 P256MB,于是,又分裂为两个子表。系统运行一段时间后,充斥着大量数据量很少
    ! l* s1 _7 U$ o- d- }* i  Z/ v, U的子表。
    % C+ b7 v- S& Z% B9 ^2 D7 b( T7 V" a为了解决分裂产生小子表的问题,需要确保分裂以后的每个子表数据量大致相
    + c, z, n5 l, u" v同。OceanBase对每个子表记录了两个元数据:数据行数row_count以及子表大小
    3 M4 L, ?- [9 A0 m5 H(occupy_size)。根据这两个值,可以计算出每行数据的平均大小,即:
    + x% }& a; |) t  ?occupy_size/row_count。
    ) [" ~$ r; G5 O1 C根据数据行平均大小,可以计算出分裂后的子表行数,从而得到分裂点。4 P' }' A2 y( B# Y3 Y* _$ ]
    子表合并相对更加麻烦,步骤如下:+ y: o9 K0 Q3 W# W) Y7 o
    1)合并准备:RootServer选择若干个主键范围连续的小子表;
    9 p7 p* c% M( Z& N1 V/ w7 E; @2)子表迁移:将待合并的若干个小子表迁移到相同的ChunkServer机器;
    , J0 |9 M% Z8 t2 }0 |, p1 Z3)子表合并:往ChunkServer机器发送子表合并命令,生成合并后的子表范围。) l6 V* I0 U4 l( C0 g/ h
    例9-2 某OceanBase集群中有3台ChunkServer:ChunkServer1(包含子表A1、
    , F; r- z' @( ~, J2 KA3),ChunkServer2(包含子表A2、A3),ChunkServer3(包含子表A1、A2),其3 q$ [; p$ j: f4 g( |2 D) w
    中,A1和A2分别为10MB,A3为256MB。RootServer扫描RootTable后发现A1和A2满足' V+ f5 x# A' V- {9 l
    子表合并条件,首先发起子表迁移,假设将A1迁移到ChunkServer2,使得A1和A2在0 k+ U+ g8 S# [/ z: _6 W
    相同的ChunkServer上,接着分别向ChunkServer2和ChunkServer3发起子表合并命令。
    0 c+ E" Q- u/ |, X子表合并完成以后,子表分布情况为:ChunkServer1(包含子表A3),: s' t7 B5 U/ f0 s
    ChunkServer2(包含子表A4(A1,A2),A3),ChunkServer3(包含子表A4(A1,4 N' R) \+ z- |$ D' p
    A2)),其中,A4是子表A1和A2合并后的结果。
    : a6 N" y7 u6 f每个子表包含多个副本,只要某一个副本合并成功,OceanBase就认为子表合并% Z7 }( ^3 W# M$ h5 R$ t0 `( m" X
    成功,其他合并失败的子表将通过垃圾回收机制删除掉。% Z( P0 O- h+ K
    9.2.4 UpdateServer选主
    ; e) E3 X  Q3 {* i& q为了确保一致性,RootServer需要确保每个集群中只有一台UpdateServer提供写+ a, L1 M. v8 k! A
    服务,这个UpdateServer称为主UpdateServer。* ?2 t2 D3 q: n& l
    RootServer通过租约(Lease)机制实现UpdateServer选主。主UpdateServer必须持0 _& m' Z" Y" S
    有RootServer的租约才能提供写服务,租约的有效期一般为3~5秒。正常情况下,
    5 f$ S+ ^# s2 l0 s: bRootServer会定期给主UpdateServer发送命令,延长租约的有效期。如果主1 W( @) s6 U$ _# u  A, H2 X/ M
    UpdateServer出现异常,RootServer等待主UpdateServer的租约过期后才能选择其他的# R- g3 @  e. D
    UpdateServer为主UpdateServer继续提供写服务。
    * P) b4 R3 G1 RRootServer可能需要频繁升级,升级过程中UpdateServer的租约将很快过期,系. x" W2 m9 o. j6 g! q
    统也会因此停服务。为了解决这个问题,RootServer设计了优雅退出的机制,即: L* X; T% {. `" g" X. C
    RootServer退出之前给UpdateServer发送一个有效期超长的租约(比如半小时),承
    " Z( B! ?+ P4 k7 g0 ~诺这段时间不进行主UpdateServer选举,用于RootServer升级。代码如下:
    0 c% z4 V3 q) i( f) H: ?enum ObUpsStatus
    * O6 _& T0 p& U) ~' N7 e' q! u{
    - Z6 K. J/ P& Y& H9 t3 ZUPS_STAT_OFFLINE=0,//UpdateServer已下线
    0 O+ j% }: y8 w( E, pUPS_STAT_NOTSYNC=1,//UpdateServer为备机且与主UpdateServer不同步
    $ q/ K/ N) U# b1 H9 f! h4 |: [UPS_STAT_SYNC=2,//UpdateServer为备机且与主UpdateServer同步% V, g. E! X6 u1 T. x- B. N8 n
    UPS_STAT_MASTER=3,//UpdateServer为主机; K2 ]5 V' Y. n0 |) `% t3 T% M* A
    };
    6 ~6 g! K% w: ?//RootServer中记录UpdateServer信息的结构
    1 Q/ m/ F5 C! k; R0 xclass ObUps3 G1 c% C6 c5 B% L6 {% H2 T
    {& C. F5 f, r8 X) A% F. T0 G* Q* p
    ObServer addr_;//UpdateServer地址
    3 S2 b# G4 L2 n+ Bint32_t inner_port_;//UpdateServer内部端口* Y! }8 |6 x. A6 L; |
    int64_t log_seq_num_;//UpdateServer的日志号& w2 t0 k" D8 l7 j
    int64_t lease_;//UpdateServer的租约
    & U$ w' \6 n" ?+ g) O2 ~ObUpsStatus stat_;//UpdateServer状态
    3 V" t  a1 x8 E0 l5 v6 \6 q1 I# ?  S};
    3 s& p( C. a2 d/ pclass ObUpsManager2 T  o) I- L; H1 K) W0 Q
    {$ _' K  [$ V1 o4 Z6 C  U
    public:
    0 G2 J, i8 c1 j; Z//UpdateServer向RootServer注册
    " v3 ~9 Y) K1 O. r. [! u  ^* Gint register_ups(const ObServer&addr,int32_t inner_port,int64_t
    # x& ^% {8 t+ F; p* _* }( v0 [' ^log_seq_num,int64_t lease,const char*server_version);3 ?# z% f' I8 S% {' Q
    //检查所有UpdateServer的租约,RootServer内部有专门的线程会定时调用该函数5 z7 }6 A+ Z" D* j4 q
    int check_lease();
    8 Q, e1 C6 R  w+ v4 K- \//RootServer给UpdateServer发送租约
    + b5 ~+ n$ d7 a. ^int grant_lease();
    $ o! X5 u+ m* Q//RootServer给UpdateServer发送超长租约5 u% `5 a6 v& d, T( s7 F; _
    int grant_eternal_lease();
    : |- M7 @* }/ W5 Lprivate:
    ! E+ E: B! @6 h5 _# zObUps ups_array_[MAX_UPS_COUNT];
    * @5 U' p0 f+ z8 A8 h9 o$ Fint32_t ups_master_idx_;$ g3 y: ]/ J# Y1 l8 f4 a. }
    };
    * U6 Y% L* A1 W/ ARootServer模块中有一个ObUpsManager类,它包含一个数组ups_array_,其中的8 u" z% `2 I. a7 f
    每个元素表示一个UpdateServer,ups_master_idx_表示主UpdateServer在数组里的下标。2 C# d7 C4 ?" ^$ {9 {
    ObUps结构记录了UpdateServer的信息,包括UpdateServer的地址(addr_)以及内部
    ! w% m, l$ I( S! ^; |3 W5 d端口(inner_port_),UpdateServer的状态(stat_,分为. I$ d: V$ }2 J9 |5 l
    UPS_STAT_OFFLINE/UPS_STAT_NOTSYNC/UPS_STAT_SYNC/UPS_STAT_MASTER
    . f) ^1 M" g0 M; ]9 Y这四种),UpdateServer的日志号(log_seq_num_)以及租约(lease_)。# K8 ~& r8 }, @' x. [; |+ r/ c
    UpdateServer首先通过register_ups向RootServer注册,将它的信息告知
    ' M! E, d0 l4 ]- N  z$ e: DRootServer。一段时间之后,RootServer会从所有注册的UpdateServer中选取一台日志
    * L, s, v9 v! _6 |, I号最大的作为主UpdateServer。ObUpsManager类中还有一个check_lease函数,由0 b- s" {' _" P% |, r
    RootServer内部线程定时调用,如果发现UpdateServer的租约快要过期,则会通过
    3 J! m! b2 k# S. Rgrant_lease给UpdateServer延长租约。如果发现主UpdateServer的租约已经失效,则会1 {, e& r& m0 f! Z
    从所有Update-Server中选择一个日志号最大的UpdateServer作为新的主UpdateServer。
    4 Q1 O1 {& H8 D9 k3 M9 T0 i. f另外,Root-Server还可以通过grant_eternal_lease给UpdateServer发送超长租约。* o4 X2 o; x' ?1 B$ B& F
    9.2.5 RootServer主备
    0 p3 E# M- K. V/ K) Q每个集群一般部署一主一备两台RootServer,主备之间数据强同步,即所有的操
    * H) N3 V/ [6 J* [  K' \作都需要首先同步到备机,接着修改主机,最后才能返回操作成功。. t. }3 Y6 W7 t, n1 F4 F2 o# x, m/ c" V
    RootServer主备之间需要同步的数据包括:RootTable中记录的子表分布信息、
    , a" B9 i! Q& @) b5 L/ l4 TChunkServerManager中记录的ChunkServer机器变化信息以及UpdateServer机器信息。
    , T3 e) C3 v: f. ~7 x4 v子表复制、负载均衡、合并、分裂以及ChunkServer/UpdateServer上下线等操作都会7 ^3 F! G. n" O9 y
    引起RootServer内部数据变化,这些变化都将以操作日志的形式同步到备! @9 T- Q+ e! W. h5 b
    RootServer。备RootServer实时回放这些操作日志,从而与主RootServer保持同步。# \) H; l& t1 [, z/ v
    OceanBase中的其他模块,比如ChunkServer/UpdateServer,以及客户端通过# x% W7 A4 ^' W3 K7 [
    VIP(Virtual IP)访问RootServer,正常情况下,VIP总是指向主RootServer。当主
    * w- E1 _/ U2 _RootServer出现故障时,部署在主备RootServer上的Linux HA(heartbeat,心跳),软8 ]7 T% J; P' g" g* I5 E3 G
    件能够检测到,并将VIP漂移到备RootServer。Linux HA软件的核心包含两个部分:
    3 G3 e/ Y% r5 Z  D心跳检测部分和资源接管部分,心跳检测部分通过网络链接或者串口线进行,主备
    & y% m* M" k7 ?RootServer上的心跳软件相互发送报文来告诉对方自己当前的状态。如果在指定的时2 p$ z; y% e7 ^4 `! M
    间内未收到对方发送的报文,那么就认为对方失败,这时需启动资源接管模块来接/ ^% d; G; H9 p9 Z% v
    管运行在对方主机上的资源,这里的资源就是VIP。备RootServer后台线程能够检测, ]% f$ Q8 D- ~6 [* b* C* s9 P9 u
    到VIP漂移到自身,于是自动切换为主机提供服务。
    + u3 O7 u0 n  \( P/ h
    ; O. W# V' t3 }& Q) e+ k. S; Z9 S4 |; H0 K9 t
    回复

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2024-11-23 16:23 , Processed in 0.134824 second(s), 30 queries .

    Powered by Javazx

    Copyright © 2012-2022, Javazx Cloud.

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