OceanBase数据库漫谈

要说明业务需求。业务需求指的是预计要多大的空间、有多大的访问量、对数据库SQL响应时间要求多少等。

申请通过后开发会从运维拿到的OceanBase数据库信息是一个连接信息跟传统数据库一样这个信息包含数据库IP或者域名、端口、用户名和密码。如下

mysql -A –h10.100.1.100 –P3306 –uroot@app_tenant#obdev -pXXXXXXX

OceanBase的连接信息跟MySQL很像因为这个连接的协议就是兼容mysql的。用户可以通过MySQL客户端Java客户端Connector/J和通用客户端Connector/ODBC连接OceanBase。这么做是考虑到MySQL的普及性让更多人和应用能接受OceanBase。其中唯一有点不一样的是用户名的格式。如root@app_tenant#obdev 它的格式定义是用户名@租户名#OB集群名。用户名还支持另外一种格式OB集群名:租户名:用户名 。

 

2.2 租户和实例

租户tenant是OceanBase特有的概念等同于一个实例。开发拿到租户后往往不知道租户在哪台机器上。实际上租户存在某几台机器上但是不是固定的。并且也不一定独占所在机器。这些都是运维细节开发可以不关心。开发只需要理解如果把全部的机器当作一个资源池的话租户只是它的一个子集。这也是租户的本意。

上面给的数据库连接信息就是一个租户的连接信息开发人员可以当这个租户是一个独立的MySQL实例不同的是租户没有独立的进程。可以在租户里运行一些MySQL命令。如

$ mysql -h10.100.1.100 -P3306 -uroot@app_tenant#obdev -p******** app_db –A

 

create database app_db;

create user app_user identified by "app123";

grant all privileges on app_db.* to 'app_user';

show grants for app_user;

 

MySQL [app_db]> show databases;

+--------------------+

| Database           |

+--------------------+

| oceanbase          |

| information_schema |

| mysql              |

| test               |

| app_db             |

+--------------------+

5 rows in set (0.02 sec)

创建一个tablegroup表分组后面用。

MySQL [app_db]> create tablegroup tg_sbtest;

Query OK, 0 rows affected (0.02 sec)

重新以用户 app_user 登录租户

$ mysql -h10.100.1.100 -uapp_user@app_tenant#obdev -P3306 -papp123 app_db

 

MySQL [app_db]> show databases;

+--------------------+

| Database           |

+--------------------+

| information_schema |

| app_db             |

+--------------------+

2 rows in set (0.00 sec)

 

MySQL [app_db]> show tables;

+------------------+

| Tables_in_app_db |

+------------------+

| sbtest1          |

| sbtest2          |

| sbtest3          |

| sbtest4          |

| sbtest5          |

| sbtest6          |

| sbtest7          |

| sbtest8          |

+------------------+

8 rows in set (0.02 sec)

 

MySQL [app_db]> show create table sbtest1\G

*************************** 1. row ***************************

       Table: sbtest1

Create Table: CREATE TABLE `sbtest1` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `k` int(11) NOT NULL DEFAULT '0',

  `c` char(120) NOT NULL DEFAULT '',

  `pad` char(60) NOT NULL DEFAULT '',

  PRIMARY KEY (`id`),

  KEY `k_1` (`k`) BLOCK_SIZE 16384

) AUTO_INCREMENT = 1000001 DEFAULT CHARSET = utf8mb4 COMPRESSION = 'lz4_1.0' REPLICA_NUM = 3 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 10

1 row in set (0.01 sec)

 

MySQL [app_db]>

所以目前可以当租户是一个MySQL实例。未来OceanBase还会支持Oracle实例模式。

 

2.3 分布式和表分组

OceanBase是一个分布式数据库但目前开发还看不出分布式体现在哪里。这个细节需要从运维视角去看后面会详细描述。这里简单的描述一下当表很多的时候分布式数据库肯定会将这些表分散存储在多个节点上。而当SQL涉及多个表连接的时候如果跨节点了对SQL的性能会有些影响。

大部分分布式系统在面临跨节点请求时性能都会有些下降。产品本身会做优化这个不说。开发在设计表的时候可以避免这个情况发生。这就是表分组的作用。

表分组是表的一个属性用于做亲和性设置。当多个表设置为同一个表分组时OceanBase在设计这些表存储分布时会尽可能的将它们分配在同一台物理机上以达到减少跨节点请求的场景。

前面的例子里创建的 tg_sbtest 就是一个tablegroup。下面设置表的tablegroup。

MySQL [app_db]> alter table sbtest1 tablegroup='tg_sbtest';

Query OK, 0 rows affected (0.04 sec)

 

MySQL [app_db]> alter table sbtest2 tablegroup='tg_sbtest';

Query OK, 0 rows affected (0.04 sec)

 

MySQL [app_db]> alter table sbtest3 tablegroup='tg_sbtest';

Query OK, 0 rows affected (0.03 sec)

 

MySQL [app_db]> alter table sbtest4 tablegroup='tg_sbtest';

Query OK, 0 rows affected (0.04 sec)

 

MySQL [app_db]> show tablegroups;

+-----------------+----------------------------+---------------+

| Tablegroup_name | Table_name                 | Database_name |

