集群架構
基本概念
集群:
集群由一個或多個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里創建unit3.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 指定集群id7,-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_profileexport 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_countfrom __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 = 1001and t2.role = 1 -- role=1為leader,為2是followerorder 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