java自学网VIP

Java自学网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 2512|回复: 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实现机制% y4 i: a. W6 D: P) s4 x2 F  t
    RootServer是OceanBase集群对外的窗口,客户端通过RootServer获取集群中其他3 _% T* k# Q. L# U
    模块的信息。RootServer实现的功能包括:
    3 Y: ~# J; I4 k●管理集群中的所有ChunkServer,处理ChunkServer上下线;# ~8 ?( ^0 p5 n$ a* H! j2 H
    ●管理集群中的UpdateServer,实现UpdateServer选主;
    ) R5 s( g) \  `8 S" q●管理集群中子表数据分布,发起子表复制、迁移以及合并等操作;
    & ?7 [6 R: d. z& a1 a$ v●与ChunkServer保持心跳,接受ChunkServer汇报,处理子表分裂;
    , k1 H4 {# j# o4 v4 [4 v/ b●接受UpdateServer汇报的大版本冻结消息,通知ChunkServer执行定期合并;
    3 p% K, S% x; P. J/ v6 R●实现主备RootServer,数据强同步,支持主RootServer宕机自动切换。
    # A. e. H5 d+ _) a7 F" m' y" j9.2.1 数据结构
    % K0 ?0 F5 W) @# L4 URootServer的中心数据结构为一张存储了子表数据分布的有序表格,称为/ d9 s7 L; H6 X- A' j4 j
    RootTable。每个子表存储的信息包括:子表主键范围、子表各个副本所在# D/ S5 s1 a; e* n
    ChunkServer的编号、子表各个副本的数据行数、占用的磁盘空间、CRC校验值以及
    * s* o; y0 M+ L& h9 M0 e8 H  N" u. R基线数据版本。
    1 W" g) i% G6 p$ g/ a+ P1 T& WRootTable是一个读多写少的数据结构,除了ChunkServer汇报、RootServer发起子0 J0 q5 G, V" G' O& Z
    表复制、迁移以及合并等操作需要修改RootTable外,其他操作都只需要从RootTable; x, ]* ~% b0 A% J: @1 l/ t( a
    中读取某个子表所在的ChunkServer。因此,OceanBase设计时考虑以写时复制的方式+ C0 K! a1 T" w4 a; b; p  H' E
    实现该结构,另外,考虑到RootTable修改特别少,实现时没有采用支持写时复制的7 Y4 \8 r) y& F0 r. b! F
    B+树或者跳跃表(Skip List),而是采用相对更加简单的有序数组,以减少工作量。+ j. _: }" h' D7 r( c
    往RootTable增加子表信息的操作步骤如下:
    ' O4 O: g' d2 S) _% v" {1)拷贝当前服务的RootTable为新的RootTable;
    ) H7 \- F1 B& y. U, R$ N& X2)将子表信息追加到新的RootTable,并对新的RootTable重新排序;
    2 Q% q, G1 o! l3)原子地修改指针使得当前服务的RootTable指向新的RootTable。- s8 w9 v0 R) {; S: {$ l( h
    ChunkServer一次汇报一批子表(默认一批包含1024个),如果每个子表修改都5 d( ^, K' u1 f6 O( x6 T- G# B
    需要拷贝整个RootTable并重新排序,性能上显然无法接受。RootServer实现时做了一
    9 U  Y. @7 `" d/ G些优化:拷贝当前服务的RootTable为新的RootTable后,将ChunkServer汇报的一批子. N) Y, k( [7 Z
    表一次性追加到新的RootTable中并重新排序,最后再原子地切换当前服务的
    % J* X: ^: I% F7 s. Q/ e! cRootTable为新的RootTable。采用批处理优化后,RootTable的性能基本满足需求,9 r: P5 n0 u; f
    OceanBase单个集群支持的子表个数最大达到几百万个。当然,这种实现方式并不优7 L. d" ~1 d3 _1 U* r8 O/ j
    雅,我们后续将改造RootTable的实现方式。8 H8 e- y/ G1 w
    ChunkServer汇报的子表信息可能和RootTable中记录的不同,比如发生了子表分
    & |9 j" C! T/ Z3 V8 n* r; z裂。此时,RootServer需要根据汇报的tablet信息更新RootTable。( A# ?- A+ K  J* Y, }
    如图9-2所示,假设原来的RootTable包含四个子表:r1(min,10]、r2(10,
    - a6 W/ A: V8 d) }7 H7 x  x0 y" d100]、r3(100,1000]、r4(1000,max]、ChunkServer汇报的子表列表为:t1(10,; o1 b& T% D3 b3 ^
    50]、t2(50,100]、t3(100,1000],表示r2发生了tablet分裂,那么,RootServer会! B3 F2 Q* d# V5 v5 k; h- u. V
    将RootTable修改为:r1(min,10]、r2(10,50]、r3(50,100]、r4(100,1000]、
    3 W) i4 ]5 x0 e! gr5(1000,max]。
    3 G: p: e2 D- p2 i  c# A' {% a0 O图 9-2 RootTable修改
    ) f0 D# r; |  A9 b8 b0 d# l. l. h8 R* WRootServer中还有一个管理所有ChunkServer信息的数组,称为ChunkServer-9 C$ k# A6 ?+ P& y8 M3 r  X9 _$ p
    Manager。数组中的每个元素代表一台ChunkServer,存储的信息包括:机器状态(已. \2 z& R2 S  G+ N2 Z  b: U2 |
    下线、正在服务、正在汇报、汇报完成,等等)、启动后注册时间、上次心跳时9 k0 W: l9 _9 ^: B0 {6 f! \0 e. i
    间、磁盘相关信息、负载均衡相关信息。OceanBase刚上线时依据每台ChunkServer磁
    2 G) p5 Q8 E1 _! F) `盘占用信息执行负载均衡,目的是为了尽可能确保每台ChunkServer占用差不多的磁# t* t# O5 r* O4 [- O
    盘空间。上线运行一段时间后发现这种方式效果并不好,目前的方式为按照每个表/ K1 C4 e7 l8 a" C2 _
    格的子表个数执行负载均衡,目的是尽可能保证对于每个表格、每台ChunkServer上
    1 ?- S% W2 }6 F$ v的子表个数大致相同。
    2 V! N" f! l8 `9.2.2 子表复制与负载均衡
    8 k1 ]# c. P8 k6 vRootServer中有两种操作都可能触发子表迁移:子表复制(rereplication)以及负
    9 `$ A) ?. k2 o- v$ F3 ]载均衡(rebalance)。当某些ChunkServer下线超过一段时间后,为了防止数据丢
    ' p! t) S  O" O, g6 H! J失,需要拷贝副本数小于阀值的子表,另外,系统也需要定期执行负载均衡,将子5 Y9 o0 b7 {. C9 B0 T# j1 e4 [
    表从负载较高的机器迁移到负载较低的机器。% n( E; {5 b4 V  a: I
    每台ChunkServer记录了子表迁移相关信息,包括:ChunkServer上子表的个数以/ M* Z5 K( [* t$ |4 |* E
    及所有子表的大小总和,正在迁入的子表个数、正在迁出的子表个数以及子表迁移8 I6 X) n3 `; j) ~5 ~
    任务列表。RootServer包含一个专门的线程定期执行子表复制与负载均衡任务,步骤
    & @1 c  n" }8 |5 c. b2 k0 Q6 b如下:
    # g4 [5 D. R+ \9 ]& }* R$ {$ r1)子表复制:扫描RootTable中的子表,如果某个子表的副本数小于阀值,选取
    ) O1 t% ?+ B( o  I/ {. j某台包含子表副本的ChunkServer为迁移源,另外一台符合要求的ChunkServer为迁移
    . }6 K- N2 ~* p4 C  a目的地,生成子表迁移任务。迁移目的地需要符合一些条件,比如,不包含待迁移
    & Q- d0 [$ X6 Z7 V/ q子表,服务的子表个数小于平均个数减去可容忍个数(默认值为10),正在进行的* q7 M; ~; j- D8 k
    迁移任务不超过阀值等。$ f- Q* B" J- Y9 O# n1 h. @: t: R
    2)负载均衡:扫描RootTable中的子表,如果某台ChunkServer包含的某个表格的
    . [1 ~5 L3 \' i  ?子表个数超过平均个数以及可容忍个数(默认值为10)之和,以这台ChunkServer为
    ( [" G- E: _9 R1 K; s( M  {迁移源,并选择一台符合要求的ChunkServer,生成子表迁移任务。
    % w3 l8 X6 L1 H1 k& q- @4 m子表复制以及负载均衡生成的子表迁移任务并不会立即执行,而是会加入到迁
    1 g# L& o* Q& b2 Q: p4 P& g移源的迁移任务列表中,RootServer还有一个后台线程会扫描所有的ChunkServer,接4 {! e! Y1 Q! O
    着执行每台ChunkServer的迁移任务列表中保存的迁移任务。子表迁移时限制了每台+ p. N) F9 j' k: Z0 _0 c" K6 a
    ChunkServer同时进行的最大迁入和迁出任务数,从而防止一台新的ChunkServer刚上, n6 `9 X! D: d& m% E- Y- V* J
    线时,迁入大量子表而负载过高。( C$ V; s5 b8 J( r% R
    例9-1 某OceanBase集群包含4台ChunkServer:ChunkServer1(包含子表A1、
    6 N, L. O' h/ dA2、A3),ChunkServer2(包含子表A3、A4),ChunkServer3(包含子表A2),: u2 L$ T2 I8 O  @) k5 C1 ~, k) H
    ChunkServer4(包含子表A4)。/ R! E& s* m1 b) \0 C1 E5 }9 e
    假设子表副本数配置为2,最多能够容忍的不均衡子表的个数为0。RootServer后
    ' s0 e3 f( h& R( c  c台线程首先执行子表复制,发现子表A1只有一个副本,于是,将ChunkServer1作为迁
    # a7 N% C7 n1 e* p/ Z9 {移源,选择某台ChunkServer(假设为ChunkServer3)作为迁移目的,生成迁移任务<
    8 N& A! r* R) F- w5 N* t' QChunkServer1,ChunkServer3,A1>。接着,执行负载均衡,发现ChunkServer1包含3' S& l1 X" A3 F& F
    个子表,超过平均值(平均值为2),而ChunkServer4包含的子表个数小于平均值,
    3 [" }( `+ b+ Z/ |# z" ^于是,将ChunkServer1作为迁移源,ChunkServer4作为迁移目的,选择某个子表(假4 k9 x+ B1 Z) G
    设为A2),生成迁移任务<ChunkServer1,ChunkServer4,A2>。如果迁移成功,A2
    0 K5 q, |) H  ]$ b" }& W将包含3个副本,可以通知ChunkServer1删除上面的A2副本。最后,tablet分布情况
    : [% _4 M$ ?, S7 g) h为:ChunkServer1(包含tablet A1、A3),ChunkServer2(包含tablet A3、A4),
    , q& n  b6 S1 n- O& N8 G; {. rChunkServer3(包含tablet A1、A2),ChunkServer4(包含tablet A2、A4),每个
    : O1 R6 P2 O, {9 b1 {& itablet包含2个副本,且平均分布在4台ChunkServer上。) g# ~5 K1 f( R& |8 s/ E4 D# @- z+ s
    9.2.3 子表分裂与合并5 \  t; z( w. ^5 Z3 W7 Z. P
    子表分裂由ChunkServer在定期合并过程中执行,由于每个子表包含多个副本,2 [3 i6 @" N# a, T+ \' _. k
    且分布在多台ChunkServer上,如何确保多个副本之间的分裂点保持一致成为问题的, K5 y6 F, J. V5 F. Q
    关键。OceanBase采用了一种比较直接的做法:每台ChunkServer使用相同的分裂规8 B7 v4 N' y1 p9 j- f% ]
    则。由于每个子表的不同副本之间的基线数据完全一致,且定期合并过程中冻结的! p. I7 P, t/ V& h8 W. Z  E
    增量数据也完全相同,只要分裂规则一致,分裂后的子表主键范围也保证相同。& m6 p- _& Y4 f4 `, |
    OceanBase曾经有一个线上版本的分裂规则如下:只要定期合并过程中产生的数5 g! y, I1 u5 e' g/ g* B: q
    据量超过256MB,就生成一个新的子表。假设定期合并产生的数据量为257MB,那
    0 u, X0 Q  v3 h& U么最后将分裂为两个子表,其中,前一个子表(记为r1)的数据量为256MB,后一
    2 G. T7 U( p- ^个子表(记为r2)的数据量为1MB。接着,r1接受新的修改,数据量很快又超过
    $ V9 M" D4 G  w; a4 e256MB,于是,又分裂为两个子表。系统运行一段时间后,充斥着大量数据量很少: {  ~4 H- u! H1 E  i8 q0 ^
    的子表。7 v# n) P0 \, \
    为了解决分裂产生小子表的问题,需要确保分裂以后的每个子表数据量大致相9 r/ J$ i  l/ U8 `
    同。OceanBase对每个子表记录了两个元数据:数据行数row_count以及子表大小4 U# M* X' y% n/ D3 Z6 A
    (occupy_size)。根据这两个值,可以计算出每行数据的平均大小,即:
    - R% M! o7 L: ?  u. l8 ~occupy_size/row_count。/ S8 A6 _. Q/ L9 Z% m$ q! O
    根据数据行平均大小,可以计算出分裂后的子表行数,从而得到分裂点。2 G6 S4 f  T+ |. y" ?8 V4 ^$ {/ o5 h
    子表合并相对更加麻烦,步骤如下:
    5 a3 T; u4 ~1 ]; P* H; [; D1)合并准备:RootServer选择若干个主键范围连续的小子表;
    5 d# @2 P2 y# Y; Z1 q" ?2)子表迁移:将待合并的若干个小子表迁移到相同的ChunkServer机器;" c' {2 r: b- \" W/ ^; s1 T
    3)子表合并:往ChunkServer机器发送子表合并命令,生成合并后的子表范围。+ J9 N/ \7 p% X. _6 a
    例9-2 某OceanBase集群中有3台ChunkServer:ChunkServer1(包含子表A1、
    / O/ d: M& Y* M9 t( V7 Y; T: T9 mA3),ChunkServer2(包含子表A2、A3),ChunkServer3(包含子表A1、A2),其/ B! K0 G- v) i% y
    中,A1和A2分别为10MB,A3为256MB。RootServer扫描RootTable后发现A1和A2满足' A1 C8 t: i2 d" }0 Z& w
    子表合并条件,首先发起子表迁移,假设将A1迁移到ChunkServer2,使得A1和A2在
    , }$ @+ l5 |0 Z. I  D4 ^- b相同的ChunkServer上,接着分别向ChunkServer2和ChunkServer3发起子表合并命令。
    6 y0 [/ H  `% P$ a子表合并完成以后,子表分布情况为:ChunkServer1(包含子表A3),
    1 w; o7 q& F+ `. e9 `  AChunkServer2(包含子表A4(A1,A2),A3),ChunkServer3(包含子表A4(A1,5 H6 z9 c/ r" W, j& ^: P; o6 p. r
    A2)),其中,A4是子表A1和A2合并后的结果。
    . J# m# c3 F* d3 S0 q! I7 }每个子表包含多个副本,只要某一个副本合并成功,OceanBase就认为子表合并
    0 y7 ^% N. a: \) ^& _9 u6 o7 u2 C+ n成功,其他合并失败的子表将通过垃圾回收机制删除掉。5 D' I9 b7 \( q# e
    9.2.4 UpdateServer选主
    4 Z1 L6 c8 ?* i为了确保一致性,RootServer需要确保每个集群中只有一台UpdateServer提供写9 V, e$ V. ?3 f$ W
    服务,这个UpdateServer称为主UpdateServer。
    # H2 b8 n6 P/ l3 b5 tRootServer通过租约(Lease)机制实现UpdateServer选主。主UpdateServer必须持; n6 j$ {; F# r3 x3 ~. L
    有RootServer的租约才能提供写服务,租约的有效期一般为3~5秒。正常情况下,! e7 K; P" N2 v1 e
    RootServer会定期给主UpdateServer发送命令,延长租约的有效期。如果主
    + K: N% p8 N! x0 yUpdateServer出现异常,RootServer等待主UpdateServer的租约过期后才能选择其他的4 b, r1 r" [; ?& X! T" c
    UpdateServer为主UpdateServer继续提供写服务。& K6 R- {- v0 [1 r. E) U
    RootServer可能需要频繁升级,升级过程中UpdateServer的租约将很快过期,系
    - ]* X) i# b8 }3 X/ L统也会因此停服务。为了解决这个问题,RootServer设计了优雅退出的机制,即; B! ]/ b* B# S5 W
    RootServer退出之前给UpdateServer发送一个有效期超长的租约(比如半小时),承( j( m/ k0 C% g; T3 j: @" N9 D
    诺这段时间不进行主UpdateServer选举,用于RootServer升级。代码如下:! n1 c) R% [. {0 v6 v* W
    enum ObUpsStatus
    + [) T0 ?: q" ]0 e% W. K) D{/ ?* m5 U& K& T9 M/ o
    UPS_STAT_OFFLINE=0,//UpdateServer已下线5 e, I2 X7 ]( ?* X
    UPS_STAT_NOTSYNC=1,//UpdateServer为备机且与主UpdateServer不同步0 F: }8 q  W; ]2 u+ s2 m$ _8 v
    UPS_STAT_SYNC=2,//UpdateServer为备机且与主UpdateServer同步& f/ \* n% O0 }7 d! R
    UPS_STAT_MASTER=3,//UpdateServer为主机
    6 s, z6 u, x) ]# s6 @! z0 h8 w7 r};; K1 ~! @1 x* z$ S; ]/ C" X/ ^
    //RootServer中记录UpdateServer信息的结构
    0 H8 |3 H/ U% w% |" Q7 Pclass ObUps
    3 K9 ?% u, c5 o6 M; C3 ]) ~" o{& r$ i, S* U0 N
    ObServer addr_;//UpdateServer地址
    # m% L' m' K: V6 kint32_t inner_port_;//UpdateServer内部端口( J: T  h6 y5 Z! q) l; J3 T: m! K# y
    int64_t log_seq_num_;//UpdateServer的日志号; S8 y) w- P; M. s4 A8 A1 b
    int64_t lease_;//UpdateServer的租约4 ]9 J' ^3 }* ]7 Q4 U
    ObUpsStatus stat_;//UpdateServer状态' o3 |2 {( ]5 i8 @/ s+ i/ U$ S$ r
    };
    + t2 x3 q, }* A% {class ObUpsManager. L4 h# s3 _) N. \' F# \
    {! C, |1 x% [9 G& n  J' T/ h
    public:
    * d! ~2 G5 g9 j- O//UpdateServer向RootServer注册
    9 o/ x, X6 _& u- Y, J% h% fint register_ups(const ObServer&addr,int32_t inner_port,int64_t5 {' o% c# j0 ]7 I5 y2 l! q$ {& ^8 z, Q
    log_seq_num,int64_t lease,const char*server_version);
    / F2 b/ Y8 Q( Z+ H* }; F//检查所有UpdateServer的租约,RootServer内部有专门的线程会定时调用该函数3 |$ J4 p& o0 t4 _( C8 `
    int check_lease();
    5 H/ [' T* G$ t& v( A' o- r$ _//RootServer给UpdateServer发送租约! Q+ _" C2 M4 q! `
    int grant_lease();: }3 n+ V: O$ v$ U9 Y
    //RootServer给UpdateServer发送超长租约- G2 V0 z) c  D$ k
    int grant_eternal_lease();- ]% I& `0 R8 a) G3 R+ ]
    private:& p8 f& `7 m5 Q3 s; Q
    ObUps ups_array_[MAX_UPS_COUNT];- D$ b, |& X9 E+ O: }
    int32_t ups_master_idx_;2 f7 k5 e* z  s
    };% m3 F. v8 U7 {" R) V. w
    RootServer模块中有一个ObUpsManager类,它包含一个数组ups_array_,其中的
    . M6 p3 u- @4 }: E! ?每个元素表示一个UpdateServer,ups_master_idx_表示主UpdateServer在数组里的下标。* d0 |" b6 |( Q8 ?
    ObUps结构记录了UpdateServer的信息,包括UpdateServer的地址(addr_)以及内部% N+ M0 V4 Q) [8 |2 T7 `
    端口(inner_port_),UpdateServer的状态(stat_,分为& |* G0 v. Q3 s+ t" d: h' p
    UPS_STAT_OFFLINE/UPS_STAT_NOTSYNC/UPS_STAT_SYNC/UPS_STAT_MASTER
    " ^6 k0 ~/ Z# w$ s2 ]2 G. e这四种),UpdateServer的日志号(log_seq_num_)以及租约(lease_)。
    " [+ [# V$ k+ t( s# FUpdateServer首先通过register_ups向RootServer注册,将它的信息告知- r' |( D1 R2 |3 j# [8 |7 y
    RootServer。一段时间之后,RootServer会从所有注册的UpdateServer中选取一台日志
    3 b+ d8 ?4 }; r  A. n9 ?号最大的作为主UpdateServer。ObUpsManager类中还有一个check_lease函数,由0 X7 Q% _! [; D& _
    RootServer内部线程定时调用,如果发现UpdateServer的租约快要过期,则会通过/ }" [% [9 {  w: H9 {) _
    grant_lease给UpdateServer延长租约。如果发现主UpdateServer的租约已经失效,则会
    4 E4 Z# @7 X( u- ]  {8 ]* `" F从所有Update-Server中选择一个日志号最大的UpdateServer作为新的主UpdateServer。
    9 K- I  {- N" q/ o另外,Root-Server还可以通过grant_eternal_lease给UpdateServer发送超长租约。
    * L7 K& c0 M' _% U% A2 w! N9.2.5 RootServer主备
    / e4 q8 s9 n) j3 {! Z7 {9 e每个集群一般部署一主一备两台RootServer,主备之间数据强同步,即所有的操& u. e5 b# A. ?( l! y; M9 n
    作都需要首先同步到备机,接着修改主机,最后才能返回操作成功。$ `* h% R4 O0 d: B" E0 N7 @
    RootServer主备之间需要同步的数据包括:RootTable中记录的子表分布信息、
    * {, H: A8 [& Z; y! IChunkServerManager中记录的ChunkServer机器变化信息以及UpdateServer机器信息。
    ! a9 m& }2 d( ^0 t子表复制、负载均衡、合并、分裂以及ChunkServer/UpdateServer上下线等操作都会
    ( K+ I& ^" M- f引起RootServer内部数据变化,这些变化都将以操作日志的形式同步到备8 J! ?+ X! I/ v: t
    RootServer。备RootServer实时回放这些操作日志,从而与主RootServer保持同步。
      N' I4 e: }, S+ iOceanBase中的其他模块,比如ChunkServer/UpdateServer,以及客户端通过
    ) o; K( p6 q* i. HVIP(Virtual IP)访问RootServer,正常情况下,VIP总是指向主RootServer。当主
      O6 I  Z( X. B' f6 K( RRootServer出现故障时,部署在主备RootServer上的Linux HA(heartbeat,心跳),软0 m2 ]$ S. @  {$ a! ~
    件能够检测到,并将VIP漂移到备RootServer。Linux HA软件的核心包含两个部分:& ?; N) W: G) R7 j( Q' B& o
    心跳检测部分和资源接管部分,心跳检测部分通过网络链接或者串口线进行,主备
    ( m) i" Q5 ?; u, }* p4 z! E( pRootServer上的心跳软件相互发送报文来告诉对方自己当前的状态。如果在指定的时
    4 T" O6 k- `2 V5 o8 C间内未收到对方发送的报文,那么就认为对方失败,这时需启动资源接管模块来接
    - c4 R& B3 O/ e" s- E0 [) A2 u管运行在对方主机上的资源,这里的资源就是VIP。备RootServer后台线程能够检测& a; ?1 }* q# V8 e+ K
    到VIP漂移到自身,于是自动切换为主机提供服务。
    7 s1 L; ?- V+ S/ I7 B
    + ?, S$ u$ g1 N0 ^0 a* c$ P: _" |9 p" _/ ]
    回复

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2024-11-23 17:37 , Processed in 0.109371 second(s), 30 queries .

    Powered by Javazx

    Copyright © 2012-2022, Javazx Cloud.

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