+-----------------+----------------------------+---------------+

| oceanbase       | NULL                       | NULL          |

| tg_sbtest       | sbtest1                    | app_db        |

| tg_sbtest       | sbtest2                    | app_db        |

| tg_sbtest       | sbtest3                    | app_db        |

| tg_sbtest       | sbtest4                    | app_db        |

| tg_sbtest       | __idx_1131397465031505_k_1 | app_db        |

| tg_sbtest       | __idx_1131397465031507_k_2 | app_db        |

| tg_sbtest       | __idx_1131397465031509_k_3 | app_db        |

| tg_sbtest       | __idx_1131397465031511_k_4 | app_db        |

+-----------------+----------------------------+---------------+

9 rows in set (0.01 sec)

 

表分组只是OceanBase提供的一个用于干预分布式存储细节的手段。其原理后面在运维人员视角里会详细阐述。实际上OceanBase还有其他类似的干预手段区别只在于干预的范围。比如说有设置还可以控制几个租户的所有分区的位置分布。

OceanBase目标是做一个通用的数据库然而数据库性能好不好除了跟数据库产品自身的能力有关外还跟应用的设计能力有关。应用和数据库需要互相配合开发和运维也需要配合。这样应用性能才好。

 

2.4 表、分区和分区组

OceanBase的表支持分区表分区策略支持RangeHash等。一个分区表有多个分区一个非分区表可以认为是只有一个分区。

在应用层面表是存储业务数据的不可分割的最小单元。在OceanBase层面分区Partition才是存储业务数据的不可分割的最小单元。所以一个分区是不能跨机器存储一个表的不同分区是可以跨机器存储的。当然如果分区表有二级分区这里说的分区就是指那个二级分区。所以当一个业务大表的数据大小远超出单台机器的磁盘容量时OceanBase可以通过分区的方式解决这个空间难题。

 

上面表分组说会将不同表尽可能的组织在一起存储但是当这些表很大单机放不下的时候显然不合适。所以大表会做成分区表这个表分组TableGroup会细化为多个分区组PartitionGroup。不同分区表设置为同一个表分组时其对应的分区就属于同一个分区组。当然前提是二者的分区策略和分区数目保持一致。如果有人说有些业务表就是没办法按相同策略分区这个问题的处理还是要顶层设计上解决就不在这里展开了。这里说的是OceanBase提供这种能力。

 

分区更大的意义在运维和调优它也是负载均衡和高可用的最小单元。后面会详细解释。

 

2.5 SQL兼容性和执行计划

OceanBase SQL基本兼容MySQL语法部分兼容Oracle SQL语法。目前1.x版本对于函数、游标和存储过程之类都还不支持2.x版本会逐步支持。

 

OceanBase的SQL解析执行是仿照Oracle的设计去做有Plan cache执行计划是基于Cost。跟SQL和执行计划相关的v$视图OceanBase也有。这些设计奠定了OceanBase的SQL性能水平。


OceanBase数据库漫谈

图OceanBase SQL执行路径

 

开发可以使用explain命令查看SQL的执行计划。OceanBase SQL执行计划支持全表扫描、索引扫描表连接算法支持Nested-Loop、Merge-Join、Hash-Join等算法。

 

MySQL [app_db]> explain select a.*, b.* from sbtest1 a join sbtest2 b on (a.id=b.k) where a.k = 5000\G

*************************** 1. row ***************************

Query Plan: =================================================

|ID|OPERATOR              |NAME  |EST. ROWS|COST|

-------------------------------------------------

|0 |NESTED-LOOP INNER JOIN|      |103      |9591|

|1 | TABLE SCAN           |a(k_1)|101      |612 |

|2 | TABLE SCAN           |b(k_2)|2        |88  |

=================================================

 

Outputs & filters:

-------------------------------------

  0 - output([a.id], [a.k], [a.c], [a.pad], [b.id], [b.k], [b.c], [b.pad]), filter(nil),

      conds(nil), nl_params_([a.id])

  1 - output([a.id], [a.k], [a.c], [a.pad]), filter(nil),

      access([a.id], [a.k], [a.c], [a.pad]), partitions(p0)

  2 - output([b.k], [b.id], [b.c], [b.pad]), filter(nil),

      access([b.k], [b.id], [b.c], [b.pad]), partitions(p0)

 

1 row in set (0.01 sec)

 

MySQL [app_db]> explain select a.*, b.*

    -> from sbtest1 a join sbtest2 b on (a.id=b.k)\G

*************************** 1. row ***************************

Query Plan: ============================================

|ID|OPERATOR        |NAME  |EST. ROWS|COST |

--------------------------------------------

|0 |MERGE INNER JOIN|      |10123    |73368|

|1 | TABLE SCAN     |a     |10000    |6444 |

|2 | TABLE SCAN     |b(k_2)|10000    |57312|

============================================

 

Outputs & filters:

-------------------------------------

  0 - output([a.id], [a.k], [a.c], [a.pad], [b.id], [b.k], [b.c], [b.pad]), filter(nil),

      equal_conds([a.id = b.k]), other_conds(nil)

  1 - output([a.id], [a.k], [a.c], [a.pad]), filter(nil),

      access([a.id], [a.k], [a.c], [a.pad]), partitions(p0)

  2 - output([b.k], [b.id], [b.c], [b.pad]), filter(nil),

      access([b.k], [b.id], [b.c], [b.pad]), partitions(p0)

 

