java自学网VIP

Java自学网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 2560|回复: 0

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

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

    [LV.Master]出神入化

    2040

    主题

    3698

    帖子

    6万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    66476

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

    发表于 2017-3-6 14:37:42 | 显示全部楼层 |阅读模式
    9.2 RootServer实现机制2 u* B1 B. O( D* ^
    RootServer是OceanBase集群对外的窗口,客户端通过RootServer获取集群中其他
    : g- M5 Y9 @1 ?模块的信息。RootServer实现的功能包括:
    ' m7 o1 W% C. @' B% Y●管理集群中的所有ChunkServer,处理ChunkServer上下线;
    / D1 X, w) P  y2 \2 {  C) l/ B●管理集群中的UpdateServer,实现UpdateServer选主;
    9 g9 L3 D5 p; @* o( Z) G0 P●管理集群中子表数据分布,发起子表复制、迁移以及合并等操作;
    5 J' g. r, ?5 L; [●与ChunkServer保持心跳,接受ChunkServer汇报,处理子表分裂;: n' l" s5 C. p9 |$ L! b2 b/ F( V' f
    ●接受UpdateServer汇报的大版本冻结消息,通知ChunkServer执行定期合并;
    ) S' d3 W$ o1 b- }+ ]3 Y●实现主备RootServer,数据强同步,支持主RootServer宕机自动切换。
    4 {( W4 ]! M, ?; F; c9 x9.2.1 数据结构
    + D( @: {# u+ s/ R. PRootServer的中心数据结构为一张存储了子表数据分布的有序表格,称为
    ( s% U- Q2 @& V. k: x, W3 JRootTable。每个子表存储的信息包括:子表主键范围、子表各个副本所在' K: v3 E$ [' ^; y
    ChunkServer的编号、子表各个副本的数据行数、占用的磁盘空间、CRC校验值以及3 E; n8 w8 z) P2 f
    基线数据版本。2 B: @) D  U2 Y$ c* }
    RootTable是一个读多写少的数据结构,除了ChunkServer汇报、RootServer发起子
    , F: i; K7 j8 F3 R8 v" @' U表复制、迁移以及合并等操作需要修改RootTable外,其他操作都只需要从RootTable
    7 ]4 z% ]  V6 Z2 I# U4 _& X/ n中读取某个子表所在的ChunkServer。因此,OceanBase设计时考虑以写时复制的方式
    , _3 l& |* D, ]7 r实现该结构,另外,考虑到RootTable修改特别少,实现时没有采用支持写时复制的
    % }/ U2 J- t. u+ f2 }! IB+树或者跳跃表(Skip List),而是采用相对更加简单的有序数组,以减少工作量。0 x5 y; H9 V. Y
    往RootTable增加子表信息的操作步骤如下:5 O- R) \/ Y- F" d$ r& q( f5 i( {
    1)拷贝当前服务的RootTable为新的RootTable;
    * G% ?1 m1 Q5 g$ ~  t* g6 u2)将子表信息追加到新的RootTable,并对新的RootTable重新排序;- n8 J# g- }6 u; y
    3)原子地修改指针使得当前服务的RootTable指向新的RootTable。# I+ e" L) l0 `* G6 V- N4 c
    ChunkServer一次汇报一批子表(默认一批包含1024个),如果每个子表修改都
    6 Z6 |8 `2 F6 _, ?2 v需要拷贝整个RootTable并重新排序,性能上显然无法接受。RootServer实现时做了一
    $ R: [8 l6 Y( z6 n, k/ v些优化:拷贝当前服务的RootTable为新的RootTable后,将ChunkServer汇报的一批子; D0 X( a& b8 ]1 ^: C1 c( A9 Q+ q9 n
    表一次性追加到新的RootTable中并重新排序,最后再原子地切换当前服务的8 F$ q4 P- o+ j( g4 v. ]8 g
    RootTable为新的RootTable。采用批处理优化后,RootTable的性能基本满足需求,- m( H! J/ w" `$ Q6 o
    OceanBase单个集群支持的子表个数最大达到几百万个。当然,这种实现方式并不优
    ; D' Y& d! |+ z; k! ?' K雅,我们后续将改造RootTable的实现方式。
    : a5 Q, r1 g8 w' A1 X8 b) z/ `+ ]ChunkServer汇报的子表信息可能和RootTable中记录的不同,比如发生了子表分$ M% O; L; C- F& o5 g
    裂。此时,RootServer需要根据汇报的tablet信息更新RootTable。1 t# }9 a9 V+ R  @; g4 j3 u
    如图9-2所示,假设原来的RootTable包含四个子表:r1(min,10]、r2(10,4 ~( F4 V# m, x( e8 b9 l7 m
    100]、r3(100,1000]、r4(1000,max]、ChunkServer汇报的子表列表为:t1(10,
    7 O! k# F6 E9 O$ ^+ X6 \50]、t2(50,100]、t3(100,1000],表示r2发生了tablet分裂,那么,RootServer会4 ?: p0 P2 C$ c
    将RootTable修改为:r1(min,10]、r2(10,50]、r3(50,100]、r4(100,1000]、
    4 N8 `+ m7 j) c& D, H5 n) @; dr5(1000,max]。. z5 n3 T& S, H+ V- L6 u" R
    图 9-2 RootTable修改# p7 b7 f7 ]8 Z; e3 W9 L9 F
    RootServer中还有一个管理所有ChunkServer信息的数组,称为ChunkServer-' p, ]# i& U  f% c! ~: C9 q3 k
    Manager。数组中的每个元素代表一台ChunkServer,存储的信息包括:机器状态(已/ d- D. u8 Q" G, [
    下线、正在服务、正在汇报、汇报完成,等等)、启动后注册时间、上次心跳时
    6 N3 O- u% `9 z6 f1 @间、磁盘相关信息、负载均衡相关信息。OceanBase刚上线时依据每台ChunkServer磁
    * d' a5 }" Q; m4 O盘占用信息执行负载均衡,目的是为了尽可能确保每台ChunkServer占用差不多的磁
    9 ]1 r: _0 a' M. N! b! E( R盘空间。上线运行一段时间后发现这种方式效果并不好,目前的方式为按照每个表
    * T# d' B) p: g$ s0 M格的子表个数执行负载均衡,目的是尽可能保证对于每个表格、每台ChunkServer上" n5 r& ?' e: j6 b
    的子表个数大致相同。
    - \  b  O" S) i! d% i; n  w9.2.2 子表复制与负载均衡
    5 P& ]5 j. o9 v* m! A6 ^RootServer中有两种操作都可能触发子表迁移:子表复制(rereplication)以及负- ]. [6 i  \# `
    载均衡(rebalance)。当某些ChunkServer下线超过一段时间后,为了防止数据丢6 d% s2 m  C9 K3 E  h& e$ V
    失,需要拷贝副本数小于阀值的子表,另外,系统也需要定期执行负载均衡,将子# |( X5 Q0 [& C7 G9 C6 Q7 l
    表从负载较高的机器迁移到负载较低的机器。
    # [+ j2 [: |. D* f- l每台ChunkServer记录了子表迁移相关信息,包括:ChunkServer上子表的个数以
    6 U, `6 r' E/ s及所有子表的大小总和,正在迁入的子表个数、正在迁出的子表个数以及子表迁移
    / j1 }5 o' l9 z3 ~  W, {6 k) }( B" q任务列表。RootServer包含一个专门的线程定期执行子表复制与负载均衡任务,步骤. X- b% }, X" @8 s: s, g
    如下:! |+ N% \1 f6 j6 r0 p8 _  P
    1)子表复制:扫描RootTable中的子表,如果某个子表的副本数小于阀值,选取
    " L( w) ~5 M) F  t5 S3 ^( g% C某台包含子表副本的ChunkServer为迁移源,另外一台符合要求的ChunkServer为迁移
    % e$ ~( g+ g: N' p2 }' n. ?目的地,生成子表迁移任务。迁移目的地需要符合一些条件,比如,不包含待迁移
    " U0 G& X. o0 k& X  B( u" J+ {子表,服务的子表个数小于平均个数减去可容忍个数(默认值为10),正在进行的/ q: ~6 T' k8 L  S
    迁移任务不超过阀值等。+ A( R7 u: y. a' ?6 h- z
    2)负载均衡:扫描RootTable中的子表,如果某台ChunkServer包含的某个表格的+ S- d3 b& c" ~
    子表个数超过平均个数以及可容忍个数(默认值为10)之和,以这台ChunkServer为: B3 Q1 ^- a* B* ]) F5 v( n
    迁移源,并选择一台符合要求的ChunkServer,生成子表迁移任务。
    ; N$ D( t5 ]; Z" T; Z1 {$ a% \子表复制以及负载均衡生成的子表迁移任务并不会立即执行,而是会加入到迁& M* B( O3 G4 L4 ^* D
    移源的迁移任务列表中,RootServer还有一个后台线程会扫描所有的ChunkServer,接- c7 C+ j3 r5 t- m5 c/ S3 f2 f
    着执行每台ChunkServer的迁移任务列表中保存的迁移任务。子表迁移时限制了每台
    8 Y* Y9 \4 F) L4 nChunkServer同时进行的最大迁入和迁出任务数,从而防止一台新的ChunkServer刚上" k2 ]# V  r8 X! k
    线时,迁入大量子表而负载过高。. a' ~- n" Y7 l& M
    例9-1 某OceanBase集群包含4台ChunkServer:ChunkServer1(包含子表A1、9 X3 D. X# ^. |+ V9 I' M( d" B! t1 P
    A2、A3),ChunkServer2(包含子表A3、A4),ChunkServer3(包含子表A2),* ~2 D& i+ Y4 E& d2 N) j5 M
    ChunkServer4(包含子表A4)。- a: y1 S. n7 J3 k- Y
    假设子表副本数配置为2,最多能够容忍的不均衡子表的个数为0。RootServer后
    & W: B% A5 z; O+ K& p; Y' P台线程首先执行子表复制,发现子表A1只有一个副本,于是,将ChunkServer1作为迁
    4 T7 ]/ D# R9 r) o/ H% R% k* O* `移源,选择某台ChunkServer(假设为ChunkServer3)作为迁移目的,生成迁移任务<
    # p7 h, V- M6 `. s' ^ChunkServer1,ChunkServer3,A1>。接着,执行负载均衡,发现ChunkServer1包含3% T. m/ V3 D! m; B4 @
    个子表,超过平均值(平均值为2),而ChunkServer4包含的子表个数小于平均值,
    - d) ?4 _" @$ {- \' ~" y+ a0 \* N& a于是,将ChunkServer1作为迁移源,ChunkServer4作为迁移目的,选择某个子表(假
    ; P' L' t/ \8 s+ |设为A2),生成迁移任务<ChunkServer1,ChunkServer4,A2>。如果迁移成功,A2) ?3 U- U$ k( E) g# _7 l
    将包含3个副本,可以通知ChunkServer1删除上面的A2副本。最后,tablet分布情况
    9 c. C. T* l( p9 @/ i为:ChunkServer1(包含tablet A1、A3),ChunkServer2(包含tablet A3、A4),
    8 u, G! X% h- _& I( N# [" }  _ChunkServer3(包含tablet A1、A2),ChunkServer4(包含tablet A2、A4),每个
    2 \. m1 }: y9 u# q: ^tablet包含2个副本,且平均分布在4台ChunkServer上。3 @3 F, H. i2 `- f/ m) j% f( o  O
    9.2.3 子表分裂与合并# e- n" ?; k. A* V2 H7 P
    子表分裂由ChunkServer在定期合并过程中执行,由于每个子表包含多个副本,5 _/ D8 L) k  f: k  ]+ N- M  n" T
    且分布在多台ChunkServer上,如何确保多个副本之间的分裂点保持一致成为问题的
    5 c0 X5 C8 c; I7 z关键。OceanBase采用了一种比较直接的做法:每台ChunkServer使用相同的分裂规
    ) n% r0 E! Z7 l$ O! z3 q则。由于每个子表的不同副本之间的基线数据完全一致,且定期合并过程中冻结的3 w2 I7 [' C7 D1 ]& g0 a9 \% Q- l$ t: f
    增量数据也完全相同,只要分裂规则一致,分裂后的子表主键范围也保证相同。
    3 H9 Y4 J, c! z6 ROceanBase曾经有一个线上版本的分裂规则如下:只要定期合并过程中产生的数% }$ G; I7 c5 \1 ?
    据量超过256MB,就生成一个新的子表。假设定期合并产生的数据量为257MB,那
    : X8 j" w- G0 H# r- w, i么最后将分裂为两个子表,其中,前一个子表(记为r1)的数据量为256MB,后一4 [6 U9 [' H1 f; V) ^" }
    个子表(记为r2)的数据量为1MB。接着,r1接受新的修改,数据量很快又超过, J* f* R2 {3 w4 ^
    256MB,于是,又分裂为两个子表。系统运行一段时间后,充斥着大量数据量很少
    , f# n' G6 \  z0 Q2 i  h的子表。
    # o, s0 H2 \2 ?为了解决分裂产生小子表的问题,需要确保分裂以后的每个子表数据量大致相" n6 }3 _/ f2 Y& @0 g
    同。OceanBase对每个子表记录了两个元数据:数据行数row_count以及子表大小
    # v5 S8 {7 L$ ^. l2 X$ G$ d  s9 M(occupy_size)。根据这两个值,可以计算出每行数据的平均大小,即:
    * |" B6 W/ _1 u+ Toccupy_size/row_count。
    / X3 J7 Y) r! i% p2 s根据数据行平均大小,可以计算出分裂后的子表行数,从而得到分裂点。
    6 t( B0 J* g5 \( u子表合并相对更加麻烦,步骤如下:
    9 e* N  @2 w9 `1 _# z0 i& y1)合并准备:RootServer选择若干个主键范围连续的小子表;
    ) }: t' Q# z, w; W2)子表迁移:将待合并的若干个小子表迁移到相同的ChunkServer机器;
    , P6 {% r8 Z( z3 K6 j8 V3)子表合并:往ChunkServer机器发送子表合并命令,生成合并后的子表范围。
    0 \6 g7 t# k( @$ u5 U例9-2 某OceanBase集群中有3台ChunkServer:ChunkServer1(包含子表A1、8 u+ H* B) |3 ^0 u+ V3 o) V/ T* G
    A3),ChunkServer2(包含子表A2、A3),ChunkServer3(包含子表A1、A2),其5 H7 n* Y" W, r" U2 k2 G9 s9 S
    中,A1和A2分别为10MB,A3为256MB。RootServer扫描RootTable后发现A1和A2满足
    . z' f( n! J. V4 @( C0 E子表合并条件,首先发起子表迁移,假设将A1迁移到ChunkServer2,使得A1和A2在; |4 y: C1 Q) V- a, {- G: G
    相同的ChunkServer上,接着分别向ChunkServer2和ChunkServer3发起子表合并命令。2 C' O5 S) \" @" h# x
    子表合并完成以后,子表分布情况为:ChunkServer1(包含子表A3),: e, n' R& X9 u% H
    ChunkServer2(包含子表A4(A1,A2),A3),ChunkServer3(包含子表A4(A1,9 K5 c6 V( x, q; a- _' h
    A2)),其中,A4是子表A1和A2合并后的结果。# o6 ]" d1 }0 K- j; s8 j* F
    每个子表包含多个副本,只要某一个副本合并成功,OceanBase就认为子表合并
    / e$ @) i  ?, H) V成功,其他合并失败的子表将通过垃圾回收机制删除掉。
    0 d  u8 T% ]5 ^/ R* U& w9.2.4 UpdateServer选主: H0 l; q$ m: g" P
    为了确保一致性,RootServer需要确保每个集群中只有一台UpdateServer提供写' l$ X/ z  W2 l5 W
    服务,这个UpdateServer称为主UpdateServer。- O; y/ X3 f: u* Q
    RootServer通过租约(Lease)机制实现UpdateServer选主。主UpdateServer必须持, d* C5 ^# J4 G1 V7 {4 [
    有RootServer的租约才能提供写服务,租约的有效期一般为3~5秒。正常情况下,1 \6 N! [5 u) T$ Q
    RootServer会定期给主UpdateServer发送命令,延长租约的有效期。如果主1 p  o5 O2 r' N
    UpdateServer出现异常,RootServer等待主UpdateServer的租约过期后才能选择其他的
    & d; d  W1 h/ I- f6 G1 T2 A, YUpdateServer为主UpdateServer继续提供写服务。
    7 F4 d* B0 b0 A5 kRootServer可能需要频繁升级,升级过程中UpdateServer的租约将很快过期,系
    ( L8 I: @7 R2 |" r  E统也会因此停服务。为了解决这个问题,RootServer设计了优雅退出的机制,即9 j' x9 K) A8 Z4 p% x9 P& A9 T( W
    RootServer退出之前给UpdateServer发送一个有效期超长的租约(比如半小时),承# C( z$ V2 ~$ w  _3 k
    诺这段时间不进行主UpdateServer选举,用于RootServer升级。代码如下:* A. y. Y8 @, V5 ~
    enum ObUpsStatus
    $ j( t; y) A0 f* }{
    / ]: q9 c7 y+ Q( ^UPS_STAT_OFFLINE=0,//UpdateServer已下线5 x) m* x) G/ U) q
    UPS_STAT_NOTSYNC=1,//UpdateServer为备机且与主UpdateServer不同步
    & j( ?6 w4 x$ S# U# g  K" \7 [! C. PUPS_STAT_SYNC=2,//UpdateServer为备机且与主UpdateServer同步1 Q* L: _5 M% e
    UPS_STAT_MASTER=3,//UpdateServer为主机* H' Q# b# h9 `! Y  j1 b  `8 X* y4 ]
    };& {) S: M" B' o
    //RootServer中记录UpdateServer信息的结构
    0 M9 P8 @, U% o; ?) fclass ObUps3 m( f4 _7 O- ?2 @/ C
    {
    3 j: ?3 m6 ~' R# L7 k5 h7 I6 X! DObServer addr_;//UpdateServer地址" u/ C5 }" p$ {. Y1 k: f; M- I
    int32_t inner_port_;//UpdateServer内部端口
    4 p! Z. _$ h; I- V) L* Dint64_t log_seq_num_;//UpdateServer的日志号
    9 \  u4 ?: }" W" g0 B8 }& v' }3 ?int64_t lease_;//UpdateServer的租约
    8 ^2 ?- @4 L, e* S# M$ U; r8 NObUpsStatus stat_;//UpdateServer状态
    ) ?+ ]' C$ p1 W! _$ M* P3 V) Q};+ T) B0 B8 Q" H, }0 s$ {: A) H: K4 x
    class ObUpsManager
    $ {2 U( L5 x7 D: A. E$ n* J{( u1 @5 F: E+ k1 ~4 x  g0 n
    public:% a7 V2 E* s' a
    //UpdateServer向RootServer注册8 s0 P6 N$ w) N4 u$ r; O: |9 |1 e7 [
    int register_ups(const ObServer&addr,int32_t inner_port,int64_t
    7 a$ d: k. e% ~log_seq_num,int64_t lease,const char*server_version);
    , }: G" N9 S9 e2 q9 J//检查所有UpdateServer的租约,RootServer内部有专门的线程会定时调用该函数; U( Z! h4 A7 G
    int check_lease();
    / E7 g. k$ [3 J8 p* K3 D7 ^//RootServer给UpdateServer发送租约
    : W, B3 z6 f5 S- L6 r" ?int grant_lease();
    8 D$ r2 p6 g  p: M& i/ A//RootServer给UpdateServer发送超长租约
    - |: k8 E  W+ [! v6 F# wint grant_eternal_lease();/ w" |3 K9 ~. c2 n# ~$ P; ?
    private:+ o; r4 C5 ~# z& d" y* }
    ObUps ups_array_[MAX_UPS_COUNT];6 f2 ~  Y$ [- H( p+ Z  g. N& ?
    int32_t ups_master_idx_;% y  U* H! P( \& N1 c
    };! g+ l" H* R7 @3 I
    RootServer模块中有一个ObUpsManager类,它包含一个数组ups_array_,其中的
    * T) B( M* [0 K/ @- t! p8 o每个元素表示一个UpdateServer,ups_master_idx_表示主UpdateServer在数组里的下标。& q$ v1 S, M* y: k
    ObUps结构记录了UpdateServer的信息,包括UpdateServer的地址(addr_)以及内部1 s  w: ?" }6 [8 O( U2 U
    端口(inner_port_),UpdateServer的状态(stat_,分为
    . Z( d) K% _" CUPS_STAT_OFFLINE/UPS_STAT_NOTSYNC/UPS_STAT_SYNC/UPS_STAT_MASTER
    ! y, C3 ?" ^! ]+ D这四种),UpdateServer的日志号(log_seq_num_)以及租约(lease_)。; e6 D: |4 U# t8 `- _; ~4 L
    UpdateServer首先通过register_ups向RootServer注册,将它的信息告知# y: ]. L" J7 ]/ m" P/ e! E
    RootServer。一段时间之后,RootServer会从所有注册的UpdateServer中选取一台日志
    3 T% E# g! j/ K/ R! }号最大的作为主UpdateServer。ObUpsManager类中还有一个check_lease函数,由
    . P( w$ ^# L# a8 c' S* QRootServer内部线程定时调用,如果发现UpdateServer的租约快要过期,则会通过
    1 F- |( x' O0 O, Igrant_lease给UpdateServer延长租约。如果发现主UpdateServer的租约已经失效,则会
    : d7 @7 d( ^+ l从所有Update-Server中选择一个日志号最大的UpdateServer作为新的主UpdateServer。: g* Q2 ~) M+ ]% I
    另外,Root-Server还可以通过grant_eternal_lease给UpdateServer发送超长租约。/ w: [5 p6 g% q0 a( U, p
    9.2.5 RootServer主备
    + h0 `. Z: f# x7 e# `每个集群一般部署一主一备两台RootServer,主备之间数据强同步,即所有的操
    0 c" F8 u' M' G6 J1 X1 g作都需要首先同步到备机,接着修改主机,最后才能返回操作成功。
    2 \$ ]6 f* i0 ^6 K+ ~RootServer主备之间需要同步的数据包括:RootTable中记录的子表分布信息、" F" G" o6 \$ _+ [7 Z
    ChunkServerManager中记录的ChunkServer机器变化信息以及UpdateServer机器信息。
    ) |  {/ o' m4 ^: Z子表复制、负载均衡、合并、分裂以及ChunkServer/UpdateServer上下线等操作都会  q* J, G/ e* x& ^
    引起RootServer内部数据变化,这些变化都将以操作日志的形式同步到备5 `2 b4 r  F+ o: d9 s
    RootServer。备RootServer实时回放这些操作日志,从而与主RootServer保持同步。5 D2 C% R# z+ d7 U# o  z- l: L1 n
    OceanBase中的其他模块,比如ChunkServer/UpdateServer,以及客户端通过' g" M8 d) i; d' e( o% i, N
    VIP(Virtual IP)访问RootServer,正常情况下,VIP总是指向主RootServer。当主
    3 h: v& P+ c. \  F4 zRootServer出现故障时,部署在主备RootServer上的Linux HA(heartbeat,心跳),软
    9 `( _' w; B* d, L8 f) p% a件能够检测到,并将VIP漂移到备RootServer。Linux HA软件的核心包含两个部分:3 l# J0 o9 `; Z
    心跳检测部分和资源接管部分,心跳检测部分通过网络链接或者串口线进行,主备
    ) s# K; {7 s) YRootServer上的心跳软件相互发送报文来告诉对方自己当前的状态。如果在指定的时
    ' o7 `) ^9 ?1 _4 v7 Y$ z间内未收到对方发送的报文,那么就认为对方失败,这时需启动资源接管模块来接* N# H# N$ I$ V% t4 Y9 H  c" c- f
    管运行在对方主机上的资源,这里的资源就是VIP。备RootServer后台线程能够检测
    + r6 g5 @4 T! h; h* W3 }到VIP漂移到自身,于是自动切换为主机提供服务。
    # Z8 D0 s0 M; r. A7 B; q- y0 s- @* ]( g2 E2 v" X

    1 \" I$ W, _% @2 k2 Y+ I* F7 y
    回复

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2025-1-22 14:56 , Processed in 0.324096 second(s), 30 queries .

    Powered by Javazx

    Copyright © 2012-2022, Javazx Cloud.

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