java自学网VIP

Java自学网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 2692|回复: 0

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

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

    [LV.Master]出神入化

    2040

    主题

    3698

    帖子

    6万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    66476

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

    发表于 2017-3-6 14:39:35 | 显示全部楼层 |阅读模式
    9.3 UpdateServer实现机制/ `3 Y2 ~& E$ ]' t* v) G9 C2 N
    UpdateServer用于存储增量数据,它是一个单机存储系统,由如下几个部分组8 V0 {1 b4 n& T7 {+ v
    成:
    " v; ]% J7 X9 c* z1 P9 \+ ~0 S●内存存储引擎,在内存中存储修改增量,支持冻结以及转储操作;
    # j7 X; q; Q4 v3 {' K3 l●任务处理模型,包括网络框架、任务队列、工作线程等,针对小数据包做了专
    3 H, o( ?9 D$ W& o% a3 s门的优化;1 j0 h1 `# G/ [6 j6 ?
    ●主备同步模块,将更新事务以操作日志的形式同步到备UpdateServer。6 @. f# f! f! F+ c
    UpdateServer是OceanBase性能瓶颈点,核心是高效,实现时对锁(例如,无锁
    ( N+ I8 C( R1 B# l数据结构)、索引结构、内存占用、任务处理模型以及主备同步都需要做专门的优" x/ V& P- D9 w2 u6 D6 N* n
    化。
    1 U$ }# M7 c% s- Y9.3.1 存储引擎
    " P" \) N2 K4 XUpdateServer存储引擎如图9-3所示。
    9 j" {' t3 z* W5 l图 9-3 UpdateServer存储引擎3 F$ U# @- ?4 K6 q
    UpdateServer存储引擎与6.1节中提到的Bigtable存储引擎看起来很相似,不同点: W' m' g! ?9 {% n' x1 ~
    在于:; U6 }6 _/ y$ w3 `+ n+ g
    ●UpdateServer只存储了增量修改数据,基线数据以SSTable的形式存储在) T6 D0 B+ D& J5 W6 ~% y; m( F2 l
    ChunkServer上,而Bigtable存储引擎同时包含某个子表的基线数据和增量数据;
    % [9 N. {8 l8 Y! g' e  n8 S3 r9 T●UpdateServer内部所有表格共用MemTable以及SSTable,而Bigtable中每个子表
    , \- J5 \  m5 M5 F5 F  q7 C1 [的MemTable和SSTable分开存放;5 `& J2 m6 t* o+ B9 P
    ●UpdateServer的SSTable存储在SSD磁盘中,而Bigtable的SSTable存储在 GFS
    0 G! E- v6 M& a( Y/ y中。2 h* t$ R7 l9 N4 w+ v
    UpdateServer存储引擎包含几个部分:操作日志、MemTable以及SSTable。更新
    ' v. w/ Q0 ^% ~) g' w' V# n6 Z/ x操作首先记录到操作日志中,接着更新内存中活跃的MemTable(Active% B) O! ]( i" z1 M, L
    MemTable),活跃的MemTable到达一定大小后将被冻结,称为Frozen MemTable,同
    $ D' m4 r" Q, J. J4 |- r5 N时创建新的Active MemTable。Frozen MemTable将以SSTable文件的形式转储到SSD磁
    5 Q3 u: f4 m4 @0 E) c8 f& h# @) X盘中。
    0 [- @3 y& r, s. ~5 u2 M1.操作日志1 c7 V* Y. d# A, {7 U( h/ ^
    OceanBase中有一个专门的提交线程负责确定多个写事务的顺序(即事务id),5 Y0 w$ ]- s6 s, Y
    将这些写事务的操作追加到日志缓冲区,并将日志缓冲区的内容写入日志文件。为( w$ S3 ]! P: v/ w. s
    了防止写操作日志污染操作系统的缓存,写操作日志文件采用Direct IO的方式实现:
    . X9 l' d  d+ ^class ObLogWriter
    1 H6 B: T. z8 y4 B# I3 `{
    . ]2 f& h: {1 H6 M3 s; Y; S6 tpublic:# y% I( j' A7 R
    //write_log函数将操作日志存入日志缓冲区
    8 C2 r/ u1 E1 R4 gint write_log(const LogCommand cmd,const char*log_data,const int64_t
    ( e- j, q7 W( V; Hdata_len);/ K+ o2 k* ?0 u3 `
    //将日志缓冲区中的日志先同步到备机再写入主机磁盘
    ' a% `+ j# v- j% G0 p1 R/ Gint flush_log(LogBuffer&tlog_buffer,const bool sync_to_slave=true,const bool( V' p- K- X& Y2 e) T9 W
    is_master=true);
    8 T( F4 d* z+ Y/ X};
    4 K1 B5 E; u7 D" \/ @! `5 t每条日志项由四部分组成:日志头+日志序号+日志类型(LogCommand)+日志5 ?! V# p3 N8 |
    内容,其中,日志头中记录了每条日志的校验和(checksum)。ObLogWriter中的
    ) ?; S7 }: W; h+ O0 f& W5 W, G! bwrite_log函数负责将操作日志拷贝到日志缓冲区中,如果日志缓冲区已满,则向调( W0 n" l/ S# W$ p0 T, \
    用者返回缓冲区不足(OB_BUF_NOT_ENOUGH)错误码。接着,调用者会通过
    ( q# j$ ]  a( D' w- I7 P! z5 o1 P" Fflush_log将缓冲区中已有的日志内容同步到备机并写入主机磁盘。如果主机磁盘的最$ R% Q+ a& Q, M6 W
    后一个日志文件超过指定大小(默认为64MB),还会调用switch_log函数切换日志
    " ?  Q1 o8 U: ]8 W9 U2 h% m0 \文件。为了提高写性能,UpdateServer实现了成组提交(Group Commit)技术,即首6 C4 Z- M' [3 |9 M
    先多次调用write_log函数将多个写操作的日志拷贝到相同的日志缓冲区,接着再调
    / x; d1 o3 j' s0 _9 n8 d7 i用flush_log函数将日志缓冲区中的内容一次性写入到日志文件中。1 o" u  l9 R2 l# k2 @
    2.MemTable  V6 x# s- T3 [
    MemTable底层是一个高性能内存B树。MemTable封装了B树,对外提供统一的读
    . Q' `, h: ~2 \5 l% ~) K写接口。( q7 i* m: i8 ]3 g) z. R
    B树中的每个叶子节点对应MemTable中的一行数据,key为行主键,value为行操
    $ b, g2 J' b: A8 W" s* {4 L4 _  s作链表的指针。每行的操作按照时间顺序构成一个行操作链表。- T1 o9 K. m7 r4 f( E$ K9 h5 |* m/ x
    如图9-4所示,MemTable的内存结构包含两部分:索引结构以及行操作链表,索
    % v2 j( m$ M1 {. D* [引结构为9.1.2节中提到的B树,支持插入、删除、更新、随机读取以及范围查询操
    ; G9 N1 A: C3 F3 ?5 q( m5 |  S' ^作。行操作链表保存的是对某一行各个列(每个行和列确定一个单元,称为Cell)的
    6 m3 ~% K7 p8 H- L# |修改操作。
    . f2 D% U6 J: L图 9-4 MemTable的内存结构
    1 D( A0 y) J0 n  J例9-3 对主键为1的商品有3个修改操作,分别是:将商品购买人数修改为: E2 K( l% X4 D: _
    100,删除该商品,将商品名称修改为“女鞋”,那么,该商品的行操作链中将保存三0 d$ g0 a% m: s
    个Cell,分别为:<update,购买人数,100>、<delete,*>以及<update,商品
    7 `" e( V6 @1 _; P% b7 w4 a名,“女鞋”>。
    # D. h9 w' ~0 a  ZMemTable中存储的是对该商品的所有修改操作,而不是最终结果。另外,
    - I# P( q) ?8 ~* J" S- H7 dMemTable删除一行也只是往行操作链表的末尾加入一个逻辑删除标记,即<delete,4 B3 w7 p- i  l  z. d& m
    *>,而不是实际删除索引结构或者行操作链表中的行内容。9 z/ a- F' ?6 A" N9 z" H/ H
    MemTable实现时做了很多优化,包括:' ?0 g! i7 D* T( d
    ●哈希索引:针对主要操作为随机读取的应用,MemTable不仅支持B树索引,还
    1 V1 M( ?( h* }' H& r支持哈希索引,UpdateServer内部会保证两个索引之间的一致性。! [% E4 s# r3 O5 d0 f' K
    ●内存优化:行操作链表中每个cell操作都需要存储操作列的编号
    ; o3 O& Q# N2 m1 a. A(column_id)、操作类型(更新操作还是删除操作)、操作值以及指向下一个cell操2 W( L0 ~1 k8 ^' c+ x- a+ q! r- J
    作的指针,如果不做优化,内存膨胀会很大。为了减少内存占用,MemTable实现时* n& b$ @/ ]% [
    会对整数值进行变长编码,并将多个cell操作编码后序列到同一块缓冲区中,共用一6 `) _/ y9 b. ^' v4 ?$ d
    个指向下一批cell操作缓冲区的指针:
    1 a* I5 ]' Z- astruct ObCellMeta" f) p3 F- x  E% a! ]! e7 l
    {; m! C9 W% t* z. Y- q5 Q; b3 }" m
    const static int64_t TP_INT8=1;//int8整数类型
    $ B0 F* P* D; o! Pconst static int64_t TP_INT16=2;//int16整数类型- A+ W+ p1 R8 ?4 i4 K$ ?( S0 D
    const static int64_t TP_INT32=3;//int32整数类型
      M3 _: x6 ~( k0 {const static int64_t TP_INT64=4;//int64整数类型
    # C* O7 V+ A( a1 mconst static int64_t TP_VARCHAR=6;//变长字符串类型9 N) B  }& |0 z4 ?+ Z$ |4 D9 d' a! x2 [$ b
    const static int64_t TP_DOUBLE=13;//双精度浮点类型
    3 A& N0 ~( r, \const static int64_t TP_ESCAPE=0x1f;//扩展类型8 J; n. z# F6 J6 z
    const static int64_t ES_DEL_ROW=1;//删除行操作$ g9 w' \0 y' f' Y: I) l  i
    };6 I9 \1 c- o) |' B( p, q! ?. ?5 Q. h
    class ObCompactCellWriter) p( _2 ^  g$ L' j' n/ A) I
    {
    . @/ W* B' r  J2 T' Rpublic:, J; Y9 A9 L+ E" A3 U
    //写入更新操作,存储成压缩格式, X: m$ T, B- |" }9 @2 h- d: {- g
    int append(uint64_t column_id,const ObObj&value);2 \! a% t; `9 a6 d$ k, G( g, U
    //写入删除操作,存储成压缩格式+ D! k9 |0 J8 H( U7 J4 k: N
    int row_delete();
    " ~) ~. \; m/ k};
    , P! T) \* ]( ~: |$ V% b4 mMemTable通过ObCompactCellWriter来将cell操作序列化到内存缓冲区中,如果为% t, h) ?- u5 F" ?
    更新操作,调用append函数;如果为删除操作,调用row_delete函数。更新操作的存* I4 h. c: A8 n9 J9 Q! T& K: k8 P
    储格式为:数据类型+值+列ID,TP_INT8/TP_INT16/TP_INT32/TP_INT64分别表示8/ r/ |! y' L5 v" X: c9 s* e6 J
    位/16位/32位/64位整数类型,TP_VARCHAR表示变长字符串类型,TP_DOUBLE表示
    , l' D- P. l6 I: x$ V0 O双精度浮点类型。删除操作为扩展操作,其存储格式为:
    5 w" p, G  c! [( c* ATP_ESCAPE+ES_DEL_ROW。例9-3中的三个Cell:<update,购买人数,100>、<, A" Q$ w; V; z3 }
    delete,*>以及<update,商品名,“女鞋”>在内存缓冲区的存储格式为:3 x! T2 u& U/ K# N
    第1~3字节表示第一个Cell,即<update,购买人数,100>;第4~5字节表示第
    0 k( W8 u0 Y8 E& a9 G2 `二个cell,即<delete,*>;第6~8字节表示第三个Cell,即<update,商品名,“女- r3 L( I$ C0 ~5 @3 J
    鞋”>。
    & Q0 N1 _9 c& @# ~7 xMemTable的主要对外接口可以归结如下:
    - i8 ^  ~, e1 c//开启一个事务( i8 c8 `0 I& y
    //@param[in]trans_type事务类型,可能为读事务或者写事务
    & k& a! U: K. I$ {6 B  m- t  O//@param[out]td返回的事务描述符# z+ A2 D- A2 u' g1 R) S
    int start_transaction(const TETransType trans_type,MemTableTransDescriptor&! E" O/ D3 |0 R- x
    td);
    0 Q+ m1 @  e: h& k' y5 |//提交或者回滚一个事务
    " N8 g8 B; j3 a//@param[in]td事务描述符
    $ G5 r( F5 @3 X//@param[in]rollback是否回滚,默认为false7 ~9 z3 r# I$ e) ^& D; R+ @
    int end_transaction(const MemTableTransDescriptor td,bool rollback=false);. I5 S! @7 @' m( \$ [
    //执行随机读取操作,返回一个迭代器% r) }" U% ]/ z6 j  E) K) h
    //@param[in]td事务描述符4 _7 t& G% {/ S' U! S* d
    //@param[in]table_id表格编号' N! [5 e$ X/ `
    //@param[in]row_key待查询的主键; @+ \! f, @1 v. j  P3 H  q
    //@param[out]iter返回的迭代器
    0 z% L5 g4 ?3 A4 n- H% o2 hint get(const MemTableTransDescriptor td,const uint64_t table_id,const
    ; G, O+ N. g0 b( U/ q1 AObRowkey&row_key,MemTableIterator&iter);. Y; r  t. Q& V% _: r
    //执行范围查询操作,返回一个迭代器
    * C* J' b: K# g4 c, f. f" c9 x//@param[in]td事务描述符- ?* S1 V$ K/ ], Q- d" h, F2 t/ C
    //@param[in]range查询范围,包括起始行、结束行,开区间或者闭区间
    : N+ W5 A4 W  I' i; ~//@param[out]iter返回的迭代器
    $ S! ?: i+ D- _9 d- |: Z$ aint scan(const MemTableTransDescriptor td,const ObRange&0 m3 ^* _* y; C  B3 u
    range,MemTableIterator&iter);- K. B) m9 w& g7 q' `; }
    //开始执行一次修改操作, s0 f+ J& D  g% [; D: W  e) @
    //@param[in]td事务描述符3 C% a7 _# F, o4 T: X
    int start_mutation(const MemTableTransDescriptor td);
    . p: a& l8 ]6 r//提交或者回滚一次修改操作+ U% f  {, V0 z6 E# H
    //@param[in]td事务描述符
    # U) O2 W' F: m  O//@param[in]rollback是否回滚& k5 [5 V- v" Z9 n
    int end_mutation(const MemTableTransDescriptor td,bool rollback);* v3 ~0 _4 `- G. l! |. x
    //执行修改操作" B: O  W: T1 B! G" `
    //@param[in]td事务描述符
    8 F% g& q0 j; q8 w1 K//@param[in]mutator修改操作,包含一个或者多个对多个表格的cell操作) p7 Q! k# S- m+ ?8 M
    int set(const MemTableTransDescriptor td,ObUpsMutator&mutator);+ {/ {* n5 S1 R
    对于读事务,操作步骤如下:
    8 V/ K2 X+ _2 l# b; }1)调用start_transaction开始一个读事务,获得事务描述符;- r& X" m$ V& x+ o" |7 X- _) x# v  E
    2)执行随机读取或者扫描操作,返回一个迭代器;接着可以从迭代器不断迭代
    : e# q4 c" L9 x" K数据;
    + u0 G: |4 _- Q+ E% ~% z3)调用end_transaction提交或者回滚一个事务。, G) }: ?1 u3 a' g
    class MemTableIterator4 \+ R5 @  ^/ u0 l* d) ?
    {5 d; ^( G0 ]. k5 H; e. {  r
    public:
    . a5 F" b( W! P2 x/ @//迭代器移动到下一个cell
    + G3 t: r9 a1 g# cint next_cell();8 Y8 ?: n2 n( _3 y
    //获取当前cell的内容, w) v8 B! t- a# `& `! k
    //@param[out]cell_info当前cell的内容,包括表名(table_id),行主键(row_
    2 R5 G  @+ v; g/ e5 V* o' Fkey),列编号(column_id)以及列值(column_value)' O* |+ }- a# [% u. y2 ^% C! C8 ?. L
    int get_cell(ObCellInfo**cell_info);
    ) j! l; G0 c, L; A//获取当前cell的内容
    7 D! g: U; |  \9 t4 r//@param[out]cell_info当前cell的内容" g0 n1 K% n8 Z% Q, s$ }
    //@param is_row_changed是否迭代到下一行& _4 B7 C& c: J6 `$ c* Q1 f8 O3 Y
    int get_cell(ObCellInfo**cell_info,bool*is_row_changed);9 A0 U: ]" e6 o4 ?
    };
    1 h7 V3 {9 Z* U! d+ V* j读事务返回一个迭代器MemTableIterator,通过它可以不断地获取下一个读到的5 F7 P/ z; E& ~/ I
    cell。在例9-3中,读取编号为1的商品可以得到一个迭代器,从这个迭代器中可以读
    : e1 C7 t' t: d3 e! o* u出行操作链中保存的3个Cell,依次为:<update,购买人数,100>,<delete,*
    0 ~9 ]) i& f( H6 [>,<update,商品名,“女鞋”>。) {8 b$ G# \* @! Q% {' _$ w
    写事务总是批量执行,步骤如下:! c; r( L3 `% Q, ?6 ?2 }1 e" W' Z
    1)调用start_transaction开始一批写事务,获得事务描述符;
    $ C' }9 M- x9 L' V2 u$ k/ p2)调用start_mutation开始一次写操作;
    7 t7 G; J( k4 V" K3)执行写操作,将数据写入到MemTable中;
    * k3 t) h* Z: V4)调用end_mutation提交或者回滚一次写操作;如果还有写事务,转到步骤
    & K% {" e; k' ~2);7 p$ T' {9 o- O/ g
    5)调用end_transaction提交写事务。
    4 L* n4 G  o' x2 D+ X1 H- _3.SSTable
    7 M, }/ _4 l& b- v( |当活跃的MemTable超过一定大小或者管理员主动发起冻结命令时,活跃的. \, `/ I( W; w- d0 d3 B
    MemTable将被冻结,生成冻结的MemTable,并同时以SSTable的形式转储到SSD磁盘3 `, z1 p7 X$ ]
    中。4 V( x* b  A: X
    SSTable的详细格式请参考9.4节ChunkServer实现机制,与ChunkServer中的
    + w% x9 B; I) A6 m/ |( xSSTable不同的是,UpdateServer中所有的表格共用一个SSTable,且SSTable为稀疏格  w' |+ }( d& @, H) d/ p
    式,也就是说,每一行数据的每一列可能存在,也可能不存在修改操作。- Z% F1 O4 w+ E0 L
    另外,OceanBase设计时也尽量避免读取UpdateServer中的SSTable,只要内存足, j- V3 I, Y7 V4 K
    够,冻结的MemTable会保留在内存中,系统会尽快将冻结的数据通过定期合并或者
    $ I0 @3 K8 d* e. q数据分发的方式转移到ChunkServer中去,以后不再需要访问UpdateServer中的0 [) M' l. J( L9 M! K- e
    SSTable数据。" s3 F- h% h2 [5 e( C5 [
    当然,如果内存不够需要丢弃冻结MemTable,大量请求只能读取SSD磁盘,( ?9 I3 h  f5 J  e9 U7 W
    UpdateServer性能将大幅下降。因此,希望能够在丢弃冻结MemTable之前将SSTable
    + w6 P) F9 I5 }' M5 ~5 v$ U: Z- L) b* C的缓存预热。) N+ C! i) l1 A+ k  v
    UpdateServer的缓存预热机制实现如下:在丢弃冻结MemTable之前的一段时间
    , K) F) ^% O8 E  J& o! x, |(比如10分钟),每隔一段时间(比如30秒),将一定比率(比如5%)的请求发给
    + U! d6 `3 S, h/ G% a: N" SSSTable,而不是冻结MemTable。这样,SSTable上的读请求将从5%到10%,再到( L$ y, y& z3 T
    15%,依次类推,直到100%,很自然地实现了缓存预热。
    0 F) F, p# Z- j: \9.3.2 任务模型" f# L& V0 Z/ z1 Y% V4 T
    任务模型包括网络框架、任务队列、工作线程,UpdateServer最初的任务模型基5 {1 K6 Z9 Y; c" F% x5 @
    于淘宝网实现的Tbnet框架(已开源,见http://code.taobao.org/p/tb-common-( z6 A$ @6 m6 h' @, o
    utils/src/trunk/tbnet/)。Tbnet封装得很好,使用比较方便,每秒收包个数最多可以达; z0 d% n+ q# j( I) J8 ]
    到接近10万,不过仍然无法完全发挥UpdateServer收发小数据包以及内存服务的特
    ' d0 N+ h' u( v5 d" Z; Z" E点。OceanBase后来采用优化过的任务模型Libeasy,小数据包处理能力得到进一步提3 ^9 R- d8 Y. C$ W
    升。
    9 J+ {$ J! H; Z+ }! e5 c5 T4 f/ w1 V1.Tbnet5 g% I: P0 k2 t
    如图9-5所示,Tbnet队列模型本质上是一个生产者—消费者队列模型,有两个线
    . V/ [" y: c1 {, h$ s程:网络读写线程以及超时检查线程,其中,网络读写线程执行事件循环,当服务
    4 g5 `% J- q( s* H4 @7 R- u器端有可读事件时,调用回调函数读取请求数据包,生成请求任务,并加入到任务
    $ |: I* h) e! Q5 D+ u队列中。工作线程从任务队列中获取任务,处理完成后触发可写事件,网络读写线; |$ s% w2 @5 r( S0 M8 c/ R' I
    程会将处理结果发送给客户端。超时检查线程用于将超时的请求移除。. V% @1 O* P! R- n/ H3 t
    图 9-5 Tbnet队列模型5 }5 R) d0 {4 D2 ]: N% X, y
    Tbnet模型的问题在于多个工作线程从任务队列获取任务需要加锁互斥,这个过
    " U# U6 X6 K# _- j2 h程将产生大量的上下文切换(context switch),测试发现,当UpdateServer每秒处理2 o9 h& C6 H- Y8 y" Y* ~  |% e
    包的数量超过8万个时,UpdateServer每秒的上下文切换次数接近30万次,在测试环
    5 z' f% k. Q$ m- Z+ h" ]) E境中已经达到极限(测试环境配置:Linux内核2.6.18,CPU为2*Intel Nehalem7 x+ u! J" v( \1 t( y
    E5520,共8核16线程)。
    / [# I& w( d$ `3 v; E+ m/ U2.Libeasy
    4 B* W% {/ }  A* n6 j* `) c2 {为了解决收发小数据包带来的上下文切换问题,OceanBase目前采用Libeasy任务
    4 b5 t  P; s2 w模型。Libeasy采用多个线程收发包,增强了网络收发能力,每个线程收到网络包后* R( R/ f. ]3 q2 N* N7 f
    立即处理,减少了上下文切换,如图9-6所示。
    * a, x- f5 u$ d9 c4 X! B1 d图 9-6 Libeasy任务模型+ T' R" W7 a! W4 T# L9 e+ A
    UpdateServer有多个网络读写线程,每个线程通过Linux epool监听一个套接字集: }5 ~% e1 @% F  \- J9 o
    合上的网络读写事件,每个套接字只能同时分配给一个线程。当网络读写线程收到
    - E7 U. m; z4 m$ k+ y& k网络包后,立即调用任务处理函数,如果任务处理时间很短,可以很快完成并回复
      J( t5 S* L  n+ l+ l客户端,不需要加锁,避免了上下文切换。UpdateServer中大部分任务为短任务,比
    $ ?# Q) O7 o' ~- k; Z; N9 A如随机读取内存表,另外还有少量任务需要等待共享资源上的锁,可以将这些任务: T# g( g5 h8 Z4 Z4 S+ y" O% ?
    加入到长任务队列中,交给专门的长任务处理线程处理。
    - A* ]" n" x9 G  A7 Z  [由于每个网络读写线程处理一部分预先分配的套接字,这就可能出现某些套接% i  ?  Z2 J+ `4 X/ r
    字上请求特别多而导致负载不均衡的情况。例如,有两个网络读写线程thread1和. T$ ^# F( U2 e$ E& M. p; S1 y
    thread2,其中thread1处理套接字fd1、fd2,thread2处理套接字fd3、fd4,fd1和fd2上每
    3 D& G; a. a, c/ f; W秒1000次请求,fd3和fd4上每秒10次请求,两个线程之间的负载很不均衡。为了处理) A8 }1 P# l9 ^
    这种情况,Libeasy内部会自动在网络读写线程之间执行负载均衡操作,将套接字从
    $ W  p  ?9 t6 r5 p$ z负载较高的线程迁移到负载较低的线程。, q3 c1 j8 c( R: d* Q% K* A
    9.3.3 主备同步
    ) K+ k. {  m7 I) D3 ?, B: V8.4.1节已经介绍了UpdateServer的一致性选择。OceanBase选择了强一致性,主
    - H- Y) X+ G( bUpdateServer往备UpdateServer同步操作日志,如果同步成功,主UpdateServer操作本/ E4 Z' N/ M, l  l* S! v
    地后返回客户端更新成功,否则,主UpdateServer会把备UpdateServer从同步列表中
    ! V9 P# b# O( ~3 u! O# ^- h剔除。另外,剔除备UpdateServer之前需要通知RootServer,从而防止RootServer将不2 o0 h# E& Z8 r3 w' E3 B
    一致的备UpdateServer选为主UpdateServer。
    & K7 V$ l1 P6 Y/ y" b2 C! c4 a如图9-7所示,主UpdateServer往备机推送操作日志,备UpdateServer的接收线程
    2 n; B' o6 G- j" k* P接收日志,并写入到一块全局日志缓冲区中。备UpdateServer只要接收到日志就可以& s# P* ]' a6 y5 Q* P, N
    回复主UpdateServer同步成功,主UpdateServer接着更新本地内存并将日志刷到磁盘% j" D" ~9 p* [) ~
    文件中,最后回复客户端写入操作成功。这种方式实现了强一致性,如果主
    * O+ b: g- I' l5 j- @UpdateServer出现故障,备UpdateServer包含所有的修改操作,因而能够完全无缝地
    $ v9 P$ }# m1 s! E切换为主UpdateServer继续提供服务。另外,主备同步过程中要求主机刷磁盘文件,% U  `# X! ?/ K5 h# ^8 T6 {
    备机只需要写内存缓冲区,强同步带来的额外延时也几乎可以忽略。  t& m! D3 ~$ U5 L: D& X
    图 9-7 UpdateServer主备同步原理
    " F" ~' P9 q( K5 ?' T6 z- p正常情况下,备UpdateServer的日志回放线程会从全局日志缓冲区中读取操作日# t8 Q8 c- A3 T( i4 e8 S9 F( p
    志,在内存中回放并同时将操作日志刷到备机的日志文件中。如果发生异常,比如9 B! j! \5 z. v+ \0 W
    备UpdateServer刚启动或者主备之间网络刚恢复,全局日志缓冲区中没有日志或者日. ?; n/ ?4 k& a7 P. |1 Z$ t( D
    志不连续,此时,备UpdateServer需要主动请求主UpdateServer拉取操作日志。主3 K6 \. L% E4 F+ n2 C0 s
    UpdateServer首先查找日志缓冲区,如果缓冲区中没有数据,还需要读取磁盘日志文
    ) |* N) ]9 z+ q& O. a" e# r7 j! ^件,并将操作日志回复备UpdateServer。代码如下:! M# G& W1 T9 i8 n/ ~3 S
    class ObReplayLogSrc
    5 `& v. n" j& x5 N9 ?{8 K5 U, F- g, x2 b
    public:
    1 c. w4 d( ^# g4 R//读取一批待回放的操作日志
    1 E% h& B+ M: B+ [0 b9 _3 i//@param[in]start_cursor日志起始点
    4 A: n" y7 Y9 P/ U! ]//@param[out]end_id读取到的最大日志号加1,即下一次读取的起始日志号
      C3 c/ M  o2 I/ I//@param[in]buf日志缓冲区
    8 Y' C, I9 j6 j7 l6 N//@param[in]len日志缓冲区长度
    ; `. c" F2 P+ |( M. o//@param[out]read_count读取到的有效字节数
    9 m0 Q* m- Y* k3 ~4 J$ F7 Nint get_log(const ObLogCursor&start_cursor,int64_t&end_id,char*buf,const
    ! J' K1 H- P% P) hint64_t len,int64_t&read_count);. S* N0 }" N5 h) O
    };7 d( ~$ [$ A2 }9 V, q* F0 R5 S
    class ObUpsLogMgr
    0 c. O  t. _* c; ^/ }9 ?3 Y{/ C2 C+ Q# d8 B
    public:: A6 i8 `5 ?4 F- X/ F+ [2 z$ X
    enum WAIT_SYNC_TYPE5 F3 S: e1 j' t+ [2 q
    {. F. e+ _" z$ X1 @+ r& n- ^' K
    WAIT_NONE=0,
      F1 F; M. ~% CWAIT_COMMIT=1,$ i+ p8 \+ `( Y( P8 s8 J4 j' c
    WAIT_FLUSH=2,
    - a7 G+ D4 T1 l" t/ r};
    5 @+ K+ ?8 c1 H, k+ ?) T% d& g7 [public:  O8 q! V9 r2 ?' F2 O4 f) F
    //备UpdateServer接收主UpdateServer发送的操作日志" E/ u% K, B5 }0 X/ }. m
    int slave_receive_log(const char*buf,int64_t len,const int64_t2 |6 `9 N# g/ \
    wait_sync_time_us,const WAIT_SYNC_TYPE wait_event_type);9 k* |4 L0 L+ s' _* i2 E/ v$ h+ A
    //备UpdateServer获取并回放操作日志  f5 J1 v6 ]' `
    int replay_log();8 O8 W0 s5 ]( Q5 F5 L% J/ U2 `; X
    };
    0 K; s3 t% K* q' p# C. Z, X% \7 [1 W备UpdateServer接收到主UpdateServer发送的操作日志后,调用ObUpsLogMgr类的
    4 W$ X5 s8 U; Y) Sslave_receive_log将操作日志保存到日志缓冲区中。备UpdateServer可以配置成不等待
    1 s. n3 m# O" g+ g/ C# A3 |(WAIT_NONE)、等待提交到MemTable(WAIT_COMMIT)或者等待提交到# G+ [) d8 Q, u/ O) h
    MemTable且写入磁盘(WAIT_FLUSH)。另外,备UpdateServer有专门的日志回放线3 E; [7 B0 c$ h9 |( z
    程不断地调用ObUpsLogMgr中的replay_log函数获取并回放操作日志。1 s' G, I9 N& A4 v( f0 @8 I
    备UpdateServer执行replay_log函数时,首先调用ObReplayLogSrc的get_log函数读8 ~2 X% S- o8 o; B; `
    取一批待回放的操作日志,接着,将操作日志应用到MemTable中并写入日志文件持
    * b$ v) s$ X5 r2 r久化。Get_log函数执行时首先查看本机的日志缓冲区,如果缓冲区中不存在日志起
    3 R+ i8 D. B, M* x始点(start_cursor)开始的操作日志,那么,生成一个异步任务,读取主, B/ ?( ~8 }& w5 d# @' o
    UpdateServer。一般情况下,slave_receive_log接收的日志刚加入日志缓冲区就被" C' c6 W; H/ I- ?. C7 T# A
    get_log读走了,不需要读取主UpdateServer。. l+ a) |6 I; ^2 i4 j2 Y$ A3 X0 [

    1 O; C& _+ R1 D2 Y4 P
    ! z+ a2 H, n- ]# u4 q
    回复

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2025-1-22 15:01 , Processed in 0.670022 second(s), 30 queries .

    Powered by Javazx

    Copyright © 2012-2022, Javazx Cloud.

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