1 row in set (0.02 sec)

 

MySQL [app_db]> explain select a.*, b.* from sbtest1 a join sbtest2 b on (a.k=b.k)\G

*************************** 1. row ***************************

Query Plan: ==========================================

|ID|OPERATOR       |NAME|EST. ROWS|COST  |

------------------------------------------

|0 |HASH INNER JOIN|    |56370    |108982|

|1 | TABLE SCAN    |a   |10000    |6444  |

|2 | TABLE SCAN    |b   |10000    |6444  |

==========================================

 

Outputs & filters:

-------------------------------------

  0 - output([a.id], [a.k], [a.c], [a.pad], [b.id], [b.k], [b.c], [b.pad]), filter(nil),

      equal_conds([a.k = b.k]), other_conds(nil)

  1 - output([a.k], [a.id], [a.c], [a.pad]), filter(nil),

      access([a.k], [a.id], [a.c], [a.pad]), partitions(p0)

  2 - output([b.k], [b.id], [b.c], [b.pad]), filter(nil),

      access([b.k], [b.id], [b.c], [b.pad]), partitions(p0)

 

1 row in set (0.01 sec)

 

对于子查询、半连接、Union等OceanBase都有相应的优化策略这里就不展开了。

2.6 总结

描述计算机资源通常有三个指标CPU、内存和空间。每个机器包含了一定的资源一堆机器组成一个很大的资源池。OceanBase数据库就是管理这批资源池的。怎么管理资源是运维细节开发人员需要考虑的是新的应用需要多少资源运维人员就提供有相应资源的租户给应用。不同的应用使用不同的租户。租户内部的数据库、表、分区都可以理解为资源使用的粒度。不同的租户资源彼此隔离。如果大家了解云计算的话就会发现OceanBase天然就适合云计算业务。

 

下图就是开发人员理解的OceanBase数据库的细节。


OceanBase数据库漫谈

图开发视角的租户和数据库对象

 

租户就是一个实例在租户里开发可以建库建表每个表至少包含一个Partition。从图中看不出表和Partition具体在哪些机器上。如果要深入了解这个细节就从运维人员视角去看OceanBase。

 

3 运维视角看OceanBase

OceanBase数据库是一个集群至少需要三台机器。这个集群不需要共享存储机器之间也不需要网络直连彼此是通过网络访问即可。为了降低跨节点之间的请求延时对性能的影响OceanBase机器也建议用万兆网卡。相应的交换机也要支持万兆。

3.1 OBServer和机器

机器的内存建议在256G以上大内存。OceanBase的数据主要缓存在内存里数据不经常落盘所以内存越大越好。磁盘建议用SSD普通的即可。后面会解释SSD的特点跟OceanBase内部读写设计理念非常契合。容量也不要太小。网卡上面解释了尽量用万兆网卡。

OceanBase数据库在每个机器上的角色就是OBServer。它只有一个进程就是 observer。当observer进程启动的时候它会把绝大部分内存和大部分存储空间都独占住CPU无法独占只要没有其他进程跟它抢CPU资源可以理解为也是observer所有。所以OBServer的作用简单说就是占住机器资源然后提供数据库服务。注这是生产环境建议行为实际上OBServer对内存和空间的使用是可以配置的可以在进程启动参数里或者参数文件里配置。在开发测试环境允许单台机器跑多个OBServer进程。

 

OceanBase数据库漫谈

图OBServer相关进程和文件。

 

OBServer提供SQL计算和数据存储两种能力并支持集群部署分布式访问。OBServer内部主要包含总控服务、SQL引擎和存储引擎。其中总控服务是可选的只存在于部分OBServer中。存储引擎就是管理分区的。observer进程运行会输出两个日志文件observer.log和observer.log.wf后者是前者的子集主要是warning级别以上的日志。如果包含RootService则还会输出rootservice.log和rootservice.log.wf 。日志文件按大小滚动输出。稍微不足的是目前日志文件格式和内容是面向OceanBase内核开发的对普通用户不是特别友好。这个后期会有改进。但对喜欢深入研究OceanBase数据库的技术人员这个日志还是能看出一些OceanBase内部运行机制。

OceanBase数据文件是一个大文件其格式内部称为SSTable。此外有Commit Log、Index Log和Storage Log文件。

3.2 OceanBase集群设计

OBServer支持集群化部署最少三台OBServer就可以组建一个OceanBase集群。这个OceanBase集群具有可扩展、自动故障切换Failover并且数据不丢的特性。用容灾的两个常用指标来说就是RPO为0RTO为20s左右。

 

OceanBase数据库漫谈

图规模为3-3-3的OceanBase集群。

 

上图是一个三副本部署的例子OceanBase集群里的各个OBServer的角色都是平等的除了包含RootService的三台OBServer略有不同外。所以搭建一个三副本集群通常机器要能等分三份分别划为不同的区域Zone存放。这是逻辑上的划分。物理上安置机器的时候尽可能是放到三个机房。如果没有三个机房放到一个机房的三个房间也行或者一个房间的三个机柜也行分别走不同的交换机。机器摆在哪里是容灾设计首先要考虑的。

