揭秘OceanBase的弹性伸缩和负载均衡原理
概述
本文内容是直播的文字稿,直播视频回放地址:https://tech.antfin.com/community/live/639
。
所有的分布式数据库都宣称有弹性伸缩能力,具体实现机制不一样,对业务的影响和风险也就不一样。本文主要展示OceanBase在资源弹性伸缩和数据负载均衡方面独特的原理。从资源的聚合和分配,以及后期资源的弹性伸缩,还有数据的分配以及负载均衡,都是OceanBase内部机制,自动化运行,极大的降低了运维成本和出错概率。
相信你看过后还会意识到,OceanBase的架构设计天生就具备云数据库的特点。不过OceanBase跟云并没有必然联系,场景绝大部分是部署在企业内部机房的物理机上。
OceanBase如何做资源管理?
如何聚合资源?
近几年分布式数据库纷纷替换传统IOE
架构,其中一个原因是随着业务增长传统的小机和存储扩容成本很高,并且还不一定满足业务对性能的需求。分布式数据库方案都是架构在普通服务器上。单个服务器能提供的资源能力相比小机和存储而言是很弱的,但多个服务器聚合在一起的资源能力就有可能超过小机和存储。
如上图是9台服务器,每台资源能力是32 CPU, 256G MEM, 5T DISK
,如果能聚合在一起,这个资源总和可以到288 CPU, 2304G MEM, 45T DISK
。OceanBase做到这点主要靠每个节点上的数据库进程OBServer
。
前文《OceanBase数据库实践入门——手动搭建OceanBase集群》里介绍了如何搭建OceanBase集群。其中OBServer
进程启动的时候指定了几个跟资源有关的参数。
cd /home/admin/node1/oceanbase && /home/admin/node1/oceanbase/bin/observer -i eth0 -P 2882 -p 2881 -z zone1 -d /home/admin/node1/oceanbase/store/obdemo -r '192.168.1.241:2882:2881;192.168.1.81:2882:2881;192.168.1.86:2882:2881' -c 20190423 -n obdemo -o "
cpu_count
=24,
memory_limit
=100G,
datafile_size
=200G,config_additional_dir=/data/data/1/obdemo/etc3;/data/log/log1/obdemo/etc2"
参数中cpu_count
, memory_limit
和datafile_size
就是跟资源有关的。如果这些参数不指定,OBServer
进程就会默认取主机逻辑CPU个数减去2
(参数cpu_reserved
默认值)、主机内存的80%
(参数memory_limit_percentage
默认值)以及数据目录空间的90%
(参数data_disk_usage_limit_percentage
默认值)。
OBServer启动成功后就拥有了主机的绝大部分资源。其中对内存和空间的占有是排它的,对CPU的占有是声明式的(因为除了主机OS,没有其他进程可以霸占CPU)。然后9个OBServer节点组建为一个OceanBase集群。这个集群就拥有了很大的资源(270C 1800G 36T
)。如下图。
总结:从上面过程可以看出OceanBase集群对机器资源的管理是在内部的,这点不同于其他分布式数据库中间件产品的方案。OceanBase集群这种管理方式极大降低了运维成本和出错的概率。
如何分配资源?
OceanBase集群做了一个超级大的“资源蛋糕”,那么后面就面临一个分配的问题。通常不会把这么大的资源全部给某一个业务用。OceanBase集群实行的是按需分配。每个业务申请数据库的时候需要提供相应的业务数据量和访问量的评估,运维人员然后给出一个相应规格的评估,然后从OceanBase集群资源里切分出这个规格的资源,以租户
的形式给到业务方使用。这个租户
拿到手的感觉就是一个MySQL
实例或者一个Oracle
实例。如果是MySQL
实例,业务可以在里面建库建表;如果是Oracle
实例,业务可以在里面建多schema
(即多用户)。
由于OceanBase集群自己也要工作,所以会保留少量资源用于内部运行,这个内部资源是一个内部租户sys
。这个sys
租户非常重要,是集群的管理核心所在。sys
租户的管理员帐号也是整个集群的管理员帐号。从上图可以看出为两个业务分配了资源,其中交易业务租户(tnt_trade
)分得60C120G
,支付业务租户(tnt_pay
)分得120C600G
。注意,这里我略去了空间资源,因为OceanBase当前的版本不对空间的内部使用做隔离。
细心的人会问,如果运维给的资源过小怎么办?这个不用担心,如果业务运行一段时间发现租户(实例)的性能瓶颈在资源,那么运维可以很方便的对租户资源进行扩容。当然前提是集群资源里还有足够的未分配的资源。同理,如果运维给的资源过大超出业务需求,就有点浪费。这个也可以随时对租户的资源进行相应的缩容。后面会介绍资源的弹性伸缩原理。
还有的人会问,上面租户资源在哪个机器上?这个就是OceanBase独特的地方,业务方不需要关心他的租户在哪个机器上。实际上这些资源有可能是在不同的机器上,而这一切都对业务透明。
不过运维人员是能看到租户资源在哪个机器上,并且还要关注这个业务租户资源的分布。因为深入的性能优化是需要了解租户资源的分布。
资源分配内部细节
首先运维人员是能够看出集群资源是由各个节点的资源聚合而成。
select zone,concat(svr_ip,':',svr_port) observer, cpu_total,cpu_assigned,cpu_assigned_percent, round(mem_total/1024/1024/1024) mem_total_gb, round(mem_assigned/1024/1024/1024) mem_assigned_gb, mem_assigned_percent, unit_Num,round(`load`,2) `load`, round(cpu_weight,2) cpu_weight, round(memory_weight,2) mem_weight, leader_count
from __all_virtual_server_stat
order by zone,svr_ip
;
定义资源规格
然后运维人员先定义几个不同的资源规格。每个规格代表了一定的资源(包括CPU
、Mem
、Disk
、session
、IOPS
.当然实际上当前版本只对CPU
和Mem
资源进行分配。
create resource unit S2 max_cpu=20, min_cpu=20, max_memory='40G', min_memory='40G', max_iops=10000, min_iops=1000, max_session_num=1000000, max_disk_size='1024G';
create resource unit S3 max_cpu=20, min_cpu=20, max_memory='100G', min_memory='100G', max_iops=10000, min_iops=1000, max_session_num=1000000, max_disk_size='1024G';
然后使用这两个资源单元规格分别创建两个资源池(Resource Pool
)。每个Resource Pool
实际是由三个资源单元(Resource Unit
)组成。如下图。最后当Resource Pool
创建成功后,还需要创建一个租户并关联这个资源池,租户就有了这个Resource Pool
的使用权。每个Resource Pool
只能关联到一个租户。
在这个过程里,Resource Unit
从哪个节点上分配是有讲究的。OceanBase会考虑每个节点的资源分配率以及各个节点的负载等情况。其中有一点内存的分配策略会受参数resource_soft_limit
和resource_hard_limit
影响。
关于Resource Unit
,它是资源分配的最小单位。同一个Unit
不能跨越节点(OBServer
)。上面交易租户(tnt_trade
)在每个Zone
里只有一个Unit
,而支付租户(tnt_pay
)在每个Zone
里有两个Unit
。在一个Zone
里有多个Unit
的时候,就能发挥多个节点的资源能力。换句话说并不一定是整个分布式数据库集群的机器都能为这个租户所用,这取决于Resource Pool
里的unit_num
的数量。
Resource Unit
是数据的容器,当资源分配到位后,接下来就要看看数据在OceanBase里怎么分布。
OceanBase数据如何分布
水平拆分方案
通常数据是存储在表里,在OceanBase里更多的是说在分区(Partitioin
)里。通常一个表就一个分区,一个分区表有多个分区。分区(动词)是水平拆分的一种途径,另外一种是分库分表。二者有一些共同点,详情参见《分布式数据库的拆分设计实践》。在蚂蚁业务里,分库分表和分区是搭配使用的。如下面例子,业务表先通过中间件拆分为百库百表,然后每个表又变化为分区表(每个分区表包含一百个分区),这样总体的业务数据就被拆分为100000个分片。每个分片就是一个分区(Partition
)。
为什么拆分为10000个分区呢?这跟分区的特性有关。我们先看看分区是怎么分配出来的。
分区的分配(运维视角)
前面说了Unit
是数据的容器,指的的就是Partition
必须从Unit
里分配出来。每个业务的数据只能在该业务租户相应的Resource Unit
里分配出来。
如下图,是交易租户(tnt_trade
)的Resource Pool
,由3个Resource Unit
构成。然后创建了2个普通表,1个分区表(3分区)。普通表t1
只有一个分区(p0
),但是OceanBase会创建3个同样的分区,这三个称为分区(t1(p0)
)的三副本,三副本会分别位于三个Resource Unit
里,并且必须是三个不同Zone
的Resource Unit
里。
三副本会有角色区分,其中蓝色底色的副本是leader
副本,默认leader
副本提供读写服务,另外两个follower
副本不提供读写服务。所以,对于一份数据来说,其leader
副本在哪个节点的Unit
里,业务读写请求就落在哪个节点上。leader
副本默认位置由表的primary_zone
属性控制。这个设置也可以继承自数据库以及租户(实例)的primary_zone
设置。即使primary_zone
设置为RANDOM
,在只有三个Unit
的情况下(即Resource Pool
的unit_num
=1的情况下),每个leader
副本会默认在第一个Zone
里。除非是建表时明确指定它。
对于分区表t3
而言,数据会在三个分区里,分别是t1(p0)
、t1(p1)
和t1(p2)
。每个分区也有三副本。
对于unit_num
大于1的租户,每个分区在分配时会多一些选择,并且leader
副本的位置也多一些选择。OceanBase会考虑各个unit
的使用率和负载等。也就是说在建表创建分区的时候,OceanBase已经考虑到负载均衡了。
分区的分布以及leader
的分布是随机的,这个对业务而言并不一定友好。在分布式数据库里,跨节点的请求时性能会有下降。
如果业务层面t3
和t4
关系很密切,经常做表连接查询和在同一个事务里。为了规避跨节点请求以及分布式事务,OceanBase提供了表分组(tablegroup
)技术来干预有业务联系的多个表的分区分布特点。如上图示例,t3
和t4
都属于同一个表分组(tgorder
),则它们的同号分区属于一个分区组(partition group
),在稳定的状况下,同一个分区组的分区一定在同一个Unit
内部(即在同一个节点内部),并且它们的leader
副本还要在同一个Unit
内部。如图中的t3(p0)
和t4(p0)
有个虚线连接,就是指在同一个分区组里。
OceanBase如何弹性伸缩和负载均衡
前面说了,当租户不满足业务性能需求时,可以做租户资源扩容。扩容的前提是集群里资源还有余量。所以有时候还要先做集群资源扩容。
集群资源扩容
集群资源扩容就是加节点。因为每个节点(OBServer
)代表了一定的资源能力。如下图是一个2-2-2
的集群,总资源能力是180C1200G
。当前有三个业务租户,每个Zone
里有4个Resource Unit
。目前分布基本平均。
集群的资源扩容就是为每个Zone
增加相应的节点。
alter system add server '11.***.84.78:3882' zone 'zone1', '11.***.84.79:3882' zone 'zone2', '11.***.84.83:3882' zone 'zone3';
新节点加入后,由于资源利用率很低,整个集群负载就不均衡。OceanBase负载均衡的思路跟传统负载均衡产品不一样,OceanBase可以靠调整自己内部Unit
的分布来间接改变各个节点的负载。因为Unit
内部有数据(Partition
),其中leader
副本集中的Unit
就会有业务读写请求。我们可以看看上图发生Resource Unit
迁移后的结果,如下图。
Resource Unit
迁移的细节是在目标节点内部先创建Unit
,然后再复制 Unit
内部的Partition
,最后做副本的角色切换(leader
跟follower
的切换),最后下线多余的Partition
和Unit
。
集群资源扩容后,业务租户的Unit
所在节点负载可能会下降一些。如果业务还是有性能问题,此时就要继续做业务租户的资源扩容。
租户资源扩容
租户资源的扩容是通过调整其Resource Unit
的规格和数量来完成。比如说从规格S2
升级到S3
.
alter resource pool pool_mysql unit='S3';
或者不调整规格,而是增加Unit
的数量。
alter resource pool pool_mysql unit_num=2;
下图演示了增加Unit
数量后也触发了负载均衡逻辑。OceanBase调整各个节点负载的另外一个办法就是改变leader
副本在其租户多个Unit
之间的分布。
如上图是扩容前的租户的Resource Pool
以及各个数据Partition
的分布。所有leader
副本默认在Zone1
。
当扩容后,部分partition
的位置发生变化,同时leader
副本也出现在其他Zone
里。
注意,在这个Partition
迁移过程中,还有个特点就是同一个分区组内的Partition
最终会迁移到同一个Unit
内部。迁移期间有短暂时间可能出现分布在两个Unit
里,可能导致有跨节点查询或者分布式事务,性能会短暂下跌一些然后很快恢复。这个是业务需要承受的。一般业务压力不大的时候是感知不到数据库性能这细小的变化。
手动负载均衡
由上面可知,当集群资源扩容和租户资源扩容的时候,都会触发负载均衡机制。缩容同理。在特殊的时期,比如说类似双11大促,为了防止负载均衡导致数据库性能抖动引起业务的雪崩,OceanBase还可以关闭自动负载均衡机制。这个是通过参数enable_rebalance
控制。同时为了控制负载均衡时Partition
迁移的速度和影响,可以调整下面几个参数:
show parameters where name in ('enable_rebalance','migrate_concurrency','data_copy_concurrency','server_data_copy_out_concurrency','server_data_copy_in_concurrency');
下面SQL可以查看业务租户内部所有leader
副本的位置
select t5.tenant_name, t4.database_name,t3.tablegroup_name,t1.table_id,t1.table_name,t2.partition_id, t2.role, t2.zone, concat(t2.svr_ip,':',t2.svr_port) observer, round(t2.`data_size`/1024/1024) data_size_mb, t2.`row_count`
from __all_table t1 join gv$partition t2 on (t1.tenant_id=t2.tenant_id and t1.table_id=t2.table_id)
left join __all_tablegroup t3 on (t1.tenant_id=t3.tenant_id and t1.tablegroup_id=t3.tablegroup_id)
join __all_database t4 on (t1.tenant_Id=t4.tenant_id and t1.database_id=t4.database_id)
join __all_tenant t5 on (t1.tenant_id=t5.tenant_id)
where t5.tenant_id=1020 and t2.role=1
order by t5.tenant_name, t4.database_name,t3.tablegroup_name,t2.partition_id
;
同时运维也可以手动发起Partition
的迁移和切换。命令参考:
alter system move replica partition_id ='2%5@1121501860381523' source='11.166.84.79:2882' destination='11.166.84.79:3882';
无论是自动负载均衡还是手动负载均衡,所有的Unit
迁移和Partition
迁移的事件都可以在总控服务的事件日志表(__all_rootservice_event_history
)里查询。有关OceanBase总控服务请参见《OceanBase数据库实践入门——了解总控服务》。
OceanBase如何做异地容灾和多活
OceanBase分区有三副本,分布在三个Zone
,这个Zone
可以是三个机房,其中还可以有一个是异地机房。。所以OceanBase架构天然就具备了异地容灾的能力。至于异地多活的能力,就形态而言有很多种。
首先看下面这种异地多活形态。
首先,分布式数据库数据分布特点通常是无规则的并且是业务无需感知的(注:分布式数据库中间件的数据分布是有规则的,弊端是数据无法自由流动)。当三机房部署时,三个Zone
的业务都可以本地写数据库这个很容易做到,因为业务不关心也无从知道数据访问的内部路由。当数据的写入点在分布式数据库内部是无规则的,分布在所有机房时,会有很多内部请求跨机房,这个业务性能就很差。如上图,每个Partition
都只标识了其leader
副本位置,业务通过OBProxy
路由访问其数据。这样的多点写入或者异地多活对业务的意义不大。
所以OceanBase为业务提供一些策略用于干预leader
副本分布位置。主要策略有:租户/数据库/表的primary_zone
设置;租户的locality
设置;OBProxy
的路由策略等等。业务稍加运用这些规则就可以得到下面这个部署形态。每个业务的数据的leader
副本都被指定到跟业务应用相同的一个Zone
,这样业务都是本地同机房访问其数据。这也算是一种异地多活。
不过有些场景业务还是希望所有机房部署的应用一样,并且都要是同机房本地访问数据。这个光靠OceanBase是做不到,还需要对业务数据做分库分表的水平拆分,并且业务应用也支持流量按相同规则拆分。通常是按用户请求拆分。这样就做到一个用户的流量请求被路由到某个机房的APP后,这个用户的相关数据也在这个机房,那么后面跟该用户有关的数据请求就都是本机房的。
有关异地多活的架构经验详情请参见文章《如何基于OceanBase构建应用和数据库的异地多活》。
试验观察OB弹性伸缩和负载均衡
观察工具 dooba
虽然每个分区的位置我们都可以查询系统视图确认,但那样不够直观。所以我们需要一个好的工具来观察到这个负载均衡过程。这个工具就是dooba
,这是一个python脚本。运行命令如下:
$python dooba.py -h11.***.84.84 -uroot@sys#obdemo -P2883 -prootpwd
这个可以观察每个租户总的业务每秒读写请求量及其响应时间(RT
)信息,同时还可以观察到每个Unit
内部发生的每秒读写请求信息。
指标说明:
SSC:sql select count
SSRT:sql select rt.
SIC: sql insert count
SIRT:sql insert rt.
SUC: sql update count
SURT:sql update rt.
SDC: sql delete count
SDRT:sql delete rt.
TCC: trans. commit count
TCRT:trans. commit rt.
由前面得知,leader
副本在哪里,业务请求就可能落在哪个节点。那反过来,如果我们通过工具观察到某个节点上有读写请求,那就表示那个节点上有Unit
内部有leader
副本。
试验内容
首先准备好业务租户
create resource unit unit_demo max_cpu=25, min_cpu=20, max_memory='20G', min_memory='10G', max_iops=10000, min_iops=1000, max_session_num=1000000, max_disk_size='1024G';
create resource pool pool_mysql unit = 'unit_demo', unit_num = 1;
create tenant tnt_mysql resource_pool_list=('pool_mysql'), primary_zone='RANDOM',comment 'mysql tenant/instance' set ob_tcp_invited_nodes='%', ob_compatibility_mode='mysql';
然后准备好租户里的表,这里使用sysbench
初始化一个测试表,不过对sysbench脚本稍作改造,把建表语句改为分区表了。
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`, `k`),
KEY `k_1` (`k`) LOCAL
) partition by hash(k) partitions 5;
刚开始的数据分布时,由于租户只有3个Unit
,所以5个partition
都在同一个Unit
内部,并且所有leader
副本都集中在第一个Zone
。从下图也能看出这点。
然后模拟业务读写这个表,持续运行一段时间。
./sysbench --test=./oltp_read_write_ob.lua --mysql-host=11.***.84.84 --mysql-port=2883 --mysql-db=sysbenchtest --mysql-user="obdemo:tnt_mysql:demouser" --mysql-password=123456 --tables=1 --table_size=1000000 --threads=32 --time=600 --report-interval=10 --db-ps-mode=disable --skip-trx=on --mysql-ignore-errors=6002,6004,4012,2013,4016 run
然后进行租户扩容
alter resource pool pool_mysql unit_num=2;
然后观察一段时间。等OB做完负载均衡后,监控界面如下。其中有个节点没有请求,因为整个租户总共也只有5个分区。
后记
由于时间紧张,直播时内容难以面面俱到。相关文章请参考:
下期直播主题预告:OceanBase的高可用和容灾原理以及运维实践。
更多后续分享敬请关注公众号:obpilot
如果你对OceanBase有什么问题,还可以搜索或者扫码恩墨公司出品的小程序“DBASK”来提问