java自学网VIP

Java自学网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 2280|回复: 0

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

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

    [LV.Master]出神入化

    2025

    主题

    3683

    帖子

    6万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    66101

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

    发表于 2017-3-6 14:37:42 | 显示全部楼层 |阅读模式
    9.2 RootServer实现机制( K4 f/ Q# `. |- n6 _+ M: q4 y
    RootServer是OceanBase集群对外的窗口,客户端通过RootServer获取集群中其他
    ! j* o4 t/ s/ g7 e2 n- O模块的信息。RootServer实现的功能包括:
    $ k, z. r* w2 S●管理集群中的所有ChunkServer,处理ChunkServer上下线;
    5 Z& `" I" m' r●管理集群中的UpdateServer,实现UpdateServer选主;& D9 f6 P/ H  _# H0 Q4 z# N, F
    ●管理集群中子表数据分布,发起子表复制、迁移以及合并等操作;2 v" _* D8 S" x
    ●与ChunkServer保持心跳,接受ChunkServer汇报,处理子表分裂;
    ) i7 x  n1 q' D- n" }●接受UpdateServer汇报的大版本冻结消息,通知ChunkServer执行定期合并;
    ' n, ^4 v6 C( `/ `●实现主备RootServer,数据强同步,支持主RootServer宕机自动切换。
    # c1 A* y7 l3 D; w, u, q8 F8 Y9.2.1 数据结构! g! A- I9 ]$ t
    RootServer的中心数据结构为一张存储了子表数据分布的有序表格,称为4 }1 _2 P( Y$ V7 F# z
    RootTable。每个子表存储的信息包括:子表主键范围、子表各个副本所在8 r7 }4 m3 w. r5 H1 k+ b
    ChunkServer的编号、子表各个副本的数据行数、占用的磁盘空间、CRC校验值以及0 g- h( a/ @3 S3 D; _7 p
    基线数据版本。% W4 L; i* h: G2 H
    RootTable是一个读多写少的数据结构,除了ChunkServer汇报、RootServer发起子/ s$ X  ~% _9 ?# e0 f8 a; g1 Y+ F9 @
    表复制、迁移以及合并等操作需要修改RootTable外,其他操作都只需要从RootTable; G+ j" L6 h/ g4 B
    中读取某个子表所在的ChunkServer。因此,OceanBase设计时考虑以写时复制的方式
    . p% ?7 ~0 ]2 Q2 O实现该结构,另外,考虑到RootTable修改特别少,实现时没有采用支持写时复制的6 P* D' x( ?' a3 q- _% \9 |0 K
    B+树或者跳跃表(Skip List),而是采用相对更加简单的有序数组,以减少工作量。" `! H& {# X! N6 {
    往RootTable增加子表信息的操作步骤如下:$ V  ?8 f, {8 l. k# W! [! ?, H/ `
    1)拷贝当前服务的RootTable为新的RootTable;1 f6 T4 f  u) v) B0 \
    2)将子表信息追加到新的RootTable,并对新的RootTable重新排序;  U+ P0 x' d& q1 y4 ]
    3)原子地修改指针使得当前服务的RootTable指向新的RootTable。6 n7 f3 I) ]: J  X) y) X+ ?
    ChunkServer一次汇报一批子表(默认一批包含1024个),如果每个子表修改都" D/ n" C0 [9 |1 p- t& S. Y& L
    需要拷贝整个RootTable并重新排序,性能上显然无法接受。RootServer实现时做了一& w: e, M5 J% s. h& E( d
    些优化:拷贝当前服务的RootTable为新的RootTable后,将ChunkServer汇报的一批子
      w. j2 ?  W3 v( j表一次性追加到新的RootTable中并重新排序,最后再原子地切换当前服务的
    6 I( k( {" S6 ^1 \RootTable为新的RootTable。采用批处理优化后,RootTable的性能基本满足需求,
    4 m7 r0 y( S, w! T  qOceanBase单个集群支持的子表个数最大达到几百万个。当然,这种实现方式并不优
    ; w8 t1 U4 E" \  K1 B雅,我们后续将改造RootTable的实现方式。
    - o5 T; U0 m) d" a2 NChunkServer汇报的子表信息可能和RootTable中记录的不同,比如发生了子表分
    # c3 \! L9 H$ i/ F: u( J裂。此时,RootServer需要根据汇报的tablet信息更新RootTable。
    8 o' k8 S5 p- v2 m如图9-2所示,假设原来的RootTable包含四个子表:r1(min,10]、r2(10,
    & R/ z. @# a! u( a100]、r3(100,1000]、r4(1000,max]、ChunkServer汇报的子表列表为:t1(10,
    , B) _) V" s, l50]、t2(50,100]、t3(100,1000],表示r2发生了tablet分裂,那么,RootServer会1 Q0 @" a( J+ o: W" d
    将RootTable修改为:r1(min,10]、r2(10,50]、r3(50,100]、r4(100,1000]、
    ( I7 S. {+ l' }" n+ i/ |) z0 hr5(1000,max]。
    ! o$ E' u  {$ H' }) s0 ~' t图 9-2 RootTable修改
    / u8 R, y: O- u1 Q) yRootServer中还有一个管理所有ChunkServer信息的数组,称为ChunkServer-
    % P0 Q9 w. x0 {% c$ l' ?Manager。数组中的每个元素代表一台ChunkServer,存储的信息包括:机器状态(已6 C2 h& ~# L. t% Q
    下线、正在服务、正在汇报、汇报完成,等等)、启动后注册时间、上次心跳时" z& k. _5 r# r2 l: D
    间、磁盘相关信息、负载均衡相关信息。OceanBase刚上线时依据每台ChunkServer磁5 l; `; {; j- [9 M
    盘占用信息执行负载均衡,目的是为了尽可能确保每台ChunkServer占用差不多的磁
    + }1 A' v. Q* e: H, R  q3 N/ m* V盘空间。上线运行一段时间后发现这种方式效果并不好,目前的方式为按照每个表, t0 j2 W- a2 G! z2 b0 _
    格的子表个数执行负载均衡,目的是尽可能保证对于每个表格、每台ChunkServer上
    3 F! H  p  ~6 h# s* @的子表个数大致相同。5 O$ H3 T" X/ k$ p$ j
    9.2.2 子表复制与负载均衡( T- }/ y. A- ~( h- z
    RootServer中有两种操作都可能触发子表迁移:子表复制(rereplication)以及负
    1 G9 {, {" Q2 {9 d, X' O载均衡(rebalance)。当某些ChunkServer下线超过一段时间后,为了防止数据丢; `3 ?& n" {- r
    失,需要拷贝副本数小于阀值的子表,另外,系统也需要定期执行负载均衡,将子2 r+ o, A( h& K' u% p
    表从负载较高的机器迁移到负载较低的机器。4 \/ U, ?8 t1 U; S9 z/ w
    每台ChunkServer记录了子表迁移相关信息,包括:ChunkServer上子表的个数以
      u! _+ s! P' t. T及所有子表的大小总和,正在迁入的子表个数、正在迁出的子表个数以及子表迁移
    * ?& q# @% J7 Y7 K任务列表。RootServer包含一个专门的线程定期执行子表复制与负载均衡任务,步骤
    2 v; N& D& x2 f1 e# j/ t! S如下:
    & ^% b; h1 ^8 q5 [  F/ E$ Z1)子表复制:扫描RootTable中的子表,如果某个子表的副本数小于阀值,选取" E* ]$ |4 T9 c' z7 R0 m) a0 R
    某台包含子表副本的ChunkServer为迁移源,另外一台符合要求的ChunkServer为迁移$ |1 M* y, n% J+ s. w
    目的地,生成子表迁移任务。迁移目的地需要符合一些条件,比如,不包含待迁移
    / Q0 [5 @  ^3 [- @6 z# f子表,服务的子表个数小于平均个数减去可容忍个数(默认值为10),正在进行的1 e% |9 ?3 y9 J2 I$ p
    迁移任务不超过阀值等。
    ; Q) Q$ r( E( ]0 N; j2)负载均衡:扫描RootTable中的子表,如果某台ChunkServer包含的某个表格的
      F" Y6 n& G5 M2 e' a/ @. m* r  y子表个数超过平均个数以及可容忍个数(默认值为10)之和,以这台ChunkServer为
    6 k6 \4 J+ s: o% Z3 a2 S  L8 Z1 ^迁移源,并选择一台符合要求的ChunkServer,生成子表迁移任务。
    : J1 C* W6 s! c# ~" I: ]% o子表复制以及负载均衡生成的子表迁移任务并不会立即执行,而是会加入到迁2 y/ c8 y1 r6 m. f" Q2 `* I# `8 I7 B
    移源的迁移任务列表中,RootServer还有一个后台线程会扫描所有的ChunkServer,接
    1 U. _0 b! k, V/ j1 z' O着执行每台ChunkServer的迁移任务列表中保存的迁移任务。子表迁移时限制了每台
    7 L5 J! J, Y6 _. QChunkServer同时进行的最大迁入和迁出任务数,从而防止一台新的ChunkServer刚上
    " d/ |; `8 n1 Q, B线时,迁入大量子表而负载过高。: s: _4 k' T! c: j
    例9-1 某OceanBase集群包含4台ChunkServer:ChunkServer1(包含子表A1、
    : w7 l$ y1 ?; u1 f" Z, xA2、A3),ChunkServer2(包含子表A3、A4),ChunkServer3(包含子表A2),
    6 {$ P7 ^$ x) ~3 `8 h4 D( GChunkServer4(包含子表A4)。
    % m+ ~! K! A! @假设子表副本数配置为2,最多能够容忍的不均衡子表的个数为0。RootServer后2 G2 C2 C5 X; `+ a7 p, `, ^' E
    台线程首先执行子表复制,发现子表A1只有一个副本,于是,将ChunkServer1作为迁/ p' M+ j; b' k. h" U
    移源,选择某台ChunkServer(假设为ChunkServer3)作为迁移目的,生成迁移任务</ Z& Z/ w) D2 p# X$ j$ f
    ChunkServer1,ChunkServer3,A1>。接着,执行负载均衡,发现ChunkServer1包含3
    ; p4 N3 Z) V. c. X5 O" A. Q- l5 ^2 v2 Q个子表,超过平均值(平均值为2),而ChunkServer4包含的子表个数小于平均值,$ ]- r/ E2 L; a7 `
    于是,将ChunkServer1作为迁移源,ChunkServer4作为迁移目的,选择某个子表(假4 z: u( A5 A2 o. u! v( x1 s
    设为A2),生成迁移任务<ChunkServer1,ChunkServer4,A2>。如果迁移成功,A2' h5 V7 |5 u# b
    将包含3个副本,可以通知ChunkServer1删除上面的A2副本。最后,tablet分布情况3 j) e; a/ r9 T1 N
    为:ChunkServer1(包含tablet A1、A3),ChunkServer2(包含tablet A3、A4),6 l- _: h1 s) d; K4 @8 z
    ChunkServer3(包含tablet A1、A2),ChunkServer4(包含tablet A2、A4),每个3 Y9 l# Q" R2 q) S
    tablet包含2个副本,且平均分布在4台ChunkServer上。
    1 h  u  b4 Q. T% \/ `7 ~- Q- ^9.2.3 子表分裂与合并
    . ~0 \1 a# ]9 D& r9 }2 f子表分裂由ChunkServer在定期合并过程中执行,由于每个子表包含多个副本,
    ' X* e* ^( R8 A: x( O# @且分布在多台ChunkServer上,如何确保多个副本之间的分裂点保持一致成为问题的
      g7 [3 F9 O; k: `1 G, y关键。OceanBase采用了一种比较直接的做法:每台ChunkServer使用相同的分裂规
      r+ F9 s  H  y9 O" {则。由于每个子表的不同副本之间的基线数据完全一致,且定期合并过程中冻结的" r7 D  E5 a0 a, i
    增量数据也完全相同,只要分裂规则一致,分裂后的子表主键范围也保证相同。, ]3 d! @7 }- V& c( ~1 \
    OceanBase曾经有一个线上版本的分裂规则如下:只要定期合并过程中产生的数
      _1 p5 t! T: z据量超过256MB,就生成一个新的子表。假设定期合并产生的数据量为257MB,那
    * P5 A- C1 c$ a0 o9 |么最后将分裂为两个子表,其中,前一个子表(记为r1)的数据量为256MB,后一2 C& N' Y/ I% x; \% f2 G
    个子表(记为r2)的数据量为1MB。接着,r1接受新的修改,数据量很快又超过
    . j2 K  {) ^2 [256MB,于是,又分裂为两个子表。系统运行一段时间后,充斥着大量数据量很少$ [# Z" O7 Z2 v3 e% m6 F! X
    的子表。
    6 i  d3 n: ?1 p- b0 }$ g4 B: ]; w# u为了解决分裂产生小子表的问题,需要确保分裂以后的每个子表数据量大致相: E) _, V+ l( ^' R3 t
    同。OceanBase对每个子表记录了两个元数据:数据行数row_count以及子表大小
    : S8 T% v7 P& ?1 q( ^: M(occupy_size)。根据这两个值,可以计算出每行数据的平均大小,即:
    ; k0 j1 O- b- B6 koccupy_size/row_count。3 w# Y9 U- f" }
    根据数据行平均大小,可以计算出分裂后的子表行数,从而得到分裂点。
    0 C0 O1 ~% j& {& a2 T子表合并相对更加麻烦,步骤如下:
    5 I5 i4 h+ d: x0 v1)合并准备:RootServer选择若干个主键范围连续的小子表;/ B5 U( d' M: u0 G( h/ _- `2 X
    2)子表迁移:将待合并的若干个小子表迁移到相同的ChunkServer机器;
      q$ A# U' u( v  X" Q  d8 p3)子表合并:往ChunkServer机器发送子表合并命令,生成合并后的子表范围。. B( @3 @/ v6 ]) V) I# ~& J
    例9-2 某OceanBase集群中有3台ChunkServer:ChunkServer1(包含子表A1、% {% D1 v' M, _3 j) e5 `
    A3),ChunkServer2(包含子表A2、A3),ChunkServer3(包含子表A1、A2),其
    8 u5 P) d7 r( q& n0 ?: U中,A1和A2分别为10MB,A3为256MB。RootServer扫描RootTable后发现A1和A2满足
    . L& ^& w3 E4 _子表合并条件,首先发起子表迁移,假设将A1迁移到ChunkServer2,使得A1和A2在! e- u; W$ K' s5 i$ o- O9 c0 M! D
    相同的ChunkServer上,接着分别向ChunkServer2和ChunkServer3发起子表合并命令。
    % `8 w' Z% O8 v% ^子表合并完成以后,子表分布情况为:ChunkServer1(包含子表A3),
    5 P+ A9 ^3 h8 \2 ~6 P6 {8 w' PChunkServer2(包含子表A4(A1,A2),A3),ChunkServer3(包含子表A4(A1,
    : V/ }6 D' m6 V: ~# UA2)),其中,A4是子表A1和A2合并后的结果。
    - v' B$ ~  j9 j- B# F每个子表包含多个副本,只要某一个副本合并成功,OceanBase就认为子表合并- A! Q8 X3 C+ }2 ^& F6 j
    成功,其他合并失败的子表将通过垃圾回收机制删除掉。8 t! F' Z9 {$ S0 R, p
    9.2.4 UpdateServer选主
    " [+ \( O) i8 Y* J- V3 G为了确保一致性,RootServer需要确保每个集群中只有一台UpdateServer提供写
    . K. n9 u  x, b! `& f9 e服务,这个UpdateServer称为主UpdateServer。
    ) ]3 Z: o5 C4 b9 CRootServer通过租约(Lease)机制实现UpdateServer选主。主UpdateServer必须持
    ; L1 d, S( z/ w有RootServer的租约才能提供写服务,租约的有效期一般为3~5秒。正常情况下,0 V. L$ s, o3 X8 Z
    RootServer会定期给主UpdateServer发送命令,延长租约的有效期。如果主6 o. B6 ~- j+ V" W- P7 a# c/ @1 A
    UpdateServer出现异常,RootServer等待主UpdateServer的租约过期后才能选择其他的4 I5 }3 |) t1 d/ g. k: d; w, g7 @
    UpdateServer为主UpdateServer继续提供写服务。
    2 n+ M; c; |2 w  p. n2 C4 lRootServer可能需要频繁升级,升级过程中UpdateServer的租约将很快过期,系" n3 q7 g/ |/ |& r1 q
    统也会因此停服务。为了解决这个问题,RootServer设计了优雅退出的机制,即4 W% ^9 T( z# P/ [3 X. d& D; c/ E, Y+ J
    RootServer退出之前给UpdateServer发送一个有效期超长的租约(比如半小时),承5 Y2 S0 E+ ]/ @$ s
    诺这段时间不进行主UpdateServer选举,用于RootServer升级。代码如下:2 r6 t' H. G; T1 g/ s3 H
    enum ObUpsStatus
    - g" p; Z( }/ W7 O" b* c+ P2 h{
    ! ]% x4 f- R9 J; jUPS_STAT_OFFLINE=0,//UpdateServer已下线- H, {5 [- O/ P. u( f0 j! B
    UPS_STAT_NOTSYNC=1,//UpdateServer为备机且与主UpdateServer不同步
    / C* {" H0 `+ a* E& ~UPS_STAT_SYNC=2,//UpdateServer为备机且与主UpdateServer同步
    ) ^4 B$ a" _4 l3 n# e7 T* CUPS_STAT_MASTER=3,//UpdateServer为主机( o4 _' v* T3 y/ }5 B
    };0 D3 S; f1 ?. l9 x  h
    //RootServer中记录UpdateServer信息的结构( I8 B& e* F* M6 i5 g8 [
    class ObUps
    # H9 E! Y9 E( R& G% m, \! j' H{
    ( b# d& N& i  N: y4 O. ZObServer addr_;//UpdateServer地址
    - z" t! k' a. K: f/ j. |int32_t inner_port_;//UpdateServer内部端口
      p& J5 b0 _) X9 [; Iint64_t log_seq_num_;//UpdateServer的日志号8 S/ w& X* ?1 e" B3 T9 w) b8 L
    int64_t lease_;//UpdateServer的租约
    " X) M/ y& ^& Q' LObUpsStatus stat_;//UpdateServer状态! ~0 ?# D0 Z9 I5 f
    };
    . V/ [7 L' J# _class ObUpsManager
    2 y% B. ]& q( d3 w{  J& G. b7 f. B8 U9 Q
    public:* s7 ^8 ~4 v6 {) b* J* w
    //UpdateServer向RootServer注册
    " [9 N' g' H. c7 L! Pint register_ups(const ObServer&addr,int32_t inner_port,int64_t
    . c* U. \; E$ v/ |6 L! dlog_seq_num,int64_t lease,const char*server_version);
    8 h  r& X# m* F. L7 m% j//检查所有UpdateServer的租约,RootServer内部有专门的线程会定时调用该函数! ~$ A/ x- E3 d' \
    int check_lease();
    , K3 a0 t* q2 I+ W# A" }//RootServer给UpdateServer发送租约
    6 l- B/ B. r$ m& g" J! y" Pint grant_lease();
    9 B6 p7 ^% K4 G8 o8 T//RootServer给UpdateServer发送超长租约
    ' S" P; G$ U, V( p; Q  P1 j3 y9 hint grant_eternal_lease();
    4 y# m- o( [2 {# j$ P  nprivate:
    % P7 f  H+ o* @2 w! bObUps ups_array_[MAX_UPS_COUNT];& ], O  C$ s+ E# y  c/ f
    int32_t ups_master_idx_;' E- s8 M* h7 Y2 |: w) m
    };
    7 R9 ]; t) o; i% y: r2 o) d, ERootServer模块中有一个ObUpsManager类,它包含一个数组ups_array_,其中的) y( y2 v; R4 J, d% z( u1 r
    每个元素表示一个UpdateServer,ups_master_idx_表示主UpdateServer在数组里的下标。& I6 A4 B6 _; C
    ObUps结构记录了UpdateServer的信息,包括UpdateServer的地址(addr_)以及内部) z1 p% `+ C5 S& P3 I# x
    端口(inner_port_),UpdateServer的状态(stat_,分为& @# {) K. @$ e/ ~
    UPS_STAT_OFFLINE/UPS_STAT_NOTSYNC/UPS_STAT_SYNC/UPS_STAT_MASTER
    ( m' }) y# _( ^+ i/ M6 m3 Q这四种),UpdateServer的日志号(log_seq_num_)以及租约(lease_)。
    ) i# T  m! \- p5 FUpdateServer首先通过register_ups向RootServer注册,将它的信息告知3 m$ O7 z2 W9 C! O. K
    RootServer。一段时间之后,RootServer会从所有注册的UpdateServer中选取一台日志( y$ O3 x% h4 ]0 j; @" Y7 L
    号最大的作为主UpdateServer。ObUpsManager类中还有一个check_lease函数,由
    7 W' Q. z; N; o' uRootServer内部线程定时调用,如果发现UpdateServer的租约快要过期,则会通过
    7 f( p4 ?  X" o2 K* \$ @grant_lease给UpdateServer延长租约。如果发现主UpdateServer的租约已经失效,则会/ T) a' `) X6 c. j: l3 }( j
    从所有Update-Server中选择一个日志号最大的UpdateServer作为新的主UpdateServer。
    ) |# a6 R1 X, w; P另外,Root-Server还可以通过grant_eternal_lease给UpdateServer发送超长租约。' _# u( M! U. l' X- K+ `. ~
    9.2.5 RootServer主备
    / O( S. n% Q/ O8 F* H* K7 T每个集群一般部署一主一备两台RootServer,主备之间数据强同步,即所有的操
    2 Y% W3 f* J4 l! K& d作都需要首先同步到备机,接着修改主机,最后才能返回操作成功。+ [- k% `- O7 F! W2 U. P; M' t! g% ]" Z
    RootServer主备之间需要同步的数据包括:RootTable中记录的子表分布信息、
    - G+ ^; s' p7 s; J! f% sChunkServerManager中记录的ChunkServer机器变化信息以及UpdateServer机器信息。
    " W8 ^9 L6 N9 y- O2 l: @子表复制、负载均衡、合并、分裂以及ChunkServer/UpdateServer上下线等操作都会+ z& k6 o( u, e& _' {
    引起RootServer内部数据变化,这些变化都将以操作日志的形式同步到备
    8 P# w* Z2 S* U" v7 w" GRootServer。备RootServer实时回放这些操作日志,从而与主RootServer保持同步。! z8 ^* G0 G5 o/ [2 V
    OceanBase中的其他模块,比如ChunkServer/UpdateServer,以及客户端通过, B: M) ?2 F/ h4 |: S- N
    VIP(Virtual IP)访问RootServer,正常情况下,VIP总是指向主RootServer。当主
    4 I& Q  U3 h, S8 p. T: WRootServer出现故障时,部署在主备RootServer上的Linux HA(heartbeat,心跳),软# M. h0 `% ]8 D& M! D
    件能够检测到,并将VIP漂移到备RootServer。Linux HA软件的核心包含两个部分:
    # D: N8 c. y' e3 g6 k心跳检测部分和资源接管部分,心跳检测部分通过网络链接或者串口线进行,主备
    ' f, Z- d# i) u$ W: a9 URootServer上的心跳软件相互发送报文来告诉对方自己当前的状态。如果在指定的时
    8 n! E; a0 s' Y" Y3 v4 F; X间内未收到对方发送的报文,那么就认为对方失败,这时需启动资源接管模块来接
      B5 \8 p! m" F$ A管运行在对方主机上的资源,这里的资源就是VIP。备RootServer后台线程能够检测" f" p  q$ ~8 S  T" g: I
    到VIP漂移到自身,于是自动切换为主机提供服务。
    & F, U' H1 S) t4 v
    / E1 j8 h. Z5 t+ b3 ^' @2 l# ]0 q( w) v7 Z( U8 [
    回复

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2024-4-29 05:51 , Processed in 0.126330 second(s), 29 queries .

    Powered by Javazx

    Copyright © 2012-2022, Javazx Cloud.

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