等分三份后三个Zone的机器就可以一一对应起来所有的机器都安装observer程序。手动搭建集群的时候要从三个Zone里分别选中一台组成一个原始最小的OceanBase集群1-1-1启动时指定rslistrootservice简称rs为这三台机器。然后再分别把其他机器加到对应的zone里。所以安装一个3-3-3的OceanBase集群实际上是先启动一个1-1-1的小集群然后逐步扩容至3-3-3模式。

初次安装的时候稍有不对可能就无法启动集群OceanBase有个OCP产品可以自动化安装OceanBase集群。详情参见OceanBase官方文档。

 

当OceanBase集群搭建起来后就拥有了全部机器的可用资源每个机器会保留少量资源用于自身OS使用。那么现在OceanBase要怎么管理这些资源呢 在设定管理目标之前先可以看看传统商业数据主备模式的机器管理。Oracle的DataguardSQL Server的Mirror架构MySQL的Master-Slave架构默认只有主库提供读写服务备库一直处于恢复状态不提供服务。当然Oracle的active standby和MySQL的slave 其实也可以提供只读服务只不过应用要接受一些延时。这是特殊用法先不提。这里机器资源利用率就是50%准确的说我们指的是CPU利用率因为备库恢复使用的CPU资源不多。

OceanBase集群以三副本模式部署的时候每个数据在集群里都存了三份所以实际最大可用空间是整体空间资源的1/3。至于CPU利用率就看有多少台服务器提供读写服务。客户一开始使用的时候往往也是用1/3的机器提供读写服务。跟商业数据库不一样的地方是OceanBase并不定义某台机器是主某台机器是备。所以理论上每台机器都可以提供读写服务。但运维人员是否选择这样做是需要做一个综合考虑即在资源利用率和容灾目标方面做一个权衡。比如说如果每台机器都提供服务CPU和空间利用率都很高当有1或者2台主机故障时剩余的机器可能没有足够的空间和CPU来接纳故障机器的数据和接管故障机器的对外服务因为新机器CPU资源会紧张。决策的关键点是故障的概率究竟有多大和业务能承受数据库服务多大的损失。这点后面我们再看几个业务案例。

 

3.3 租户资源的分配

当OceanBase集群把所有机器资源聚合在一起的时候运维就可以管理这片资源了。当接到开发申请数据库的需求时运维评估给多大的资源比较合适。OceanBase相对传统商业数据库的一个优势就在这里运维不用特别担心租户资源评估出入太大。如果资源给少了后面可以弹性扩容如果资源给多了后面可以弹性收缩。能做到弹性是因为资源的分配都是逻辑的不跟具体的物理机绑定。

OceanBase对资源的分配采取了一定的策略。首先是定义几种常用的资源规格。每个规格定义了可以使用的cpu、memory、iops、disk size、session number的份额。如下例子是开发环境的几种规格给的都比较小。定义资源规格是标准化工作是规模化运维的前提。如果规模很小的话只要定义少数几类规格即可。

$ mysql -h100.82.127.89 -P3306 -uroot@sys#ant_dev_g -pcybE8oI9~ oceanbase -A

MySQL [oceanbase]> select unit_config_id, name, min_cpu, max_cpu, round(min_memory/1024/1024/1024) min_memory_gb, round(max_memory/1024/1024/1024) max_memory_gb,round(max_disk_size/1024/1024/1024) max_disk_size_gb from __all_unit_config;

+----------------+---------------------------+---------+---------+---------------+---------------+------------------+

| unit_config_id | name                      | min_cpu | max_cpu | min_memory_gb | max_memory_gb | max_disk_size_gb |

+----------------+---------------------------+---------+---------+---------------+---------------+------------------+

|              1 | sys_unit_config           |     2.5 |       5 |            23 |            27 |             4173 |

|           1001 | B_unit_config             |    0.25 |    0.25 |             1 |             1 |              120 |

|           1002 | LogOnlyNormal_unit_config |       1 |       1 |             2 |             2 |              500 |

|           1003 | LogOnlySystem_unit_config |       5 |       5 |            35 |            35 |              500 |

|           1004 | S0_unit_config            |       1 |     1.5 |             4 |             4 |              500 |

|           1005 | S1_unit_config            |     1.5 |     1.5 |             6 |             6 |              500 |

|           1006 | S2_unit_config            |       3 |       3 |            12 |            12 |              500 |

|           1007 | S3_unit_config            |       6 |       6 |            20 |            20 |              500 |

|           1008 | S4_unit_config            |      12 |      12 |            40 |            40 |              500 |

+----------------+---------------------------+---------+---------+---------------+---------------+------------------+

9 rows in set (0.01 sec)

 

有了资源规格就很容易想到在分配资源的时候选择合适的规格或者将多种规格叠加。OceanBase可以创建资源池ResourcePool一个资源池定义只能是一种规格但允许分配多个同等规格不允许包含两个不同规格的。注后续版本资源池改为支持多种规格的资源。当创建资源池的时候就会实际在每个Zone里分配一块或者多块资源单元Unit主要是CPU和内存、空间。具体是在哪台机器上分配Unit由OceanBase依据一定的分配策略判断。这个策略跟以下几点因素有关

  • Unit的规格对应的最大CPU和memory
  • OBServer的理论cpu利用率和memory利用率
  • 参数resource_soft_limit 和 resource_hard_limit
  • 同一个OBServer内不能有多个该资源池的Unit

 

