java自学网VIP

Java自学网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 2797|回复: 0

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

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

    [LV.Master]出神入化

    2096

    主题

    3754

    帖子

    6万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    66788

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

    发表于 2017-3-6 14:39:35 | 显示全部楼层 |阅读模式
    9.3 UpdateServer实现机制
    ; }4 C2 Z% C3 UUpdateServer用于存储增量数据,它是一个单机存储系统,由如下几个部分组& X8 n; G" I# f
    成:0 F( @" f4 A% k
    ●内存存储引擎,在内存中存储修改增量,支持冻结以及转储操作;9 @* O, v. U5 d( r* _
    ●任务处理模型,包括网络框架、任务队列、工作线程等,针对小数据包做了专; m- Q6 O! ~1 ?" k3 I
    门的优化;/ _8 l7 t- H5 Q4 N* G
    ●主备同步模块,将更新事务以操作日志的形式同步到备UpdateServer。! Y5 |' |4 E; C" d
    UpdateServer是OceanBase性能瓶颈点,核心是高效,实现时对锁(例如,无锁
    8 _3 K$ e  v: _! J% K/ x数据结构)、索引结构、内存占用、任务处理模型以及主备同步都需要做专门的优
    : e2 `! e  L# h6 ]$ b9 `/ J+ K化。' `0 o7 J0 H! g/ w
    9.3.1 存储引擎
    ' B% g1 D2 i0 ]3 vUpdateServer存储引擎如图9-3所示。
    0 _- W8 u% K& [% E9 l6 ?图 9-3 UpdateServer存储引擎; g0 H8 N* ^0 c  h$ ?: [  E
    UpdateServer存储引擎与6.1节中提到的Bigtable存储引擎看起来很相似,不同点
    & R5 H1 Y# a6 v  m1 T在于:
    2 ~; x3 K4 W! \●UpdateServer只存储了增量修改数据,基线数据以SSTable的形式存储在7 J. t: J  [9 |) P: ?* S
    ChunkServer上,而Bigtable存储引擎同时包含某个子表的基线数据和增量数据;
    ) A* T( d2 `; L. ^- g8 j●UpdateServer内部所有表格共用MemTable以及SSTable,而Bigtable中每个子表( b0 x, x( `. B" l( F- V
    的MemTable和SSTable分开存放;; y, S# r# i- w
    ●UpdateServer的SSTable存储在SSD磁盘中,而Bigtable的SSTable存储在 GFS& I+ ~9 R  J5 k. C5 Q4 S4 E
    中。' s2 T3 M8 v+ E$ p
    UpdateServer存储引擎包含几个部分:操作日志、MemTable以及SSTable。更新( s; _, m' A$ H5 L  F1 N1 D: `9 w
    操作首先记录到操作日志中,接着更新内存中活跃的MemTable(Active# t7 \+ W; d, N# W
    MemTable),活跃的MemTable到达一定大小后将被冻结,称为Frozen MemTable,同- F. G" h7 d% v2 N9 i0 l
    时创建新的Active MemTable。Frozen MemTable将以SSTable文件的形式转储到SSD磁1 n( p* ]# G$ D  _3 s, J" ]
    盘中。
    8 ^0 u& H2 R0 s1.操作日志
    2 u. w% U6 B. |9 s2 M& UOceanBase中有一个专门的提交线程负责确定多个写事务的顺序(即事务id),+ g: V; g" Z/ P/ H
    将这些写事务的操作追加到日志缓冲区,并将日志缓冲区的内容写入日志文件。为* D% h) Y% `4 X/ Z, u2 R" C3 M
    了防止写操作日志污染操作系统的缓存,写操作日志文件采用Direct IO的方式实现:
    ' R' o( G/ a+ x* H& p4 V" w6 A! Uclass ObLogWriter
    ! ^4 F* v- F, ], f5 e  e{
    5 d. @' H8 ^: M* H: rpublic:
    ( U$ a; |7 _% b//write_log函数将操作日志存入日志缓冲区1 k/ f& s4 ~6 f3 r4 a' v! K
    int write_log(const LogCommand cmd,const char*log_data,const int64_t0 [+ E. e2 u+ [: n, C/ T
    data_len);/ u( ], [* L0 g# r9 x+ D8 U
    //将日志缓冲区中的日志先同步到备机再写入主机磁盘/ k1 d% g  B! F5 @
    int flush_log(LogBuffer&tlog_buffer,const bool sync_to_slave=true,const bool
    , Q' F$ l- F- Y3 zis_master=true);1 h7 m$ z- w9 [3 @0 ^
    };! Y- X' t3 K; S$ [
    每条日志项由四部分组成:日志头+日志序号+日志类型(LogCommand)+日志
    - n3 A) h* w) ?6 G7 v6 B内容,其中,日志头中记录了每条日志的校验和(checksum)。ObLogWriter中的* A0 S* Y: o% \2 ~5 H
    write_log函数负责将操作日志拷贝到日志缓冲区中,如果日志缓冲区已满,则向调8 B" K; t5 O* P5 R
    用者返回缓冲区不足(OB_BUF_NOT_ENOUGH)错误码。接着,调用者会通过
    % Z2 e$ |$ O( p. _6 p7 |0 q" vflush_log将缓冲区中已有的日志内容同步到备机并写入主机磁盘。如果主机磁盘的最8 h5 B3 ?2 u. G# E3 p, V
    后一个日志文件超过指定大小(默认为64MB),还会调用switch_log函数切换日志
    / N9 j/ ?& f6 x7 O文件。为了提高写性能,UpdateServer实现了成组提交(Group Commit)技术,即首
    3 C" u6 a4 b: U9 N$ K; y& ?9 L先多次调用write_log函数将多个写操作的日志拷贝到相同的日志缓冲区,接着再调
    / E3 S9 \& u% m用flush_log函数将日志缓冲区中的内容一次性写入到日志文件中。
      n  |6 j' H: ]) `5 `2.MemTable
    9 U0 q" d. w3 `% u# dMemTable底层是一个高性能内存B树。MemTable封装了B树,对外提供统一的读6 a# }  A6 ]9 ]) e
    写接口。' ~/ a9 ?& q  v' t& o
    B树中的每个叶子节点对应MemTable中的一行数据,key为行主键,value为行操
      N' c8 [4 g" |# {- u* R% b; S1 J% q作链表的指针。每行的操作按照时间顺序构成一个行操作链表。' m9 T2 q3 |( \$ |* ]* k! J
    如图9-4所示,MemTable的内存结构包含两部分:索引结构以及行操作链表,索1 E) W  j9 j5 U$ y3 X  W9 W7 P
    引结构为9.1.2节中提到的B树,支持插入、删除、更新、随机读取以及范围查询操
    2 p! Q, H4 q+ i& {; ]3 K6 b* [作。行操作链表保存的是对某一行各个列(每个行和列确定一个单元,称为Cell)的% D8 n# ]2 C; x, C, y
    修改操作。
    & c( r( L; B: m+ \图 9-4 MemTable的内存结构
    , e# F: l9 Y# J) L$ I/ a6 ~例9-3 对主键为1的商品有3个修改操作,分别是:将商品购买人数修改为
    - d" z' P# r4 Q8 O- ^7 c100,删除该商品,将商品名称修改为“女鞋”,那么,该商品的行操作链中将保存三
    3 c, J- p. O% ?2 L" O, z个Cell,分别为:<update,购买人数,100>、<delete,*>以及<update,商品# h7 p/ ^* i' G3 n
    名,“女鞋”>。5 L1 r4 W; T( w
    MemTable中存储的是对该商品的所有修改操作,而不是最终结果。另外,
    3 `& q. T% R+ w* U1 ^) @MemTable删除一行也只是往行操作链表的末尾加入一个逻辑删除标记,即<delete,
    " e. A! `5 u( Y* u; T- v/ ^; e- M*>,而不是实际删除索引结构或者行操作链表中的行内容。
    ! i5 w' u+ }7 a- YMemTable实现时做了很多优化,包括:' J7 p( }. F: j7 G% J
    ●哈希索引:针对主要操作为随机读取的应用,MemTable不仅支持B树索引,还$ M- H! x. }$ c0 g# M4 y, ~
    支持哈希索引,UpdateServer内部会保证两个索引之间的一致性。
    ! d, l9 i. s/ Q●内存优化:行操作链表中每个cell操作都需要存储操作列的编号
    4 I; E+ v' r+ u5 Z9 I8 w/ h(column_id)、操作类型(更新操作还是删除操作)、操作值以及指向下一个cell操  m5 n  ]" }" S4 a
    作的指针,如果不做优化,内存膨胀会很大。为了减少内存占用,MemTable实现时' [" E! Z  h7 @5 J( Q0 |* V
    会对整数值进行变长编码,并将多个cell操作编码后序列到同一块缓冲区中,共用一3 Y) n( [; a, i1 F2 I2 X  {9 ?
    个指向下一批cell操作缓冲区的指针:
    / w1 {! c( Q" d! W3 Nstruct ObCellMeta
    3 I) w! M6 f+ \  v{
    ! F* P9 d2 D) B' b' f9 i0 xconst static int64_t TP_INT8=1;//int8整数类型& k; [- R' h+ c1 `! y. k+ |
    const static int64_t TP_INT16=2;//int16整数类型( C! z- x; N" n) |3 x8 n  z
    const static int64_t TP_INT32=3;//int32整数类型4 Q% Q) E8 M, u; t1 E
    const static int64_t TP_INT64=4;//int64整数类型
    ) M8 L! q0 F7 C8 r* t8 {! D0 bconst static int64_t TP_VARCHAR=6;//变长字符串类型, u& W$ k% v( H/ g# z0 X. ^4 ?
    const static int64_t TP_DOUBLE=13;//双精度浮点类型
    & L" _$ O4 _# a1 D& m1 E, q* I4 Bconst static int64_t TP_ESCAPE=0x1f;//扩展类型" j5 d7 k( u+ _' F! s7 f
    const static int64_t ES_DEL_ROW=1;//删除行操作* ~) ^: m, D4 k) ]8 f$ m
    };* n0 G6 e7 [! u2 \; e7 n
    class ObCompactCellWriter5 ]" h$ J+ u* T* V( m& L
    {( l. x6 G$ e5 y- H; u3 a2 N+ u. P
    public:
    & j5 E3 h' h% Y//写入更新操作,存储成压缩格式4 g. c% J8 h, p1 T, v6 D: o
    int append(uint64_t column_id,const ObObj&value);
    2 z2 I) l1 H% N) G8 r//写入删除操作,存储成压缩格式
    1 w+ O5 r9 F! @- oint row_delete();
    * c7 N& d* I& {0 [/ x0 w1 C, K* H- s};
    ( J" V( p  Q7 X+ u1 \0 ~MemTable通过ObCompactCellWriter来将cell操作序列化到内存缓冲区中,如果为
    : }0 o& n  m# H更新操作,调用append函数;如果为删除操作,调用row_delete函数。更新操作的存1 N0 v2 j4 B8 m4 Q
    储格式为:数据类型+值+列ID,TP_INT8/TP_INT16/TP_INT32/TP_INT64分别表示8
    ; X8 a+ Y0 U% ]% d! a4 Q$ |位/16位/32位/64位整数类型,TP_VARCHAR表示变长字符串类型,TP_DOUBLE表示
      q$ r' |, k" j9 s" Q双精度浮点类型。删除操作为扩展操作,其存储格式为:
    ' |8 B- k. A2 q7 FTP_ESCAPE+ES_DEL_ROW。例9-3中的三个Cell:<update,购买人数,100>、<. T# Y% G9 i8 {1 ?: p
    delete,*>以及<update,商品名,“女鞋”>在内存缓冲区的存储格式为:
    7 F1 A6 e7 b" {$ n- @& [6 t第1~3字节表示第一个Cell,即<update,购买人数,100>;第4~5字节表示第$ R3 R6 s$ U* K( X2 P
    二个cell,即<delete,*>;第6~8字节表示第三个Cell,即<update,商品名,“女
    ' I! {2 c5 c9 r鞋”>。
    , m& a# y3 ^4 y$ t0 g+ iMemTable的主要对外接口可以归结如下:, c' @# G/ P( ]
    //开启一个事务
    * m, s% N5 q& ?+ b( Y//@param[in]trans_type事务类型,可能为读事务或者写事务
    " P+ f) k2 ?( O: \( ^) s6 m7 ^; g//@param[out]td返回的事务描述符
    ! u7 K4 U. d" m, _! Q9 A+ Cint start_transaction(const TETransType trans_type,MemTableTransDescriptor&$ }! K" i+ T0 V
    td);% t1 E# Z" u. q2 ^* c/ _
    //提交或者回滚一个事务
    9 {/ A( q2 D* K3 E- F' b5 H6 k( L//@param[in]td事务描述符7 @  I8 X) t3 @/ A
    //@param[in]rollback是否回滚,默认为false
    0 Y0 |& H- ^6 l  h: P5 K1 j  z( L" w! Rint end_transaction(const MemTableTransDescriptor td,bool rollback=false);. E6 d6 ?3 p/ Q% w: z
    //执行随机读取操作,返回一个迭代器
    & Z7 I% s, {" C  v0 n" t" v//@param[in]td事务描述符7 F! b# r) s  g/ `, ?# y
    //@param[in]table_id表格编号/ R2 o& x& }4 h" u- A
    //@param[in]row_key待查询的主键9 c( y$ M1 _: B; |  [9 v& k- U  k
    //@param[out]iter返回的迭代器
    # J8 J+ S: h+ w8 ?4 b9 eint get(const MemTableTransDescriptor td,const uint64_t table_id,const
    . Y0 J/ w9 Y# k" iObRowkey&row_key,MemTableIterator&iter);- o) [2 J. P. Y) V! `  u- d
    //执行范围查询操作,返回一个迭代器' n% W/ a+ J! ~* n! f; c  X
    //@param[in]td事务描述符
    - S7 c4 \. ^2 ?6 e( n5 C# a0 l% A//@param[in]range查询范围,包括起始行、结束行,开区间或者闭区间4 {/ M+ T, G1 e' p3 u( q( s! ^: }
    //@param[out]iter返回的迭代器8 e# t3 S+ p; ]8 n3 |
    int scan(const MemTableTransDescriptor td,const ObRange&
    6 i, ~; H  U- e3 d/ ~; Drange,MemTableIterator&iter);% O7 h# u7 n: {+ R2 B4 n2 D: c9 ^
    //开始执行一次修改操作
    ; D# V% D& @% ~, y: a//@param[in]td事务描述符
    % V" @6 x. \1 Q2 N$ tint start_mutation(const MemTableTransDescriptor td);8 M- L% p% y4 I* C' m4 o& s# {
    //提交或者回滚一次修改操作
    # C: e3 v8 W" H//@param[in]td事务描述符: e  a( _" a, K
    //@param[in]rollback是否回滚0 z- T3 Y) ^; a* t% ~$ F# k
    int end_mutation(const MemTableTransDescriptor td,bool rollback);6 z" Z7 _2 A( m" Y. X2 g8 _
    //执行修改操作6 f, c2 T5 @, [3 d$ b3 n
    //@param[in]td事务描述符
    - p% R: u! j- i( `- a//@param[in]mutator修改操作,包含一个或者多个对多个表格的cell操作: V, Z4 l& D5 u; G/ y+ Z
    int set(const MemTableTransDescriptor td,ObUpsMutator&mutator);
    * D2 l" o' v# D0 l* Z+ U对于读事务,操作步骤如下:( u. @& O  h( C
    1)调用start_transaction开始一个读事务,获得事务描述符;
    / M1 r0 c8 }( E9 v" _. s9 ^! J2)执行随机读取或者扫描操作,返回一个迭代器;接着可以从迭代器不断迭代
    . @$ Y' `) b  Z2 ?7 A1 m数据;) m, b# v( v# f
    3)调用end_transaction提交或者回滚一个事务。4 Q- K$ `. P3 I9 m) k# j0 E
    class MemTableIterator
      k, {5 B/ n# U/ J" H3 s5 v  J: Q) O( r{" f+ G# G) Q% M' ?6 F; E/ O2 x
    public:
      E) w! B( c% u8 g4 F: {2 Z6 l//迭代器移动到下一个cell8 [9 W' x" v! V; K
    int next_cell();
    : Z5 [- N) x# [//获取当前cell的内容9 }9 r9 f7 T7 v" B2 N: Z. q8 K
    //@param[out]cell_info当前cell的内容,包括表名(table_id),行主键(row_
    ; [9 ~: H% q* _1 Z% f& Z0 k6 ykey),列编号(column_id)以及列值(column_value). k1 a. |6 j+ v# S$ W) u" N
    int get_cell(ObCellInfo**cell_info);
    - e$ m: k3 T- O3 ^5 k$ A//获取当前cell的内容
    " V2 T) l. L" F4 @1 P//@param[out]cell_info当前cell的内容
    . V7 k; x! _& U, G% x$ _7 M5 v//@param is_row_changed是否迭代到下一行
    ! s+ S5 q, U" l. v; oint get_cell(ObCellInfo**cell_info,bool*is_row_changed);
    ' X+ @9 t% ]" V' P};
    & V, ^( S- E2 `1 S! s! ~5 |读事务返回一个迭代器MemTableIterator,通过它可以不断地获取下一个读到的+ z. D: _0 U9 @1 G- |6 P4 R
    cell。在例9-3中,读取编号为1的商品可以得到一个迭代器,从这个迭代器中可以读
    9 G0 m) h; n! Y3 C出行操作链中保存的3个Cell,依次为:<update,购买人数,100>,<delete,*
    % ^9 q( ~, V" f9 F4 W( q6 @1 ^>,<update,商品名,“女鞋”>。- h/ A2 Q/ @$ [
    写事务总是批量执行,步骤如下:
    3 `" {9 `2 g0 s  p5 p2 {* x/ p1)调用start_transaction开始一批写事务,获得事务描述符;
    7 T3 F. n4 A0 J" I2)调用start_mutation开始一次写操作;8 D2 P& h* L: z, W% H2 E' S7 C
    3)执行写操作,将数据写入到MemTable中;
    3 c0 x/ S" j* r* i0 U+ F4)调用end_mutation提交或者回滚一次写操作;如果还有写事务,转到步骤
    ( M4 w  _, r1 c0 U- N( r2);
    4 Z: D/ n. p* M! K) f9 \8 n2 d5)调用end_transaction提交写事务。6 [2 N/ f- v' l
    3.SSTable4 `3 O: V# h$ ]# e! V0 L
    当活跃的MemTable超过一定大小或者管理员主动发起冻结命令时,活跃的- q! u; N/ s0 R0 p4 v! W' j( J' o
    MemTable将被冻结,生成冻结的MemTable,并同时以SSTable的形式转储到SSD磁盘9 k* A$ K+ s3 w0 e: g& F) D5 R7 S) m4 J
    中。
    , X! s4 m- u1 \5 j5 j$ ]6 gSSTable的详细格式请参考9.4节ChunkServer实现机制,与ChunkServer中的
    1 [& B4 \) I+ Z9 B3 r+ q& w. ASSTable不同的是,UpdateServer中所有的表格共用一个SSTable,且SSTable为稀疏格# m3 l. o5 o! x3 Q1 N
    式,也就是说,每一行数据的每一列可能存在,也可能不存在修改操作。
    6 j& ?2 c. Y6 B+ @  Q另外,OceanBase设计时也尽量避免读取UpdateServer中的SSTable,只要内存足: f  C+ j& _6 C: L$ w5 |
    够,冻结的MemTable会保留在内存中,系统会尽快将冻结的数据通过定期合并或者
      Q8 I  t! d3 O/ v  |数据分发的方式转移到ChunkServer中去,以后不再需要访问UpdateServer中的
    * T+ }, D* [5 {  pSSTable数据。
    - k0 S2 E$ P/ _6 s" D0 o; E! m# L当然,如果内存不够需要丢弃冻结MemTable,大量请求只能读取SSD磁盘,
      G- Z$ t( W* R; |UpdateServer性能将大幅下降。因此,希望能够在丢弃冻结MemTable之前将SSTable" A- V1 b- p- t- \7 ]* V+ ~2 {
    的缓存预热。- Q- o* x6 Y9 D, z. O
    UpdateServer的缓存预热机制实现如下:在丢弃冻结MemTable之前的一段时间7 G& d) \- _) X" G' Q1 K
    (比如10分钟),每隔一段时间(比如30秒),将一定比率(比如5%)的请求发给
    3 F- p$ u+ x9 q$ F- A) E. gSSTable,而不是冻结MemTable。这样,SSTable上的读请求将从5%到10%,再到, y. u3 r" E" V. n; ]3 X. U
    15%,依次类推,直到100%,很自然地实现了缓存预热。$ V6 F* V* U8 F" I! j
    9.3.2 任务模型
    * \7 e% Z9 E! g任务模型包括网络框架、任务队列、工作线程,UpdateServer最初的任务模型基* {, ~6 c* b6 ]! G8 S1 `7 |$ F
    于淘宝网实现的Tbnet框架(已开源,见http://code.taobao.org/p/tb-common-: K: `0 V5 s2 V% ]" w, R- `
    utils/src/trunk/tbnet/)。Tbnet封装得很好,使用比较方便,每秒收包个数最多可以达
    $ ?! R2 ^* g/ L* y( `& d7 h到接近10万,不过仍然无法完全发挥UpdateServer收发小数据包以及内存服务的特. R4 h8 {& f. C( K9 H7 c
    点。OceanBase后来采用优化过的任务模型Libeasy,小数据包处理能力得到进一步提
    5 R8 P7 r' l) h$ G$ {% i, X升。
    ! _' y: f- n# B$ _# @% V( O2 y1.Tbnet' s8 w% [& I% I/ l
    如图9-5所示,Tbnet队列模型本质上是一个生产者—消费者队列模型,有两个线' Z2 P  c2 h+ h4 h% E2 \, l
    程:网络读写线程以及超时检查线程,其中,网络读写线程执行事件循环,当服务% d/ q9 n& t% ^( _+ \
    器端有可读事件时,调用回调函数读取请求数据包,生成请求任务,并加入到任务) U  r3 Y! S+ a' V3 ~
    队列中。工作线程从任务队列中获取任务,处理完成后触发可写事件,网络读写线
    . c" c9 v* }! y6 s# K程会将处理结果发送给客户端。超时检查线程用于将超时的请求移除。
    4 {$ a3 K! a3 b2 V- R! m# q( V图 9-5 Tbnet队列模型6 G" k: Z  g2 K: y! c
    Tbnet模型的问题在于多个工作线程从任务队列获取任务需要加锁互斥,这个过' p( b, _1 h$ _& P4 U" L, h
    程将产生大量的上下文切换(context switch),测试发现,当UpdateServer每秒处理
    / a; ~8 P6 G: A: K包的数量超过8万个时,UpdateServer每秒的上下文切换次数接近30万次,在测试环
    # P  H3 E" p, r境中已经达到极限(测试环境配置:Linux内核2.6.18,CPU为2*Intel Nehalem6 a+ `6 f3 F# b. G" Z
    E5520,共8核16线程)。
    * i2 N) F' M; l+ T2.Libeasy6 ?) Y: O  @/ d; Z$ v8 j
    为了解决收发小数据包带来的上下文切换问题,OceanBase目前采用Libeasy任务
    * s( z+ r1 e& a! Y5 C模型。Libeasy采用多个线程收发包,增强了网络收发能力,每个线程收到网络包后
    4 {  h. R1 L3 n# v立即处理,减少了上下文切换,如图9-6所示。; @* t6 A8 u9 R9 \' z$ P' \
    图 9-6 Libeasy任务模型- i! O% p' k: U- f
    UpdateServer有多个网络读写线程,每个线程通过Linux epool监听一个套接字集
    " ]$ ^( E; M2 ?5 ~/ v% w4 s合上的网络读写事件,每个套接字只能同时分配给一个线程。当网络读写线程收到
    $ I/ V5 l% v9 }8 T( P网络包后,立即调用任务处理函数,如果任务处理时间很短,可以很快完成并回复
    4 r# i# V" N' A( {) g, [4 [* g客户端,不需要加锁,避免了上下文切换。UpdateServer中大部分任务为短任务,比' `8 }8 r# L( X/ l
    如随机读取内存表,另外还有少量任务需要等待共享资源上的锁,可以将这些任务% u/ j/ V: E" F% |( T% D/ q* V
    加入到长任务队列中,交给专门的长任务处理线程处理。) w6 \+ E) ]0 x" v/ C1 d
    由于每个网络读写线程处理一部分预先分配的套接字,这就可能出现某些套接" D, ~) w5 _; V# B
    字上请求特别多而导致负载不均衡的情况。例如,有两个网络读写线程thread1和
    0 A" d- Q$ S" q, S7 b. d0 s8 dthread2,其中thread1处理套接字fd1、fd2,thread2处理套接字fd3、fd4,fd1和fd2上每5 D- J4 r! _7 C, Z; L! r
    秒1000次请求,fd3和fd4上每秒10次请求,两个线程之间的负载很不均衡。为了处理3 x+ s9 K( d) ^
    这种情况,Libeasy内部会自动在网络读写线程之间执行负载均衡操作,将套接字从, B7 j$ R" q7 H- m/ x3 T
    负载较高的线程迁移到负载较低的线程。% k+ N6 S" K4 U5 n. M3 Y4 b
    9.3.3 主备同步4 |, `" i# \+ t8 [+ \
    8.4.1节已经介绍了UpdateServer的一致性选择。OceanBase选择了强一致性,主" G# {7 C. V& y
    UpdateServer往备UpdateServer同步操作日志,如果同步成功,主UpdateServer操作本
    - y& c. ?( v; ]地后返回客户端更新成功,否则,主UpdateServer会把备UpdateServer从同步列表中7 y0 F  S. s$ V$ ]9 e  p
    剔除。另外,剔除备UpdateServer之前需要通知RootServer,从而防止RootServer将不
    . d8 G1 C: r3 u9 I一致的备UpdateServer选为主UpdateServer。9 B  b( f6 m. z$ _% ~9 t
    如图9-7所示,主UpdateServer往备机推送操作日志,备UpdateServer的接收线程
    4 x8 x% ?. p1 X7 L; a6 r5 z接收日志,并写入到一块全局日志缓冲区中。备UpdateServer只要接收到日志就可以
    8 `! P0 M. q. Z" _- x回复主UpdateServer同步成功,主UpdateServer接着更新本地内存并将日志刷到磁盘2 y3 n$ a  [1 l2 o7 _
    文件中,最后回复客户端写入操作成功。这种方式实现了强一致性,如果主
    4 u2 d/ b$ P. Y) X/ jUpdateServer出现故障,备UpdateServer包含所有的修改操作,因而能够完全无缝地3 T1 R# i* r5 B
    切换为主UpdateServer继续提供服务。另外,主备同步过程中要求主机刷磁盘文件,  U2 r2 G2 G( J* I
    备机只需要写内存缓冲区,强同步带来的额外延时也几乎可以忽略。' d1 ?# U1 T; B+ V# C8 H  S
    图 9-7 UpdateServer主备同步原理
    : a7 C7 q: @+ d8 b& x! Q4 C) ]正常情况下,备UpdateServer的日志回放线程会从全局日志缓冲区中读取操作日
    & u( x$ `1 O5 r4 a& H1 b志,在内存中回放并同时将操作日志刷到备机的日志文件中。如果发生异常,比如0 X( U2 {8 H( Q( z6 C: x! W! T
    备UpdateServer刚启动或者主备之间网络刚恢复,全局日志缓冲区中没有日志或者日1 O" z, A  ^& E4 k
    志不连续,此时,备UpdateServer需要主动请求主UpdateServer拉取操作日志。主, ^, G# t) Q( ~2 ^% Y
    UpdateServer首先查找日志缓冲区,如果缓冲区中没有数据,还需要读取磁盘日志文
    ( q: l) A! e1 v/ o- I. h, A  m件,并将操作日志回复备UpdateServer。代码如下:
    4 X+ P& Y5 V( Y# x* Mclass ObReplayLogSrc1 U5 q6 r/ ?/ K' m3 U
    {
    / m1 F! K5 l0 J; Wpublic:; T; l  \- N, L1 r; G% N# B
    //读取一批待回放的操作日志: w. i" g6 n) V; P+ V- N7 D: Z
    //@param[in]start_cursor日志起始点
    % P* i6 l4 V" r. t//@param[out]end_id读取到的最大日志号加1,即下一次读取的起始日志号# p, i0 Q6 @: H$ D% I
    //@param[in]buf日志缓冲区
    5 `- T3 M  L  e9 ?/ h//@param[in]len日志缓冲区长度
    9 @: M; C! O8 o8 Q, S//@param[out]read_count读取到的有效字节数
    # ?5 b" F/ D/ \+ h0 K  r3 u" [' D" `int get_log(const ObLogCursor&start_cursor,int64_t&end_id,char*buf,const
    : M! }1 k: m- B1 Q$ ~0 P% c- Nint64_t len,int64_t&read_count);
    / [* p+ g/ ^* {& A8 u" ^4 A};
    + p1 F$ T  k( b% `6 {. Aclass ObUpsLogMgr
    2 P$ P3 L# S' l2 ]; I0 U5 Q{* n/ E/ s4 n9 N. m7 N8 C
    public:
    : R7 ?! |/ M& q3 G$ Nenum WAIT_SYNC_TYPE
    : Z8 c" \- w8 }  J{
    7 n) a6 q- g! `WAIT_NONE=0,  J3 n5 a" M5 Y$ q: t! {! y
    WAIT_COMMIT=1,) G6 U5 ]4 L' u' ~$ w! M
    WAIT_FLUSH=2,) _' S2 t# Q& U
    };8 B( h1 f2 D3 ~' k: j1 w
    public:
    ) i6 i  i4 P6 i8 m, W- t* A//备UpdateServer接收主UpdateServer发送的操作日志2 g& ?3 g. M. I1 r& h5 X( V- Z' o
    int slave_receive_log(const char*buf,int64_t len,const int64_t; Q0 {3 \8 p9 u# Y0 l0 X$ X% }
    wait_sync_time_us,const WAIT_SYNC_TYPE wait_event_type);: @+ N/ B% P, s- s9 ?
    //备UpdateServer获取并回放操作日志
    0 N8 U) t  K6 m1 Qint replay_log();% C) l3 A# ~" i
    };
    % M" K6 Y' }' E; H" w$ t备UpdateServer接收到主UpdateServer发送的操作日志后,调用ObUpsLogMgr类的
    $ ^$ J2 [( k: G2 g) ?slave_receive_log将操作日志保存到日志缓冲区中。备UpdateServer可以配置成不等待
    ( h( N) |  w( F  e/ F1 _(WAIT_NONE)、等待提交到MemTable(WAIT_COMMIT)或者等待提交到' b* K: w& B% c/ z1 a8 z
    MemTable且写入磁盘(WAIT_FLUSH)。另外,备UpdateServer有专门的日志回放线
    " h" O8 v& f- |# f! d+ u( a程不断地调用ObUpsLogMgr中的replay_log函数获取并回放操作日志。
    ) h+ L+ C) t2 l- @. j+ @6 D8 Y备UpdateServer执行replay_log函数时,首先调用ObReplayLogSrc的get_log函数读6 Y* Z2 X1 L& _4 K
    取一批待回放的操作日志,接着,将操作日志应用到MemTable中并写入日志文件持" [& m1 f+ r  L
    久化。Get_log函数执行时首先查看本机的日志缓冲区,如果缓冲区中不存在日志起
    . O, J, d9 `+ s) [/ {3 w始点(start_cursor)开始的操作日志,那么,生成一个异步任务,读取主4 `7 a0 T8 p: y/ p  {0 l- t8 M$ V- |
    UpdateServer。一般情况下,slave_receive_log接收的日志刚加入日志缓冲区就被" M& X# M# d, A. M0 P! ]7 O3 a
    get_log读走了,不需要读取主UpdateServer。
    % `- O/ i9 o$ _4 s9 u0 j
    7 P4 t" d$ [  P: I3 H
    0 O7 l9 t# o. Z9 E- ?* [& X4 F
    回复

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2025-4-1 14:27 , Processed in 0.243918 second(s), 32 queries .

    Powered by Javazx

    Copyright © 2012-2022, Javazx Cloud.

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