OceanBase-obcp-v3考试资料梳理
集群架构
基本概念
集群:
集群由一个或多个Region组成,Region 由一个或多个Zone组成,Zone由一个或多个OBServer组成,每个OBServer里有若干个partition的Replica。
Region:
对应物理上的一个城市或地域,当OB集群由多个Region组成时, 数据库的数据和服务能力就具备地域级容灾能力,当集群只有一个Region时,如果出现整个城市级别的故障,则会影响数据库的数据和服务能力;
Zone:
一般情况下对应一个有独立网络和供电容灾能力的数据中心,在一个Region内的多个Zone之间OB数据库拥有Zone故障时的容灾能力
OBServer:
运行observer进程的物理机,一台物理机上可以部署一个或多个observer,在ob中,ip+端口唯一
Partition:
ob中以分区为单位组织用户数据,分区在不同机器上的数据拷贝称为副本(replica),同一分区的多个副本使用paxos一致性协议保障副本的强一致性,每个分区和他的副本构成一个独立的paxos组,其中一个分区为主副本(leader),其他分区为从副本(Follower),主副本具备强一致性读写能力,从副本具备弱一致性读能力
部署模式
为保证单一机器故障时同一分区的多数派副本可用,OB数据库会保证同一个分区的多个副本不调度在同一台机器上。由于同一个分区的副本分布在不同的zone/region下,在城市级灾难或者数据中心故障的时保证了数据的可靠性,由保证了数据服务的可用性,达到可靠性与可用性的平衡。OB数据库创新的容灾能力有三地五中心可以无损容忍城市级灾难,以及同城三中心可以无损容忍数据中心级故障。
三地五中心部署
同城三中心部署
RootService
Ob数据库集群会有一个总控服务(Root Service),其运行在某个OBServer上。当Root Service所在的机器故障时,其余的OBServer会选举出新的Root Service,Root Service主要提供资源管理、负载均衡、schema管理等功能,一般情况下,一个zone一个root service
- 资源管理
- 包括Region/Zone/OBServer/Resource Pool/Unit 等元信息管理,比如:上下线OBServer、改变Tenant资源规格等;
- 负载均衡
- 决定Unit/Partition在多个机器间的分布,均衡机器上主分区的个数,在容灾场景下通过自动复制/迁移补充缺失的Replica
- schema管理
- 负责处理DDL请求并生成新的schema
Locality
Locality描述 表或者租户下副本的分布情况。这里的副本分布情况指在Zone上包含的副本的数量以及副本的类型。
- 租户的partition的副本数,增加副本数量:3变5,由
F@z1,F@z2,F@z3
变更为F@z1,F@z2,F@z3,F@z4,F@z5
可以逆向 - 集群搬迁:从
F@hz1,F@hz2,F@hz3
变更为F@hz1,F@sh1,F@sh2
,
租户下分区在各个zone上的副本分布和类型称为Locality,我们可以通过建租户指定locality的方式决定租户下分区初始的副本类型和分布。后续可通过改变租户Locality的方式进行修改。
-- 创建租户时指定locality
CREATE TENANT mysql_tenant RESOURCE_POOL_LIST =('resource_pool_1'), primary_zone = "z1;z2;z3", locality ="F@z1, F@z2, F@z3" set ob_tcp_invited_nodes='%',ob_timestamp_service='GTS';
-- 修改租户的locality
ALTER TENANT mysql_tenant set locality = "F@z1, F@z2, L@z3";
-- 表示z1有一个全能型副本,2个只读型副本
locality = "F{1}@z1, R{2}@z1"
-- 表示z1有一个全能型副本,并在同zone其余机器上创建只读型副本
locality = "F{
1}@z1, R{ALL_SERVER}@z1
- 一个分区,在一个zone内,最多存在一个paxos副本,可以有若干个非paxos副本;
- RootService会根据用户设置的locality,通过创建/删除/迁移/类型转换的方式,使分区的副本分布和类型满足用户配置的locality;
primary zone
- 通过设置Primary zone来设置Leader副本的位置偏好,实际是一个zone的列表
- 用
;
分隔,表示从高到低的优先级; - 用
,
分隔,表示相同优先级 ;
和,
可以混合使用
- 用
- Primary zone具有继承关系,向上继承 Table级-> Table Group级 -> Database级(MySQL)/Schema级(Oracle)-> Tenant级
- Table 优先使用自己,自己没有使用Table Group的
- Table Group 优先使用自己的,自己没有直接使用Tenant的
- Database 优先使用自己的,自己没有,直接使用Tenant的
RootService会根据用户设置的primary zone按照优先级高低顺序,尽可能把分区leader调度到更高优先级的zone内。
多租户架构
- ob数据库通过租户实现资源隔离,每个数据库服务的实例不感知其他实例的存在;
- 通过权限控制确保租户数据的安全性;
- 租户是一个逻辑概念,租户是资源分配的单位。可以理解为一个MySQL实例
有以下特点:
- 不允许跨租户的数据访问,以确保用户的数据资产没有被其他租户窃取的风险
- 租户独占其资源配额
第一章:分布式架构高级技术
聚合资源的物理表示
一个集群由若干个zone组成。zone在oceanbase.__all_zone
表
zone是什么?
- zone是可用区 availability zone的简写
- 是一个逻辑概念,是对物理机进行管理的容器
- 一般是同一机房的一组机器的组合;
- 从物理层面讲,一个zone通常等价一个机房、一个数据中心或一个IDC;
- zone可以是一个机架,也可以是一个机房,也可以是一个区域,一般为了性能最起码一个zone要在一个机房里
- 为了交付高级别的Oceanbase,通常会将3个Zone分布在3个机房中,
- 每一个机房对应的是一个IDC,3个机房都在一个Reigon中,表示归属性
zone主要有两种类型
-
读写zone
- 具备读写属性的zone,支持部署全功能型副本、只读副本、普通日志型副本
-
加密zone
- 具备加密属性的zone,仅支持部署加密日志型副本。
zone 操作(只能在sys租户中执行)
-
可以添加:
ALTER SYSTEM ADD ZONE zone_name [zone_option_list];
,zone_option_list指定目标zone的属性,有多个用,分隔- region:zone所在region的名称,默认
default_region
- idc: 指定机房名称
- Zone_type: 指定zone为读写zone(readwrite)或加密zone(encryption)
- region:zone所在region的名称,默认
-
可以删除:
ALTER SYSTEM DELETE ZONE zone_name ;
- 删除之前必须清空zone里的observer
-
可以启动: 每次只能操作一个zone,当前zone操作完以后才可以下一个
ALTER SYSTEM START ZONE zone_name;
-
可以停止,停止的时候都会检查多数派副本均在线,是一个必要条件
- 每条语句每次仅支持启动或停止一个 Zone
- 主动停止zone:
ALTER SYSTEM STOP ZONE zone1;
,- 会检查各分区数据副本的日志是否同步
- 多数派副本均在线
- 达到以上两个条件才能执行成功
- 强制停止zone:
ALTER SYSTEM FORCE STOP ZONE Zone1
,- 只检查多数派均在线,不管日志是否同步
- ocp上停止zone,包含了停zone和停zone上observer的操作
-
可以隔离:
ALTER SYSTEM ISOLATE ZONE 'zone1'
-
可以重启,只支持ocp 白屏操作,重启时融合多了多个命令的集合;
-
可以修改:其实就是给zone设置各种标签,
ALTER SYSTEM ALTER ZONE zone1 SET REGION='HANGZHOU',IDC='HZ1';
OB资源的分配流程
- 集群初始化以后,默认会创建一个系统租户sys,sys租户的配置可以在启动observer的时候指定
可根据用户需求创建业务租户
- 先创建资源规格;
- 根据资源规格创建资源池;
- 根据资源池创建用户;
配置管理☆
配置项,用于运维,控制机器及以上级别的行为,paramters
配置项主要用于运维,常用于控制机器及其以上级别的系统行为;使用paramters
- **生效范围:**集群、租户、zone、机器。
- 生效方式:动态生效、和重启生效;
- 修改方式:
- 通过sql语句修改:
alter system set 参数名=参数值
- 通过启动参数修改:
xxx -o "参数名='参数值'"
- 通过sql语句修改:
- 持久化:持久化到内部表与配置文件中;
- 查询方式:
show paramters
- 示例:
show parameters like '参数名'
- 示例:
查看配置项语法:
-- 语法:
SHOW PARAMETERS [LIKE 'pattern' | WHERE expr] [TENANT = tenant_name]
-- 示例:
SHOW PARAMETERS WHERE scope = 'tenant';
SHOW PARAMETERS WHERE svr_ip != 'XXX.XXX.XXX.XXX';
SHOW PARAMETERS WHERE INFO like '%ara%';
SHOW PARAMETERS LIKE 'large_query_threshold';
系统变量和session绑定,控制session级别的行为,通过variables查询
系统变量通常和用户session
绑定,用于控制session
级别的sql行为;
支持设置Global
和session
级别的变量。
-
global变量设置以后,当前session上不会生效,新建的session生效;
-
生效范围: 租户的Global级别或session级别;
-
生效方式:设置session级别的,直接生效;全局级别的,新的连接才会生效;
-
修改方式:
- 两种模式都支持的语法:
- session级别:
set 变量名= 变量值
- global级别:
set global 变量名= 变量值
- session级别:
- Oracle模式多了下面的语法:
alter session set 变量名= 变量值
alter system set 变量名= 变量值
- 两种模式都支持的语法:
-
持久化:仅global级别会持久化,session级别,session关闭就失效;
-
查询方式:
show variables
加上global 查全局- 两种模式都支持的语法
- session级别
SHOW VARIABLES LIKE 'ob_query_timeout';
- global级别:
SHOW GLOBAL VARIABLES LIKE 'ob_query_timeout';
- session级别
- MySQL模式支持的语法:在对应的表中查询系统变量
- session级别
SELECT * FROM INFORMATION_SCHEMA.SESSION_VARIABLES WHERE VARIABLE_NAME = 'ob_query_timeout';
- global级别:
SELECT * FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME = 'ob_query_timeout';
- session级别
- Oracle 模式支持的语法,在对应的表里查询系统变量
- session级别:
SELECT * FROM SYS.TENANT_VIRTUAL_GLOBAL_VARIABLE WHERE VARIABLE_NAME = 'ob_query_timeout';
- global级别:
SELECT * FROM SYS.TENANT_VIRTUAL_SESSION_VARIABLE WHERE VARIABLE_NAME = 'ob_query_timeout';
- session级别:
- 两种模式都支持的语法
对比项 | 配置项 | 系统变量 |
---|---|---|
作用 | 租户、机器、zone、集群 | 当前租户的会话session |
生效方式 | 动态生效、重启 | session级别的,直接生效,全局级别的,新的连接才会生效 |
查询方式 | show paramters like ‘’ |
session级别的查询:show variables like ‘xxxx’ global级别的查询: show global variables like ‘xxxx’ |
设·置方式 | Sql,修改:alter system set 变量名 = 启动加参数`xxx -o "参数名=‘参数值’ |
MySQL和Oracle都支持: session级别:set 变量名=变量值 global级别:set global 变量名= 变量值 MySQL存储在information_schema的两张session表中 |
持久化 | 会 | global会持久化,session级别关闭就失效 |
资源管理
- 资源单元(Resource Unit )
- 资源单元是一个容器(可以理解为一个虚拟机)。实际上,副本是存储在资源单元之中的,所以资源单元是副本的容器。资源单元包含了计算存储资源(memory、cpu和IO等)同时资源单元也是集群负载均衡的一个基本单位,在集群节点上下线、扩容、缩容时会动态调整资源单元在节点上的分布进而达到资源的使用均衡。
- 资源池(Resource Pool)
- 一个资源池由具有**相同资源配置(创建以后可以修改配置)**的若干个资源单元组成,一个资源池只能属于一个租户
- 一个租户在同一个server上最多有一个资源单元
- 资源配置(Resource Unit Config)
- 资源配置是资源单元的配置信息,用来描述资源池中每个资源单元可用的CPU、内存、存储空间和IOPS等,修改资源配置可以动态调整资源单元的规格。
资源单元(unit)可复用
-- 创建或修改资源单元
CREATE/ALTER RESOURCE UNIT unitname cpu/memory/iops/disk_size/session_num等;
- session_num 最小为64,
- iops:MIN_IOPS最小为128,MAX_IOPS
- memory 最小1G ,需要配置参数,配置以后才可以
- cpu可以0.5个单位
- disk_size 最小512M
-- 删除资源单元
drop resource unit unitname;
-- 查看资源单元配置
select * from __all_unit_config;
示例
# 创建资源单元
create resource unit ut_5c2g max_cpu=2,max_memory='1G',max_iops=10000,max_disk_size='10G',max_session_num=10000;
#修改资源单元
alter resource unit ut_5c2g max_cpu=5,max_memory='2G';
# 查看资源单元配置
select * from __all_unit_config where name ='ut_5c2g';
# 删除资源单元
drop resource unit ut_5c2g;
资源池(pool)
资源池的创建,
- 可以指定在哪些zone创建,
- 一个zone里创建几个(一个zone里创建的unit_num<=observer的数量)
资源池的修改
- 只能由
sys
租户的管理员执行 - 修改资源池的命令一次仅支持修改一个参数
-- 创建或修改资源池
create/alter resource pool poolname # 资源池名称
unit='资源规格'
unit_num =(要小于zone中server的数量) #表示在集群的一个zone里面包含的资源单元的个数,该值<=一个zone中的observer的数量
zone_list=(‘z1’,‘z2’) # 表示资源池所在的zone列表,显示该创建的资源使用哪些zone,创建到哪些zone里
-- 查看资源池
select * from __all_resource_pool;
-- 删除资源池
drop resource pool poolname;
-- 合并资源池 把哪些资源池merge到一个资源池中
ALTER RESOURCE POOL MERGE
('pool_name'[, 'pool_name' ...])
INTO ('merge_pool_name')
-- 分裂资源池 ,主要是解决zone上的物理机规格差异较大的时候,可以分裂开以后,给每个资源池单独设规格
ALTER RESOURCE POOL SPLIT INTO ('pool_name' [, 'pool_name' ...]) ON ('zone' [, 'zone' ...])
-- 从租户中移除资源池,主要用于在减少租户副本数的场景中
ALTER TENANT tenant_name RESOURCE_POOL_LIST [=](pool_name [, pool_name...]) ;
-- 查看资源的分配情况
SELECT * FROM oceanbase.gv$unit;
- 资源池可包含不同规格的资源规格
- 资源池创建时只能指定一个资源规格
- 资源池可以合并
- 资源池在不同的ObServer上规格可以不同
示例
-- 创建资源池
create resource pool p1_5c2g unit=ut_2c3g,unit_num=1;
-- 修改资源池
alter resource pool p1_5c2g unit=ut_5c2g;
-- 查看资源池
select * from oceabase.__all_resource_pool where name = 'p1_5c2g'
-- 删除资源池
drop resource pool p1_5c2g;
租户相关操作
租户分为系统租户
和普通租户
- 系统租户,即
sys
租户,是OB的内置租户,按照兼容模式属于MySQL租户,默认创建 - 普通租户,由
sys
创建
注意事项:
- 租户只能由
sys
租户的root操作,创建时只能绑定一个资源池; primary_zone
中;
由高到低,,
优先级相同,RANDOM
随机,必须大写;ob_compatibility_mode
不指定默认是MySQL模式;- 租户的
zone_list
不指定,默认使用资源池的zone_list,只能比resource_pool的少
-- 新增租户
create tenant [if not exists] tenantName
[tenant_characteristic_list] [opt_set_sys_var]
-- 说明
- tenant_characteristic_list:
tenant_characteristic [, tenant_characteristic...]
# 租户参数列表
- tenant_characteristic:
COMMENT 'string' # 租户的注释信息
|{
CHARACTER SET | CHARSET} [=] charsetname # 指定字符集
|COLLATE [=] collationname # 指定租户的字符序
|ZONE_LIST [=] (zone [, zone…]) # 指定租户的zone列表,默认集群内所有的zone
|PRIMARY_ZONE [=] zone #指定主zone的顺序,为leaber副本的偏好顺序 例如:'zone1;zone2,zone3' zone1>zone2=zone3,不设置,默认均匀分布,也就是以,分隔
|DEFAULT TABLEGROUP [=] {
NULL | tablegroup}# 表组信息
|RESOURCE_POOL_LIST [=](poolname [, poolname…]) # 指定资源池,创建时只能指定一个
|LOGONLY_REPLICA_NUM [=] num
|LOCALITY [=] 'locality description' # 指定副本在zone之间的分布情况, 例如:F@z1,F@z2,F@z3,R@z4 表示 z1、z2、z3 为全功能副本,z4 为只读副本
opt_set_sys_var:
{ SET | SET VARIABLES | VARIABLES } system_var_name = expr [,system_var_name = expr]
- ob_compatibility_mode -- 用于指定租户的兼容模式,可选MySQL或ORACLE,默认为MySQL模式
- ob_tcp_invited_nodes -- 指定租户连接时的白名单,%为所有
-- 修改租户(对租户扩容,可以修改资源单元的规格,不能直接替换资源池)
alter tenant tenantName|ALL [SET] [tenant_option_list] [opt_global_sys_vars_set]
- tenant_option_list:
tenant_option [, tenant_option ...]
- tenant_option:
COMMENT [=]'string'
|{
CHARACTER SET | CHARSET} [=] charsetname
|COLLATE [=] collationname
|ZONE_LIST [=] (zone [, zone…])
|PRIMARY_ZONE [=] zone
|RESOURCE_POOL_LIST [=](poolname [, poolname…])
|DEFAULT TABLEGROUP [=] {
NULL | tablegroupname}
|{
READ ONLY | READ WRITE}
|LOGONLY_REPLICA_NUM [=] num
|LOCALITY [=] 'locality description'
|LOCK|UNLOCK;
- opt_global_sys_vars_set:
VARIABLES system_var_name = expr [,system_var_name = expr]
-- 删除租户
drop tenant teantName [force/purge]
-- 删除租户,只有开启回收站的情况下,才会进入回收站
drop tenant tenantName;
- 开启回收站功能:进入回收站
- 关闭回收站:表示延迟删除租户,默认7天后删除(schema_history_expire_time),租户下的表和数据也会被删除
- purge 删除
- drop tenant tenantName purge;
- 延迟删除
- 不进入回收站(无论回收站是否开启)
- force 立即删除租户,无论是否开启回收站
- drop tenant tenantName force;
-- 查看租户
select * from __all_tenant;
注意:绑定到租户上的资源池,不能直接替换掉,可以通过修改资源池的资源规格来调整
示例:
-- 创建租户
create tenant obcp_t1 charset='utf8mb4', zone_list=('zone1,zone2,zone3'), primary_zone='zone1,zone2,zone3', resource_pool_list=('pl_5c2g') set ob_tcp_invited_nodes='%';
-- 修改租户,
ALTER TENANT obcp_t1 primary_zone='zone2';# 修改租户的主zone
-- 删除租户
DROP TENANT obcp_t1 force;
## 其他示例
-- 创建一个3副本的MySQL租户
CREATE TENANT IF NOT EXISTS test_tenant CHARSET='utf8mb4', ZONE_LIST=('zone1','zone2','zone3'), PRIMARY_ZONE='zone1;zone2,zone3', RESOURCE_POOL_LIST=('pool1');
-- 创建一个3副本的Oracle租户
CREATE TENANT IF NOT EXISTS test_tenant CHARSET='utf8mb4', ZONE_LIST=('zone1','zone2','zone3'), PRIMARY_ZONE='zone1;zone2,zone3', RESOURCE_POOL_LIST=('pool1') SET ob_compatibility_mode='oracle';
-- 创建 MySQL 租户,同时指定允许连接的客户端 IP
CREATE TENANT IF NOT EXISTS test_tenant CHARSET='utf8mb4',ZONE_LIST=('zone1','zone2','zone3'), PRIMARY_ZONE='zone1;zone2,zone3', RESOURCE_POOL_LIST=('pool1') SET ob_tcp_invited_nodes='%' ;
-- 确认租户是否创建成功,通过查询oceanbase.gv$tenant视图来确认租户是否创建成功
SELECT * FROM oceanbase.gv$tenant;
-- 管理员登录,默认管理员密码为空(管理员账户:MySQL模式为root,Oracle为sys)
obclient -h10.10.10.1 -P2883 -uusername@tenantname#clustername -p -A
回收站相关操作☆
- 回收站开启的时候,并不是所有的删除都会进回收站,比如 purge,只是延迟删除,并不进入
- 回收的数据库、表恢复的时候,可以重命名
- 回收站的管理主要由
sys
租户来完成
-- 查看回收站是否开启,是会话级
obclient> SHOW VARIABLES LIKE 'recyclebin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| recyclebin | ON |
+---------------+-------+
# 开启回收站
-- 全局级
SET GLOBAL recyclebin = ON/OFF
-- 会话级
SET recyclebin = ON/OFF
-- 查看回收站中的内容
show recyclebin;
-- 清空整个回收站(物理删除)
purege recyclebin;
-- 清空租户(物理删除)
PURGE TENANT tenant_name;
-- 清空数据库(物理删除)
PURGE DATABASE object_name;
PURGE TABLE object_name;
PURGE INDEX object_name;
# 恢复使用FLASHBACK恢复
FLASHBACK TENANT/DATABASE/TABLE object_name TO BEFORE DROP [RENAME to new_object_name];
-- FLASHBACK的执行顺序有从属关系的,要符合从属关系。
-- 从回收站中恢复被删除的表
FLASHBACK TABLE tbl1 TO BEFORE DROP;
-- 从回收站中恢复被删除的表x至y中表名重命名
FLASHBACK TABLE x TO BEFORE DROP RENAME TO user1.y;
- MySQL模式:进入回收站的对象有:索引、表、数据库、租户
- 直接drop索引不会进入回收站,删除表时,表的索引会跟随主表一起进入回收站
- 恢复表的时候会连同索引一起恢复
- 通过
PURGE
命令可以单独删除索引 - 表组的表删除以后恢复,如果标准还在,归位,不在,就独立
- Oracle模式:进入回收站的对象有索引和表,不支持数据库和租户
资源分配情况
-
查看集群资源由各个节点的聚合情况
select zone, concat(svr_ip, ':', svr_port) observer, cpu_capacity, cpu_total, cpu_assigned, cpu_assigned_percent, mem_capacity, mem_total, mem_assigned, 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;
创建租户时的资源分配
通过指定资源池分配资源
# 创建资源规格
create resource unit S2 max_cpu=20,max_mem=40G,…
create resource unit S3 max_cpu=20,max_mem=100G,…
# 创建资源池p_trade和租户tnt_trade,默认全部节点都有
create resource pool p_trade unit=S2,unit_num=1;
create tenant tnt_trade resourcepool=p_trade
# 创建资源池p_pay 和租户tnt_pay
create resource pool p_pay unit=S3,unit_num=2;
create tenant tnt_pay resource pool=p_pay;
资源单元及租户的相关要点
- 资源单元unit是资源分配的最小单元,同一个unit不能跨节点(Obserer)
- 每个租户在一台observer上只能有一个unit
- unit是数据的容器
- 一个租户可以拥有若干个资源池
- 一个资源池只能属于一个租户
- 资源单元是集群负载均衡的一个基本单位
创建分区表时的资源分配
租户有1个unit
- 每个分区有3个副本,默认leader副本提供读写服务,follower副本不提供服务
- 每个分区的三副本内容是一样的
# 创建t1,只有一个分区,会有3个副本,tnt_trade有在集群1-1-1上
create table t1(…);
# 创建t2,只有一个分区,会有3个副本,指定主leader在zone2
create table t2(…) primary_zone=‘zone2’;
# 创建t3,按照指定字段hash分区,分3个,每个分区的主分区会均匀的分开
create table t3(…) partition by hash(<column name>) partitions 3;
租户有多个unit
- 每个分区有三个副本,默认leader副本提供读写服务,在一个zone里只会有一个副本
- 同一个分区不能跨unit,同一个分区表不同分区可以跨unit
- 同号
分区组
的分区
会聚集在同一个unit
内部,不是尽可能
# 创建表组,分为3个分区
create tablegroup tgorder partition by hash partitions 3;
# 创建表t3时,通过hash进行3分区,并指定表组
create table t3(…) partition by hash(…)partitions 3 tablegroup=tgorder;
# 创建表t4时,通过hash进行3分区,并指定和t3一样的表组
create table t4(…) partition by hash(…)partitions 3 tablegroup=tgorder
-- 相同表组的表只能在一个unit中,同号分区组的分区会聚集在同一个unit内部,不是尽可能
- 表组是为了减少sql跨区操作,所以同号分区组的分区会聚集在同一个unit内部
- 发生负载均衡的时候,会将一个分区组的分区放到一个机器中,大概率地保障某些操作涉及的跨表数据在同一分区组中,并不是完全保证**,保障不了我理解就是故障了????**
租户扩容
租户的扩容有两种方式
-
升级规格
alter resource pool pool_mysql unit='S3’;
-
增加unit的数量,前提是ob的模式必须是n-n-n,num<=n
alter resource pool pool_mysql unit_num=2;
如图实例:
- 租户资源初始状态为:unit_num = 1
- 分区分布初始状态是t1、t3、t4
- 租户资源扩容:uit_num 由1变为2
- 分区复制时,分区组聚合一起,leader打散
- 切换分区服务
- 删除旧分区
在操作的时候,需要注意
- 分区是数据迁移的最小单元,同一个分区不能跨unit,同一个分区表不同分区可以跨unit
- 同一个
分区组
的分区
会聚集在同一个unit
内部
zone管理及状态 ☆
ps: primary zone 表示leader副本的偏好位置,指定了primary zone实际上是指定了leader更趋向于被调度到哪个zone上。
zone主要有两种类型:
- 读写zone
- 具备读写属性的zone,支持部署全功能型副本、只读型副本、普通日志副本;
- 加密zone
- 仅支持部署加密日志型副本;
zone一共有2个状态active状态和inactive状态,分别对应4个操作
-
新增zone时的inactive状态
ALTER SYSTEM ADD ZONE ’zone’;
-
上线zone时的active状态
ALTER SYSTEM START ZONE ’zonename’
; -
下线zone时的inavtive状态
ALTER SYSTEM STOP ZONE ’zonename’;
- 虽然ocp上observer的状态是停止的,但是还是可以使用;
-
删除zone时的无状态
ALTER SYSTEM DELETE ZONE ’zone’;
server管理及状态 ☆
server的操作,只能是sys租户中进行操作。
查看server的状态,需要通过__all_server
内部表中service_start_time、stop_time 两个字段来确认observer的状态
stop_time
- 不为0,表示OBServer被
stop
了,此时stop_time
的值为OBServer被stop的时间戳
- 不为0,表示OBServer被
status
有3个状态,分别是active
表示该observer 为正常状态,可能是start了,也可能被stop了- start_service_time >0 并且stop_time=0 表示该server可用
- Start_service_time >0 但是 stop_time >0 表示该server停用了
inactive
表示该observer 为下线状态deleting
表示该observer 正在被删除,可以被取消
Ps: 需要注意的是,stop server之前,需要确认enable_auto_leader_switch=true,并且分区副本满足多数派;
- stop server的时候,会将该server上的分区Leader切到其他节点,当没有Leader以后,标记为stopped状态,客户端请求不会发送到该server上,该server也不会再对外提供服务。
注意事项:
- 不能跨zone执行stop server的操作,主要是分区可用性的问题,(同一个zone可以同时stop多个server)
- 一个stop操作没有结束之前,不能发起第二个;
- 如果分区数多,observer 上的leader较多,stop server操作时间会比较长,会超时,sql超时默认是10秒,由
ob_query_timeout
控制单位为微秒。 - stop server的日志会在
__all_rootservice_event_history
记录的是rootservice控制的日志; - stop 以后
__all_server
内部表的stop_time由0 变为stop的时间点 - delete server 会迁移资源到其他unit ,uinit的迁移是unit自动均衡的过程,由RootService控制,迁移成功,delete成功。如果其他zone的资源不足,会迁移失败;
- 日志在:/home/admin/oceanbase/log/rootservice.log
- 1-1-1集群是无法stop或delete observer的,因为无法构成多数派;
-- 1,当我们添加一个server的时候,此时start_service_time=0,stop_time=0此时server的状态为inactive,n秒后状态变为active,start_service_time变为对应操作的时间戳
ALTER SYSTEM ADD SERVER 'ip:port' [,'ip:port'…] [ZONE=’zone_name’];
-- 2,当我们删除server的时候,状态为deleting
ALTER SYSTEM DELETE SERVER 'ip:port' [,'ip:port'…] [ZONE=’zone_name’];
-- 需要注意的是:哪种状态删除的observer,取消删除后会回到哪个状态
-- 3,当我们取消删除server的时候,状态被修改为active
ALTER SYSTEM CANCEL DELETE SERVER 'ip:port' [,'ip:port'…] [ZONE=’zone_name’];
-- 4,当我们由stop启动的时候,进行Start Server后start_service_time不变,stop_time归0
ALTER SYSTEM START SERVER 'ip:port' [,'ip:port'...] [ZONE='zone'];
-- 5,当我们停止server的时候,此时stop_time为操作的时间戳,状态仍为active,start_service_time不变
ALTER SYSTEM STOP SERVER 'ip:port' [,'ip:port'...] [ZONE='zone'];
集群扩容
集群扩容在生产上是必不可少的。我们的集群初始状态为2-2-2,每个zone有4个unit,,此时流量高峰来了,我们需要扩容到3-3-3
-- 操作流程:
# 首先集群扩容由2-2-2 扩容到3-3-3;
扩容有三种方案:
-- 方案一: 资源规格够,只是当前observer的流量大
1.1,自动均衡,将原来的一些unit漂移到新的observer上;
-- 方案二:资源规格不够,调整unit的规格
2.1,调整租户的unit规格;
2.2, 自动漂移;
-- 方案三、各方面压力都大,比较适合多分区的表
3.1,调整unit num的数量,最大为3,会在新的zone里创建unit
3.2,自动进行分区复制;
3.3,复制完成后,进行主从切换;
3.4,切换完以后,下线多余的分区及unit;
-- 需要注意以下的两个参数:
enable_rebalance # 是否自动负载均衡
enable_auto_leader_switch #是否自动切换leader
示例:zone2里的unit 10~12 迁移到了zone3里。
增加observer
# 新增zone
#启动observer
/home/admin/oceanbase/bin/observer -i eth0 -P XXXX -p YYYY -z zone1 -d /home/admin/oceanbase/store/obdemo -r 'xxx.xxx.xxx.xxx:xxxx:xxx.xxx.xxx.xxx:xxxx xxx.xxx.xxx.xxx:xxxx:yyyy' -c 20190716 -n obdemo -o "memory_limit_percentage=90,memstore_limit_percentage=60,datafile_disk_percentage=80,config_additional_dir=/data/1/obdemo/etc3;/data/log1/obdemo/etc2"
1, -P 指定RPC端口号
2, -p 指定直连端口号
3,-z 指定待加入的zone名称
4,-d 指定数据的存储目录
5,-r 指定待添加的observer的ip列表
6,-c 指定集群id
7,-n 指定集群名
8,-o 指定启动配置项
# 新增observer
obclient> ALTER SYSTEM ADD SERVER '$IP:$PORT' ZONE 'zone4';
# 重启observer 需要增加一个环境变量,路径为observer安装的目录
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/observer/lib/' >> ~/.bash_profile
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/observer/lib/
OB的资源弹性伸缩与负载均衡相关参数 ☆
- 资源单元的均衡和分区副本均衡开关:enable_rebalance 可选True或False
enable_rebalance
是负载均衡的总开关,控制资源单元的均衡和分区副本均衡开关- 资源单元均衡需要参考
resource_soft_limit
配置,小于100时,资源单元均衡开启,大于等于100,资源单元均衡关闭 server_balance_cpu_mem_tolerance_percent
为触发资源单元均衡的阈值,大于该值,该是调度均衡
- 控制负载均衡时Partition迁移的速度和影响
Migrate_concurrency
:迁移并发线程,过大会影响observer- 用于控制内部数据迁移的并发度
data_copy_concurrency
: 数据copy并发数量- 用于设置系统中并发执行的数据迁移复制任务的最大并发数
server_data_copy_out_concurrency
: 数据copy迁出并发线程数- 用于设置单个节点迁出数据的最大并发数
Server_data_copy_in_concurrency
: 数据copy 迁入并发线程数- 用于设置单个节点迁入数据的最大并发数
查看业务租户内部所有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_virtual_table t1 -- 表的基本信息,可查看虚拟表
join gv$partition t2 -- 集群中所有的partition meta信息
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 = 1001
and t2.role = 1 -- role=1为leader,为2是follower
order by t5.tenant_name,t4.database_name,
t3.tablegroup_name,t2.partition_id;
异地多活会有很多内部请求跨机房
/**
异地多活的情况下,会有很多的内部请求跨机房;
针对多分区的表,如果obproxy能分区裁剪,路由到正确的server上,就是本地查询
如果没有裁剪,就会在一个observer进行请求的分发。
*/
通过primary zone 设置优先级,适配不同业务 ☆
/**
我们在可以通过primary_zone设置优先级,适配不同业务的需求
比如:
*/
-- 我们在跑批处理的时候,希望尽快跑完所有的任务。这个时候,我们是希望主副本(leader)均匀的分散到个observer,综合性能最快;
设置Primary_zone=('z1','z2','z3') 意味着每个zone的的权重都是一样,分区就会均匀分布到z1、z2、z3上;
-- 当我们操作对时延敏感的在线业务,业务量不大,不超过一台机器的处理能力,尽量避免跨服务器访问,从而降低延迟
设置Primary_zone=('z1';'z2';'z3'),意味着z1的优先级最高,只有在z1不可用的时候,才会选举z2, z1>z2>z3;
-- 当我们做容灾方案时,将业务汇聚到比较近的城市,距离较远的城市,只承担从副本的角色;,那同城市内的优先级大于异地
设置primary_zone=('z1','z2';'z3'),意味着z1和z2的优先级一样,z1=z2>z3;
primary zone 有租户、数据库和表不同的级别 ☆
/**租户有primary zone, 数据库也有primary zone 建表的时候也可以指定primary zone;
当我们建库的时候,如果不指定会继承租户的primary zone的设置;
当我们建表的时候,如果不指定primary zone 会继承数据库的primary zone的配置;
但是,当表或库都自己设置了primary zone的时候
表 > 库 > 租户;
*/
租户有primary zone ,数据库也有primary zone ,表也有primary zone ,有相应的优先级
- 表级别的优先级最高,其次数据库,最后租户,可以提供灵活的负载均衡
- 如果不指定,创建库的时候,会继承租户的,创建表的时候会继承数据库的
小结
/**
ob的负载均衡是按partition为单位进行负载均衡的;单表只有一个partition;ob会根据多维度达到均衡;如果设置了表组,均衡的时候,是以表组为单位均衡;
负载均衡有多个维护的
- 分区维度
- 表组维度,同一个表组是为了解决跨分区访问的问题;
- unit 维度
最终的目的是让流量,存储等各方面达到均衡;
ob的迁移是以unit为单位迁移的,
*/
- OB的资源分配流程是:定义资源规格-> 创建资源池-> 分配资源池给租户
- partition自动负载均衡:同一个分区表的不同分区、租户内的所有分区、不同租户建的分区会自动调整,使得分区分布在多个维度上都达到均衡
- 管理员可以通过设置primary_zone,影响租户、数据库、表等对象主副本的分布策略
- 对于关系密切的表,可以通过表组(tablegroup)干预他们的分区分布,使表组内所有的同号分区在同一个unit内部,避免跨节点请求对性能的影响
- unit负载均衡:集群扩容或缩容后,unit自动在不同的observer之间调整,租户的数据自动在unit之间重新均衡;整个过程在线完成,极大的简化运维难度。
第二章、存储引擎
OBServer
安装目录
通常observer安装后有audit
、bin
、etc
、etc2
、etc3
、log
、run
、store
这 8 个目录。
- audit目录存放的是审计日志
- bin 存储observer的二进制文件
etc
、etc2
、etc3
都是配置文件目录,内容完全一致,后两个是observer启动后创建,用于备份配置文件- log 目录,存放运行日志的目录,包含observer的运行日志,rs日志和选举日志,单个日志文件大小为256MB
- 通过
enable_syslog_recycle
和max_syslog_file_count
来控制日志的回收enable_syslog_recycle=true
开启日志回收max_syslog_file_count=n
设置每种日志的最大日志数量
- 其他参数
enable_syslog_wf
单独将warn级别以上的日志复制到wf日志中enable_async_syslog
是否启用异步写日志,默认truesyslog_io_bandwidth_limit
日志限流量,默认30Msyslog_level
日志级别
- 日志类型
- observer.log observer的日志
- observer.log 以及 observer.log.20210901123456
- observer.log.wf 以及 observer.log.wf.20210901123456
rootservice.log
RS日志election.log
选举日志
- observer.log observer的日志
- 通过
- run
- store是数据文件目录,包含clog、ilog、slog、sstable这4个子目录
- clog、ilog、slog 是事务日志目录
- clog存储动态数据写入的事务日志
- slog存储静态数据写入的事务日志
- ilog存储日志目录
- sstable存储基线数据目录,会有一个block_file,observer启动就会被创建
- 由
datafile_size
或datafile_disk_percentage
控制 - datafile_size 设置数据文件的大小
- datafile_disk_percentage 表示单用所在磁盘的百分比
- 由
- clog、ilog、slog 是事务日志目录
内存管理
系统架构概念图
OB的存储引擎基于LSM Tree 架构
/**
OB 分为两部分,
一部分数据是动态增量数据放在MemTable中,存储在内存,允许读写
一部分是静态基线数据放在SSTable中,一旦生成不能修改,存储与磁盘;
在Memtable中,有两种组织形式,都是存储的指针
hashtable 优化单值查询场景性能;
b+tree 优化范围查询场景性能;
内存实现了 Block Cache 和 Row cache,避免对基线数据的随机读。
ob最小的读取单位是一个宏快2MB,每个宏块,拆分出来多个可变长的微块。
合并的时候没有更新的宏快不会被重新打开读取。减少了合并期间的写放大。
单个查询,点查,使用多级缓存保证极低的响应时间
内存 布隆过滤器 -> row Cache ->block cache -> 基线数据
由于增量更新的策略,查询每一行数据的时候需要根据版本从新到旧遍历所有的 MemTable 以及 SSTable,将每个 Table 中对应主键的数据融合在一起返回
针对大查询场景,SQL 层会下压过滤条件到存储层,利用存数据特征进行底层的快速过滤,并支持向量化场景的批量计算和结果返回。
Block Cache: 微块缓存,存放从磁盘读取到微块中的信息,类似于Buffer pool,主要用于满足比较大的SQL语句
Block Index Cache : 对应下图中的in-memory b+ tree,微块索引缓存(类似于Block Cache的索引),由于微块数量很多,需要index把block cache中的信息串起来
Row Cache: 基线数据和转储数据的行数据缓存(hash table),对于高频的点查,使用cache可以快速的响应,这里实现MVCC,OB的MVCC是在行级别实现的。
Bloom Filter Cache : 宏块的布隆过滤器缓存,宏块的hash table,用户快速判断行在基线数据或转储数据是否存在
*/
- 静态基线数据(放在SSTable中)
- SSTable是只读的,一旦生成不再被修改,存储于磁盘
Mini SSTable
MemTable转储以后直接形成的对象,mini SSTable 有多层Minor SSTable
多个mini SSTable会定期合并成Minor SSTableMarjor SSTable
每日合并开始后,所有的Mini 和Minor SSTable 都会合并成为Major SSTable- 存储的基本粒度是宏块(Micro Block),单个SSTable实质上是多个宏块的集合,一个宏块大小2MB,启动的时候就进行了切分
- 宏块会切分为多个微块,微块的概念与传统数据库的page/block概念类似,可以通过
block_size
指定大小,默认16kb,不同编码格式的微块,数据的存储格式不一样
- 为了加速基线数据的读取(避免对基线数据的随机读)
- Row Cache 行级缓存,
- 加速对单行的查询性能,
- 对于不存在的空行会构建布隆过滤器,并对布隆过滤器进行缓存(有时效),一般是静态数据表
- Block Cache 块级缓存,缓存的是位置信息,ob以2MB为一个宏块,然后继续拆分出多个可变长微块
- 不存在行的空查,构建布隆过滤器
- Row Cache 行级缓存,
- SSTable是只读的,一旦生成不再被修改,存储于磁盘
- 动态增量数据(放在MemTable中)
- 存储于内存,支持读写
- 通过转储变成mini SSTable
TODO
本质上OB是一个基线加增量的的存储引擎,跟关系数据库差别很大,同时也借鉴了部分传统关系型数据库存储引擎的优点
/**
下图展示的是ob 典型的table get示例
点查询:
1,先查询Fuse Row Cache,有数据,直接返回,(fuse 熔合)
2,将以下几个点的数据融合做归并
2.1 查询MemeTable数据,只是更改的数据的字段;
2.2 通过BloomFilter 查询有命中,
2.3 查询 minor sstabe 的row cache(数据从memTable到minor ssTable的时候,是不是已经和原来的数据合并了?)
2.4 查询 major sstable 的 block index cache ,从这里查找到应该去哪个微块里查数据,微块的数据缓存到了Block Cache 中
2.5 将以上的结果做融合归并,然后缓存到Fuse Row Cache中
*/
- BloomFilter Cache
- 构建在宏块上,根据实际空查率构建,空查次数超过一定阈值,就会自动构建,并放入缓存中;
- Row Cache
- 针对每个SSTable缓存具体的行数据,在get/multiGet的时候,会将查到的数据放入到Row Cache中,避免下次二分定位
- Block Index Cache
- 缓存微块的索引,以宏为单位,描述宏块中所有的微块范围,访问宏块的时候,需要提前装载微块索引。优先级高
- Block Cache
- 缓存具体的数据微块,每个微块都会解压后装载到Block Cache
- Fuse Row Cache
- 对于增量更新的熔合结果缓存
- Partition Location Cache :缓存Partition的位置信息,用于查询路由
- Schema Cache 缓存数据表的元信息,用于执行计划的生成以及后续的查询
- Clog Cache 缓存 clog 数据,用于加速某些情况下 Paxos 日志的拉取
点查:
范围查:
查询读写
- 插入
- 所有数据表都可以看成索引聚簇表,无主键,会维护一个隐藏主键
- 写入新的数据前,会检查当前数据表是否已经存在相同的主键数据
- 为了加速重复主键查询性能,对于每个sstable都会由后台线程针对不同宏块判重频率来异步调度构建bloomfilter
- 更新
- 更新是在memtable中更新,只包含更新列的新值以及主键列
- 删除
- 和更新列类似,直接以主键写入一行数据,通过行头标记删除动作
- 大量删除对lsm-tree不友好,ob做了优化
- 查询
- 利用cache加速
- 大查询场景,会下压过滤条件到存储层
- 多级缓存
- 对于查询提供针对数据微块的
Block cache
- 针对每个SSTable的
Row cache
- 针对查询熔合结果的
Fuse Row Cache
- 针对插入判空检查的
Bloomfilter cache
- 对于查询提供针对数据微块的
内存分配
/**
OB 是准内存分布式数据库,会占用物理服务器的大部分内存。
物理服务器的总内存,操作系统占一小部分,剩下的大部分由OBserver占用。
设置参数:
- 通过memory_limit_percentage 设置observer占用的百分比,
- 通过 memory_limit设置占用的内存。
当两个参数都存在的时候,memmory_limit 起作用。
*/
- OB是支持多租户架构的准内存分布式数据库,对大容量内存的管理和使用提出了很高要求
- OB会占据物理服务器的大部分内存并进行统一管理,通过以下参数可以限制OB的内存大小
memory_limit_percentage
内存占用百分比- 当memory_limit 等于0的时候生效
memory_limit
占用内存大小,默认单位为MB也可以设置为40G- 动态修改后,后台reload线程会使其动态生效
- 当设置了memory_limit以后,memory_limit_percentage失效
OB内部内存分配
/**
租户是核心,除了租户,ob自己运行也需要一些数据。不属于任何租户,但所有租户共享的资源,称为系统内部内存
由system_memory 控制ob自己用的内存,安装的时候,注意设置,要不然会占用比较多
ob 3.x 占用30G
刨除system_memory的内存以后,才是租户可用的内存
测试环境一般给system_memory 4~8g就可以了
*/
- 每一个observer都包含多个租户的数据,但observer的内存并不是全部分配给租户
- 通过参数
system_memory
设置系统内存上限,3.x默认上限是30G - 租户可用内存为
ob内存上限-系统内部内存
租户内内存划分
/**
租户内部的内存,主要分为两块,
1,不可动态伸缩的内存:memStore ,用来保存DML产生的增量数据
由参数 memstore_limit_percentage决定,默认是50,表示占50%,密集型写入的话,适当的调大该值
当memStore占用超过freeze_trigger_percentage定义的百分比,默认70%,触发冻结以及后续的转储/合并
- 先冻结没毛病(冻结memtable的数据)
- 当没有达到转储的次数的时候,触发的是转储
- 当达到一定次数的时候,触发的是合并;
2,可动态伸缩的内存:KVCache
保存sstable的热数据,提高查询数据
大小可动态伸缩,会被其他cache挤占
除了memstore使用的50内存以外,一部分是租户运行占用的内存,
另一部分是kv cache 、plan cache 、sql area、other area
*/
- 不可动态伸缩的内存:MemStore
- 用于保存DML产生的增量数据,空间不可被占用
- 由
memstore_limit_percentage
决定,表示占总租户大小的百分比,默认50,表示占50% - 当MemStore占比较高的时候(超过
freeze_trigger_percentage
定义的的百分比,默认70%),触发冻结及后续的转储/合并操作- 当没有到达转储的次数的时候,是触发的转储
- 当达到一定次数的时候,触发的是合并
- 可动态伸缩的内存:KVCache
- 保存来自SSTAble的热数据,提高查询速度
- 大小可动态伸缩,会被其他各种Cache挤占
写入密集型的应用,适当的调大发生转储的内存阈值,通过
memstore_limit_percentage
和minor_freeze_times
控制,确保业务在高峰运行时,不发生合并,而是把产生的数据暂时转储到此篇中;查询密集型的应用,需要把
memory_limit_percentage
调大,让OB占用更多的内存。
数据存储
在存储结构里,最上层是parttion group ,对应一个分区组,简称PG,。
- 一个pg中可能包含多个parttion(不分区只有一个),这些partition的分区键和分区规则要完全相同
- pg是ob数据库的leader选举和迁移复制的最小单位,
MemTable内存结构
OB的内存存储引擎Memtable采用双索引结构,由BTree和Hashtable组成,其中存储的均为指向对应数据的指针。
每次事务执行时,MemTable会自动维护B+树索引与hash索引的一致性;
- HashTable:对应Row Cache的索引
- 针对单行查询的优化
- 校验数据是否已经存在
- 加行锁,放在数据的行头数据结构中,mvcc控制在行
- BTree: 对应Block index cache
- 针对范围查找的优化
- 有序,范围查效果好
- undo 流程
- 需要读取历史快照,顺着内存的反向指针往前回溯即可。
SSTable
用户表每个分区管理数据的基本单元就是SStable,当Memtable 的大小达到某个阈值后,Ob,会将memtable冻结,然后将其中的数据转储与磁盘上。转储后的结构称之为sstable或者minor sstable。
sstable存储于磁盘,存储静态数据并且只读。
当集群发生全局合并的时,每个用户表分区所有的minor sstable 会根据合并快照点,一起参与做marjor 合并,最后生成 major sstable。
宏块(macro block)
ob将磁盘切分为大小为2MB的定长数据块,称之为宏块(macro block)。
- 宏块是数据文件写IO的基本单位
- 每个sstable由若干个宏块构成
- 宏块2MB的大小不可更改
- IO会顺序读写
微块(Micro Block)
宏块内部数据被组织为多个大小为16kb左右的变长数据块,称之为微块(Micro Block).
- 微块中包含若干数据行(Row),数据按照主键排序
- 微块是数据文件读IO的最小单位
- 每个微块在构建时,都会根据用户指定的压缩算法进行压缩,因此宏块上存储的实际上是压缩后的数据
- 读取的时,会解压后放入数据块缓存中
- 微块的大小在创建表时可以指定,也可以通过语句指定
ALTER TABLE mytest SET block_size = 131072;
内存数据落盘策略-合并和转储
LSM 技术简介
LSM Tree ,顾名思义,就是The Log-Structured Merge-Tree 的缩写。从这个名称里面可以看到几个关键的信息:
- 第一: log-structred,通过日志的方式来组织的
- 第二:merge,可以合并的
- 第三:tree,一种树形结构
实际上它并不是一棵树,也不是一种具体的数据结构,它实际上是一种数据保存和更新的思想。简单的说,就是将数据按照key来进行排序(在数据库中就是表的主键),之后形成一棵一棵小的树形结构,或者不是树形结构,是一张小表也可以,这些数据通常被称为基线数据;之后把每次数据的改变(也就是log)都记录下来,也按照主键进行排序,之后定期的把log中对数据的改变合并(merge)到基线数据当中。
核心:利用顺序写来提高写性能。典型的以空间换时间。基于归并排序的数据存储思想
- 将某个对象(partition)中的数据按照“k-v”形式在磁盘有序存储(SSTable)
- 数据插入,先记录在MemStore中的MemTable里,然后再合并(Merge)到底层的sstable里
- SSTable和Memtable之间可以有多级中间数据,同样以kv形式保存在磁盘上,逐级向下合并
合并
- 合并是将动静数据做归并,会比较耗时。
- 是全局级别的操作,产生一个全局快照;
- 全局分区一起做MemTable的冻结操作,要求主备的Partition保持一致;
- 会把当前的大版本的SSTable和MemTable与前一个大版本的全量静态数据进行合并。
OB最简单的LSM Tree只有C0层(MemTable)和C1层(SSTable),其合并过程如下:
合并的时候并不是先转储,而是直接冻结MemTable然后合并到SSTable中
- 将所有observer上的Memtable数据做大版本冻结(Major Freeze),其余内存作为新的Memtable继续使用
- 将冻结后的Memtable数据合并到SSTable中,形成新的SSTable,并覆盖旧的SSTable(已经包含了新旧数据)
- 合并完成后,冻结的MemTable内存才可以被清空并重新使用
这种合并很容易出现问题。
合并按照合并的宏块不同,可以细化为全量合并、增量合并、渐进合并三种方式,ob默认使用增量合并
- 全量合并:合并时间长,耗费IO和CPU,把所有的静态数据都读取出来,和动态数据归并,再写到磁盘中
- 增量合并:只会读取被修改过的宏块数据,和动态数据归并,并写入磁盘,对于未修改过的宏块,则直接重用,ob默认使用
- 渐进合并:每次全量合并一部分,若干轮次后整体数据被重新一遍
执行方式:
-
并行合并 :分区内并行合并,提升分区的合并速度,可以应用到全量、增量、和渐进中
-
轮转合并:以副本为单位合并,将流量切到其他副本
**合并带来的问题:**通过转储来解决
- 集群性的动作
- 高消耗
- 时间长
定时合并
由major_freeze_duty_time 参数控制定时合并的时间
# 设置定时合并时间
alter system set major_freeze_duty_time='02:00';
# 查看定时合并时间,按zone的维度来
show parameters like '%major_freeze_duty_time%'
自动触发合并
当MemStore 达到参数freeze_trigger_percentage
配置的值,并且转储的次数达到了minor_freeze_times
参数的值,自动触发
# 查看各租户的MemStore使用情况
select * from oceanbase.v$memstore;
# 查询转储次数gv$memstore,__all_virtual_tenant_memstore_info 中的freeze_cnt列
- active_memstore_used 某台服务器上的活动 MemStore 的大小
- total_memstore_used 某台服务器上的总 MemStore 的使用大小
- major_freeze_trigger MemStore 使用量触发转储或合并的阈值
- memstore_limit 在某台服务器上的 Memstore 的上限
- freeze_cnt 触发转储的计数器
手动触发合并
# 在root@sys下执行
alter system major freeze;
# 查看合并状态
select * from __all_zone where name = 'merge_status'
-
转储
为了解决2层LSM Treee合并是引发的问题,OB引入了转储机制(C1层)
引入转储是为了解决合并影响性能的问题,先内存刷入磁盘成minor sstable,刷入成功以后内存空间就释放了
- 将MemTable数据做小版本冻结(Minor Freeze)后写到磁盘上单独的转储文件里,不与SSTable 数据做合并
- 转储文件写完之后,冻结的MemTable内存被清空并重新使用
- 每次转储会将MemTable数据与前一次转储的数据合并,转储文件最终会合并到SSTable中
分层转储(从2.2版本开始)
为了优化转储越来越慢的问题,2.2引入分层转储的机制
- 新增L0层,被冻结的MemTable会直接flush为Mini SSTable,可同时存在多个Mini SSTable(2.2之前只会存在一个转储SSTable)
- L0层 : mini SSTable L0层通过server级配置参数来设置L0内部分层和每层最大SSTable个数;
- 内部分为level-0 到 level-n层,每层最大容纳SSTable的个数相同;
- L0中当低层级的 level-n 的 SSTable 到达一定数目上限或阈值后开始整体 compaction,合并成一个 SSTable 写入到高层级 level-n+1 层中
- 当L0层的Max level内的SSTable个数达到上限后,开始将L0层到L1层的转储,也就是生成一个Minor sstable;
- 这样做的好处是:降低写放大,但是会带来读放大
minor_compact_trigger
控制L0层mini sstable总数
- L1层: minor sstable
- 当L0层的sstable合并过来后,会和L1层的minor sstable合并,是有条件的
- L1层的Minor SSTable 仍然维持rowkey有序;
- L2 层: 是基线Major SSTable
- 为了保持多副本间基线数据完全一致,major sstable 保持只读,不发生合并动作
- **
major_compact_trigger
控制 memtable dump flush次数达到时触发 合并 **
- ob通过queuing 表来解决小数据量表大批量执行insert和delete带来的读放大;
- 引入 自适应buffer表转储策略(queuing 又称buffer表)
- 在mini sstable 和major sstable之间生成一个 buf minor sstable,生成的时候会消除增量数据里的所有delete标记,避免读放大
- 在查询buf minor sstable的时候,可以避免大量无效扫描动作
- L0层 : mini SSTable L0层通过server级配置参数来设置L0内部分层和每层最大SSTable个数;
- 架构变化:由3层 ------> 4层
- 3层架构:MemTable-> minor SSTable(L1) ->major SSTable(L2)
- 4层架构:MemTable->mini SSTable(L0)->minor SSTable(L1) ->major SSTable(L2)
转储的基本概念:
**转储功能的引入,是为了解决合并操作引发的一系列问题。**转储是租户级的
- 解决合并时资源消耗高,对在线业务性能影响较大
- 解决合并时单个租户MemStore使用率高会触发集群级合并,其他租户成为受害者
- 解决合并耗时长导致MemStore内存释放不及时,容易造成MemStore满二数据写入失败的情况
设计思路:
- 租户级:每个MemStore触发单独的冻结(freeze_trigger_percentage)及数据合并,不影响其他租户
- **粒度可控:**也可以通过命令为指定租户、指定observer、指定分区做转储
- 只和上一次转储的数据做合并,不和SSTable的数据做合并
转储相关参数:
-
freeze_trigger_percentage
MemStore达到多少的时候触发转储- 达到
memstore_limit_percentage *freeze_trigger_percentage
,先冻结,再根据minor_freeze_times
判断是转储还是合并 - 写并发大的业务,减小
freeze_trigger_percentage
的值,比如40,使MemStore尽早释放,进一步降低MemStore写满的概率
- 达到
-
minor_merge_concurrency
: 转储工作线程数,默认为0,表示10个线程- 并发转储的分区个数,单分区不支持拆分转储,分区表可加速
- 并发过少会影响转储的性能和效果
- 并发过多,消耗过多资源,影响在线业务的性能
-
minor_freeze_times
: 控制两次合并之间的转储次数- 达到此数着自动触发合并(major freeze)
- 设置为0 表示关闭转储,则达到冻结阈值(freeze_trigger_percentage)直接触发集群合并
- 增大
minor_freeze_times
的值,尽量避免业务峰值时段触发合并,将合并的时机延到低峰期
-
minor_compact_trigger
控制mini sstable的个数
转储适用场景:
- 批处理、大量数据导入等场景,写MemStore的速度快,需要MemStore内存尽快释放,MemStore主要保存增量数据
- 业务峰值交易量大,写入MemStore的数据很多,又不想在峰值时段触发合并,希望将合并延后
转储对数据库的影响
优势
- 租户级别,不会影响集群
- 资源消耗少,对在线业务性能影响较低
- 耗时短,MemStore更快释放,降低发生MemStore写满的概率
劣势
- 数据层级增多,查询链路变成,查询性能下降
- 冗余数据增多,占用更多磁盘空间
手动触发
ALTER SYSTEM MINOR FREEZE
[{TENANT[=] (‘tt1' [, 'tt2'...]) | PARTITION_ID [=] 'partidx%partcount@tableid‘}]
[SERVER [=] ('ip:port' [, 'ip:port'...])];
-- 可以指定租户TENANT,可以指定分区PARTITION_ID,可以指定server
# 集群级别转储
ALTER SYSTEM MINOR FREEZE;
# server级别转储
ALTER SYSTEM MINOR FREEZE SERVER='10.10.10.1:2882';
# 租户级别转储
ALTER SYSTEM MINOR FREEZE TENANT=('prod_tenant');
#分区级别转储
ALTER SYSTEM MINOR FREEZE PARTITION_ID = '8%1@1099511627933';
- 可选的控制参数:
tenant
: 指定要执行minor freeze的租户Partition_id
:指定要执行minor freeze的partitionserver
: 指定要执行 minor freeze的observer
- 当什么选项都不指定时,默认对所有的observer上的所有租户执行转储
- 手动触发转储次数,不受参数 minor_freeze_times的限制,手动触发次数过多不会触发合并
自动触发
达到参数freeze_trigger_percentage 配置的值
memstore_limit_percentage *freeze_trigger_percentage
,先冻结,再根据minor_freeze_times
判断是转储还是合并
查看转储
自动触发的转储在__all_server_event_history
表中
select * from __all_server_event_history where (event like '%merge%' or event like '%minor%') order by gmt_create desc limit 10;
+----------------------------+---------------+----------+--------+-------------------------+-----------+--------+-------+--------+-------+--------+-------+--------+-------+--------+-------+--------+------------+
| gmt_create | svr_ip | svr_port | module | event | name1 | value1 | name2 | value2 | name3 | value3 | name4 | value4 | name5 | value5 | name6 | value6 | extra_info |
+----------------------------+---------------+----------+--------+-------------------------+-----------+--------+-------+--------+-------+--------+-------+--------+-------+--------+-------+--------+------------+
| 2023-06-06 20:41:20.768582 | 192.168.80.15 | 2882 | freeze | do minor freeze success | tenant_id | 0 | | NULL | | | | | | | | | |
| 2023-06-06 20:41:20.763871 | 192.168.80.10 | 2882 | freeze | do minor freeze success | tenant_id | 0 | | NULL | | | | | | | | | |
| 2023-06-06 20:41:20.760225 | 192.168.80.13 | 2882 | freeze | do minor freeze success | tenant_id | 0 | | NULL | | | | | | | | | |
| 2023-06-06 20:41:20.642135 | 192.168.80.13 | 2882 | freeze | do minor freeze | tenant_id | 0 | | NULL | | | | | | | | | |
| 2023-06-06 20:41:20.642120 | 192.168.80.15 | 2882 | freeze | do minor freeze | tenant_id | 0 | | NULL | | | | | | | | | |
| 2023-06-06 20:41:20.642010 | 192.168.80.10 | 2882 | freeze | do minor freeze | tenant_id | 0 | | NULL | | | | | | | | | |
| 2023-06-06 20:39:31.851705 | 192.168.80.13 | 2882 | freeze | do minor freeze success | tenant_id | 0 | | NULL | | | | | | | | | |
| 2023-06-06 20:39:31.624214 | 192.168.80.15 | 2882 | freeze | do minor freeze success | tenant_id | 0 | | NULL | | | | | | | | | |
| 2023-06-06 20:39:31.581254 | 192.168.80.10 | 2882 | freeze | do minor freeze success | tenant_id | 0 | | NULL | | | | | | | | | |
| 2023-06-06 20:39:30.796525 | 192.168.80.15 | 2882 | freeze | do minor freeze | tenant_id | 0 | | NULL | | | | | | | | | |
+----------------------------+---------------+----------+--------+-------------------------+-----------+--------+-------+--------+-------+--------+-------+--------+-------+--------+-------+--------+------------+
10 rows in set (0.060 sec)
手动触发的转储__all_rootservice_event_history
表中
select * from __all_rootservice_event_history where event like '%minor%'
重点在value2 这个字段中
select * from __all_rootservice_event_history where event like '%minor%' order by gmt_create desc limit 10;
+----------------------------+--------------+-------------------+-------+--------+-------+------------------------------------------------------------------------------------------------------------------------------------------------+-------+--------+-------+--------+-------+--------+-------+--------+------------+---------------+-------------+
| gmt_create | module | event | name1 | value1 | name2 | value2 | name3 | value3 | name4 | value4 | name5 | value5 | name6 | value6 | extra_info | rs_svr_ip | rs_svr_port |
+----------------------------+--------------+-------------------+-------+--------+-------+------------------------------------------------------------------------------------------------------------------------------------------------+-------+--------+-------+--------+-------+--------+-------+--------+------------+---------------+-------------+
| 2023-06-06 20:41:20.768811 | root_service | root_minor_freeze | ret | 0 | arg | {tenant_ids:[], partition_key:{tid:18446744073709551615, partition_id:-1, part_idx:268435455, subpart_idx:268435455}, server_list:[], zone:""} | | | | | | | | | | 192.168.80.10 | 2882 |
| 2023-06-06 20:39:31.851875 | root_service | root_minor_freeze | ret | 0 | arg | {tenant_ids:[], partition_key:{tid:18446744073709551615, partition_id:-1, part_idx:268435455, subpart_idx:268435455}, server_list:[], zone:""} | | | | | | | | | | 192.168.80.10 | 2882 |
| 2023-06-06 19:59:18.528693 | root_service | root_minor_freeze | ret | 0 | arg | {tenant_ids:[], partition_key:{tid:18446744073709551615, partition_id:-1, part_idx:268435455, subpart_idx:268435455}, server_list:[], zone:""} | | | | | | | | | | 192.168.80.10 | 2882 |
| 2023-06-06 02:00:01.530634 | root_service | root_minor_freeze | ret | 0 | arg | {tenant_ids:[], partition_key:{tid:18446744073709551615, partition_id:-1, part_idx:268435455, subpart_idx:268435455}, server_list:[], zone:""} | | | | | | | | | | 192.168.80.10 | 2882 |
| 2023-06-05 02:00:01.766393 | root_service | root_minor_freeze | ret | 0 | arg | {tenant_ids:[], partition_key:{tid:18446744073709551615, partition_id:-1, part_idx:268435455, subpart_idx:268435455}, server_list:[], zone:""} | | | | | | | | | | 192.168.80.10 | 2882 |
| 2023-06-04 02:00:01.794636 | root_service | root_minor_freeze | ret | 0 | arg | {tenant_ids:[], partition_key:{tid:18446744073709551615, partition_id:-1, part_idx:268435455, subpart_idx:268435455}, server_list:[], zone:""} | | | | | | | | | | 192.168.80.10 | 2882 |
| 2023-06-03 02:00:01.845602 | root_service | root_minor_freeze | ret | 0 | arg | {tenant_ids:[], partition_key:{tid:18446744073709551615, partition_id:-1, part_idx:268435455, subpart_idx:268435455}, server_list:[], zone:""} | | | | | | | | | | 192.168.80.10 | 2882 |
| 2023-06-02 02:00:01.319800 | root_service | root_minor_freeze | ret | 0 | arg | {tenant_ids:[], partition_key:{tid:18446744073709551615, partition_id:-1, part_idx:268435455, subpart_idx:268435455}, server_list:[], zone:""} | | | | | | | | | | 192.168.80.10 | 2882 |
| 2023-06-01 02:00:01.891988 | root_service | root_minor_freeze | ret | 0 | arg | {tenant_ids:[], partition_key:{tid:18446744073709551615, partition_id:-1, part_idx:268435455, subpart_idx:268435455}, server_list:[], zone:""} | | | | | | | | | | 192.168.80.10 | 2882 |
| 2023-05-31 02:00:01.036797 | root_service | root_minor_freeze | ret | 0 | arg | {tenant_ids:[], partition_key:{tid:18446744073709551615, partition_id:-1, part_idx:268435455, subpart_idx:268435455}, server_list:[], zone:""} | | | | | | | | | | 192.168.80.10 | 2882 |
+----------------------------+--------------+-------------------+-------+--------+-------+------------------------------------------------------------------------------------------------------------------------------------------------+-------+--------+-------+--------+-------+--------+-------+--------+------------+---------------+-------------+
10 rows in set (0.004 sec)
查看OB集群合并和冻结状态__all_zone
obclient [oceanbase]> select * from __all_zone;
+----------------------------+----------------------------+-------+--------------------------+------------------+------------+
| gmt_create | gmt_modified | zone | name | value | info |
+----------------------------+----------------------------+-------+--------------------------+------------------+------------+
| 2023-05-22 13:44:48.980066 | 2023-05-22 13:44:48.980066 | | cluster | 0 | obcluster |
| 2023-05-22 13:44:48.980585 | 2023-05-23 19:55:14.184675 | | config_version | 1684842914178885 | |
| 2023-05-22 13:44:48.980585 | 2023-06-06 19:59:17.780305 | | frozen_time | 1686052767285953 | |
| 2023-05-22 13:44:48.980585 | 2023-06-06 19:59:17.780305 | | frozen_version | 17 | |
| 2023-05-22 13:44:48.981652 | 2023-05-22 13:44:48.981652 | | gc_schema_version | 0 | |
| 2023-05-22 13:44:48.980585 | 2023-06-06 19:59:37.581624 | | global_broadcast_version | 17 | |
| 2023-05-22 13:44:48.980585 | 2023-05-22 13:44:48.980585 | | is_merge_error | 0 | |
| 2023-05-22 13:44:48.980585 | 2023-06-06 20:05:38.376554 | | last_merged_version | 17 | |
| 2023-05-22 13:44:48.980585 | 2023-06-06 20:05:38.376729 | | lease_info_version | 1686053138375756 | |
| 2023-05-22 13:44:48.980585 | 2023-06-06 20:05:38.376729 | | merge_status | 0 | IDLE |
| 2023-05-22 13:44:48.980585 | 2023-05-22 13:44:48.980585 | | privilege_version | 0 | |
| 2023-05-22 13:44:48.981652 | 2023-05-22 13:44:48.981652 | | proposal_frozen_version | 1 | |
| 2023-05-22 13:44:48.981652 | 2023-05-22 13:44:48.981652 | | snapshot_gc_ts | 0 | |
| 2023-05-22 13:44:48.981652 | 2023-05-22 13:44:48.981652 | | storage_format_version | 4 | |
| 2023-05-22 13:44:48.981652 | 2023-05-22 13:44:48.981652 | | time_zone_info_version | 0 | |
| 2023-05-22 13:44:48.980585 | 2023-05-22 13:44:48.980585 | | try_frozen_version | 1 | |
| 2023-05-22 13:44:48.981652 | 2023-05-22 13:44:48.981652 | | warm_up_start_time | 0 | |
| 2023-05-22 13:44:48.981652 | 2023-06-06 20:05:27.955867 | zone1 | all_merged_version | 17 | |
| 2023-05-22 13:44:48.981652 | 2023-06-06 19:59:37.864609 | zone1 | broadcast_version | 17 | |
| 2023-05-22 13:44:48.982720 | 2023-05-22 19:45:48.340319 | zone1 | idc | 0 | dev |
| 2023-05-22 13:44:48.982720 | 2023-06-06 20:05:27.954791 | zone1 | is_merge_timeout | 0 | |
| 2023-05-22 13:44:48.981652 | 2023-06-06 20:05:27.954791 | zone1 | is_merging | 0 | |
| 2023-05-22 13:44:48.981652 | 2023-06-06 20:05:27.954791 | zone1 | last_merged_time | 1686053127954511 | |
| 2023-05-22 13:44:48.981652 | 2023-06-06 20:05:27.954791 | zone1 | last_merged_version | 17 | |
| 2023-05-22 13:44:48.982720 | 2023-06-06 19:59:37.864609 | zone1 | merge_start_time | 1686052777863600 | |
| 2023-05-22 13:44:48.982720 | 2023-06-06 20:05:27.955867 | zone1 | merge_status | 0 | IDLE |
| 2023-05-22 13:44:48.982720 | 2023-05-22 13:44:48.9