OceanBase数据库漫谈

图OceanBase Resource和Unit

 

上图是一个4-4-4的OceanBase集群。创建了一个Resource Pool 01使用的是某种资源规格S并且Unit数量设置为2。于是OceanBase在每个Zone 挑2个有资源的机器从中分配2个同等大小的Unit绿色的。图中灰色的Unit是其他资源池的Unit。每个OBServer上每分配一个Unit其对应的资源CPU、Memory和disksize就相应的扣减该规格。

资源池创建好后再绑定到具体的租户里则租户就可以开始建库建表了。租户有个Primary zone属性会指定默认每个Partition的主副本主副本可以提供读写服务在哪个Zone。假设是Zone1。则建表的时候OceanBase会查该租户对应的资源池在Zone1里的Unit分布。如上图有2个OBServer上有Unit。有两个Unit可以选在哪个上面分配就依据一定的策略了。这个策略跟以下几个因素有关

  • Unit的load值
  • tablegroup和partitiongroup
  • Unit内的PartitionGroup数量
  • 单个OBServer内最大partition数量

 

OceanBase数据库漫谈

图: Partition在Unit内的分布

 

上图就是分区表order、order_item和非分区表base_config、以及其他表xxx的Partition可能的分布。分区表order和order_item是属于同一个tablegroup的它们的同号分区组成一个partition group。分区表的不同分区可能在一个Unit内部也可能不在一个Unit内部当有多个Unit的时候同一个partition group的多个分区一定在一个Unit内部。

Zone2和Zone3的机器跟Zone1里的机器一一对应其内部的Unit分布也是一一对应。Unit内的Partition可以理解为也是一样的。图中就省略了。

 

租户资源里的Unit使用的内存是独占的空间是名义上独占多个节点之间不能挪用。CPU资源是名义上独占但是可以在租户内部Unit之间挪用一部分只要总的使用配额符合定义即可。

3.4 总结

总结一下OceanBase 租户相关的对象创建过程。


OceanBase数据库漫谈


OceanBase集群搭建好后默认有个系统租户sys。sys租户的root用户是集群里最大权限可以定义资源规格、创建资源池、OBServer的扩容和缩容、Partition的相关操作等等。

sys租户的root创建好用户租户后用户租户默认有root用户。

用户租户的root用户登录后可以创建数据库、定义表分组tablegroup、创建业务用户。

用户租户的业务用户登录后可以创建数据库对象。如表、视图、索引等等。

每个表至少有一个Partition。Partition是资源分配的最小单元、也是OBServer和Unit做负载均衡时的最小调度单元。

Zone内的OBServer可以在不停集群的情况下动态下线和上线租户的资源池的Unit可以在同一个Zone内多台OBServer之间内部动态迁移租户的表的Partition可以在租户的多个Unit内部动态迁移。这就是OceanBase扩展、均衡能力的基本原理。

 

运维人员可以通过sys租户的下面视图了解OceanBase细节。

视图名

作用

__all_server

所有observer信息

__all_unit_config

所有资源规格定义

__all_unit

所有已分配资源信息

__all_resource_pool

所有资源池定义

__all_tenant

所有租户信息

__all_table

所有表信息

__all_meta_table

所有副本信息

__all_root_table

所有系统表信息

__all_tablegroup

所有表分组信息

__all_rootservice_event_history

所有rootservice事件信息。内部元数据的变更、负载均衡和高可用都有rootservice服务管理。

v$sql

缓存的sql信息

v$sql_audit

所有sql运行日志

……

其他查看官方文档或者自己探索

 

 

4  再说分区Partition

4.1 分区的副本类型

前面提了分区是存储用户数据的最小粒度是负载均衡调度的最小单元。当整体集群是三副本模式部署时每份数据都有三份具体体现就是每个分区也有三份。这三份就是三个副本内容一致。但是角色分工有不同。副本的类型有全功能副本Full、日志副本Log、只读副本Readonly。前面所提到的所有Partition都指的全能副本指既包含数据又包含事务日志。每个分区的三个副本一定是存在三个Zone里因为分区所在的Unit一定是分布在三个Zone里。副本的角色有主Leader副本和Follower副本。每个分区最多只有一个Leader副本如果都是Follower应用读写会报错找不到Leader副本。这种情形一般是副本角色切换的中间过程持续时间在10s以内是中间态。

OceanBase的数据更新同样遵守WALWrite-Ahead Log协议。在写数据之前会先写事务日志。在提交的时候Leader副本的事务日志会同时发往到其他Follower副本。三个副本里只要有一半成员副本很可能也包括Leader副本自己投票反馈事务日志落盘成功主副本上的事务就可以提交。这个机制就是通过Paxos协议保证的。OceanBase具体使用的是Multi-Paxos。

