提交 ab09b320 编写于 作者: O obpilot 提交者: LINGuanRen

Add chapter05/06/07/08 #692

上级 f41ee5fb
# 第 5 章:如何运维 OceanBase 社区版
[TOC]
## 运维概述
OceanBase 运维目标主要是维护集群稳定性,管理集群资源,保证集群性能最优。
OceanBase 运维具体的工作包含:
+ 集群扩容/缩容/替换/搬迁/重启/备份/监控/升级
+ 租户扩容/缩容/性能调优
+ 故障处理
OceanBase 社区版提供了自动化运维工具 OBD 方便集群部署,也能承担部分扩容和升级操作,但严格来说这个工具的定位不是“运维平台”。OceanBase 的官方运维产品 OCP 后期也会支持社区版。那个产品有产品文档手册,这里就不再重复。本课主要是介绍 OceanBase 运维操作的具体原理和步骤,自动化产品只是将这些基本操作封装了一下而已。第三方运维平台也可以参考这里的原理和步骤介绍进行开发,将 OceanBase 纳入自己的产品支持列表中。
## 本章目录
+ [如何管理 OceanBase 集群](5.1.md)
+ [如何管理 OceanBase 租户](5.2.md)
+ [如何对 OceanBase 进行备份和恢复](5.3.md)
+ [如何监控 OceanBase 和配置告警](5.4.md)
+ [如何对 OceanBase 进行简单性能诊断](5.5.md)
+ [如何快速处理 OceanBase 故障](5.6.md)
+ [如何使用 OBD 运维](5.7.md)
+ [附录](5.8.md)
## 如何联系我们
欢迎广大 OceanBase 爱好者、用户和客户有任何问题联系我们反馈:
+ 社区版官网论坛:[https://open.oceanbase.com/answer](https://open.oceanbase.com/answer)
+ 社区版项目网站提 `Issue`[https://github.com/oceanbase/oceanbase/issues](https://github.com/oceanbase/oceanbase/issues)
+ 钉钉群:群号 `33254054`
\ No newline at end of file
# 如何监控 OceanBase 和配置告警
## 如何用传统监控产品监控 OB
OceanBase 是单进程软件,其性能瓶颈通常不会首先是 IO ,而更可能是 CPU 和内存、网络等。当然,IO 也会影响 OceanBase 租户的读写性能。
在部署的时候,介绍 OceanBase 软件的 IO 有三类:运行日志、数据文件 和事务日志。生产环境建议用三块独立的磁盘存储,或者至少用 三个独立的文件系统。
针对 OceanBase 主机,建议部署下面监控。
| 监控项 | 描述 | 应对策略 |
|----------------------|-------------------------------------------|----------------------------------------------------------------|
| CPU | 监控CPU 的 USER、SYS、IOWAIT 和整体利用率。 | 分析 CPU 利用率高的进程。如果是 OBSERVER 进程,则进一步分析 SQL。 |
| LOAD | 主机的负载,跟 CPU 通常密切相关。 | |
| 内存 | 监控剩余内存。 | 剩余内存通常很稳定,如果小于1G ,则进程 OBSERVER 或 OBPROXY 有 OOM 风险。 |
| 监听 2881/2883/2884 端口 | 2881 是 OBSERVER 连接端口,2883 是 OBPROXY 连接端口。 | 确认进程是否存活,查看进程运行日志,分析进程故障或监听失败原因。立即重启进程。 |
| 网络流量 | 监控流量是否打满网卡(万兆) | 分析集群是否发生负载均衡,调低数据迁移的并发或者分析是否有大量数据抽取。 |
| IO 吞吐量、利用率和延时 | 监控数据盘和日志盘的IO 延时、吞吐量和IO 利用率。 | 分析集群是否发生负载均衡,调低数据迁移的并发或者分析是否有大量数据抽取。分析是否坏盘。分析业务SQL 是否执行计划有问题等。 |
## 如何用 PROMETHEUS 监控 OB
在第 2 课里介绍 OceanBase 监控插件 OBAgent 部署。OBAgent 启动后会自动生成适合 `Prometheus` 系统的配置文件目录 `prometheus_config`
### PROMETHEUS 安装部署
+ 下载 `Promethueus` 软件,地址:`https://prometheus.io/download/`
+ 解压缩安装
```bash
sudo tar zxvf prometheus-2.30.3.linux-amd64.tar.gz -C /usr/local/
# 复制 OBAgent 生成的 Prometheus 配置文件到 Prometheus 安装目录中。
sudo mv prometheus_config/ /usr/local/prometheus-2.30.3.linux-amd64/
```
+ Prometheus 服务文件
```bash
sudo mkdir /var/lib/prometheus
sudo vim /etc/systemd/system/prometheus.service
[Unit]
Description=Prometheus Server
Documentation=https://prometheus.io/docs/introduction/overview/
After=network-online.target
[Service]
Restart=on-failure
ExecStart=/usr/local/prometheus-2.30.3.linux-amd64/prometheus --config.file=/usr/local/prometheus-2.30.3.linux-amd64/prometheus_config/prometheus.yaml --storage.tsdb.path=/var/lib/prometheus --web.enable-lifecycle --web.external-url=http://172.20.249.54:9090
[Install]
WantedBy=multi-user.target
```
启动服务
```bash
sudo systemctl daemon-reload
sudo systemctl start prometheus
sudo systemctl status prometheus
[admin@obce00 ~]$ sudo systemctl status prometheus
● prometheus.service - Prometheus Server
Loaded: loaded (/etc/systemd/system/prometheus.service; disabled; vendor preset: disabled)
Active: active (running) since Thu 2021-10-21 15:54:42 CST; 49s ago
Docs: https://prometheus.io/docs/introduction/overview/
Main PID: 902555 (prometheus)
Tasks: 13 (limit: 195588)
Memory: 40.6M
CGroup: /system.slice/prometheus.service
└─902555 /usr/local/prometheus-2.30.3.linux-amd64/prometheus --config.file=/usr/local/prometheus-2.30.3.linux-amd64/prometheus_config/prometheus.yaml --storage.tsdb.path=/var/lib/prometheus --web.enable-lifecycle --web.external-url=http://172.20.249.54:9090
Oct 21 15:54:42 obce00 prometheus[902555]: level=info ts=2021-10-21T07:54:42.275Z caller=head.go:479 component=tsdb msg="Replaying on-disk memory mappable chunks if any"
Oct 21 15:54:42 obce00 prometheus[902555]: level=info ts=2021-10-21T07:54:42.275Z caller=head.go:513 component=tsdb msg="On-disk memory mappable chunks replay completed" duration=2.127µs
Oct 21 15:54:42 obce00 prometheus[902555]: level=info ts=2021-10-21T07:54:42.275Z caller=head.go:519 component=tsdb msg="Replaying WAL, this may take a while"
Oct 21 15:54:42 obce00 prometheus[902555]: level=info ts=2021-10-21T07:54:42.275Z caller=head.go:590 component=tsdb msg="WAL segment loaded" segment=0 maxSegment=0
Oct 21 15:54:42 obce00 prometheus[902555]: level=info ts=2021-10-21T07:54:42.275Z caller=head.go:596 component=tsdb msg="WAL replay completed" checkpoint_replay_duration=39.378µs wal_replay_duration=185.207µs total_replay_duration=242.438µs
Oct 21 15:54:42 obce00 prometheus[902555]: level=info ts=2021-10-21T07:54:42.277Z caller=main.go:849 fs_type=XFS_SUPER_MAGIC
Oct 21 15:54:42 obce00 prometheus[902555]: level=info ts=2021-10-21T07:54:42.277Z caller=main.go:852 msg="TSDB started"
Oct 21 15:54:42 obce00 prometheus[902555]: level=info ts=2021-10-21T07:54:42.277Z caller=main.go:979 msg="Loading configuration file" filename=/usr/local/prometheus-2.30.3.linux-amd64/prometheus_config/prometheus.yaml
Oct 21 15:54:42 obce00 prometheus[902555]: level=info ts=2021-10-21T07:54:42.281Z caller=main.go:1016 msg="Completed loading of configuration file" filename=/usr/local/prometheus-2.30.3.linux-amd64/prometheus_config/prometheus.yaml totalDuration=4.630509ms db_storage=1>
Oct 21 15:54:42 obce00 prometheus[902555]: level=info ts=2021-10-21T07:54:42.281Z caller=main.go:794 msg="Server is ready to receive web requests."
[admin@obce00 ~]$
```
+ 确认 Prometheus 启动成功
```bash
sudo netstat -ntlp | grep 9090
[admin@obce00 ~]$ sudo netstat -ntlp | grep 9090
tcp6 0 0 :::9090 :::* LISTEN 902555/prometheus
```
### PROMETHEUS 使用
使用浏览器测试:`http://172.20.249.54:9090/alerts`
+ 查看告警事件
![](media/16321093510830/16348034195467.jpg)
+ 查看节点 LOAD
![](media/16321093510830/16348064335069.jpg)
这里面会涉及到很多自定义的指标名。目前支持的指标名如下。
+ 主机指标
| 指标名 | Ladel | 描述 | 类型 |
|------------------------------------|---------------------------------|--------------|---------|
| node_cpu_seconds_total | cpu,mode,svr_ip | CPU 时间 | counter |
| node_disk_read_bytes_total | device,svr_ip | 磁盘读取字节数 | counter |
| node_disk_read_time_seconds_total | device,svr_ip | 磁盘读取消耗总时间 | counter |
| node_disk_reads_completed_total | device,svr_ip | 磁盘读取完成次数 | counter |
| node_disk_written_bytes_total | device,svr_ip | 磁盘写入字节数 | counter |
| node_disk_write_time_seconds_total | device,svr_ip | 磁盘写入消耗总时间 | counter |
| node_disk_writes_completed_total | device,svr_ip | 磁盘写入完成次数 | counter |
| node_filesystem_avail_bytes | device,fstype,mountpoint,svr_ip | 文件系统可用大小 | gauge |
| node_filesystem_readonly | device,fstype,mountpoint,svr_ip | 文件系统是否只读 | gauge |
| node_filesystem_size_bytes | device,fstype,mountpoint,svr_ip | 文件系统大小 | gauge |
| node_load1 | svr_ip | 1 分钟平均 load | gauge |
| node_load5 | svr_ip | 5 分钟平均 load | gauge |
| node_load15 | svr_ip | 15 分钟平均 load | gauge |
| node_memory_Buffers_bytes | svr_ip | 内存 buffer 大小 | gauge |
| node_memory_Cached_bytes | svr_ip | 内存 cache 大小 | gauge |
| node_memory_MemFree_bytes | svr_ip | 内存 free 大小 | gauge |
| node_memory_MemTotal_bytes | svr_ip | 内存总大小 | gauge |
| node_network_receive_bytes_total | device,svr_ip | 网络接受总字节数 | counter |
| node_network_transmit_bytes_total | device,svr_ip | 网络发送总字节数 | counter |
| node_ntp_offset_seconds | svr_ip | NTP 时钟偏移 | |
# 如何快速处理 OceanBase 故障
## 如何判断定位 OceanBase 故障
当 OceanBase 故障时,应用端可能会有很多报错信息反馈过来。此时需要初步判断应用是全部失败,还是有成功有失败。理论上 OceanBase 局部节点故障,应用只会是局部数据库读写故障或者中断,在1分钟左右应用就能全部恢复。
同时需要尽快确认 OceanBase 集群的状态。
+ 首先确认集群节点状态
```sql
select a.zone,concat(a.svr_ip,':',a.svr_port) observer, cpu_total, (cpu_total-cpu_assigned) cpu_free, round(mem_total/1024/1024/1024) mem_total_gb, round((mem_total-mem_assigned)/1024/1024/1024) mem_free_gb, usec_to_time(b.last_offline_time) last_offline_time, usec_to_time(b.start_service_time) start_service_time, b.status, usec_to_time(b.stop_time) stop_time
from __all_virtual_server_stat a join __all_server b on (a.svr_ip=b.svr_ip and a.svr_port=b.svr_port)
order by a.zone, a.svr_ip
;
```
关注:
+ 节点状态 `status` :升级前没有 `inactive` 值,升级过程中会有。
+ 节点服务时间 `start_service_time` 的时间不是默认值(`1970-01-01 08:00:00.000000`)。如果是,则表示节点还没有恢复结束。
+ 节点停止时间 `stop_time` 的时间是默认值(`1970-01-01 08:00:00.000000`)。如果不是,则表示节点被停服( `stop server`) 了,先启动服务(`start server`)。
+ 其次确认集群近期事件
```sql
SELECT DATE_FORMAT(gmt_create, '%b%d %H:%i:%s') gmt_create_ , module, event, name1, value1, name2, value2, rs_svr_ip
FROM __all_rootservice_event_history
WHERE 1 = 1
AND module IN ('server','root_service','balancer')
AND gmt_create > SUBDATE(now(),interval 1 hour)
ORDER BY gmt_create DESC
LIMIT 50;
```
注意留意节点掉线和上线事件、合并超时事件、数据迁移事件等。
## 如何处理节点掉线或宕机故障
## 修改租户变量允许 DDL
如果有租户的架构是 `1-1-1` 并且宕机的节点就是该租户的成员,宕机后可能导致租户的 DDL 报错。
```sql
MySQL [test]> create table t2 like t1;
ERROR 4624 (HY000): machine resource is not enough to hold a new unit
```
此时需要将全局变量 `ob_create_table_strict_mode` 值设置为 OFF 。
```sql
set global ob_create_table_strict_mode = off;
```
然后重新登录业务租户,就可以做 DDL 了。
注意:这个变量关闭还是有一点风险,需要尽快修复故障节点。
## 启动 OBSERVER 进程
如果节点掉线了,但是节点的进程还在,则很可能有两个原因:
+ 节点跟其他节点的时间误差超过 200ms 。这个时候首先检查时间是否同步。
+ 节点的事务日志目录空间使用率超过参数(`clog_disk_usage_limit_percentage`,默认值是 95 ,意为 95% )定义。
如果节点进程不在,则首先尝试拉起进程。然后再观察几分钟,看进程是否再次推出。启动进程时注意用正确的方式。
```bash
# 切换到正确用户下
cd /home/admin/oceanbase-ce && bin/observer
```
如果进程还是会退出,则查看最近的进程运行日志,搜索 ERROR 信息。
```bash
cd /home/admin/oceanbase-ce
grep ERROR log/observer.log | vim -
```
## 如何处理节点时间不同步故障
### 判断集群节点时间同步误差
节点时间同步问题需要准确判断。需要对集群中所有节点进行相互测试,测试命令是:`clockdiff`
```bash
[root@obce01 ~]# clockdiff 172.20.249.49
...
host=172.20.249.49 rtt=428(314)ms/0ms delta=0ms/0ms Thu Sep 30 21:33:16 2021
[root@obce01 ~]# clockdiff 172.20.249.51
...
host=172.20.249.51 rtt=426(314)ms/0ms delta=0ms/0ms Thu Sep 30 21:33:22 2021
[root@obce01 ~]#
[root@obce02 ~]# clockdiff 172.20.249.50
.
host=172.20.249.50 rtt=750(187)ms/0ms delta=0ms/0ms Thu Sep 30 21:34:10 2021
[root@obce02 ~]# clockdiff 172.20.249.52
...
host=172.20.249.52 rtt=423(315)ms/0ms delta=-1ms/-1ms Thu Sep 30 21:34:14 2021
```
在某些客户机房环境了,命令 `clockdiff` 可能会报错(原因不明)。此时可以换用 `ping` 命令,查看返回的报文信息判断时间误差。
```bash
ping -T tsandaddr 172.20.249.49 -c 3
输出:
[root@obce01 ~]# ping -T tsandaddr 172.20.249.49 -c 3
PING 172.20.249.49 (172.20.249.49) 56(124) bytes of data.
64 bytes from 172.20.249.49: icmp_seq=1 ttl=64 time=26.3 ms
TS: 172.20.249.52 48963954 absolute
172.20.249.49 13
172.20.249.49 0
172.20.249.52 13
64 bytes from 172.20.249.49: icmp_seq=2 ttl=64 time=0.333 ms
TS: 172.20.249.52 48964963 absolute
172.20.249.49 1
172.20.249.49 0
172.20.249.52 0
64 bytes from 172.20.249.49: icmp_seq=3 ttl=64 time=0.480 ms
TS: 172.20.249.52 48966011 absolute
172.20.249.49 0
172.20.249.49 0
172.20.249.52 0
--- 172.20.249.49 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2057ms
rtt min/avg/max/mdev = 0.333/9.038/26.302/12.207 ms
```
### 修复集群节点时间同步服务
此时,主要是检查每个节点的时间 同步服务是否正常。强烈推荐使用 `chrony` 同步服务,不要使用 `ntpd`
+ 检查 `chrony` 节点同步服务
使用 `chrony` 时间服务是为了保证 OceanBase 集群各个节点时间尽可能保证同步,下面这些命令供参考。具体使用请查看 `chrony` 官方使用说明:[Chronyc Frequently Asked Questions](https://chrony.tuxfamily.org/faq.html)
```bash
查看时间同步活动
chronyc activity
查看时间服务器
chronyc sources
查看同步状态
chronyc sources -v
校准时间服务器:
chronyc tracking
```
+ 检查节点跟选中的时间服务器之间的时间差
这里也是使用 `clockdiff` 命令。
有些时间服务器是禁止`ping` 命令的,或者防火墙是禁止 `ping` 命令的,则无法探知时间误差。
这里面的判断可能会比较复杂。
一个快速的校正时间方法就是直接使用 `ntpdate` 命令同步本节点跟时间源之间的时间。测试环境如果时间总是不同步,可以将这个命令写到 `crontab` 里,每分钟运行一次。
```bash
ntpdate -b xxx.xxx.xxx.xxx
```
# 如何使用 OBD 运维
## 如何使用 OBD 调整 OceanBase 集群参数
示例:通过 OBD 给集群节点内存扩容(从 8G 扩容到 16G)。
+ 使用 OBD 命令编辑参数文件
```bash
obd cluster edit-config obce-3zones
修改:
memory_limit: 16G # The maximum running memory for an observer
保存:
oceanbase-ce-3.1.0 already installed.
obproxy-3.1.0 already installed.
Search param plugin and load ok
Parameter check ok
Save deploy "obce-3zones" configuration
```
+ 使用 OBD 命令 `reload` 参数
```bash
obd cluster reload obce-3zones
输出:
Get local repositories and plugins ok
Open ssh connection ok
Cluster status check ok
Connect to observer ok
Connect to obproxy ok
obce-3zones reload
```
+ 登录 SYS 租户确认参数变更
```sql
MySQL [oceanbase]> show parameters like 'memory_limit';
+-------+----------+---------------+----------+--------------+-----------+-------+--------------------------------------------------------------------------------------------------------------------------------+----------+---------+---------+-------------------+
| zone | svr_type | svr_ip | svr_port | name | data_type | value | info | section | scope | source | edit_level |
+-------+----------+---------------+----------+--------------+-----------+-------+--------------------------------------------------------------------------------------------------------------------------------+----------+---------+---------+-------------------+
| zone2 | observer | 172.20.249.49 | 2882 | memory_limit | NULL | 16G | the size of the memory reserved for internal use(for testing purpose), 0 means follow memory_limit_percentage. Range: 0, [8G,) | OBSERVER | CLUSTER | DEFAULT | DYNAMIC_EFFECTIVE |
| zone1 | observer | 172.20.249.52 | 2882 | memory_limit | NULL | 16G | the size of the memory reserved for internal use(for testing purpose), 0 means follow memory_limit_percentage. Range: 0, [8G,) | OBSERVER | CLUSTER | DEFAULT | DYNAMIC_EFFECTIVE |
| zone3 | observer | 172.20.249.51 | 2882 | memory_limit | NULL | 16G | the size of the memory reserved for internal use(for testing purpose), 0 means follow memory_limit_percentage. Range: 0, [8G,) | OBSERVER | CLUSTER | DEFAULT | DYNAMIC_EFFECTIVE |
+-------+----------+---------------+----------+--------------+-----------+-------+--------------------------------------------------------------------------------------------------------------------------------+----------+---------+---------+-------------------+
3 rows in set (0.005 sec)
```
+ 确认集群可用内存变化
```sql
select a.zone,concat(a.svr_ip,':',a.svr_port) observer, cpu_total, (cpu_total-cpu_assigned) cpu_free, round(mem_total/1024/1024/1024) mem_total_gb, round((mem_total-mem_assigned)/1024/1024/1024) mem_free_gb, usec_to_time(b.last_offline_time) last_offline_time, usec_to_time(b.start_service_time) start_service_time, b.status, usec_to_time(b.stop_time) stop_time, b.build_version
from __all_virtual_server_stat a join __all_server b on (a.svr_ip=b.svr_ip and a.svr_port=b.svr_port)
order by a.zone, a.svr_ip
;
输出:
+-------+--------------------+-----------+----------+--------------+-------------+----------------------------+----------------------------+--------+----------------------------+------------------------------------------------------------------------+
| zone | observer | cpu_total | cpu_free | mem_total_gb | mem_free_gb | last_offline_time | start_service_time | status | stop_time | build_version |
+-------+--------------------+-----------+----------+--------------+-------------+----------------------------+----------------------------+--------+----------------------------+------------------------------------------------------------------------+
| zone1 | 172.20.249.52:2882 | 14 | 0 | 13 | 8 | 1970-01-01 08:00:00.000000 | 2021-09-25 08:19:06.622351 | active | 1970-01-01 08:00:00.000000 | 3.1.0_3-b20901e8c84d3ea774beeaca963c67d7802e4b4e(Aug 10 2021 08:10:38) |
| zone2 | 172.20.249.49:2882 | 14 | 0 | 13 | 8 | 1970-01-01 08:00:00.000000 | 2021-09-25 08:19:07.392669 | active | 1970-01-01 08:00:00.000000 | 3.1.0_3-b20901e8c84d3ea774beeaca963c67d7802e4b4e(Aug 10 2021 08:10:38) |
| zone3 | 172.20.249.51:2882 | 14 | 0 | 13 | 8 | 1970-01-01 08:00:00.000000 | 2021-09-26 14:05:58.641570 | active | 1970-01-01 08:00:00.000000 | 3.1.0_3-b20901e8c84d3ea774beeaca963c67d7802e4b4e(Aug 10 2021 08:10:38) |
+-------+--------------------+-----------+----------+--------------+-------------+----------------------------+----------------------------+--------+----------------------------+------------------------------------------------------------------------+
3 rows in set (0.009 sec)
select t1.name resource_pool_name, t2.`name` unit_config_name, t2.max_cpu, t2.min_cpu, round(t2.max_memory/1024/1024/1024) max_mem_gb, round(t2.min_memory/1024/1024/1024) min_mem_gb, t3.unit_id, t3.zone, concat(t3.svr_ip,':',t3.`svr_port`) observer,t4.tenant_id, t4.tenant_name
from __all_resource_pool t1 join __all_unit_config t2 on (t1.unit_config_id=t2.unit_config_id)
join __all_unit t3 on (t1.`resource_pool_id` = t3.`resource_pool_id`)
left join __all_tenant t4 on (t1.tenant_id=t4.tenant_id)
order by t1.`resource_pool_id`, t2.`unit_config_id`, t3.unit_id
;
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
| resource_pool_name | unit_config_name | max_cpu | min_cpu | max_mem_gb | min_mem_gb | unit_id | zone | observer | tenant_id | tenant_name |
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
| sys_pool | sys_unit_config | 5 | 5 | 2 | 2 | 1 | zone1 | 172.20.249.52:2882 | 1 | sys |
| sys_pool | sys_unit_config | 5 | 5 | 2 | 2 | 2 | zone2 | 172.20.249.49:2882 | 1 | sys |
| sys_pool | sys_unit_config | 5 | 5 | 2 | 2 | 3 | zone3 | 172.20.249.51:2882 | 1 | sys |
| my_pool | unit1 | 9 | 9 | 3 | 3 | 1001 | zone1 | 172.20.249.52:2882 | 1002 | obmysql |
| my_pool | unit1 | 9 | 9 | 3 | 3 | 1002 | zone2 | 172.20.249.49:2882 | 1002 | obmysql |
| my_pool | unit1 | 9 | 9 | 3 | 3 | 1003 | zone3 | 172.20.249.51:2882 | 1002 | obmysql |
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
6 rows in set (0.010 sec)
```
新增可用内存 8G 。
+ 调大业务租户内存规格
```sql
alter resource unit unit1 max_memory='11G',min_memory='11G';
# 重复查看集群可用内存
+-------+--------------------+-----------+----------+--------------+-------------+----------------------------+----------------------------+--------+----------------------------+------------------------------------------------------------------------+
| zone | observer | cpu_total | cpu_free | mem_total_gb | mem_free_gb | last_offline_time | start_service_time | status | stop_time | build_version |
+-------+--------------------+-----------+----------+--------------+-------------+----------------------------+----------------------------+--------+----------------------------+------------------------------------------------------------------------+
| zone1 | 172.20.249.52:2882 | 14 | 0 | 13 | 0 | 1970-01-01 08:00:00.000000 | 2021-09-25 08:19:06.622351 | active | 1970-01-01 08:00:00.000000 | 3.1.0_3-b20901e8c84d3ea774beeaca963c67d7802e4b4e(Aug 10 2021 08:10:38) |
| zone2 | 172.20.249.49:2882 | 14 | 0 | 13 | 0 | 1970-01-01 08:00:00.000000 | 2021-09-25 08:19:07.392669 | active | 1970-01-01 08:00:00.000000 | 3.1.0_3-b20901e8c84d3ea774beeaca963c67d7802e4b4e(Aug 10 2021 08:10:38) |
| zone3 | 172.20.249.51:2882 | 14 | 0 | 13 | 0 | 1970-01-01 08:00:00.000000 | 2021-09-26 14:05:58.641570 | active | 1970-01-01 08:00:00.000000 | 3.1.0_3-b20901e8c84d3ea774beeaca963c67d7802e4b4e(Aug 10 2021 08:10:38) |
+-------+--------------------+-----------+----------+--------------+-------------+----------------------------+----------------------------+--------+----------------------------+------------------------------------------------------------------------+
3 rows in set (0.005 sec)
```
## 如何使用 OBD 对 OceanBase 集群 或 OBPROXY 集群扩容
OBD 对 OceanBase 集群扩容主要是增加每个 ZONE 内的节点数。前面已经介绍过集群扩容手动步骤,这里主要是介绍 OBD 工具的操作步骤。
注意,OBD 的定位并不是运维工具,所以对于集群扩容节点并不是特别顺畅。
OBD 对集群扩容步骤:
+ OBD 初始化新机器节点目录。
+ OBD 启动新机器节点进程。
在操作之前,先查看当前部署的集群信息。
```bash
[admin@obce00 ~]$ obd cluster display obce-3zones
Get local repositories and plugins ok
Open ssh connection ok
Cluster status check ok
Connect to observer ok
Wait for observer init ok
+-------------------------------------------------+
| observer |
+---------------+---------+------+-------+--------+
| ip | version | port | zone | status |
+---------------+---------+------+-------+--------+
| 172.20.249.49 | 3.1.0 | 2881 | zone2 | active |
| 172.20.249.51 | 3.1.0 | 2881 | zone3 | active |
| 172.20.249.52 | 3.1.0 | 2881 | zone1 | active |
+---------------+---------+------+-------+--------+
Connect to obproxy ok
+-------------------------------------------------+
| obproxy |
+---------------+------+-----------------+--------+
| ip | port | prometheus_port | status |
+---------------+------+-----------------+--------+
| 172.20.249.52 | 2883 | 2884 | active |
| 172.20.249.49 | 2883 | 2884 | active |
| 172.20.249.51 | 2883 | 2884 | active |
+---------------+------+-----------------+--------+
[admin@obce00 ~]$
[admin@obce00 ~]$ obd --version
OceanBase Deploy: 1.1.0
REVISION:
BUILD_BRANCH:
BUILD_TIME: Aug 10 2021 11:50:40OURCE
Copyright (C) 2021 OceanBase
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
```
### OBD 初始化新机器节点目录
当要给集群扩容节点的时候,建议每个 ZONE 新增相同配置的机器。但是不能直接在现有配置文件中编辑。当前版本(`1.1.0`) 采取的方式是复制现有的配置文件为新文件,然后修改配置文件中的 `servers` 下的 IP 为新的 IP 。如果 OBPROXY 集群不扩容,就删除 OBPROXY 配置节。
然后再运行 `obd cluster deploy` 命令。 `deploy` 命令会自动在新节点安装 OceanBase 软件包、初始化相关目录。
```bash
obd cluster deploy obce-3zones2 -c obce-3zones2.yaml
```
注意,deploy 成功后,不要运行 `obd cluster start` 命令,那个会初始化一个新的集群,不符合原本的目的。
### OBD 启动老集群节点
此时需要使用命令 `obd cluster edit-config` 编辑当前运行的集群的配置文件,在 `servers` 下加入新的节点 IP 。新 IP 紧跟在老的 IP 之后(换行)。
然后针对老的集群运行 `obd cluster start` 命令。这个会启动集群中所有节点。如果节点的进程已经启动,就会跳过。所以,只会启动新增节点的进程。
```bash
obd cluster start obce-3zones
```
### OceanBase 集群添加新节点
对于 OceanBase 集群扩容节点,还需要在集群 SYS 租户里添加新的节点到相应 ZONE 里。
命令是:`ALTER SYSTEM ADD SERVER '节点IP:RPC端口' ZONE '节点所属ZONE';`
```sql
alter system add server '11.166.87.5:2882' zone 'zone1';
```
以上命令立即返回,然后查看节点状态就可以看到新节点的状态是 `active`
# 附录
## A1. OceanBase 的常用指标的查询表达式
本文介绍 OBAgent 常用指标的查询表达式。
常用指标在查询时,必须将变量替换成待查询的具体信息。需要替换的字段如下:
+ @LABELS,替换为具体 Label 的过滤条件。
+ @INTERVAL,替换为计算周期。
+ @GBLABELS,替换为聚合的 Label 名称。
| 指标 | 表达式 | 单位 |
|------------------------|---------|------|
| 活跃 MEMStore 大小 | sum(ob_sysstat{stat_id="130000",@LABELS}) by (@GBLABELS) / 1048576| MB |
| 当前活跃会话数 | sum(ob_active_session_num{@LABELS}) by ([@GBLABELS)| - |
| 块缓存命中率 | 100 * 1 / (1 + sum(rate(ob_sysstat{stat_id="50009",@LABELS}[@INTERVAL])) by ([@GBLABELS)](/GBLABELS)) / sum(rate(ob_sysstat{stat_id="50008",@LABELS}[@INTERVAL])) by (@GBLABELS))| % |
| 块缓存大小 | sum(ob_cache_size_bytes{cache_name="user_block_cache",@LABELS}) by ([@GBLABELS)](/GBLABELS)) / 1048576| MB |
| 每秒提交的事务日志大小 | sum(rate(ob_sysstat{stat_id="80057",@LABELS}[@INTERVAL])) by ([@GBLABELS)](/GBLABELS))| byte |
| 每次事务日志写盘平均耗时 | sum(rate(ob_sysstat{stat_id="80041",@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_sysstat{stat_id="80040",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| CPU 使用率 | 100 * (1 - sum(rate(node_cpu_seconds_total{mode="idle", @LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(node_cpu_seconds_total{@LABELS}[@INTERVAL])) by (@GBLABELS))| % |
| 磁盘分区已使用容量 | sum(node_filesystem_size_bytes{@LABELS} - node_filesystem_avail_bytes{@LABELS}) by (@GBLABELS) / 1073741824| GB |
| 每秒读次数 | avg(rate(node_disk_reads_completed_total{@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 每次读取数据量 | avg(rate(node_disk_read_bytes_total{@LABELS}[@INTERVAL])) by (@GBLABELS) / 1048576| MB |
| SSStore 每秒读次数 | sum(rate(ob_sysstat{stat_id="60000",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| SSStore 每次读取平均耗时 | sum(rate(ob_sysstat{stat_id="60001",@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_sysstat{stat_id="60000",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| SSStore 每秒读取数据量 | sum(rate(ob_sysstat{stat_id="60002",@LABELS}[@INTERVAL])) by (@GBLABELS)| byte |
| 每秒读取平均耗时 | 1000000 * (avg(rate(node_disk_read_time_seconds_total{@LABELS}[@INTERVAL])) by (@GBLABELS)) / (avg(rate(node_disk_reads_completed_total{@LABELS}[@INTERVAL])) by (@GBLABELS))| us |
| 平均每次 IO 读取耗时 | 1000000 * (avg(rate(node_disk_read_time_seconds_total{@LABELS}[@INTERVAL])) by (@GBLABELS)) / (avg(rate(node_disk_reads_completed_total{@LABELS}[@INTERVAL])) by (@GBLABELS))| us |
| 每秒写次数 | avg(rate(node_disk_writes_completed_total{@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 每次写入数据量 | avg(rate(node_disk_written_bytes_total{@LABELS}[@INTERVAL])) by (@GBLABELS) / 1048576| MB |
| SSStore 每秒写次数 | sum(rate(ob_sysstat{stat_id="60003",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| SSStore 每次写入平均耗时 | sum(rate(ob_sysstat{stat_id="60004",@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_sysstat{stat_id="60003",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| SSStore 每秒写入数据量 | sum(rate(ob_sysstat{stat_id="60005",@LABELS}[@INTERVAL])) by (@GBLABELS)| byte |
| 每秒写入平均耗时 | 1000000 * (avg(rate(node_disk_write_time_seconds_total{@LABELS}[@INTERVAL])) by (@GBLABELS)) / (avg(rate(node_disk_writes_completed_total{@LABELS}[@INTERVAL])) by (@GBLABELS))| us |
| 平均每次 IO 写入耗时 | 1000000 * (avg(rate(node_disk_write_time_seconds_total{@LABELS}[@INTERVAL])) by (@GBLABELS)) / (avg(rate(node_disk_writes_completed_total{@LABELS}[@INTERVAL])) by (@GBLABELS))| us |
| 过去 1 分钟系统平均负载 | avg(node_load1{@LABELS}) by (@GBLABELS)| - |
| 过去 15 分钟系统平均负载 | avg(node_load15{@LABELS}) by (@GBLABELS)| - |
| 过去 5 分钟系统平均负载 | avg(node_load5{@LABELS}) by (@GBLABELS)| - |
| 触发合并阈值 | sum(ob_sysstat{stat_id="130002",@LABELS}) by (@GBLABELS) / 1048576| MB |
| 内核 Buffer Cache 大小 | avg(node_memory_Buffers_bytes{@LABELS}) by (@GBLABELS) / 1073741824| GB |
| 可用物理内存大小 | avg(node_memory_MemFree_bytes{@LABELS}) by (@GBLABELS) / 1073741824| GB |
| 使用物理内存大小 | (avg(node_memory_MemTotal_bytes{@LABELS}) by (@GBLABELS)| GB |
| || |
| | avg(node_memory_MemFree_bytes{@LABELS}) by (@GBLABELS)| |
| || |
| | avg(node_memory_Cached_bytes{@LABELS}) by (@GBLABELS)| |
| || |
| | avg(node_memory_Buffers_bytes{@LABELS}) by (@GBLABELS)) / 1073741824| |
| MEMStore 的上限 | sum(ob_sysstat{stat_id="130004",@LABELS}) by (@GBLABELS) / 1048576| MB |
| MEMStore 使用百分比 | 100 * sum(ob_sysstat{stat_id="130001",@LABELS}) by (@GBLABELS) / sum(ob_sysstat{stat_id="130004",@LABELS}) by (@GBLABELS)| % |
| 写锁等待失败次数 | sum(rate(ob_sysstat{stat_id="60022",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 写锁等待成功次数 | sum(rate(ob_sysstat{stat_id="60021",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 写锁平均等待耗时 | sum(rate(ob_sysstat{stat_id="60023",@LABELS}[@INTERVAL])) by (@GBLABELS) / (sum(rate(ob_sysstat{stat_id="60021",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="60022",@LABELS}[@INTERVAL])) by (@GBLABELS))| us |
| 每秒接收数据量 | avg(rate(node_network_receive_bytes_total{@LABELS}[@INTERVAL])) by (@GBLABELS) / 1048576| MB |
| 每秒发送数据量 | avg(rate(node_network_transmit_bytes_total{@LABELS}[@INTERVAL])) by (@GBLABELS) / 1048576| MB |
| CPU 使用率 | 100 * sum(ob_sysstat{stat_id="140006",@LABELS}) by (@GBLABELS) / sum(ob_sysstat{stat_id="140005",@LABELS}) by (@GBLABELS)| % |
| 分区数量 | sum(ob_partition_num{@LABELS}) by (@GBLABELS)| - |
| 执行计划缓存命中率 | 100 * sum(rate(ob_plan_cache_hit_total{@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_plan_cache_access_total{@LABELS}[@INTERVAL])) by (@GBLABELS)| % |
| 执行计划缓存大小 | sum(ob_plan_cache_memory_bytes{@LABELS}) by (@GBLABELS) / 1048576| MB |
| 平均每秒 SQL 进等待队列的次数 | sum(rate(ob_sysstat{stat_id="20001",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| SQL 在等待队列中等待耗时 | sum(rate(ob_sysstat{stat_id="20002",@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_sysstat{stat_id="20001",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| 行缓存命中率 | 100 * 1 / (1 + sum(rate(ob_sysstat{stat_id="50001",@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_sysstat{stat_id="50000",@LABELS}[@INTERVAL])) by (@GBLABELS))| % |
| 缓存大小 | sum(ob_cache_size_bytes{@LABELS}) by (@GBLABELS) / 1048576| MB |
| RPC 收包吞吐量 | sum(rate(ob_sysstat{stat_id="10001",@LABELS}[@INTERVAL])) by (@GBLABELS)| byte |
| RPC 收包平均耗时 | (sum(rate(ob_sysstat{stat_id="10005",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="10006",@LABELS}[@INTERVAL])) by (@GBLABELS)) / sum(rate(ob_sysstat{stat_id="10000",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| RPC 发包吞吐量 | sum(rate(ob_sysstat{stat_id="10003",@LABELS}[@INTERVAL])) by (@GBLABELS)| byte |
| RPC 发包平均耗时 | (sum(rate(ob_sysstat{stat_id="10005",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="10006",@LABELS}[@INTERVAL])) by (@GBLABELS)) / sum(rate(ob_sysstat{stat_id="10002",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| 每秒处理 SQL 语句数 | sum(rate(ob_sysstat{stat_id="40002",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="40004",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="40006",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="40008",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="40000",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 服务端每条 SQL 语句平均处理耗时 | (sum(rate(ob_sysstat{stat_id="40003",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="40005",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="40007",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="40009",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="40001",@LABELS}[@INTERVAL])) by (@GBLABELS)) /(sum(rate(ob_sysstat{stat_id="40002",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="40004",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="40006",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="40008",@LABELS}[@INTERVAL])) by (@GBLABELS) + sum(rate(ob_sysstat{stat_id="40000",@LABELS}[@INTERVAL])) by (@GBLABELS)) | us |
| 每秒处理 Delete 语句数 | sum(rate(ob_sysstat{stat_id="40008",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| 服务端每条 Delete 语句平均处理耗时 | sum(rate(ob_sysstat{stat_id="40009",@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_sysstat{stat_id="40008",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| 每秒处理分布式执行计划数 | sum(rate(ob_sysstat{stat_id="40012",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 每秒处理 Insert 语句数 | sum(rate(ob_sysstat{stat_id="40002",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 服务端每条 Insert 语句平均处理耗时 | sum(rate(ob_sysstat{stat_id="40003",@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_sysstat{stat_id="40002",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| 每秒处理本地执行数 | sum(rate(ob_sysstat{stat_id="40010",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 每秒处理远程执行计划数 | sum(rate(ob_sysstat{stat_id="40011",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 每秒处理 Replace 语句数 | sum(rate(ob_sysstat{stat_id="40004",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 服务端每条 Replace 语句平均处理耗时 | sum(rate(ob_sysstat{stat_id="40005",@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_sysstat{stat_id="40004",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| 每秒处理 Select 语句数 | sum(rate(ob_sysstat{stat_id="40000",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 服务端每条 Select 语句平均处理耗时 | sum(rate(ob_sysstat{stat_id="40001",@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_sysstat{stat_id="40000",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| 每秒处理 Update 语句数 | sum(rate(ob_sysstat{stat_id="40006",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 服务端每条 Update 语句平均处理耗时 | sum(rate(ob_sysstat{stat_id="40007",@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_sysstat{stat_id="40006",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| 表数量 | max(ob_table_num{@LABELS}) by (@GBLABELS)| - |
| MEMStore 总大小 | sum(ob_sysstat{stat_id="130001",@LABELS}) by (@GBLABELS) / 1048576| MB |
| 每秒处理事务数 | sum(rate(ob_sysstat{stat_id="30005",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 服务端每个事务平均处理耗时 | sum(rate(ob_sysstat{stat_id="30006",@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_sysstat{stat_id="30005",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| 每秒提交的事务日志数 | sum(rate(ob_sysstat{stat_id="30002",@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 每次事务日志网络同步平均耗时 | sum(rate(ob_sysstat{stat_id="30000",@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_sysstat{stat_id="30001",@LABELS}[@INTERVAL])) by (@GBLABELS)| us |
| 每秒等待事件次数 | sum(rate(ob_waitevent_wait_total{@LABELS}[@INTERVAL])) by (@GBLABELS)| 次/s |
| 等待事件平均耗时 | sum(rate(ob_waitevent_wait_seconds_total{@LABELS}[@INTERVAL])) by (@GBLABELS) / sum(rate(ob_waitevent_wait_total{@LABELS}[@INTERVAL])) by (@GBLABELS)| s |
# 第 6 章:如何测试 OceanBase 社区版性能
## 本章目录
+ [性能测试概述](6.1.md)
+ [影响性能的因素](6.2.md)
+ [如何跑 SYSBENCH 测试](6.3.md)
+ [如何跑 TPC-C 测试](6.4.md)
+ [如何跑 TPC-H 测试](6.5.md)
+ [如何使用 跑业务场景测试](6.6.md)
## 如何联系我们
欢迎广大 OceanBase 爱好者、用户和客户有任何问题联系我们反馈:
+ 社区版官网论坛:[https://open.oceanbase.com/answer](https://open.oceanbase.com/answer)
+ 社区版项目网站提 `Issue`[https://github.com/oceanbase/oceanbase/issues](https://github.com/oceanbase/oceanbase/issues)
+ 钉钉群:群号 `33254054`
\ No newline at end of file
# 性能测试概述
## 测试标准
测试OceanBase集群性能的方法有很多,如`sysbench``benchmarksql`。不过如果想对比OceanBase数据库跟传统数据库、其他分布式数据库产品的性能差异,还需要找到一个能适用彼此的测试方案。`sysbench`的功能太弱,且只有0.4 支持ORACLE,不同版本的sql还不完全一样。`benchmarksql`倒是很合适,有一定业务模型,且标准简单容易统一比较。不过如果客户还想测试一点点业务sql,那就只能自己写程序了。如果嫌写程序麻烦,还有个简单的办法就是使用`JMeter`
## 多副本同步
传统数据库或者基于 MYSQL 或 PG 数据库的分布式数据库,通常都能以单副本实例运行。为了高可用和容灾,也支持一主多备形态部署。不过主备之间同步多是异步同步。异步同步的优点是写性能好,风险是主副本实例故障的时候,主备很可能会不一致。即使使用了 MYSQL 的半同步技术,也不能严格保证主备数据强一致。OceanBase 生产环境默认就是以三副本形态运行,主备副本之前同步的是事务日志,物理同步,同步协议使用 PAXOS 强同步协议。OceanBase 不支持异步同步,不支持牺牲数据安全换取性能。
## 故障应对能力
OceanBase 集群是部署在普通商用服务器上,充分考虑了服务器的故障风险。所以OceanBase 的架构设计始终把数据安全放在首位,平时就是三副本强同步协议,这样任意时刻发生故障,OceanBase都能自动切换,选出新的主副本出来。这个故障切换不需要 DBA 介入,数据库恢复时间 RTO 大概是 30s 左右,数据库恢复后数据绝对不丢 (RPO = 0)。OceanBase 的这个能力平时很难看到,只有发生故障了才能体现。
所以,测试 OceanBase 的性能时需要带着业务压力,并随机注入故障,才是接近实际生产情况。
OceanBase 的在线扩容和缩容、大表在线 DDL,都是有高可用保障。节点故障后恢复时,原有的任务都自动继续进行。
# 影响性能的因素
## 磁盘划分
如果 OBSERVER 运行日志、事务日志、数据文件都在一块盘上,称为单盘部署。这个风险比较大,目前有一些已知问题。
+ 事务日志(CLOG) 空间利用率超过 80% 才开始回收,超过 95% 就节点停写、掉线。
+ 转储和合并时需要额外的 IO、CPU 资源,跟业务读写的 IO 可能会互相影响。
+ 运行日志(LOG)写的太多太快,可能导致节点掉线;LOG 写满可能导致节点异常。
+ 影响 `liboblog` 同步,影响 CLOG 回收或者副本迁移慢等。
+ 参数文件 `observer.config.bin` 因为盘满导致持久化失败。
以上影响 TPC-C 、SYSBENCH 测试多一些,对 TPC-H 影响少一些。
应对策略:
+ CLOG 、LOG、SSTABLE 分多个独立文件系统。不管是多盘还是单盘用 LVM 划分出多个 LV 。
+ 开启 LOG 限流,参数 `syslog_io_bandwidth_limit` 。 SQL: `alter system set syslog_io_bandwidth_limit='10M';`
+ LOG 滚动输出,设置最大数目。SQL: `alter system set enable_syslog_recycle=true; alter system set max_syslog_file_count=50;`
+ CLOG 参数 `clog_disk_usage_limit_percentage` 参数默认 95 ,不要轻易调大。`clog_disk_utilization_threshold` 默认 80,可以下调到 50,但是不能太低。
## OceanBase & OBPROXY 参数
### OceanBase 参数
```sql
alter system set net_thread_count = 32; -- 网络线程数
alter system set _clog_aggregation_buffer_amount=4;
alter system set _flush_clog_aggregation_buffer_timeout='1ms' ; -- 把大概几毫秒之内的日志都聚合到一个rpc中发送,减小网络开销,提高并发读
alter system set trace_log_slow_query_watermark='10s' ; --打印SQL QUERY的阈值
alter system set large_query_threshold='1s' ; -- large query的阈值,超过后进入大查询队列,避免大查询阻塞小查询
alter system set syslog_level='PERF'; -- 控制日志输出级别
alter system set clog_sync_time_warn_threshold='2000ms' ; -- clog日志同步慢的时候触发debug日志的输出。
alter system set syslog_io_bandwidth_limit='10M' ; -- 超过10M会日志限流,减小写日志文件带来的IO消耗
alter system set enable_sql_audit=false -- 关闭sql audit
alter system set enable_perf_event=false; -- 关闭监控项
alter system set clog_max_unconfirmed_log_count=5000; -- 增大滑动窗口,解决clog滑动窗口满导致的性能问题
alter system set minor_warm_up_duration_time=0 ; -- 转储sstable预热时间,转储完成后到预热时间内,所有对应partition的流量会逐步从memtable过渡到sstable,设置0转储之后流量马上切到sstable,使得memtable快速释放 ,内存释放更快。
alter system set clog_transport_compress_all = True; -- 开启clog传输压缩,提高clog做Paxos同步的效率。但是会在clog传输前和接受后增加压缩的开销
alter system set enable_clog_persistence_compress = true; -- 开启clog存储压缩,提高clog落盘效率。这个参数会减少clog落盘的IO,但是会在clog落盘前增加压缩的开销
alter system set memory_chunk_cache_size = '150G'; -- 降低OB内部2MB内存块被OS回收的概率,增大2MB内存块在OB内部的复用率,减少RPC由于内存操作慢而导致超时的风险。
alter system set minor_merge_concurrency = 32; -- 增大转储的线程数,期望提高转储的速度。
alter system set _mini_merge_concurrency = 8; -- 增大mini_merge的线程数,期望提高mini_merge的速度
alter system set freeze_trigger_percentage = 40; -- 触发转储的时机
alter system set autoinc_cache_refresh_interval='86400s'; -- 调大自增列刷新的频率,减少性能损耗。
alter system set cpu_quota_concurrency=2; -- 这个数*租户cpu=工作线程数,具体调整的数值需要根据业务模型和机器配置调整,工作线程超过实际CPU核数也没有意义同时会增加CPU调度和上下文切换的开销
alter system set builtin_db_data_verify_cycle = 0 ; -- 宏块巡检周期参数,当设置为0时关闭巡检
alter system set micro_block_merge_verify_level = 0; -- 微块校验等级设置,0:不做任何校验;1:对encoding做decode校验;2:对encoding做decode校验,对压缩做解压校验
alter system set _ob_get_gts_ahead_interval = '0ms';
alter system set bf_cache_priority = 10; -- 为频繁空查的宏块建立bloomfilter并缓存,减少磁盘IO和CPU消耗,提升写入性能
alter system set user_block_cache_priority=5;
alter system set merge_stat_sampling_ratio = 1 ; -- 合并时统计信息采样率,当设置为0时则关闭统计信息采集
alter system set _enable_static_typing_engine=true;
alter system set autoinc_cache_refresh_interval='86400s'; -- 调大自增列刷新的频率,对自增列和sequence场景有性能帮助
alter system set enable_early_lock_release=false tenant=all; -- 提前解行锁场景下,用于租户级别控制,是否打开该优化
```
### OBPROXY 参数
```sql
alter proxyconfig set enable_ob_protocol_v2=false ;
alter proxyconfig set enable_qos=false ;
alter proxyconfig set enable_compression_protocol=false ;
alter proxyconfig set automatic_match_work_thread = false; -- 关闭自动计算线程个数
alter proxyconfig set work_thread_num = 32; -- 手动设置工作线程个数,需要restart
alter proxyconfig set syslog_level='ERROR';
alter proxyconfig set monitor_log_level = 'ERROR';
alter proxyconfig set enable_monitor_stat = false ;
alter proxyconfig set xflush_log_level="ERROR";
```
### JDBC URL 参数
```bash
conn=jdbc:mysql://x.x.x.x(ip):xx(port)/xxxx(dbname)?rewriteBatchedStatements=true&allowMultiQueries=true&useLocalSessionState=true&useUnicode=true&characterEncoding=utf-8&socketTimeout=3000000
```
+ rewriteBatchedStatements :该参数非常重要,会严重影响导数据效率,不可以忽略。
+ useLocalSessionState :是否使用autocommit,read_only和transaction isolation的内部值(jdbc端的本地值),建议设置为true,如果设置为false,则需要发语句到远端请求,增加发送请求频次,影响性能。
## 转储&合并
转储是租户级别的,当一台服务器中一个租户中MemTable的内存占用超过租户内存上限的一定比例时,就会触发小版本冻结(Minor Freeze)。所谓冻结,就是禁止当前活跃MemTable的写入,并同时创建新的空MemTable以支持新的写入。被冻结后的MemTable就成为一个静态的数据结构,可以被后台线程读取并转储为SSTable。
合并是全局级别的,合并会把当前大版本的SSTable和MemTable与前一个大版本的全量静态数据进行合并,产生新的全量数据。合并操作包含转储操作。
转储和合并是把内存数据刷到磁盘上,存储层统计信息可以更准确,生成的执行计划也就更稳定准确。
MemTable 扫描性能很差,合并后 RANGE 查询性能会有提升。
## PRIMARY_ZONE 设置
租户的 PRIMARY_ZONE 是用来控制租户里主副本的分布。如果设置为 RANDOM,就可以将不同分区的主副本分散在不同 ZONE 的节点上,机器利用率最大化。反过来,设置为某个具体的 ZONE,则只能利用部分节点。
## 使用分区表
分区表是使用一定的水平拆分策略将一个大表切割为多个独立的分区。独立的分区并不是分表,依然是分区表的一部分。
分区表有两个作用:
+ 将海量数据分散到不同分区存储,不同分区可以分散在不同节点存储,从而化解单机空间瓶颈。
+ 将海量请求分散到不同分区,不同分区主副本可以分散在不同节点,从而化解单机处理能力瓶颈。
分区表的索引分为全局索引和本地索引。
全局索引对某些不带分区键的查询比较友好,但是对写不友好,可能导致分布式事务。此外,当 RANGE 查询要返回很多数据时,全局索引也会有很多分布式查询,性能并不一定比本地索引好。
使用本地索引读写数据,可以规避分布式事务,可以多分区多节点并行读,性能也很不错。
## 使用表分组 TABLE GROUP
表分组是将一组业务联系紧密的表的分区分布调度在一起。
+ `table_group` :TABLE GROUP 是一个逻辑概念,它和物理数据文件没有关联关系,TABLE GROUP 只影响表分区的调度方法,OceanBase 数据库会优先把属于同一个 Table Group 的相同分区号的分区,调度到同一台节点上,以减少跨节点分布式事务。
+ `partition_group`: TABLE GROUP 的每个分区表中下标相同的一组分区为 PARTITION GROUP,作用是将分区号相同的partition放到 1 个 PG 的物理结构里面达到提升性能的目的。
同一个表分组里面的分区表的分区策略必须一致。
# 如何跑 TPC-H 测试
## TPC-H 简介
TPC-H 基准测试是由 TPC-D(由 TPC 组织于 1994 年指定的标准,用于决策支持系统方面的测试基准)发展而来的.TPC-H 用 3NF 实现了一个数据仓库,共包含 8 个基本关系,其主要评价指标是各个查询的响应时间,即从提交查询到结果返回所需时间.TPC-H 基准测试的度量单位是每小时执行的查询数( QphH@size),其中 H 表示每小时系统执行复杂查询的平均次数,size 表示数据库规模的大小,它能够反映出系统在处理查询时的能力.TPC-H 是根据真实的生产运行环境来建模的,这使得它可以评估一些其他测试所不能评估的关键性能参数.总而言之,TPC 组织颁布的TPC-H 标准满足了数据仓库领域的测试需求,并且促使各个厂商以及研究机构将该项技术推向极限。
详细可以参考 <http://www.tpc.org/tpc_documents_current_versions/pdf/tpc-h_v2.17.3.pdf>
## 测试准备
### 软件安装
+ 下载软件
TPC-H Tool 官方提供,下载地址:[http://tpc.org/TPC_Documents_Current_Versions/download_programs/tools-download-request5.asp?bm_type=TPC-H&bm_vers=3.0.0&mode=CURRENT-ONLY](http://tpc.org/TPC_Documents_Current_Versions/download_programs/tools-download-request5.asp?bm_type=TPC-H&bm_vers=3.0.0&mode=CURRENT-ONLY) 。文件名:`TPC-H_Tools_v3.0.0.zip`
```bash
cd /root/TPC-H_Tools_v3.0.0/dbgen
cp makefile.suite Makefile
```
+ 修改 `Makefile` 文件中的`CC``DATABASE``MACHINE``WORKLOAD` 等参数定义。
```bash
vim Makefile +103
CC = gcc
# Current values for DATABASE are: INFORMIX, DB2, TDAT (Teradata)
# SQLSERVER, SYBASE, ORACLE, VECTORWISE
# Current values for MACHINE are: ATT, DOS, HP, IBM, ICL, MVS,
# SGI, SUN, U2200, VMS, LINUX, WIN32
# Current values for WORKLOAD are: TPCH
DATABASE= MYSQL
MACHINE = LINUX
WORKLOAD = TPCH
```
+ 修改 `tpcd.h` 文件,并添加新的宏定义。
```bash
vim tpcd.h +$
#ifdef MYSQL
#define GEN_QUERY_PLAN ""
#define START_TRAN "START TRANSACTION"
#define END_TRAN "COMMIT"
#define SET_OUTPUT ""
#define SET_ROWCOUNT "limit %d;\n"
#define SET_DBASE "use %s;\n"
#endif
```
+ 对文件进行编译。
```bash
make
```
编译完成后该目录下会生成两个可执行文件:
- dbgen:数据生成工具。在使用InfiniDB官方测试脚本进行测试时,需要用该工具生成tpch相关表数据。
- qgen:SQL生成工具。生成初始化测试查询,由于不同的seed生成的查询不同,为了结果的可重复性,请使用附件提供的22个查询。
### 数据文件准备
`dbgen` 命令可以生成指定大小的数据。生成环境测试建议不少于 1000G 。本文示例 `10g`
```bash
./dbgen -s 10
输出:
TPC-H Population Generator (Version 3.0.0)
Copyright Transaction Processing Performance Council 1994 - 2010
mkdir tpch10
mv *.tbl tpch10/
[root@obce-0000 dbgen]# ls -lrth tpch10/
总用量 11G
-rw-r--r-- 1 root root 14M 10月 1 16:04 supplier.tbl
-rw-r--r-- 1 root root 389 10月 1 16:04 region.tbl
-rw-r--r-- 1 root root 233M 10月 1 16:04 part.tbl
-rw-r--r-- 1 root root 1.2G 10月 1 16:04 partsupp.tbl
-rw-r--r-- 1 root root 1.7G 10月 1 16:04 orders.tbl
-rw-r--r-- 1 root root 2.2K 10月 1 16:04 nation.tbl
-rw-r--r-- 1 root root 7.3G 10月 1 16:04 lineitem.tbl
-rw-r--r-- 1 root root 234M 10月 1 16:04 customer.tbl
```
### 查询语句准备
```bash
cp qgen queries/
cp dists.dss queries/
cd queries/
for i in `seq 22`; do echo $i; ./qgen -d $i -s 10 > db"$i".sql; done
dos2unix *.sql
```
去掉生成的 SQL 文件中的”limit -xx”, 去掉day 后面的(3), 并且加上parallel(96)并发,以 `db1.sql` 为例。
+ Q1
```sql
select /*+ TPCH_Q1 parallel(16) */
l_returnflag,
l_linestatus,
sum(l_quantity) as sum_qty,
sum(l_extendedprice) as sum_base_price,
sum(l_extendedprice * (1 - l_discount)) as sum_disc_price,
sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) as sum_charge,
avg(l_quantity) as avg_qty,
avg(l_extendedprice) as avg_price,
avg(l_discount) as avg_disc,
count(*) as count_order
from
lineitem
where
l_shipdate <= date '1998-12-01' - interval '90' day (3)
group by
l_returnflag,
l_linestatus
order by
l_returnflag,
l_linestatus;
```
+ Q2
```sql
select /*+ TPCH_Q2 parallel(16) */
s_acctbal,
s_name,
n_name,
p_partkey,
p_mfgr,
s_address,
s_phone,
s_comment
from
part,
supplier,
partsupp,
nation,
region
where
p_partkey = ps_partkey
and s_suppkey = ps_suppkey
and p_size = 15
and p_type like '%BRASS'
and s_nationkey = n_nationkey
and n_regionkey = r_regionkey
and r_name = 'EUROPE'
and ps_supplycost = (
select
min(ps_supplycost)
from
partsupp,
supplier,
nation,
region
where
p_partkey = ps_partkey
and s_suppkey = ps_suppkey
and s_nationkey = n_nationkey
and n_regionkey = r_regionkey
and r_name = 'EUROPE'
)
order by
s_acctbal desc,
n_name,
s_name,
p_partkey;
```
以下其他 SQL 就不重复说明。
### 参数准备
+ 租户参数
```sql
set global autocommit=ON;
set global ob_query_timeout=36000000000;
set global ob_trx_timeout=36000000000;
set global max_allowed_packet=67108864;
set global ob_sql_work_area_percentage=80;
/*
parallel_max_servers和parallel_servers_target的值
推荐设置为测试租户分配的resource unit cpu数的10倍
如测试租户使用的unit配置为:create resource unit $unit_name max_cpu 26
那么该值设置为260
*/
set global parallel_max_servers=260;
set global parallel_servers_target=260;
```
### 建表
```sql
create tablegroup tpch_tg_10g_lineitem_order_group binding true partition by key 1 partitions 9;
create tablegroup tpch_tg_10g_partsupp_part binding true partition by key 1 partitions 9;
drop database if exists tpch_10g_part;
create database tpch_10g_part;
use tpch_10g_part;
CREATE TABLE lineitem (
l_orderkey bigint NOT NULL,
l_partkey bigint NOT NULL,
l_suppkey bigint NOT NULL,
l_linenumber bigint NOT NULL,
l_quantity bigint NOT NULL,
l_extendedprice bigint NOT NULL,
l_discount bigint NOT NULL,
l_tax bigint NOT NULL,
l_returnflag char(1) DEFAULT NULL,
l_linestatus char(1) DEFAULT NULL,
l_shipdate date NOT NULL,
l_commitdate date DEFAULT NULL,
l_receiptdate date DEFAULT NULL,
l_shipinstruct char(25) DEFAULT NULL,
l_shipmode char(10) DEFAULT NULL,
l_comment varchar(44) DEFAULT NULL,
primary key(l_orderkey, l_linenumber)
) tablegroup = tpch_tg_10g_lineitem_order_group
partition by key (l_orderkey) partitions 9;
create index I_L_ORDERKEY on lineitem(l_orderkey) local;
create index I_L_SHIPDATE on lineitem(l_shipdate) local;
CREATE TABLE orders (
o_orderkey bigint NOT NULL,
o_custkey bigint NOT NULL,
o_orderstatus char(1) DEFAULT NULL,
o_totalprice bigint DEFAULT NULL,
o_orderdate date NOT NULL,
o_orderpriority char(15) DEFAULT NULL,
o_clerk char(15) DEFAULT NULL,
o_shippriority bigint DEFAULT NULL,
o_comment varchar(79) DEFAULT NULL,
PRIMARY KEY (o_orderkey))
tablegroup = tpch_tg_10g_lineitem_order_group
partition by key(o_orderkey) partitions 9;
create index I_O_ORDERDATE on orders(o_orderdate) local;
CREATE TABLE partsupp (
ps_partkey bigint NOT NULL,
ps_suppkey bigint NOT NULL,
ps_availqty bigint DEFAULT NULL,
ps_supplycost bigint DEFAULT NULL,
ps_comment varchar(199) DEFAULT NULL,
PRIMARY KEY (ps_partkey, ps_suppkey))
tablegroup tpch_tg_10g_partsupp_part
partition by key(ps_partkey) partitions 9;
CREATE TABLE part (
p_partkey bigint NOT NULL,
p_name varchar(55) DEFAULT NULL,
p_mfgr char(25) DEFAULT NULL,
p_brand char(10) DEFAULT NULL,
p_type varchar(25) DEFAULT NULL,
p_size bigint DEFAULT NULL,
p_container char(10) DEFAULT NULL,
p_retailprice bigint DEFAULT NULL,
p_comment varchar(23) DEFAULT NULL,
PRIMARY KEY (p_partkey))
tablegroup tpch_tg_10g_partsupp_part
partition by key(p_partkey) partitions 9;
CREATE TABLE customer (
c_custkey bigint NOT NULL,
c_name varchar(25) DEFAULT NULL,
c_address varchar(40) DEFAULT NULL,
c_nationkey bigint DEFAULT NULL,
c_phone char(15) DEFAULT NULL,
c_acctbal bigint DEFAULT NULL,
c_mktsegment char(10) DEFAULT NULL,
c_comment varchar(117) DEFAULT NULL,
PRIMARY KEY (c_custkey))
partition by key(c_custkey) partitions 9;
CREATE TABLE supplier (
s_suppkey bigint NOT NULL,
s_name char(25) DEFAULT NULL,
s_address varchar(40) DEFAULT NULL,
s_nationkey bigint DEFAULT NULL,
s_phone char(15) DEFAULT NULL,
s_acctbal bigint DEFAULT NULL,
s_comment varchar(101) DEFAULT NULL,
PRIMARY KEY (s_suppkey)
) partition by key(s_suppkey) partitions 9;
CREATE TABLE nation (
n_nationkey bigint NOT NULL,
n_name char(25) DEFAULT NULL,
n_regionkey bigint DEFAULT NULL,
n_comment varchar(152) DEFAULT NULL,
PRIMARY KEY (n_nationkey));
CREATE TABLE region (
r_regionkey bigint NOT NULL,
r_name char(25) DEFAULT NULL,
r_comment varchar(152) DEFAULT NULL,
PRIMARY KEY (r_regionkey));
```
创建结束后查看表和表分组。
```sql
MySQL [tpch_10g_part]> show tables;
+-------------------------+
| Tables_in_tpch_10g_part |
+-------------------------+
| customer |
| lineitem |
| nation |
| orders |
| part |
| partsupp |
| region |
| supplier |
+-------------------------+
8 rows in set (0.365 sec)
MySQL [tpch_10g_part]> show tablegroups;
+----------------------------------+------------------+---------------+
| Tablegroup_name | Table_name | Database_name |
+----------------------------------+------------------+---------------+
| oceanbase | NULL | NULL |
| tpch_tg_10g_lineitem_order_group | lineitem | tpch_10g_part |
| tpch_tg_10g_lineitem_order_group | orders | tpch_10g_part |
| tpch_tg_10g_partsupp_part | part | tpch_10g_part |
| tpch_tg_10g_partsupp_part | partsupp | tpch_10g_part |
+----------------------------------+------------------+---------------+
5 rows in set (0.068 sec)
```
### 加载数据
使用 OceanBase 自带的 LOAD 命令逐个加载数据。
也可以像下面编写一个脚本批量加载数据。
+ 创建加载脚本目录
```bash
cd /root/TPC-H_Tools_v3.0.0/dbgen
mkdir load
cd load
cp ../dss.ri ../dss.ddl ./
```
+ 编写加载脚本
```bash
#/usr/bin/evn python
#-*- encoding:utf-8 -*-
import os
import sys
import time
import commands
hostname='172.20.249.52' #注意!!请填写某个observer所在服务器的ip地址
port='2881' #端口号
tenant='obmysql' #组户名
user='u_tpch' #用户名
password='123456' #密码
data_path='/tmp/tpch10' #注意!!请填写某个observer所在服务器下tbl所在目录
db_name='tpch_10g_part' #数据库名
cmd_str='mysql --default-auth=mysql_native_password,db_name -h%s -P%s -u%s@%s -c -A %s -e "show tables;" '%(hostname,port,user,tenant,db_name)
print cmd_str
result = commands.getstatusoutput(cmd_str)
print result
cmd_str=""" mysql --default-auth=mysql_native_password,db_name -h%s -P%s -u%s@%s -c -A %s -e "load data /*+ parallel(8) */ infile '%s/customer.tbl' into table customer fields terminated by '|';" """ %(hostname,port,user,tenant,db_name,data_path)
print cmd_str
result = commands.getstatusoutput(cmd_str)
print result
cmd_str=""" mysql --default-auth=mysql_native_password,db_name -h%s -P%s -u%s@%s -c -A %s -e "load data /*+ parallel(8) */ infile '%s/lineitem.tbl' into table lineitem fields terminated by '|';" """ %(hostname,port,user,tenant,db_name,data_path)
result = commands.getstatusoutput(cmd_str)
print result
cmd_str=""" mysql --default-auth=mysql_native_password,db_name -h%s -P%s -u%s@%s -c -A %s -e "load data /*+ parallel(8) */ infile '%s/nation.tbl' into table nation fields terminated by '|';" """ %(hostname,port,user,tenant,db_name,data_path)
result = commands.getstatusoutput(cmd_str)
print result
cmd_str=""" mysql --default-auth=mysql_native_password,db_name -h%s -P%s -u%s@%s -c -A %s -e "load data /*+ parallel(8) */ infile '%s/orders.tbl' into table orders fields terminated by '|';" """ %(hostname,port,user,tenant,db_name,data_path)
result = commands.getstatusoutput(cmd_str)
print result
cmd_str=""" mysql --default-auth=mysql_native_password,db_name -h%s -P%s -u%s@%s -c -A %s -e "load data /*+ parallel(8) */ infile '%s/partsupp.tbl' into table partsupp fields terminated by '|';" """ %(hostname,port,user,tenant,db_name,data_path)
result = commands.getstatusoutput(cmd_str)
print result
cmd_str=""" mysql --default-auth=mysql_native_password,db_name -h%s -P%s -u%s@%s -c -A %s -e "load data /*+ parallel(8) */ infile '%s/part.tbl' into table part fields terminated by '|';" """ %(hostname,port,user,tenant,db_name,data_path)
result = commands.getstatusoutput(cmd_str)
print result
cmd_str=""" mysql --default-auth=mysql_native_password,db_name -h%s -P%s -u%s@%s -c -A %s -e "load data /*+ parallel(8) */ infile '%s/region.tbl' into table region fields terminated by '|';" """ %(hostname,port,user,tenant,db_name,data_path)
result = commands.getstatusoutput(cmd_str)
print result
cmd_str=""" mysql --default-auth=mysql_native_password,db_name -h%s -P%s -u%s@%s -c -A %s -e "load data /*+ parallel(8) */ infile '%s/supplier.tbl' into table supplier fields terminated by '|';" """ %(hostname,port,user,tenant,db_name,data_path)
```
```bash
export MYSQL_PWD=123456
python load.py
```
## 性能调优
### 转储&合并
| | 合并转储前 | 合并后 | 提升率 |
|-------------------------|-----------|-----------|--------|
| tpch_10g(总用时) | 57.26 | 23.28 | 59.30% |
| Q1 | 2.43s | 2.02s | |
| Q2 | 0.86s | 0.32s | |
| Q3 | 2.08s | 1.49s | |
| Q4 | 0.45s | 0.34s | |
| Q5 | 0.96s | 1.58s | |
| Q6 | 1.29s | 0.83s | |
| Q7 | 2.63s | 1.41s | |
| Q8 | 3.60s | 1.39s | |
| Q9 | 1.53s | 1.85s | |
| Q10 | 4.23s | 2.38s | |
| Q11 | 0.59s | 0.47s | |
| Q12 | 2.01s | 1.00s | |
| Q13 | 2.34s | 0.85s | |
| Q14 | 0.51s | 0.21s | |
| Q15 | 1.09s | 0.57s | |
| Q16 | 1.18s | 0.41s | |
| Q17 | 10.54s | 0.94s | |
| Q18 | 1.94s | 0.98s | |
| Q19 | 2.32s | 1.02s | |
| Q20 | 10.46s | 1.25s | |
| Q21 | 2.49s | 1.42s | |
| Q22 | 1.73s | 0.55s | |
### 调整 PRIMARY_ZONE
| | 集中式部署(zone1) | random部署 |
|------------------------|-----------------|------------------|
| tpch | 590.42 | 149.13 |
| Q1 | 50.74s | 15.14s |
| Q2 | 2.25s | 0.47s |
| Q3 | 37.69s | 13.42s |
| Q4 | 42.78s | 2.57s |
| Q5 | 6.94s | 2.52s |
| Q6 | 26.62s | 7.38s |
| Q7 | 0.35s | 0.23s |
| Q8 | 67.92s | 11.69s |
| Q9 | 20.19s | 6.17s |
| Q10 | 54.79s | 24.00s |
| Q11 | 0.39s | 0.19s |
| Q12 | 40.71s | 8.60s |
| Q13 | 20.99s | 7.82s |
| Q14 | 22.99s | 1.52s |
| Q15 | 46.28s | 2.95s |
| Q16 | 7.41s | 2.88s |
| Q17 | 32.04s | 8.64s |
| Q18 | 26.64s | 8.29s |
| Q19 | 31.25s | 9.42s |
| Q20 | 38.14s | 10.74s |
| Q21 | 0.24s | 0.15s |
| Q22 | 13.07s | 4.34s |
### 使用 TABLEGROUP
| | 未使用pg | 使用pg |
|------------------------|----------------|----------------|
| tpch | 25.3 | 23.28 |
| Q1 | 2.23s | 2.02s |
| Q2 | 0.59s | 0.32s |
| Q3 | 1.59s | 1.49s |
| Q4 | 0.46s | 0.34s |
| Q5 | 1.65s | 1.58s |
| Q6 | 0.80s | 0.83s |
| Q7 | 1.58s | 1.41s |
| Q8 | 1.52s | 1.39s |
| Q9 | 1.83s | 1.85s |
| Q10 | 2.41s | 2.38s |
| Q11 | 0.54s | 0.47s |
| Q12 | 1.14s | 1.00s |
| Q13 | 0.93s | 0.85s |
| Q14 | 0.23s | 0.21s |
| Q15 | 0.63s | 0.57s |
| Q16 | 0.46s | 0.41s |
| Q17 | 0.98s | 0.94s |
| Q18 | 1.18s | 0.98s |
| Q19 | 1.03s | 1.02s |
| Q20 | 1.34s | 1.25s |
| Q21 | 1.55s | 1.42s |
| Q22 | 0.63s | 0.55s |
# 如何使用 跑业务场景测试
## 数据库驱动
使用 JMeter 测试OceanBase性能的时候也需要加载OceanBase的 Java 驱动。文件可以从官网的下载文件中获取。地址:[https://help.aliyun.com/document_detail/212815.html](https://help.aliyun.com/document_detail/212815.html)
下载的`oceanbase-client-1.x.x.jar`需要放到 `Jmeter``lib` 文件夹夹中。
## 业务场景定义
### 建表语句
请在obmysql租户下业务账户执行下面SQL 。
```sql
$obclient -h127.1 -utpcc@obbmsql#obdemo -P2883 -p123456 -c -A tpcc
CREATE TABLE account(id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY
, name varchar(50) NOT NULL UNIQUE
, value bigint NOT NULL
, gmt_create timestamp DEFAULT current_timestamp NOT NULL
, gmt_modified timestamp DEFAULT current_timestamp NOT NULL );
```
### 场景SQL
此次测试模拟一个分布式事务,SQL类似如下:
```sql
-- session A
begin ;
select value from account where id = 174 for update ;
update account set value = value - 7 , gmt_modified = current_timestamp where id = 174 ;
update account set value = value + 7 , gmt_modified = current_timestamp where id = 165 ;
-- commit or rollback;
commit;
```
## 新建JMeter测试计划
JMeter能在命令行下运行,也可以以图形界面运行。这里我简单点直接用图形界面这种方式。
新建测试计划
### 测试计划属性
![-w1152](media/15847798056795.jpg)
新建 `Thread-Group`
![-w1152](media/15847798711697.jpg)
这里有很多跟多线程运行有关的JMeter参数,具体说明可以查看JMeter官网文档。
注意这里的`bigint of Threads`是指压测的客户端线程数。
### JDBC连接属性
![-w1440](media/15848380171617.jpg)
这里面属性很多,都是一个连接池常具备的参数。有兴趣的可以看看网上关于Java连接池配置的经验。
这里面也有几个参数强调一下:
+ `Max bigint of Connections`:这个指连接池里最多多少个连接。如果压测线程数远高于这个值,那么压测线程可能会需要等待这个连接池创建或返还数据库连接(即到OceanBase的连接)给它。如果等不到可能会报错。在这个环节,客户端压测线程拿不到连接,不一定跟OceanBase数据库有直接关系。在Java应用里面也同理。
+ `Transaction Isolation`:这个是数据库连接使用的事务隔离级别,OceanBase支持两种事务隔离级别:读已提交(`Read-Committed`)和序列化(`Serializable`)。前者很常见容易理解,后者是ORACLE特有隔离级别,OceanBase也兼容了。有兴趣的可以看看《OceanBase事务引擎特性和应用实践分享》。理解序列化隔离级别的特点和场景可以加深自己对数据库事务的理解。
+ `Test While Idle`:这个是连接探活(`keepalive`)设置。这个设置对应用却很优必要。有时候应用会说数据库连接报错说在一个关闭的连接上执行SQL报错,这个就是因为连接池中的数据库连接因为其他原因已断开了。所以,数据库连接池通常都需要探活机制。这里由于是压测场景基本无闲置连接,所以可以设置为`False`
+ `Database URL`:数据库连接URL格式,直接类似填写 `jdbc:oceanbase://11.***.***.5:2883/tpcc`
+ `JDBC Driver Class`:数据库驱动中的Main类名,这个要按OceanBase格式填写,`com.alipay.oceanbase.obproxy.mysql.jdbc.Driver`
+ `Username`:用户格式,OceanBase的用户名格式比较特别,是`租户里用户名@租户名#集群名``集群名:租户名:租户里用户名`。如 `tpcc@obbmsql#obdemo`
### 事务参数(变量)
在这个测试里,有三个变量:账户A,账户B,转账金额,所以需要设置参数
![-w1440](media/15847856661453.jpg)
账户参数和金额采取随机数,随机数的值不要超出测试数据实际范围。
### 事务控制器
这里面是维护每个事务的逻辑。事务由一组`JDBC`请求组成。
+ 开启事务
![-w1440](media/15847810253110.jpg)
选择 `Autocommit(false)`,开启显式事务。
+ 查询账户A的余额
这里查询账户A的记录会同时锁住这笔记录,即常用的悲观锁技术。 这一步看测试需要,不是必需的。
```sql
select value from account where id = ? for update ;
```
![-w1440](media/15847852394348.jpg)
注意所有参数都使用`Prepared Statement`,以下同。
这一步后面按业务设计应该检查一下返回值是否大于要转账的值,如果不满足就是“转账余额不足”。这里我没有去研究JMeter如果根据查询返回值进行逻辑判断。有兴趣的朋友可以自己研究。
+ 扣减账户A的余额
SQL很简单。
```sql
update account set value = value - ? , gmt_modified = current_timestamp where id = ? ;
```
![-w1440](media/15847853936218.jpg)
多个绑定参数使用逗号(,)分隔。
然后要新增一个Post处理逻辑,获取更新返回值
![-w1440](media/15847854836534.jpg)
+ 增加账户B的余额
SQL很简单。
```sql
update account set value = value + ? , gmt_modified = current_timestamp where id = ? ;
```
![-w1440](media/15847857206738.jpg)
同样,需要增加一个Post处理器
![-w1440](media/15847857858980.jpg)
+ 判断逻辑——成功流程
如果上面两笔账户的更新成功,则提交事务。
新增判断控制 `IF`
![-w1440](media/15847858525132.jpg)
新增动作
![-w1440](media/15847860017082.jpg)
### 判断逻辑——失败流程
如果上面两笔账户的更新有一笔失败,则回滚事务。
新增判断控制 `IF`
![-w1440](media/15847859478920.jpg)
新增动作
![-w1440](media/15847859751936.jpg)
### 查看结果
可以查看成功、失败的结果
![-w1440](media/15847861075504.jpg)
查看汇总报告
![-w1440](media/15847861285271.jpg)
![-w1440](media/15847861581557.jpg)
# 第 7 章:如何诊断和调优 OceanBase 社区版性能
本章主要介绍OceanBase 性能诊断和调优技巧以及部分原理。
## 本章目录
+ [性能诊断调优概述](7.1.md)
+ [OBPROXY SQL 路由原理](7.2.md)
+ [如何管理 OceanBase 数据库连接](7.3.md)
+ [如何分析 SQL 审计视图](7.4.md)
+ [如何诊断和调优 OceanBase SQL 执行计划](7.5.md)
## 如何联系我们
欢迎广大 OceanBase 爱好者、用户和客户有任何问题联系我们反馈:
+ 社区版官网论坛:[https://open.oceanbase.com/answer](https://open.oceanbase.com/answer)
+ 社区版项目网站提 `Issue`[https://github.com/oceanbase/oceanbase/issues](https://github.com/oceanbase/oceanbase/issues)
+ 钉钉群:群号 `33254054`
\ No newline at end of file
# 性能诊断调优概述
客户端 SQL执行过程会分为下面几步:
+ 客户端将 SQL 发往 OBPROXY 节点。
+ OBPROXY 将 SQL 发往后端 OBSERVER 节点。
+ OBSERVER 解析 SQL,执行SQL,返回结果给 OBPROXY 节点。
+ OBPROXY 将数据返回给客户端。
OceanBase 的 SQL 性能诊断就是围绕这个链路上几个关键环节进行。如 OBPROXY 的 路由、OBSERVER 的 SQL引擎等。
## SQL 引擎简介
OBSERVER 进程包含 SQL 引擎、事务引擎和存储引擎。其中 SQL 引擎的设计跟 ORACLE SQL引擎设计理念一致。都有 SQL 语法解析、执行计划缓存、软解析、大纲等技术。
OBPROXY 进程并不包含 SQL 引擎,只能做很简单的 SQL 分析,判断 SQL 该路由到后端那个 OBSERVER 节点。未来 OBPROXY 可能会把 OBSERVER 的 SQL 引擎能力也包含进去。
### SQL 执行计划简介
SQL 执行计划简单来说就是 SQL 执行的路径或者算法,影响 SQL 的执行性能。差之毫厘,谬以千里。
OceanBase SQL 引擎支持SQL解析,解析后的执行计划会缓存,下次就可以复用。跟传统数据库一样,SQL 文本不一样,执行计划不会复用。此外,如果SQL文本一样,但是执行环境不一样,执行计划也不会复用。目前所知的执行环境包括运行节点IP、会话级别的并行参数。当然可能还有其他的。所有数据库的SQL执行引擎的成熟度可以从这个小点入手判断。
OceanBase 缓存执行计划的两个视图是:`gv$plan_cache_plan_stat``gv$plan_cache_plan_explain` 。这是实际运行使用的执行计划,是更准确的。不过初学的时候看这个比较麻烦。所以本文后面先探讨用 EXPLAIN 命令解析的执行计划。
先介绍一些执行计划的大的特点。
+ 没有绝对完美正确的执行计划,尤其是 SQL 比较复杂的时候。
+ 同样的SQL,不同 OBSERVER 版本可能会有执行计划的变化。
+ 同样的SQL,执行计划也不一定一尘不变。影响执行计划变化的因素可能有数据量(或者统计信息)、会话参数(或变量)、数据分布特点、SQL被路由到的节点等。后两者是 OceanBase 特有的。
+ EXPLAIN 解析的执行计划可能跟实际运行的执行计划不一样,受 OBPROXY 和 OBSERVER 的 LOCATION CACHE 和 SQL 路由策略影响。
+ SQL 执行计划可以通过 SQL 注释干预,这个需要改 SQL。如果 SQL 不可以改,可以通过大纲(OUTLINE)在线干预。干预的结果可能成功也可能失败。 这点以后也详细介绍。
### SQL HINTS
OceanBase 支持在 SQL 里使用 HTINTS 来改变执行计划或控制其他行为。
+ 语句级别的hint
```sql
FROZEN_VERSION
QUERY_TIMEOUT
READ_CONSISTENCY
LOG_LEVEL
QB_NAME
ACTIVATE_BURIED_POINT
TRACE_LOG
MAX_CONCURRENT
```
**说明:**
+ `/*+ QUERY_TIMEOUT(100000000) */`:是对SQL 超时时间进行控制。
+ `/*+ READ_CONSISTENCY(weak) */`:是用来设置弱一致性读。
+ `/*+ MAX_CONCURRENT(8) */`: 是用来对 SQL 的并发进行限制的。
+ 计划相关的hint
```sql
FULL
INDEX
LEADING
USE_MERGE
USE_HASH
USE_NL
ORDERED
NO_REWRITE
```
**说明:**
+ `/*+ FULL(表名) */`:是强制对表进行全表扫描。
+ `/*+ LEADING(表名) */`:是强制某表为驱动表。可以间接改变连接算法。
+ `/*+ USE_NL(表a, 表b, 表c) */`:设置表 a b c 的连接算法为嵌套循环。
+ `/*+ ORDERED */`:按照 `from` 子句后的表顺序进行表链接。
## OceanBase 性能诊断思路
OceanBase 性能问题通常包括:
+ 大表查询性能,大批量数据导出性能等。
+ 大事务性能,大批量数据加载性能等。
+ 普通的读写性能。
有些性能问题往往是集群出现稳定性问题之后出现的。比如说网络延时增大或者节点时间误差增大导致的节点状态不稳定,进而影响了性能。磁盘故障或者磁盘空间满导致的合并异常问题,也可能间接影响性能。由于 OceanBase 可靠性很高,即使集群不稳定,也不会出现不可用的状态,所以,稳定性问题隐藏的有点深,不为业务所见。业务更多的时候感知的是性能问题。
当然,运维使用一些监控手段,也是能够及时发现稳定性问题。
下面性能诊断就不考虑稳定性问题带来的性能问题。
性能诊断的方向有下面几个:
+ OBPROXY 节点的负载。包括 CPU、网络、内存等。
+ OBSERVER 节点的负载。包括 CPU、磁盘、内存、网络等。
+ SQL 的执行类型分布(本地 SQL 和远程 SQL 的比例)。
+ SQL 的执行计划。
+ 集群和租户的内存管理(内存参数、转储和合并进度等)。
+ 业务事务的具体 SQL 逻辑。
# 第 8 课:OceanBase 社区版生态工具介绍
本章主要简单介绍OceanBase 相关的工具、产品等用法。
更多工具欢迎大家补充。
## 本章目录
+ [主机监控产品](8.1.md)
+ [数据迁移产品](8.2.md)
+ [运维工具](8.3.md)
## 如何联系我们
欢迎广大 OceanBase 爱好者、用户和客户有任何问题联系我们反馈:
+ 社区版官网论坛:[https://open.oceanbase.com/answer](https://open.oceanbase.com/answer)
+ 社区版项目网站提 `Issue`[https://github.com/oceanbase/oceanbase/issues](https://github.com/oceanbase/oceanbase/issues)
+ 钉钉群:群号 `33254054`
# 主机监控产品
## 传统监控产品
OceanBase 本质上是一个单进程软件,进程名是:`observer` 。进程启动时,默认会将主机的 CPU、内存和磁盘空间(指数据盘,启动参数里可以定义)大部分资源都占据。其中 CPU 的占用是声明式的,并不会实际占有。内存的占用是预分配的,默认会占用 80% 主机可用内存(参数`memory_limit_percentage` 指定)。通常 OceanBase 进程所在主机不适合在跑其他数据库或者应用软件等。磁盘的占用也是预分配的,默认会占用 90%(参数 `datafile_disk_percentage` 指定)。进程正常启动后会监听两个端口:默认是 `2881``2882`
跟其他传统数据库一样,可以使用已有的监控平台监控 OceanBase 主机的:
+ 主机可用性,可以 `ping`
+ 主机 `load`
+ 主机 `cpu`
+ 主机 `mem`。注意监控可用内存就行。可用内存低于 1G ,进程`observer` 有 OOM 风险。
+ 主机磁盘,包括 IO利用率、IO延时、IO 吞吐量等。分数据盘和日志盘等。
+ 主机网络监听端口,包括 `ssh` 的端口(默认 22 ,可以改)、`observer` 的连接端口(默认 2881,可以改)、`observer` 的 RPC 端口(默认 2882,可以改)。
+ 主机网卡流量。包括进程 `observer` 监听的那个网卡。当网卡流量接近能力上限(通常是10000Mb), SQL 性能变慢,load 变高。
## `tsar` 工具
`tsar` 很早就在 `github` 上开源了,项目地址是:`https://github.com/alibaba/tsar`
`tsar` 是淘宝的一个用来收集服务器系统和应用信息的采集报告工具,如收集服务器的系统信息(`cpu``mem` 等),以及应用数据(`nginx``swift` 等),收集到的数据存储在服务器磁盘上,可以随时查询历史信息,也可以将数据发送到 `nagios` 报警。`tsar` 带来的性能影响很小,存储空间也很小。可以独立运行,作为现有监控手段的一个补充。
`tsar` 还能够比较方便的增加模块,只需要按照 `tsar` 的要求编写数据的采集函数和展现函数,就可以把自定义的模块加入到`tsar` 中。
# 数据迁移产品
## DataX 数据交换平台
DataX 也是阿里巴巴开源的一款实用产品,项目地址是:`https://github.com/alibaba/datax`
DataX 是阿里云 DataWorks数据集成 的开源版本,在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。DataX 实现了包括 OceanBase、MySQL、Oracle、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、Hologres、DRDS 等各种异构数据源之间高效的数据同步功能。
## Canal 数据增量工具
Canal 也是阿里巴巴开源的一款数据同步产品,项目地址时候:`https://github.com/alibaba/canal`
Canal 主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费。
基于日志增量订阅和消费的业务包括
+ 数据库镜像
+ 数据库实时备份
+ 索引构建和实时维护(拆分异构索引、倒排索引等)
+ 业务 cache 刷新
+ 带业务逻辑的增量数据处理
当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x 。
Canal 跟 DataX 结合可以实现将 MySQL 数据实时同步到 OceanBase 的 MySQL 租户中。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册