能参与投票的副本类型目前只有全能副本F和日志副本L只读副本R只接受事务日志但不投票。所以如果觉得多副本方案比较耗费机器的话另外一种选择就是将某个副本类型改为日志副本。但我们建议最少还是有三份数据。

 

只读副本的资源规格可以跟全能副本规格不一致因为租户可以关联多个不同资源规格的资源池只读副本可以从另外一个资源池的Unit里分配出来的。只读副本的使用场景一是读写分离二是可以跟一些分区表的副本做本地连接以规避跨节点表连接时性能问题。

 

4.2 分区Partition和高可用

再换个角度说每个分区的三副本构成一个Paxos组合OB里这个组合能实现这样一个效果

  • 每次选举只会选出最多一个Leader副本。即不会有脑裂。
  • 只要多数派成员存活参与投票就一定可以选举出一个Leader副本。
  • 当Leader副本失效时会在两个租约时间内自动选举出新的Leader副本。即自动故障切换Failover并且数据是强一致的。又叫高可用。

 

OceanBase数据库漫谈

图Partition和Paxos

 

如上图OceanBase并不定义一个OBServer是主或者备。每个OBServer上有些Partition是Leader副本图中粉红色有些Partition是Follower副本。三个Zone的服务器都在提供服务。当其中一个OBServer发生故障的时候只有上面的Leader副本代表的Partition访问会出现中断并很快会自动从其他Zone的Follower副本里选出一个新的Leader副本提供服务。所以说Partition是高可用的最小单元。这也是OceanBase区别与传统商业数据库的地方。当OceanBase有机器宕机后应用也不会收到类似数据库不可用的错误而是不断有具体的表访问报错类似表无主的信息。默认大概20s后会逐步恢复。

 

4.3 分区的反向代理OBProxy

按照经验光数据库自身高可用是不够的还要做客户端连接的故障切换Failover。数据库内部发生切换后应用得能够连接到新的某个可用的地址上才能恢复服务。这首先要应用连接池能支持自动重连功能。其次就是数据库连接信息里的域名或者IP能够路由到新的某个地址上。传统的解决方案有如下几种

  • 如果是域名连接数据库提供域名的服务会指向新的可用地址。
  • 如果是通过负载均衡设备如F5或软件如LVS、SLB提供的VIP连接数据库这些VIP最终也要能指向新的可用地址。
  • 如果是通过数据库主机上的VIP那么VIP要能够漂移到新的可用机器上。如Oracle RAC和SQL Server Cluster的VIP。

 

在OceanBase里由于所谓的“切换”并不是整个机器角色的切换如主备互换而是某些Leader副本的角色切换。新的Leader副本可能分布在任意一台OBServer上。所以OceanBase在集群前面提供了一个反向代理OBProxy方便应用访问Leader副本。应用只需要连接OBProxy发出SQLOBProxy会解析SQL会选择发往其中某个Partition的Leader副本所在的OBServer上。即使SQL涉及到多个表或者事务涉及到多个表OBProxy也只会选择发往选中的那一个OBServer上。此外即使没有发生Partition切换由于Leader副本都是分布式存储在多个OBServer*问也是依赖OBProxy做SQL路由。所以OBProxy的主要工作就是做SQL路由和容灾不做类似分布式数据库中间件的那个中间节点还对数据二次处理的逻辑。这使得OBProxy可以做得非常轻量级并且高效。通常建议跟应用服务器部署在同一网段。

 

再细想一下OBProxy实际上就是OceanBase集群的代理人了。如果OBProxy不可用则等同于整个OceanBase集群不可访问。为此OBProxy有独立的守护进程监控自己的可用性。同时在实际部署的时候建议每个机房部署多个OBProxy前端再结合负载均衡设备或者软件生成一个VIP供应用使用这样OBProxy的负载均衡和高可用都有了。有关OBProxy的更多思考可以参考OceanBase公众号里的文章《支撑蚂蚁金服上百个关键业务的OBProxy到底牛在哪里》。

 

4.4 水平扩展和机器替换

在新建资源池、租户以及租户的库表时OceanBase会决策Unit如何分布以及Partition如何分布。如果Unit和Partition分布存在某种不均衡则OceanBase会做相应调整。具体就是通过迁移Partition实现。迁移Partition的时候实际是在目标地点生成一个新的Follower副本并同步事务日志和应用事务日志。这个过程完全不影响Leader副本的读写。新副本数据同步后再决定是否要发起一个Leader切换。这个Leader副本的读写会有短暂的不可用。这就是均衡的实现方式。

这个同样会发生在OceanBase水平扩展中。当集群资源足够而只是租户资源不够的时候将租户对应的资源池的资源规格提升一档即可。不过这可能触发Unit和Partition的均衡逻辑。所以通常都是在低峰期做这个平常也可以通过参数禁用这个负载均衡逻辑。当集群资源已经不满足租户的扩容时就可以新增机器到集群中。通常加机器要在每个Zone里增加相应的机器。新的机器进来很可能也会触发负载均衡逻辑所以也要选择相应的时机。

下线和更换机器也是同理。命令都是立即返回实际分区的迁移操作是后台异步做的。对运维人员来说要做的事情很少。

 

4.5 分区的亲和性设置

分区的负载均衡设计很巧妙不过不同的业务场景下运维人员可能有不同的需求。OceanBase提供了一些设置来控制分区的分布规律。这就是“亲和性”设置。前面提到的表分组TableGroup就是一个途径。除此之外分区所在的表、租户都有个默认属性Primary Zone 指定默认Leader副本放在哪个Zone。同时租户还可以指定Leader切换时优先选择哪个Zone的Follower副本当Leader。

类似TableGroup租户也有TenantGroup。有业务联系的两个租户也可以通过TenantGroup设置让它们的Partition尽可能的在同一个OBServer。比如说支付业务租户和交易业务租户都是按照用户拆分同一个用户的支付数据和交易数据就可以分布在同一个OBServer上以减少业务层面的跨机调用。

这些设置让前面提到的Unit均衡策略和Partition均衡策略更加的丰富复杂。

在OceanBase后期的版本里不同Zone的相应OBServer内部的Unit和Partition也不再保持完全一致了前面提的完全一致的分布我们称之为同构所以不一致就是异构。这使得每个Zone的OBServer可以独立做自己的负载均衡动作。

 

5 数据拆分讨论

数据拆分通常有两种做法一是使用分区拆分。如Oracle 12c的sharding、OceanBase的分区表等。二是使用分库分表。通过分布式数据库中间件来做。如阿里云的DRDS、腾讯的TDSQL。业界还有一个做法稍有不同就是拆分为比分区更细的粒度。如TiDB数据拆分为一定大小的Region。

分区的特点是所有分区在数据库内部是有内在联系的有强约束。比如说分区的结构要一致、可能有全局约束逻辑、要有全局索引等。分表的特点就是站数据库角度都是独立的表彼此没有内在联系。分表之间的联系由中间件去维护使得它适合做灰度表结构变更。

做数据拆分会面临一些共同的问题根据问题的范围选择在何种层面去解决。

5.1 分布式事务

一个事务多个SQL读写多个表时其分区的Leader副本可能在多个节点上这个事务的ACID就不能靠本机事务保证了。OceanBase支持分布式事务使用XA协议是强一致。不同于分布式事务中间件产品常用的最终一致的解决思路。

分布式事务的性能会比本机事务性能差一点。所以在表设计上首先应该能通过表分组TableGroup技术尽可能的避免有业务联系的表的分区分布在不同节点上。这是第一层保证。当业务规模非常大的时候数据量和访问量都很大远超出单台OBServer的能力时则只能尽量将有业务联系的表的分区的Leader副本约束在同一个机房Zone。这样依然可以依靠OceanBase的分布事务去解决。

至此这些数据还是在一个租户里。当业务规模大到那种把所有数据放在一个租户里风险很大的时候在应用层就要设计数据拆分先拆分到多个租户里那就需要借助中间件的分库分表能力。由于OceanBase原生的分布式事务不能跨租户。所以这个时候的分布式事务就要借助中间件的能力。通常就是用TCC实现最终一致或者用XA实现强一致。OceanBase可以为此做的事情就是利用将有业务联系的分表的Leader副本约束在同一个机房Zone或机器OBServer里让中间件层的分布式事务少发生跨机房调用。

 

5.2 全局一致性快照

上面OceanBase的分布式事务在1.x版本里有个缺陷。当一个SQL读取或者修改多个分区并且这些分区跨节点的时候OceanBase 1.x是不支持的会提示“strong consistency across distributed node not supported”。这是因为还不支持全局一致性快照。这样的SQL隐含要求是如果访问的数据在多个节点这些数据必须是来自于同一个时间点之前含已提交的数据。要实现这个要求需要有一个全局的时钟。Google Spanner是通过硬件原子钟和API实现。实现这个的目前还有TiDB。DRDS和TDSQL只是在server节点将各个db节点返回的数据做聚合处理直接忽略了一致性要求。

OceanBase 2.x版本将会在内部用软件实现一个全局时钟解决全局一致性快照难题。所以硬件层面所有OceanBase机器的时间必须使用同一NTP源。彼此误差不能超过50ms。这点对IDC来说应该是基本要求。

所以这个全局一致性快照只能在数据库内核层面解决。如果是使用分布式数据库中间件的话做这个就不容易了。这点供拆分决策参考。

 

5.3 多活和单元化

多活通常说的是同城双活或者异地多活等。应用只要稍作一些服务化拆分支持集群化部署集群节点都是无状态的很容易就可以实现多活部署。难的是数据库支持多活。OceanBase是很适合做数据库多活能支持不同层次的多活不过需要应用一起设计。

 

当使用分区的方式拆分时OceanBase把相同租户的很多分区的Leader副本分散在多个Zone或者把不同租户的很多分区的Leader副本分散在多个Zone。只有Leader副本才能提供写入能力Follower副本不能。这种效果是多个Zone机房都有写入但是写入的是不同的表或者分区Partition。这是初级的多活离业务的需求还有点距离。

业务想的是两个机房都能写入相同的表。但是也必须是不同的数据否则就双写冲突了双写的危害就是数据被覆盖难以追回。这是讨论多活的前提。

要满足业务这个需求仅靠数据库是不够的。业务必须做顶层的拆分设计。即将业务数据能按照某个维度拆分最好的是按用户拆分然后不同的用户可以在不同的机房读取和写入数据这才是业务要的多活。这种拆分设计又叫单元化设计。如果业务做不了这种拆分还要求多活就是一种奢想。

 

实现方法是用户请求应用时在域名解析层面就能够按某种策略将用户引导到相应地区应用的接入层。然后后面用户所有的请求都可以在本地区完成。理想的单元化设计就是用户所有的操作都可以在该单元地区内完成。现实会有点差距总有些业务数据不适合按用户拆分。比如说商品数据、库存数据等。这些数据的写请求只能集中在某一单元这种跨单元的访问也是不可避免的。要支持单元化数据库设计还要做分库分表配合。业务数据按用户ID拆分到多个分表里不同分表的Leader副本设置到不同的机房这样做到业务层面多个机房同时写入同类业务表只是写入的分表不同这也是一种多活形态。蚂蚁选择的是这一形态。

 

当然多活做得更彻底的就是阿里电商的基于MySQL的分布式数据库单元化设计。异地之间的MySQL彼此没有内在联系通过外在同步工具DTS做数据同步异地之间可以同时读写相同的分表只是读写的数据是属于不同用户的。这就要求应用顶层流量分发和接入层要严格保证用户请求访问正确否则很容易导致双写。此外DTS同步不保证强一致可能有延时所以同步的候会有实时增量比源和目的端数据并且每晚有一次全量校

在流量切换的时候应用层面还要通过“禁止更新”和“禁止读写”一些策略保护同步中的数据不会出现双写。

 

6  OceanBase部署形态介绍

OceanBase的基本特点简单总结就是分布式、弹性扩展、高可用强一致。OceanBase里的Zone是逻辑划分实际一个Zone由部署决定。小至一个机柜大至一个机房。运维可以根据业务需求搭建合适模式的OceanBase集群。需求不同部署模式就不同。下面简单介绍OceanBase各种部署形态。

 

6.1 单机房三副本部署

  • 部署说明就是三个Zone放在单机房内。建议放在不同的房间或者机柜下以及不同的交换机下。
  • 场景说明生产环境使用意义不大不能抵抗单机房不可用故障。开发测试环境可用。

 

6.2 同城三机房部署

OceanBase数据库漫谈

 

  • 部署说明同城多个核心机房延迟一般在0.5 ~ 2ms之间
  • 场景说明同城多机房容灾。很少有客户有第三个机房可以租用一个运营商机房或者公有云机器专门存放日志副本不包含数据所以没有丢数据的风险。不能抵抗同城整体不可用比如说城市大规模断电断网、地震或海啸等天灾。

 

6.3 两地三中心部署

OceanBase数据库漫谈

 

  • 部署说明主业务所在城市或者可靠性相对高的城市部署双机房异地部署一个机房。如果深圳挂了一个机器或者机房则事务提交延时会因为异地请求投票而多30ms。
  • 场景说明同城单机房容灾有高可用有异地灾备。容灾时应用性能会下降。不能抵抗同城整体不可用。

 

6.4 两地三中心五副本部署

      OceanBase数据库漫谈


  • 部署说明是上面两地三中心三副本升级版。可以容忍同城单机房的机器不可用性能无损耗。单机房挂掉后性能会下降。但是可以立即从五副本降级为三副本模式性能就会恢复。
  • 场景说明同城双机房容灾不能抵抗同城整体不可用。

 

6.5 三地三中心五副本部署

OceanBase数据库漫谈


 

  • 部署说明上面的升级版。可以容忍单机房不可用和单个城市不可用。单个城市不可用的适合应用性能会有下降可以立即从五副本降级为三副本应用性能恢复。
  • 场景说明有高可用、有容灾、城市容灾。

 

6.6 三地三中心五副本容灾及多活、读写分离部署

OceanBase数据库漫谈


  • 部署说明容灾就不用说了。多活方面业务分库分表OceanBase有多个集群多个租户不同租户的Primary Zone和切换备用Zone不同。多个机房同时写入。同时部分机房部署了只读副本专门提供读服务给某些查询量大的业务业务能容忍查询有些许延时。
  • 场景说明满足拆分、容灾、多活、读写分离场景。

 

 

7 其他

7.1 学习资源

OceanBase官网地址https://oceanbase.alipay.com/ 上面有公开的技术文档。文档体系目前还显得有点乱和不够丰满产品团队在设法完善中。

7.2 对外输出

OceanBase目前支持独立部署输出 和通过专有云输出。

独立部署时客户只需要提供机器即可。通过专有云输出时客户需要使用云的基础设施阿里云的飞天或者蚂蚁金融云。如果需要合作可以联系阿里云或者金融云的业务拓展专家、或者在群内联系OceanBase服务组人员。

 

 

8 总结

OceanBase是蚂蚁金服完全自主研发的分布式关系数据库目标是做一个通用型的分布式数据库。OceanBase基本兼容MySQL常用SQL部分兼容Oracle后续会兼容Oracle更多常用功能如存储过程、游标等。

OceanBase是分布式架构可以弹性扩展能自动做故障切换并且不丢一点数据非常适合做异地容灾/多活等。

上一篇:Linux文本处理入门深入解析动手实操


下一篇:MySQL数据库快速部署实践