Merge branch 'pr_2087'

This commit is contained in:
ob-robot 2024-08-14 05:24:55 +00:00
commit d4579da469
80 changed files with 5503 additions and 91 deletions

View File

@ -28,30 +28,21 @@ jobs:
# Build job
build:
runs-on: ubuntu-latest
env:
MDBOOK_VERSION: 0.4.36
steps:
- uses: actions/checkout@v4
- name: Prepare
run: |
mkdir mdbook
mv docs mdbook/src
- name: Install mdBook
- name: Install Depdencies
run: |
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh
rustup update
cargo install --version ${MDBOOK_VERSION} mdbook
python3 -m pip install -r ./docs/requirements.txt
- name: Setup Pages
id: pages
uses: actions/configure-pages@v4
- name: Build with mdBook
run: mdbook build
working-directory: mdbook
run: |
cd docs
mkdocs build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./mdbook/book
path: ./docs/site
# Deployment job
deploy:
@ -63,4 +54,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v4

29
.github/workflows/translate.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: 'translator'
on:
issues:
types: [opened, edited]
issue_comment:
types: [created, edited]
discussion:
types: [created, edited]
discussion_comment:
types: [created, edited]
pull_request_target:
types: [opened, edited]
pull_request_review_comment:
types: [created, edited]
jobs:
translate:
permissions:
issues: write
discussions: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: lizheming/github-translate-action@1.1.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
IS_MODIFY_TITLE: true
APPEND_TRANSLATION: false

View File

@ -40,29 +40,12 @@ English | [中文版](README_CN.md)
# Key features
- **Transparent Scalability**
An OceanBase cluster can be scaled to 1,500 nodes transparently, handling petabytes of data and a trillion rows of records.
- **Ultra-fast Performance**
The only distributed database that has refreshed both the TPC-C record, at 707 million tmpC, and the TPC-H record, at 15.26 million QphH @30000GB.
- **Real-time Operational Analytics**
A unified system for both transactional and real-time operational analytics workloads.
- **Continuous Availability**
OceanBase Database adopts the Paxos Consensus algorithm to achieve Zero RPO and less than 8 seconds of RTO. Supports intra-city/remote disaster recovery, enabling multi-activity in multiple locations and zero data loss.
- **MySQL Compatible**
OceanBase Database is highly compatible with MySQL, which ensures that zero or a few modifications are needed for migration.
- **Cost Efficiency**
The cutting-edge compression technology saves 70%–90% of storage costs without compromising performance. The multi-tenancy architecture achieves higher resource utilization.
- **Transparent Scalability**: 1,500 nodes, PB data and a trillion rows of records in one cluster.
- **Ultra-fast Performance**: TPC-C 707 million tmpC and TPC-H 15.26 million QphH @30000GB.
- **Cost Efficiency**: saves 70%–90% of storage costs.
- **Real-time Analytics**: supports HTAP without additional cost.
- **Continuous Availability**: RPO = 0(zero data loss) and RTO < 8s(recovery time)
- **MySQL Compatible**: easily migrated from MySQL database.
See also [key features](https://en.oceanbase.com/product/opensource) for more details.
@ -87,17 +70,25 @@ obd demo
## 🐳 Start with docker
**Note**: We provide images on [dockerhub](https://hub.docker.com/r/oceanbase/oceanbase-ce/tags), [quay.io](https://quay.io/repository/oceanbase/oceanbase-ce?tab=tags) and [ghcr.io](https://github.com/oceanbase/docker-images/pkgs/container/oceanbase-ce). If you have problems pulling images from dockerhub, please try the other two registries.
1. Start an OceanBase Database instance:
```shell
# Deploy a mini standalone instance.
docker run -p 2881:2881 --name oceanbase-ce -e MODE=mini -d oceanbase/oceanbase-ce
# Deploy a mini standalone instance using image from quay.io.
# docker run -p 2881:2881 --name oceanbase-ce -e MODE=mini -d quay.io/oceanbase/oceanbase-ce
# Deploy a mini standalone instance using image from ghcr.io.
# docker run -p 2881:2881 --name oceanbase-ce -e MODE=mini -d ghcr.io/oceanbase/oceanbase-ce
```
2. Connect to the OceanBase Database instance:
```shell
docker exec -it oceanbase-ce ob-mysql sys # Connect to the root user of the sys tenant.
docker exec -it oceanbase-ce obclient -h127.0.0.1 -P2881 -uroot # Connect to the root user of the sys tenant.
```
See also [Docker Readme](https://github.com/oceanbase/docker-images/tree/main/oceanbase-ce) for more details.
@ -107,7 +98,7 @@ See also [Docker Readme](https://github.com/oceanbase/docker-images/tree/main/oc
You can deploy and manage OceanBase Database instance in kubernetes cluster with [ob-operator](https://github.com/oceanbase/ob-operator) quickly. Refer to the document [Quick Start for ob-operator](https://oceanbase.github.io/ob-operator) to see details.
## 👨‍💻 Start developing
See [OceanBase Developer Document](https://oceanbase.github.io/oceanbase/build-and-run.html) to learn how to compile and deploy a manually compiled observer.
See [OceanBase Developer Document](https://oceanbase.github.io/oceanbase/build-and-run) to learn how to compile and deploy a manually compiled observer.
# Roadmap

View File

@ -39,29 +39,13 @@
# 关键特性
- **水平扩展**
- **水平扩展**:单机群支持超过1500节点、PB级数据量和单表超万亿行数据;
- **极致性能**:TPC-C 7.07亿tmpC和TPC-H 1526 万 QphH @30000GB
- **低成本**:存储成本节省70%-90%;
- **实时分析**:不需要额外开销,支持HTAP;
- **高可用**:RPO = 0(0数据丢失),RTO < 8秒恢复时间);
- **MySQL 兼容**:很容易的从MySQL迁移过来。
实现透明水平扩展,支持业务快速的扩容缩容,同时通过准内存处理架构实现高性能。支持集群节点超过数千个,单集群最大数据量超过 3PB,最大单表行数达万亿级。
- **极致性能**
唯一一个刷新了 TPC-C 记录(7.07 亿 tmpC)和 TPC-H 记录(1526 万 QphH @30000GB)的分布式数据库。
- **实时分析**
基于“同一份数据,同一个引擎”,同时支持在线实时交易及实时分析两种场景,“一份数据”的多个副本可以存储成多种形态,用于不同工作负载,从根本上保持数据一致性。
- **高可用**
独创“三地五中心”容灾架构方案,建立金融行业无损容灾新标准。支持同城/异地容灾,可实现多地多活,满足金融行业 6 级容灾标准(RPO=0,RTO< 8s数据零丢失
- **MySQL 兼容**
高度兼容 MySQL,覆盖绝大多数常见功能,支持过程语言、触发器等高级特性,提供自动迁移工具,支持迁移评估和反向同步以保障数据迁移安全,可支撑金融、政府、运营商等关键行业核心场景。
- **低成本**
基于 LSM-Tree 的高压缩引擎,存储成本降低 70% - 90%;原生支持多租户架构,同集群可为多个独立业务提供服务,租户间数据隔离,降低部署和运维成本。
更多信息请参考 [OceanBase 产品](https://www.oceanbase.com/product/oceanbase)。
@ -86,28 +70,36 @@ obd demo
## 🐳 使用 docker
**注意**: 我们在 [dockerhub](https://hub.docker.com/r/oceanbase/oceanbase-ce/tags), [quay.io](https://quay.io/repository/oceanbase/oceanbase-ce?tab=tags) 和 [ghcr.io](https://github.com/oceanbase/docker-images/pkgs/container/oceanbase-ce) 提供镜像。如果您在从 dockerhub 拉取镜像时遇到问题,请尝试其他两个镜像库。
1. 启动 OceanBase 数据库实例
```shell
# 部署一个mini模式实例
docker run -p 2881:2881 --name oceanbase-ce -e MODE=mini -d oceanbase/oceanbase-ce
# 使用 quay.io 仓库的镜像部署 OceanBase.
# docker run -p 2881:2881 --name oceanbase-ce -e MODE=mini -d quay.io/oceanbase/oceanbase-ce
# 使用 ghcr.io 仓库的镜像部署 OceanBase.
# docker run -p 2881:2881 --name oceanbase-ce -e MODE=mini -d ghcr.io/oceanbase/oceanbase-ce
```
2. 连接 OceanBase
```shell
docker exec -it oceanbase-ce ob-mysql sys # 连接root用户sys租户
docker exec -it oceanbase-ce obclient -h127.0.0.1 -P2881 -uroot # 连接root用户sys租户
```
更多信息参考[docker 文档](https://github.com/oceanbase/docker-images/tree/main/oceanbase-ce)。
## ☸️ 使用 Kubernetes
使用 [ob-operator](https://github.com/oceanbase/ob-operator) 可在 Kubernetes 环境中快速部署和管理 OceanBase 数据库实例,可参考文档 [ob-operator 快速上手](https://oceanbase.github.io/ob-operator/README-CN.html)了解具体的使用方法。
使用 [ob-operator](https://github.com/oceanbase/ob-operator) 可在 Kubernetes 环境中快速部署和管理 OceanBase 数据库实例,可参考文档 [ob-operator 快速上手](https://oceanbase.github.io/ob-operator/zh-Hans/)了解具体的使用方法。
## 👨‍💻 使用源码编译部署
参考 [OceanBase 开发者文档](https://oceanbase.github.io/oceanbase/build-and-run.html)了解如何编译和部署手动编译的observer。
参考 [OceanBase 开发者文档](https://oceanbase.github.io/oceanbase/build-and-run)了解如何编译和部署手动编译的observer。
# Roadmap

View File

@ -1,14 +0,0 @@
# Summary
[OceanBase Development Guide](README.md)
[Install toolchain](toolchain.md)
[Get the code, build and run](build-and-run.md)
[Set up an IDE](ide-settings.md)
[Coding Convensions](coding-convension.md)
[Write and run unit tests](unittest.md)
[Running MySQL test](mysqltest.md)
[Debug](debug.md)
[Logging System](logging.md)
[Memory Management](memory.md)
[Containers](container.md)
[Coding Standard](coding_standard.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
docs/docs/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -17,13 +17,16 @@ At present, the guide is composed of the following parts:
5. [Write and run unit tests](unittest.md)
6. [Running MySQL test](mysqltest.md)
7. [Debug](debug.md)
8. Commit code and submit a pull request
8. [Commit code and submit a pull request](contributing.md)
More information before you start a big feature developing, you should read content below and it can help you understand oceanbase better.
1. [Logging System](logging.md)
2. [Memory Management](memory.md)
3. [Containers](container.md)
4. [Coding Standard](coding_standard.md)
3. [Bais Data Structures](container.md)
4. [Architecture](architecture.md)
5. [Coding Standard](coding_standard.md)
3. **Contribute to OceanBase**: helps you quickly get involved in the OceanBase community, which illustrates what contributions you can make and how to quickly make one.
## User documents

View File

@ -0,0 +1,39 @@
---
title: Architecture
---
# OceanBase DataBase Architecture
OceanBase Database adopts a shared-nothing architecture, where each node is equal to each other. That is, each node has its own SQL engine, storage engine, and transaction engine, and runs on a cluster of ordinary PC servers. OceanBase Database provides core features such as high scalability, high availability, high performance, cost-effectiveness, and strong compatibility with mainstream databases.
## Zone
A cluster of OceanBase database consists of several nodes. These nodes belong to several availability zones, and each node belongs to an availability zone. Availability zone is a logical concept. OceanBase uses zones to achieve high availability and disaster recovery features of data. Availability zones can be in different computer rooms, regions, etc., to achieve disaster recovery in different scenarios. OceanBase uses the strong consistency protocol Paxos to achieve high availability. Data under the same Paxos group is located in different availability zones.
## Partition
In the OceanBase database, the data of a table can be horizontally split into multiple shards according to certain partitioning rules. Each shard is called a table partition, or Partition. Partition support includes hash, range, list and other types, and also supports secondary partitioning. For example, the order table in the transaction database can first be divided into several first-level partitions according to user ID, and then each first-level partition can be divided into several second-level partitions according to the month. For a second-level partition table, each sub-partition of the second level is a physical partition, while the first-level partition is just a logical concept. Several partitions of a table can be distributed on multiple nodes within an Availability Zone. Each physical partition has a storage layer object used to store data, called Tablet, which is used to store ordered data records.
## Log Stream
When the user modifies the records in the Tablet, in order to ensure data persistence, the redo log (REDO) needs to be recorded to the Log Stream corresponding to the Tablet. One log stream corresponds to multiple Tablets on the node where it is located. Tablet uses a multi-replication mechanism to ensure high availability. Typically, replicas are spread across different Availability Zones. Among multiple replicas, only one replica accepts modification operations, which is called the leader, and the others are called followers. Data consistency between leader and follower replicas is achieved through a distributed consensus protocol based on Multi-Paxos. Multi-Paxos uses Log Stream to implement data replication. Tablets can be migrated between Log Streams to achieve load balancing of resources.
## OBServer
A service process called observer runs on each node in the cluster. Each service is responsible for accessing partitioned data on its own node, and is also responsible for parsing and executing SQL statements routed to the node. These service processes communicate with each other through the TCP/IP protocol. At the same time, each service will listen for connection requests from external applications, establish connections and database sessions, and provide database services.
## Multi-Tenant
In order to simplify the management of large-scale deployment of multiple business databases and reduce resource costs, OceanBase database provides multi-tenant features. Within an OceanBase cluster, multiple isolated database "instances" can be created, called a tenant. From an application perspective, each tenant is equivalent to an independent database instance. Each tenant can choose MySQL or Oracle compatibility mode. After the application is connected to the MySQL tenant, users and databases can be created under the tenant, and the experience is the same as using an independent MySQL instance. After a new cluster is initialized, there will be a special tenant named sys, called the system tenant. The system tenant stores the metadata of the cluster and it is in MySQL compatibility mode.
In addition to the system tenant and user tenant, OceanBase also has a tenant called Meta. Every time a user tenant is created, the system will automatically create a corresponding Meta tenant, whose life cycle is consistent with the user tenant. The Meta tenant is used to store and manage the cluster private data of user tenants. This data does not require cross-instance physical synchronization and physical backup and recovery. The data includes: configuration items, location information, replica information, log stream status, backup and recovery related information, merge information, etc.
## Resource Unit
In order to isolate tenant resources, each observer process can have multiple virtual containers belonging to different tenants, called resource units. Resource units include CPU, memory and disk resources. Multiple resource units form a resource pool. Using the resource pool, you can specify which resource unit to use, how many resource units, and availability zones for resource distribution. When creating a tenant, specify the list of resource pools used to control the resources and data distribution locations used by the tenant.
## obproxy
Applications usually do not directly establish a connection with OBServer, but connect to obproxy, and then obproxy forwards the SQL request to the appropriate OBServer node. obproxy will cache information related to data partitions and can route SQL requests to the most appropriate OBServer node. Obproxy is a stateless service. Multiple obproxy nodes can provide a unified network address to applications through network load balancing (SLB).

View File

@ -1,3 +1,7 @@
---
title: Coding Convention
---
OceanBase is a giant project that has been developed for more than ten years and contains millions of lines of C++ code. It already has many unique programming habits. Here are some OceanBase programming habits to help people who come into contact with the OceanBase source code for the first time have an easier time accepting and understanding. For more detailed information, please refer to ["OceanBase C++ Coding Standard"](./coding_standard.md).
# Naming Convention

View File

@ -1,3 +1,6 @@
---
title: Coding Standard
---
| Number | Document Version | Revised Chapter | Reason for Revision | Revision Date |
| -------| ---------------- | --------------- | ------------------- | ------------- |

View File

@ -1,3 +1,7 @@
---
title: Basic Data Structures
---
# Introduction
C++ STL provides many convenient containers, such as vector, map, unordered_map, etc. Due to OceanBase programming style and memory control, the use of STL containers is prohibited in OceanBase. OceanBase provides some container implementations, including arrays, linked lists, HashMap, etc. This document will introduce some of these containers.

View File

@ -0,0 +1,85 @@
# OceanBase Contributing Guidelines
Thank you for considering contributing to OceanBase.
We welcome any type of contribution to the OceanBase community. You can contribute code, help our new users in the DingTalk group (Group No.: 33254054), [Slack group](https://join.slack.com/t/oceanbase/shared_invite/zt-1e25oz3ol-lJ6YNqPHaKwY_mhhioyEuw) or in [StackOverflow](https://stackoverflow.com/search?q=oceanbase&s=4b3eddc8-73a2-4d90-8039-95fcc24e6450), test releases, or improve our documentation.
If you are interested in contributing code to OceanBase, please read the following guidelines and follow the steps.
## Start off with the right issue
As a new contributor, you can start off by looking through our [good first issues](https://github.com/oceanbase/oceanbase/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). If none of them feels right for you, you can create a new issue when you find one. If you find an issue for you, please assign this issue to yourself in the issue topic and add the _developing_ label to indicate that this issue is being developed.
## Contribute code changes
1. Fork the OceanBase repository.
1. Visit the [OceanBase GitHub repository](https://github.com/oceanbase/oceanbase).
2. Click the **Fork** button in the upper-right corner to fork one branch.
2. Configure the local environment.
```bash
working_dir=$HOME/{your_workspace} # Define the working directory of your choice.
user={github_account} # Make sure the user name is the same as your GitHub account.
```
3. Git clone the code.
```bash
mkdir -p $working_dir
cd $working_dir
git clone git@github.com:$user/oceanbase.git
# Add the upstream branch.
cd $working_dir/oceanbase
git remote add upstream git@github.com:oceanbase/oceanbase.git
# Or choose: git remote add upstream https://github.com/oceanbase/oceanbase
# Set no_push for the upstream branch.
git remote set-url --push upstream no_push
# Check if the upstream works as expected.
git remote -v
```
4. Create a new branch.
```bash
# Check out the local master.
new_branch_name={issue_id} # Define the branch name. It is recommended that you use {issue+id} for the branch name.
cd $working_dir/oceanbase
git fetch upstream
git checkout master
git rebase upstream/master
git checkout -b $new_branch_name
```
5. Finish all the developing tasks in the new branch, including the testing tasks.
6. Submit changes to your branch.
```
# Check the local status.
git status
# Add files for submission.
# Directly add all changes using `git add .`
git add <file> ...
# In order to relate the pull request automatically to the issue,
# it is recommended that the commit message contain "fixed #{issue_id}".
git commit -m "fixed #{issue_id}: {your_commit_message}"
# Sync upstream before push.
git fetch upstream
git rebase upstream/master
git push -u origin $new_branch_name
```
7. Create a pull request.
1. Visit your fork repository.
2. Click the **Compare & pull request** button next to the {new_branch_name} to create a pull request.
8. Sign the [Contributor License Agreement (CLA)](https://cla-assistant.io/oceanbase/oceanbase) aggreement. The workflow can continue only after you sign the CLA aggreement.
![CLA](https://user-images.githubusercontent.com/5435903/204097095-6a19d2d1-ee0c-4fb6-be2d-77f7577d75d2.png#crop=0&crop=0&crop=1&crop=1&from=url&id=Mmj8a&margin=%5Bobject%20Object%5D&originHeight=271&originWidth=919&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
After you submit your updates, OceanBase will review and comment if needed. Once we approve your updates, the system will automatically run CI testing and stress testing. If no issues are found in the tests, your updates will be merged. Now you have successfully completed your contribution task and become one of our contributors.

View File

@ -1,3 +1,7 @@
---
title: Debug
---
# Abstract
This document describes some methods to debug OceanBase. We have many ways to debug OceanBase, such as gdb, logging, etc.

View File

@ -1,3 +1,7 @@
---
title: Set up an IDE
---
# Abstract
In order to easily read the code of OceanBase, we suggest using one IDE which is easily index the symbols of OceanBase. In Windows, we recommend `Souce Insight` can be used, and in Mac or Linux, we recommend that `VSCode + ccls` can be used to read the oceanbase code. Due to it is very easy to use `Source Ingisht`, so this document skip introduction how to use `Souce Insight`.

View File

Before

Width:  |  Height:  |  Size: 407 KiB

After

Width:  |  Height:  |  Size: 407 KiB

View File

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

View File

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

View File

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 326 KiB

View File

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 159 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,3 +1,7 @@
---
title: System Log
---
# OceanBase System Log Introduction
## Introduction

View File

@ -1,3 +1,7 @@
---
title: Memory Management
---
# Introduction
Memory management is one of the most important modules in any large C++ project. Since OceanBase also needs to deal with the issue of multi-tenant memory resource isolation, OceanBase's memory management is more complicated than ordinary C++ projects. Generally, a good memory management module needs to consider the following issues:

35
docs/docs/zh/README.md Normal file
View File

@ -0,0 +1,35 @@
# OceanBase 开发者手册
## 介绍
* **面向人群** 手册的目标受众是OceanBase的贡献者,无论是新人还是老手。
* **目标** 手册的目标是帮助贡献者成为OceanBase的专家,熟悉其设计和实现,从而能够在现实世界中流畅地使用它以及深入开发OceanBase本身。
## 手册结构
当前,手册由以下部分组成:
1. **开始**: 设置开发环境,构建和连接到OceanBase服务器,子部分基于一个想象的新手用户旅程。
1. [安装工具链](toolchain.md)
2. [获取代码,编译运行](build-and-run.md)
3. [配置IDE](ide-settings.md)
4. [编程惯例](coding-convension.md)
5. [编写并运行单元测试](unittest.md)
6. [运行MySQL测试](mysqltest.md)
7. [调试](debug.md)
8. [提交代码和Pull Request](contributing.md)
2. **OceanBase设计和实现**: 介绍了OceanBase的设计和实现细节,这些细节对于理解OceanBase的工作原理至关重要。
在开始编写稍大的功能之前,你应该阅读以下内容,它可以帮助你更好地理解OceanBase。
1. [日志系统](logging.md)
2. [内存管理](memory.md)
3. [基础数据结构](container.md)
4. [架构](architecture.md)
5. [编程规范](coding_standard.md)
## 用户文档
本手册不包含用户文档。
可以参考 [oceanbase-doc](https://github.com/oceanbase/oceanbase-doc) 查看用户文档。

View File

@ -0,0 +1,39 @@
---
title: 架构
---
# OceanBase 数据库架构
OceanBase采用了无共享(Shared-Nothing)的集群架构,集群由若干完全对等的计算机(称为节点)组成,每个节点都有其私有的物理资源(包括CPU、内存、硬盘等),并且在这些节点上运行着独立的存储引擎、SQL引擎、事务引擎等。集群中各个节点之间相互独立,但通过连接彼此的网络设备相互协调,共同作为一个整体完成用户的各种请求。由于节点之间的独立性,使得 OceanBase 具备可扩展、高可用、高性能、低成本等核心特性。
![architecture](images/architecture.jpg)
## 可用区(Zone)
OceanBase 数据库的一个集群由若干个节点组成。这些节点分属于若干个可用区(Zone),每个节点属于一个可用区。可用区是一个逻辑概念,OceanBase使用zone来实现数据的高可用性和灾备特性。可用区可以在不同的机房、区域等,进而实现不同场景的容灾。OceanBase 使用强一致性协议 Paxos 实现高可用,同一个Paxos组下的数据,位于不同的可用区。
## 分区(Partition)
在 OceanBase 数据库中,一个表的数据可以按照某种划分规则水平拆分为多个分片,每个分片叫做一个表分区,简称分区(Partition)。分区支持包括hash、range、list等类型,还支持二级分区。例如,交易库中的订单表,可以先按照用户 ID 划分为若干一级分区,再按照月份把每个一级分区划分为若干二级分区。对于二级分区表,第二级的每个子分区是一个物理分区,而第一级分区只是逻辑概念。一个表的若干个分区可以分布在一个可用区内的多个节点上。每个物理分区有一个用于存储数据的存储层对象,叫做 Tablet,用于存储有序的数据记录。
## 日志流(Log Stream)
当用户对 Tablet 中记录进行修改的时候,为了保证数据持久化,需要记录重做日志(REDO)到 Tablet 对应的日志流(Log Stream)里。一个日志流对应其所在节点上的多个 Tablet。Tablet 使用多副本机制来保证高可用。一般来说,副本分散在不同的可用区里。多个副本中有且只有一个副本接受修改操作,叫做主副本(Leader),其它的叫做从副本(Follower)。主从副本之间通过基于 Multi-Paxos 的分布式共识协议实现了副本之间数据的一致性。而 Multi-Paxos 使用 Log Stream 来实现数据复制。Tablet 可以在Log Stream之间迁移,以实现资源的负载均衡。
## OBServer
在集群的每个节点上会运行一个叫做 observer 的服务进程。每个服务负责自己所在节点上分区数据的存取,也负责路由到本机的 SQL 语句的解析和执行。这些服务进程之间通过 TCP/IP 协议进行通信。同时,每个服务会监听来自外部应用的连接请求,建立连接和数据库会话,并提供数据库服务。
## 多租户
为了简化大规模部署多个业务数据库的管理并降低资源成本,OceanBase 数据库提供了独特的多租户特性。在一个 OceanBase 集群内,可以创建多个相互隔离的数据库"实例",叫做一个租户。从应用程序的视角来看,每个租户等同于一个独立的数据库实例。每个租户可以选择 MySQL 或 Oracle 兼容模式。应用连接到 MySQL 租户后,可以在租户下创建用户、database,与一个独立的 MySQL 库的使用体验是一样的。一个新的集群初始化之后,会存在一个特殊的名为 sys 的租户,叫做系统租户。系统租户中保存了集群的元数据,是一个 MySQL 兼容模式的租户。
除了系统租户和用户租户,OceanBase 还有一个称为Meta的租户。每创建一个用户租户系统就会自动创建一个对应的 Meta 租户,其生命周期与用户租户保持一致。Meta 租户用于存储和管理用户租户的集群私有数据,这部分数据不需要进行跨库物理同步以及物理备份恢复,这些数据包括:配置项、位置信息、副本信息、日志流状态、备份恢复相关信息、合并信息等。
## 资源单元
为了隔离租户的资源,每个 observer 进程内可以有多个属于不同租户的虚拟容器,叫做资源单元(resource unit),资源单元包括 CPU 、内存和磁盘资源。多个资源单元组成一个资源池(resource pool),使用资源池可以指定使用哪个资源单元、多少个资源单元以及资源分布的可用区。创建租户时,指定所使用的资源池列表,这样控制租户使用的资源和数据分布位置。
## obproxy
应用程序通常并不直接与 OBServer 建立连接,而是连接obproxy,然后由 obproxy 转发 SQL 请求到合适的 OBServer 节点。obproxy 会缓存数据分区相关的信息,可以将SQL请求路由到尽量合适的 OBServer 节点。obproxy 是无状态的服务,多个 obproxy 节点可以通过网络负载均衡(SLB)对应用提供统一的网络地址。

View File

@ -0,0 +1,62 @@
# 获取代码,编译运行
## 前置条件
检查支持的操作系统列表([安装工具链](toolchain.md))和GLIBC版本要求,以及如何安装C++工具链。
## Clone 代码
把代码clone到本地:
```shell
git clone https://github.com/oceanbase/oceanbase.git
```
## 构建
构建debug或release版本的OceanBase源码:
### Debug 模式
```shell
bash build.sh debug --init --make
```
### Release 模式
```shell
bash build.sh release --init --make
```
## 运行
`observer` 二进制文件已经编译出来了,可以使用 `obd.sh` 工具部署一个 OceanBase 实例:
```shell
./tools/deploy/obd.sh prepare -p /tmp/obtest
./tools/deploy/obd.sh deploy -c ./tools/deploy/single.yaml
```
OceanBase 服务程序会监听 10000 端口。
## 连接
可以使用官方的 MySQL 客户端连接 OceanBase:
```shell
mysql -uroot -h127.0.0.1 -P10000
```
也可以使用 `obclient` 连接 OceanBase:
```shell
./deps/3rd/u01/obclient/bin/obclient -h127.0.0.1 -P10000 -uroot -Doceanbase -A
```
## 停止
停止服务并清理部署:
```shell
./tools/deploy/obd.sh destroy --rm -n single
```

View File

@ -0,0 +1,155 @@
---
title: 编程惯例
---
OceanBase 编程惯例
OceanBase 是一个发展了十几年的、包含几百万行C++代码的巨型工程,它已经有了很多自己特有的编程习惯,这里介绍一些首次接触OceanBase源码同学一些需要注意的事项,也可以让大家更方便的阅读OceanBase源码。更详细的内容可以参考[OceanBase C++编程规范](coding_standard.md)。
# 命名习惯
## 文件命名
OceanBase中代码文件名都以`ob_`开头。但也有一些陈旧的例外文件。
## 类命名
类都以`Ob`开头,也有一些陈旧类的意外。
## 成员变量命名
成员变量都以`_`作为后缀。
# 函数编程习惯
## 禁止使用STL容器
由于OceanBase支持多租户资源隔离,为了方便控制内存,OceanBase禁止使用STL、boost等容器。同时,OceanBase提供了自己实现的容器,比如 `ObSEArray` 等。
## 单入口单出口
强制要求所有函数在末尾返回,禁止中途调用return、goto、exit等全局跳转指令。这一条也是所有首次接触OceanBase代码的人最迷惑的地方。
为了实现这一要求,代码中会出现很多 `if/else if` ,并且在 `for` 循环中存在 `OB_SUCC(ret)` 等多个不那么直观的条件判断。同时为了减少嵌套,会使用宏 `FALSE_IT` 执行某些语句。比如
```cpp
int ObMPStmtReset::process()
{
int ret = OB_SUCCESS;
...
if (OB_ISNULL(req_)) {
ret = OB_INVALID_ARGUMENT;
LOG_WARN("invalid packet", K(ret), KP(req_));
} else if (OB_INVALID_STMT_ID == stmt_id_) {
ret = OB_INVALID_ARGUMENT;
LOG_WARN("stmt_id is invalid", K(ret));
} else if (OB_FAIL(get_session(session))) {
LOG_WARN("get session failed");
} else if (OB_ISNULL(session)) {
ret = OB_ERR_UNEXPECTED;
LOG_WARN("session is NULL or invalid", K(ret), K(session));
} else if (OB_FAIL(process_kill_client_session(*session))) {
LOG_WARN("client session has been killed", K(ret));
} else if (FALSE_IT(session->set_txn_free_route(pkt.txn_free_route()))) {
} else if (OB_FAIL(process_extra_info(*session, pkt, need_response_error))) {
LOG_WARN("fail get process extra info", K(ret));
} else if (FALSE_IT(session->post_sync_session_info())) {
} else if (FALSE_IT(need_disconnect = false)) {
} else if (OB_FAIL(update_transmission_checksum_flag(*session))) {
LOG_WARN("update transmisson checksum flag failed", K(ret));
} else {
// ...
}
return ret;
}
```
代码中使用了大量的 `if/else if` ,并且使用了FALSE_IF宏尽量减少if的嵌套。
值得一提的是,类似的函数中都会在函数开头写 `int ret = OB_SUCCESS;`,将ret作为函数返回值,并且很多宏,也会默认ret的存在。
## 函数返回错误码
对于绝大多数函数,都要求函数具备int返回值,返回值可以使用错误码 `ob_errno.h` 解释。
这里说的绝大多数函数,包含一些获取值的函数,比如 ObSEArray的at函数
```cpp
int at(int64_t idx, T &obj);
```
哪些函数不需要返回int值?
比较简单的返回类属性的函数,比如 ObSEArray 的函数:
```cpp
int64_t get_capacity();
```
将直接返回值,而不带int错误码。
或者类似的简单判断的函数不需要返回int错误码。
## 需要判断所有函数值与函数参数有效性
OceanBase 要求只要函数有返回值,就必须对返回值做检测,做到“能检就检”。对于函数参数,特别是指针,在使用前都必须检查其有效性。
比如:
```cpp
int ObDDLServerClient::abort_redef_table(const obrpc::ObAbortRedefTableArg &arg, sql::ObSQLSessionInfo *session)
{
int ret = OB_SUCCESS;
...
obrpc::ObCommonRpcProxy *common_rpc_proxy = GCTX.rs_rpc_proxy_;
if (OB_UNLIKELY(!arg.is_valid())) { // 这里对传入的参数做有效性检查
ret = OB_INVALID_ARGUMENT;
LOG_WARN("invalid arg", K(ret), K(arg));
} else if (OB_ISNULL(common_rpc_proxy)) { // 在使用指针之前,也要做检查
ret = OB_ERR_UNEXPECTED;
LOG_WARN("common rpc proxy is null", K(ret));
} else {
...
}
return ret;
}
```
# 一些约定函数接口
## init/destroy
OceanBase 要求在构造函数中,仅实现一些非常轻量级的数据初始化工作,比如变量初始化为0,指针初始化为nullptr等。因为构造函数中,不太容易处理一些复杂的异常场景,并且无法给出返回值。OceanBase绝大多数的类都有 init 函数,通常在构造函数之后执行,并且拥有int错误码作为返回值。这里做一些比较复杂的初始化工作。相对应的,通常也会提供destroy函数做资源销毁工作。
## reuse/reset
内存缓存是提高性能非常有效的手段。OceanBase 很多类都会有reuse/reset接口,以方便某个对象后续重用。reuse 通常表示轻量级的清理工作,而reset会做更多的资源清理工作。但是需要看具体实现类,不能一概而论。
## 操作符重载
C++ 提供了非常方便编写程序的运算符重载功能,但是这些重载往往会带来很多负担,使得代码难以阅读、功能误用。比如运算符重载可能会导致程序员不知情的情况下出现类型隐式转换、或者看起来一个简单的操作却有比较高的开销。
另外,要尽量避免使用 `operator=` ,尽量以`deep_copy``shallow_copy`的方式实现对象的复制。
# 常用宏
OB_SUCC
判断某个语句是否返回成功,等价于OB_SUCCESS == (ret = func())
```cpp
ret = OB_SUCCESS;
if (OB_SUCC(func())) {
// do something
}
```
OB_FAIL
类似OB_SUCC,只是判断某个语句是否执行失败:
```cpp
ret = OB_SUCCESS;
if (OB_FAIL(func())) {
// do something
}
```
OB_ISNULL
判断指针是否为空,等价于nullptr == ptr,
```cpp
if (OB_ISNULL(ptr)) {
// do something
}
```
OB_NOT_NULL
判断指针是否非空,等价于nullptr != ptr,
```cpp
if (OB_NOT_NULL(ptr)) {
// do something
}
```
K
通常用在输出日志中,用法 K(obj),其中obj可以是普通类型变量,也可以是类对象(必须实现to_string),会被展开会 `"obj", obj`,最终在日志中会输出 "obj=123"。比如:
```cpp
LOG_WARN("fail to exec func, ", K(ret));
```
DISALLOW_COPY_AND_ASSIGN
用在类的声明中,表示此类禁止复制赋值等操作。

File diff suppressed because it is too large Load Diff

659
docs/docs/zh/container.md Normal file
View File

@ -0,0 +1,659 @@
---
title: 基础数据结构
---
# 介绍
C++ STL提供了很多很方便的容器,比如vector、map、unordered_map等,由于OceanBase编程风格与内存控制等原因,在OceanBase中禁止使用STL的容器。OceanBase 提供了一些容器实现,包括数组、链表、HashMap等,本篇文档会对这些容器做一些介绍。
> 本篇文档假设你对C++ STL 容器已经有了一定的理解。
> pair不属于容器,因此可以使用。
> 由于历史遗留原因,OceanBase中包含了一些不再建议使用但是没有删除的容器代码。
# 字符串
OceanBase 提供的字符串类是 ObString。代码参考 ob_string.h。
在介绍ObString的接口之前,先介绍一下ObSring的内存管理方式,这样会更加容易理解ObString的接口设计。
与STL string最大的区别有两个:
1. ObString 不管理内存,内存由外部传入,内存buffer的生命周期也由外部控制;
2. ObString 并不以 '\0' 结尾。
这也是使用 ObString 时需要重点关注的点。
ObString 的内存由外部传入,内部保存三个成员变量:
```cpp
char *ptr_; /// 内存指针
obstr_size_t buffer_size_; /// 内存buffer长度
obstr_size_t data_length_; /// 有效数据长度
```
> ObString 中都使用 obstr_size_t 表示长度,其类型是 int32_t
参考 ObString 当前内存维护模式与字符串常用的接口,ObString 常用的接口如下:
```cpp
/**
* 构造函数
*
* 构造字符串的buffer数据和有效数据长度
*
* 还有一些衍生的构造函数,比如省略buffer长度(buffer长度与数据长度一致)
*/
ObString(const obstr_size_t size, const obstr_size_t length, char *ptr);
/**
* 是否空字符串
*/
bool empty() const;
/**
* 重新赋值一个新的buffer/字符串
*/
void assign_buffer(char *buffer, const obstr_size_t size);
/**
* 有效数据的长度,或者称为字符串的长度
*/
obstr_size_t length() const;
/**
* 内存buffer的长度
*/
obstr_size_t size() const;
/**
* 获取指针
*/
const char *ptr() const;
/**
* 不区分大小写进行比较
*
* @NOTE: 虽然ObString没有说明是以'\0'结尾,但是这里实现时使用strncasecmp,所以使用此函数时要留意
*/
int case_compare(const ObString &obstr) const;
int case_compare(const char *str) const;
/**
* 区分大小写比较
*
* @NOTE: 与case_compare相比较来说,这里没有使用strncmp,而是使用memcmp来比较的buffer长度
*/
int compare(const ObString &obstr) const;
int32_t compare(const char *str) const;
```
ObString 还有一些其它的接口,需要时浏览下 ob_string.h 代码即可。
# 数组
OceanBase的数组接口设计与STL vector类似,只是更加符合OceanBase的风格。比如接口会有一个int返回值表示执行成功或失败。OceanBase 提供了多个不同实现的数组,不过它们提供的接口是类似的。
常用的数组实现类都继承了同一个接口 `ObIArray`。我们先看一下接口定义,然后再分别介绍不同的数组实现之间的差别。
## ObIArray
数组的接口类中并没有指定内存分配器。
```cpp
/**
* 默认空构造函数
*/
ObIArray();
/**
* 直接接受指定数组
*
* 接口类不会接管data相关的内存,内存处理需要看具体的实现类。
*/
ObIArray(T *data, const int64_t count);
/**
* 类似 vector::push_back,在最后添加一个元素
* @return 成功返回OB_SUCCESS
*/
int push_back(const T &obj);
/**
* 移除最后一个元素
* @note 很可能不会调用析构函数,需要看具体的实现类
*/
void pop_back();
/**
* 移除最后一个元素,并将最后一个元素复制到obj
* @return 成功返回OB_SUCCESS
*/
int pop_back(T &obj);
/**
* 移除指定位置的元素
*/
int remove(int64_t idx);
/**
* 获取指定位置的元素
* @return 成功返回OB_SUCCESS。如果指定位置不存在,会返回失败
*/
int at(int64_t idx, T &obj);
/**
* 重置数组。类似vector::clear
*/
void reset();
/**
* 重用数组。具体看实现
*/
void reuse();
/**
* 销毁此数组,作用与调用析构函数相同
*/
void destroy();
/**
* 预留指定大小的内存空间。不会做对象初始化
*/
int reserve(int64_t capacity);
/**
* 预留指定大小的内存空间,通常实现类会执行对象的构造函数
*/
int prepare_allocate(int64_t capacity);
/**
* 从另一个数组复制并销毁当前数据
*/
int assign(const ObIArray &other);
```
## ObArray
ObArray 自己管理内存,在声明ObArray模板类时,需要指定分配器,或者使用默认分配器 `ModulePageAllocator`。由于OceanBase要求所有的动作都要判断返回值,因此ObArray的 `operator=` 等不带返回值的函数,不建议使用。
ObArray的很多行为表现与STL vector类似,每次内存扩展时表现也类似,会扩展两倍当前数据大小,但最多 `block_size_` 大小。一个 `block_size_` 默认值是 `OB_MALLOC_NORMAL_BLOCK_SIZE` (可以认为是8K)。
代码参考 ob_array.h。
## ObSEArray
与ObArray类似,扩展时也会按照两倍大小,不超过`block_size_`
与ObArray不同的是,ObSEArray多了一个模板参数 `LOCAL_ARRAY_SIZE`,不需要额外的内存分配即可容纳一定量的元素。因此OBSEArray可能可以直接使用栈内存而不是堆内存:
```cpp
char local_data_buf_[LOCAL_ARRAY_SIZE * sizeof(T)];
```
如果后续空间不足,需要扩充,那 `local_data_buf_` 将不再存放有效数据而是另外申请内存。所以要综合考虑,给出一个合理的`LOCAL_ARRAY_SIZE`才能让ObSEArray效率比较高。
参考代码 `ob_se_array.h`
## ObFixedArray
顾名思义,就是一个固定大小的数组。一旦容量大小确定下来就不能再变了。代码参考 `ob_fixed_array.h`
## ObVector
ObVector 不属于 ObIArray的子类,其表现和接口设计与ObIArray很类似,所以使用 ObIArray的子类即可。如果有兴趣,请阅读源码 `ob_vector.h` 和 它的实现文件 `ob_vector.ipp`
# 链表
链表不像数组,没有统一的接口。不过这里的接口设计与STL中也是很相近的。最常用的链表有两个,一个是 ObList,另一个是 ObDList。
## ObList
ObList 是一个普通的循环双链表,代码参考`ob_list.h`。在构造时,需要传入内存分配器。常用的接口如下。
```cpp
/**
* 声明
* @param T 元素类型
* @param Allocator 内存分配器
*/
template <class T, class Allocator = ObMalloc>
class ObList;
/**
* 构造函数。必须传入内存分配器
*/
ObList(Allocator &allocator);
/**
* 在链表结尾插入指定元素
*/
int push_back(const value_type &value);
/**
* 在链表开头插入指定元素
*/
int push_front(const value_type &value);
/**
* 释放最后一个元素
* @note 并没有执行元素的析构函数
*/
int pop_back();
/**
* 两个pop_front函数都是把第一个元素删掉,区别是一个会复制对象一个不会
*/
int pop_front(value_type &value);
int pop_front();
/**
* 在指定位置插入指定元素
*/
int insert(iterator iter, const value_type &value);
/**
* 删除指定位置的元素
* @return 返回删除成功或失败
*/
int erase(iterator iter);
/**
* 删除第一个与value值相同的元素
* @return 没有找到元素也会返回成功
*/
int erase(const value_type &value);
/**
* 获取第一个元素
*/
T &get_first();
const T &get_first() const;
/**
* 获取最后一个元素
*/
T &get_last();
/**
* 与STL类似,ObList支持iterator相关的接口
*/
iterator begin();
const_iterator begin();
iterator end();
const_iterator end() const;
/**
* 删除所有的元素
*/
void clear();
/**
* 判断是否空链表
*/
bool empty() const;
/**
* 元素个数
*/
int64_t size() const;
```
## ObDList
> 代码参考 `ob_dlist.h`
ObDList 也是一个双链表,与ObList不同的是,它的元素内存布局与内存管理方式不一样。ObList 由用户传入对象,ObList 内部申请内存复制对象,构造链表节点的前后指针。而 ObDList 由用户直接传入包含前后节点指针的对象。由于ObDList的这个特性,会导致它与使用STL list的方法不同。
ObDList 不管理内存也完全不需要管理内存,它的模板参数没有内存分配器,只有一个 `DLinkNode``DLinkNode` 需要包含你需要的元素对象、前后节点指针和并实现一些通用的操作(有辅助实现基类),ObDList 的声明和一些接口如下:
```cpp
template <typename DLinkNode>
class ObDList;
/// 把当前链表上的元素都移动到list上去
int move(ObDList &list);
/// 获取头节点(不是第一个元素)
DLinkNode *get_header();
const DLinkNode *get_header() const;
/// 获取最后一个元素
DLinkNode *get_last();
/// 获取第一个元素
const DLinkNode *get_first() const;
const DLinkNode *get_first_const() const;
/// 在尾巴上添加一个节点
bool add_last(DLinkNode *e);
/// 在头上添加一个节点
bool add_first(DLinkNode *e);
/// 在指定位置添加节点
bool add_before(const DLinkNode *pos, DLinkNode *e);
/// 指定节点移动到最前面
bool move_to_first(DLinkNode *e);
/// 指定节点移动到最后
bool move_to_last(DLinkNode *e);
/// 删除最后一个节点
DLinkNode *remove_last();
/// 删除最前面一个节点
DLinkNode *remove_first();
/// 在链表开头插入另一个链表
void push_range(ObDList<DLinkNode> &range);
/// 从开头删除指定个数的元素,删除的元素放到了range中
void pop_range(int32_t num, ObDList<DLinkNode> &range);
/// 是否空链表
bool is_empty() const
/// 元素个数
int32_t get_size() const
/// 删除指定元素
DLinkNode *remove(DLinkNode *e);
/// 清空链表
void clear();
```
OceanBase 提供了辅助 `DLinkNode` 实现 `ObDLinkNode``ObDLinkDerived`,只需要使用任一复制类即可轻松地使用 ObDList。
在介绍这两个辅助类之前,先简单看一下一个基础的辅助接口实现 `ObDLinkBase`,它是上面两个辅助类的基类。它包含了 ObDList 需要的前后节点指针与一些基本的节点操作,两个辅助类都是通过继承基类来实现,而且只是使用方法不同而已。
第一个辅助类 ObDLinkNode,声明如下:
```cpp
template<typename T>
struct ObDLinkNode: public ObDLinkBase<ObDLinkNode<T> >
```
给定自己的真实链表元素类型即可,缺点是获取到链表元素时,需要使用 `ObDLinkNode::get_data` 来获取自己的对象,比如
```cpp
class MyObj;
ObDList<ObDLinkNode<MyObj>> alist;
ObDLinkNode<MyObj> *anode = OB_NEW(ObDLinkNode<MyObj>, ...);
alist.add_last(anode);
ObDLinkNode<MyObj> *nodep = alist.get_first();
MyObj &myobj = nodep->get_data();
// do something with myobj
```
第二个辅助类 ObDLinkDerived,比ObDLinkNode使用更简单一些,它的声明是这样的:
```cpp
template<typename T>
struct ObDLinkDerived: public ObDLinkBase<T>, T
```
注意看,它直接继承了模板类T本身,也就是不需要再像ObDLinkNode一样通过 get_data获取真实对象,直接可以使用T的方法,复制上面的示例:
```cpp
class MyObj;
ObDList<ObDLinkDerived<MyObj>> alist;
ObDLinkDerived<MyObj> *anode = OB_NEW(ObDLinkDerived<MyObj>, ...);
alist.add_last(anode);
ObDLinkDerived<MyObj> *nodep = alist.get_first();
// MyObj &myobj = nodep->get_data(); // no need any more
// MyObj *myobj = nodep; // nodep is a pointer to MyObj too
// do something with myobj or directly with nodep
```
由于 ObDList 不管理节点内存,那么使用时就需要特别小心,注意管理好各个元素的生命周期,在执行清理动作之前,比如`clear``reset`,一定要把内存先释放掉。ObDList的接口声明非常清晰,只是与STL::list命名习惯不同,可以直接参考代码 `ob_dlist.h` 的接口声明使用即可,不再罗列。
# Map
Map 是一个常用的数据结构,它的插入和查询的效率都非常高。通常情况下,Map有两种实现方法,一种是平衡查找树,典型的是红黑树,常见的编译器使用这种方式实现,一种是散列表,STL中是unordered_map。
OceanBase中实现了非常多的Map,其中包括平衡查找树实现 ObRbTree 和适用于不同场景的hash map,比如 ObHashMap、ObLinkHashMap和ObLinearHashMap。
> OceanBase 实现了很多种hash map,但是推荐使用这里介绍的几个,除非你对其它实现理解特别清晰。
## ObHashMap
ObHashMap 的实现在 ob_hashmap.h 中,为了方便理解 ObHashMap 的实现,我会对照STL::unordered_map来介绍。
### ObHashMap 介绍
在STL中,unordered_map的声明如下:
```cpp
template<
class Key, /// 键类型
class T, /// 值类型
class Hash = std::hash<Key>, /// 根据Key计算hash值
class KeyEqual = std::equal_to<Key>, /// 判断Key是否相等
class Allocator = std::allocator<std::pair<const Key, T>> /// 内存分配器
> class unordered_map;
```
模板参数中 Key 是我们的键值,T 就是我们值的类型,Hash 是根据键值计算hash值的类或函数,KeyEqual 是判断两个键值是否相等的方法,Allocator 是一个分配器,分配的对象是键和值组成在一起的pair。
OceanBase 中的声明是类似的:
```cpp
template <class _key_type,
class _value_type,
class _defendmode = LatchReadWriteDefendMode,
class _hashfunc = hash_func<_key_type>,
class _equal = equal_to<_key_type>,
class _allocer = SimpleAllocer<typename HashMapTypes<_key_type, _value_type>::AllocType>,
template <class> class _bucket_array = NormalPointer,
class _bucket_allocer = oceanbase::common::ObMalloc,
int64_t EXTEND_RATIO = 1>
class ObHashMap;
```
其中 `_key_type``_value_type``_hashfunc``_equal`,与STL::unordered_map的声明参数含义是一样的。这里多了一些参数:
- `_defendmode`: OceanBase 提供了有限条件的线程安全hashmap实现,可以使用默认值,当前先忽略,稍后会介绍;
- `_allocer``_bucket_allocer`:STL::unordered_map只需要一个分配器,而这里要求提供两个分配器。hashmap中,通常会有一个数组作为桶(bucket)数组,元素进行hash后,找到对应桶,然后将元素”挂载“在对应的桶上。`_bucket_allocer` 就是桶数组的分配器,而`_allocer` 是元素的分配器,也就是建值对的分配器;
- EXTEND_RATIO:如果EXTEND_RATIO是1,就不会进行扩展。
### ObHashMap 接口介绍
```cpp
/**
* ObHashMap的构造函数并不做什么事情。必须调用 create 才会进行真正的初始化。
* create 函数的参数主要是 桶的个数 (bucket_num)和内存分配器的参数。
* 合理的给出桶的个数,可以让hashmap运行的更加高效又不至于浪费太多内存。
*
* 通过下面几个接口可以看到,可以提供两个内存分配器,一个是bucket数组的分配器,
* 一个是元素节点的分配器
*/
int create(int64_t bucket_num,
const ObMemAttr &bucket_attr,
const ObMemAttr &node_attr);
int create(int64_t bucket_num, const ObMemAttr &bucket_attr);
int create(int64_t bucket_num,
const lib::ObLabel &bucket_label,
const lib::ObLabel &node_label = ObModIds::OB_HASH_NODE,
uint64_t tenant_id = OB_SERVER_TENANT_ID,
uint64_t ctx_id = ObCtxIds::DEFAULT_CTX_ID);
int create(int64_t bucket_num,
_allocer *allocer,
const lib::ObLabel &bucket_label,
const lib::ObLabel &node_label = ObModIds::OB_HASH_NODE);
int create(int64_t bucket_num,
_allocer *allocer,
_bucket_allocer *bucket_allocer);
/// 直接销毁当前对象
int destroy();
/// 这两个函数都会删除所有的元素
int clear();
int reuse();
/**
* 获取指定键值的元素值
* 虽然也提供了get函数,但是建议使用当前函数。
* @param timeout_us:获取元素的超时时间。超时的实现原理后面会介绍
* @return 找到了返回成功
*/
int get_refactored(const _key_type &key, _value_type &value, const int64_t timeout_us = 0) const;
/**
* 设置某个键值的值
* @param key: 建
* @param value: 值
* @param flag:0表示已经存在不会覆盖,否则会覆盖掉原先的值
* @param broadcast:是否唤醒等待获取当前键的线程
* @param overwrite_key:没使用
* @param callback:插入或更新成功后,可以使用callback对值做一些额外操作
*/
template <typename _callback = void>
int set_refactored(const _key_type &key,
const _value_type &value,
int flag = 0,
int broadcast = 0,
int overwrite_key = 0,
_callback *callback = nullptr);
/**
* 遍历所有元素
* @note
* 1. 不能在遍历的过程中做删除元素、插入等动作。
* 因为遍历的过程中会加一些锁,而插入、删除等动作也会加锁,所以可能会产生锁冲突;
* 2. callback 动作尽量小,因为它是在锁范围内工作的
*/
template<class _callback>
int foreach_refactored(_callback &callback) const;
/**
* 删除指定键值。如果value指针不是空,会返回对应元素
* @return 元素不存在会返回OB_HASH_NOT_EXIST
*/
int erase_refactored(const _key_type &key, _value_type *value = NULL);
/**
* 不存在就插入,否则调用callback来更新
*/
template <class _callback>
int set_or_update(const _key_type &key, const _value_type &value,
_callback &callback);
/**
* 删除指定键值并满足特定条件的元素
*/
template<class _pred>
int erase_if(const _key_type &key, _pred &pred, bool &is_erased, _value_type *value = NULL);
/**
* 不需要复制元素,直接以callback的方式访问指定键值的元素
* @note callback 是在写锁保护下执行
*/
template <class _callback>
int atomic_refactored(const _key_type &key, _callback &callback);
/**
* 不需要将元素值复制出来,直接拿到元素通过callback来访问
* @note callback是在读锁保护下执行的
*/
template <class _callback>
int read_atomic(const _key_type &key, _callback &callback);
```
### ObHashMap的实现
熟悉STL unordered_map实现原理的同学肯定能猜到ObHashMap的实现原理。ObHashMap的实现也是一个线性表,作为桶数组,再利用拉链表的方法解决键hash冲突。但是这里还有一些细节,希望能够帮助大家了解它的实现,而更高效地利用ObHashMap。
ObHashMap 底层依赖 ObHashTable,代码参考 `ob_hashtable.h`,ObHashMap 是在ObHashTable上封装了一下 Key Value 的语义而已。
**有条件的线程安全**
如果模板参数`_defendmode` 选择有效的锁模式,而 ObHashTable 的每个桶都有一个读写锁,那么ObHashTable就会提供有条件的线程安全。在访问桶上的元素时,都会加对应的锁,包括带有 `callback` 的接口也是,所以`callback`中的动作应该尽量轻量而且不应该再访问ObHashTable的其它元素防止死锁。
ObHashMap 在扩容时不是线程安全的。如果提供的模板参数 EXTEND_RATIO 不是1,在需要的时候就会扩容,并且这对用户是透明的。
ObHashMap `_defendmode` 的默认值就是一个有效的线程安全保护模式 `LatchReadWriteDefendMode`
**_defendmode**
_defendmode 定义了不同的桶加锁方式,在 `ob_hashutils.h` 中提供了6中模式:
1. LatchReadWriteDefendMode
2. ReadWriteDefendMode
3. SpinReadWriteDefendMode
4. SpinMutexDefendMode
5. MultiWriteDefendMode
6. NoPthreadDefendMode
其中前5种都能提供线程安全保护,只是使用的锁模式不同。在不同的业务场景、不同的线程读写并发,选择合理的模式,可以提高效率和稳定性。而第6种模式 `NoPthreadDefendMode`,则不提供任何保护。
**get超时等待**
如果在获取某个元素时指定的元素不存在,可以设置一个等待时间。ObHashTable将会在对应的桶上插入一个 `fake` 元素,然后等待。在另一个线程插入对应元素时,会唤醒等待线程,不过需要插入元素线程明确指定需要唤醒,即 set_refactor 的 broatcast 值设置为非0。
## ObHashSet
与 ObHashMap类似,ObHashSet是基于ObHashTable封装了一个只有key没有value的实现,请参考代码ob_hashset.h,不再赘述。
## ObLinkHashMap
ObLinkHashMap 是一个读写性能兼顾、线程安全(包括扩容)的无锁hash map,使用拉链式方法解决 hash 冲突。
下面列举一下这个类的特点:
- 读写性能兼顾;
- 基于无锁方案实现线程安全;
- 引入retire station,节点会延迟释放,因此建议 Key 尽量小;
- 存在一定的内存浪费;
- 扩缩容时采用批量搬迁方式完成;
- 有热点key时,get性能由于引用计数问题不佳;
- bucket 过多扩容时,初始化Array较慢。
> 关于 retire station,请参考论文 [Reclaiming Memory for Lock-Free Data Structures:There has to be a Better Way](https://www.cs.utoronto.ca/%7Etabrown/debra/fullpaper.pdf)。
下面列一些常用的接口以及使用时的注意事项。
```cpp
/**
* ObLinkHashMap的声明
* 模板参数:
* @param Key 键值类型
* @param Value 值的类型,需要继承自 LinkHashValue(参考 ob_link_hashmap_deps.h)
* @param AllocHandle 分配释放值和节点的类 (参考 ob_link_hashmap_deps.h)
* @param RefHandle 引用计数的函数。如果你没有深入理解它的原理,不要修改
* @param SHRINK_THRESHOLD 当前节点的个数太多或者太少时就会扩缩容,尽量让当前节点保持在
* 比例[1/SHRINK_THRESHOLD, 1]之间(非精准控制)
*/
template<typename Key,
typename Value,
typename AllocHandle=AllocHandle<Key, Value>,
typename RefHandle=RefHandle,
int64_t SHRINK_THRESHOLD = 8>
class ObLinkHashMap;
/// 当前元素的个数
int64_t size() const;
/**
* 插入一个元素
* @note 如果返回成功,需要执行 hash.revert(value)
*/
int insert_and_get(const Key &key, Value* value);
/// 删除指定元素
int del(const Key &key);
/**
* 获取指定元素
* @note 如果返回成功,需要执行 revert
*/
int get(const Key &key, Value*& value);
/// 释放指定元素的引入计数。可以跨线程释放
void revert(Value* value);
/**
* 判断是否存在指定元素
* @return OB_ENTRY_EXIST 存在
*/
int contains_key(const Key &key);
/**
* 遍历所有元素
* @param fn : bool fn(Key &key, Value *value); 其中bool返回值表示是否还要继续遍历
*/
template <typename Function> int for_each(Function &fn);
/**
* 删除满足条件的元素
* @param fn bool fn(Key &key, Value *value); 其中bool返回值表示是否需要删除
*/
template <typename Function> int remove_if(Function &fn);
```
## ObRbTree
ObRbTree 是一个红黑树实现,支持插入、删除、查找等基本操作,非线程安全。由于ObRbTree 在OceanBase中并没有使用,因此不再介绍,有兴趣的请阅读源码 `ob_rbtree.h`
# 其它
OceanBase 还有很多基础容器的实现,比如一些队列(ObFixedQueue、ObLightyQueue、ObLinkQueue)、bitmap(ObBitmap)、tuple(ObTuple)等。如果常见的容器不能满足你的需求,可以在 `deps/oblib/src/lib` 目录下找到更多。

View File

@ -0,0 +1,99 @@
# OceanBase 贡献指南
OceanBase 社区热情欢迎每一位对数据库技术热爱的开发者,期待携手开启思维碰撞之旅。无论是文档格式调整或文字修正、问题修复还是增加新功能,都是对 OceanBase 社区参与和贡献方式之一,立刻开启您的 First Contribution 吧!
## 如何找到一个合适issue
* 通过[good first issue](https://github.com/oceanbase/oceanbase/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)标签可以找到适合新手入门的issue
* 通过`bug`/`new feature`找到当前版本的bug和建议添加的功能
找到合适的issue之后,可以在issue下回复`/assign` 将issue分配给自己
## 代码贡献流程
以 Centos7 操作系统为例
### 1. Fork 项目仓库
1. 访问项目的 [GitHub 地址](https://github.com/oceanbase/oceanbase)。
2. 点击 Fork 按钮创建远程分支。
### 2. 配置本地环境变量
```bash
working_dir=$HOME/workspace # 定义工作目录
user={GitHub账户名} # 和github上的用户名保持一致
```
### 3. 克隆代码
```bash
mkdir -p $working_dir
cd $working_dir
git clone git@github.com:$user/oceanbase.git
# 也可以使用: git clone https://github.com/$user/oceanbase
# 添加上游分支
cd $working_dir/oceanbase
git remote add upstream git@github.com:oceanbase/oceanbase.git
# 或: git remote add upstream https://github.com/oceanbase/oceanbase
# 为上游分支设置 no_push
git remote set-url --push upstream no_push
# 确认远程分支有效
git remote -v
```
### 4. 创建新分支
```bash
# 更新本地 master 分支。
new_branch_name={issue_xxx} # 设定分支名,建议直接使用issue+id的命名
cd $working_dir/oceanbase
git fetch upstream
git checkout master
git rebase upstream/master
git checkout -b $new_branch_name
```
### 5. 开发
在新建的分支上完成开发
### 6. 提交代码
```
# 检查本地文件状态
git status
# 添加您希望提交的文件
# 如果您希望提交所有更改,直接使用 `git add .`
git add <file> ...
# 为了让 github 自动将 pull request 关联上 github issue,
# 建议 commit message 中带上 "fixed #{issueid}", 其中{issueid} 为issue 的id,
git commit -m "fixed #xxxx: update the xx"
# 在开发分支执行以下操作
git fetch upstream
git rebase upstream/master
git push -u origin $new_branch_name
```
### 7. 创建 PR
1. 访问您 Fork 的仓库。
2. 单击 {new_branch_name} 分支旁的 Compare & pull request 按钮。
### 8. 签署 CLA 协议
签署[Contributor License Agreement (CLA)](https://cla-assistant.io/oceanbase/oceanbase) ;在提交 Pull Request 的过程中需要签署后才能进入下一步流程。如果没有签署,在提交流程会有如下报错:
![image](https://user-images.githubusercontent.com/5435903/204097095-6a19d2d1-ee0c-4fb6-be2d-77f7577d75d2.png)
### 9. 代码审查与合并
有review、合并权限的维护者,会帮助开发者进行代码review;review意见通过后,后续的操作都会由维护者进行,包括运行各项测试(目前包括centos和ubuntu的编译),最终代码会由维护者通过后合入
### 10. 祝贺成为贡献者
当 pull request 合并后, 则所有的 contributing 工作全部完成, 恭喜您, 您成为 OceanBase 贡献者.

423
docs/docs/zh/debug.md Normal file
View File

@ -0,0 +1,423 @@
---
title: 调试
---
# 背景
这里介绍一些调试 OceanBase 的方法。我们有很多种调试方法,比如 gdb,日志等。
建议编译 OceanBase 时使用 debug 模式,这样更容易调试。
# GDB
GDB 是一个强大的调试工具,但是使用 gdb 调试 OceanBase 是比较困难的,而且场景比较有限。
如果要调试单进程并且只有某一个线程,可以使用 gdb,否则建议使用日志。
假设已经部署了源码编译的 oceanbase。
调试 OceanBase 与调试其他 C++ 程序类似,你可以使用 gdb,如下:
1. 找到进程 id
```bash
ps -ef | grep observer
```
或者
```bash
pidof observer
```
2. attach 进程
```bash
gdb observer <pid>
```
接着就可以设置断点,打印变量等。更多信息请参考 [gdb 手册](https://sourceware.org/gdb/current/onlinedocs/gdb.html/)。
## 使用 debug-info 包调试 OceanBase
要调试RPM部署的OceanBase,或者查看 coredump 文件,需要先安装或者加载 debug-info 包。推荐使用加载的模式,因为系统中会有很多 debug-info 包,而且很难清理。
首先,从网上下载 debug-info 包,然后加载到gdb。之后,你就可以很容易地调试 OceanBase 了。
下面是一些提示。
**如何查找 debug-info 包**
使用下面的命令获取包的revision。
```bash
# in the observer runtime path
clusters/local/bin [83] $ ./observer -V
./observer -V
observer (OceanBase_CE 4.1.0.1)
REVISION: 102000042023061314-43bca414d5065272a730c92a645c3e25768c1d05
BUILD_BRANCH: HEAD
BUILD_TIME: Jun 13 2023 14:26:23
BUILD_FLAGS: RelWithDebInfo
BUILD_INFO:
Copyright (c) 2011-2022 OceanBase Inc.
```
如果看到下面的错误信息
```
./observer -V
./observer: error while loading shared libraries: libmariadb.so.3: cannot open shared object file: No such file or directory
```
就换成这个命令来获取revision
```bash
clusters/local/bin [83] $ LD_LIBRARY_PATH=../lib:$LD_LIBRARY_PATH ./observer -V
./observer -V
observer (OceanBase_CE 4.1.0.1)
REVISION: 102000042023061314-43bca414d5065272a730c92a645c3e25768c1d05
BUILD_BRANCH: HEAD
BUILD_TIME: Jun 13 2023 14:26:23
BUILD_FLAGS: RelWithDebInfo
BUILD_INFO:
Copyright (c) 2011-2022 OceanBase Inc.
```
**下载 debug-info 包**
上面输出的版本信息中,我们需要的是 revision 字段的前半部分,即
```
REVISION: 102000042023061314-43bca414d5065272a730c92a645c3e25768c1d05
```
这个是我们需要的:`102000042023061314`
接着在RPM网站上搜索 `102000042023061314`
![download debug info package](images/download-debug-info-package.png)
RPM网站列表:
- [x86_64 for el7](http://mirrors.aliyun.com/oceanbase/community/stable/el/7/x86_64/)
- [x86_64 for el8](https://mirrors.aliyun.com/oceanbase/community/stable/el/8/x86_64/)
- [aarch64(arm) for el7](https://mirrors.aliyun.com/oceanbase/community/stable/el/7/aarch64/)
- [aarch64(arm) for el8](https://mirrors.aliyun.com/oceanbase/community/stable/el/8/aarch64/)
**从RPM中提取 debug-info package**
从RPM中提取 debug-info 包,例如
```bash
rpm2cpio oceanbase-ce-debuginfo-4.1.0.1-102000042023061314.el7.x86_64.rpm | cpio -div
```
解开后是这样的
```bash
~/tmp/debug-info [83] $ tree -a
.
└── usr
└── lib
└── debug
├── .build-id
│ └── ee
│ ├── f87ee72d228069aab083d8e6d2fa2fcb5c03f2 -> ../../../../../home/admin/oceanbase/bin/observer
│ └── f87ee72d228069aab083d8e6d2fa2fcb5c03f2.debug -> ../../home/admin/oceanbase/bin/observer.debug
└── home
└── admin
└── oceanbase
└── bin
└── observer.debug
```
`observer.debug` 是我们要的 debug-info 包,`f87ee72d228069aab083d8e6d2fa2fcb5c03f2.debug` 是一个软链接。
**使用 debug-info 包调试 OceanBase**
使用gdb命令 attch 到一个进程或者打开coredump文件。
```bash
# attach 进程
gdb ./observer `pidof observer`
```
```bash
# 打开coredump文件
gdb ./observer <coredump file name>
```
正常情况下,会看到这个信息
```
Type "apropos word" to search for commands related to "word"...
Reading symbols from clusters/local/bin/observer...
(No debugging symbols found in clusters/local/bin/observer)
Attaching to program: clusters/local/bin/observer, process 57296
```
意思是没有调试符号。
运行一些调试命令,比如 `bt`,会看到这个信息。
```bash
(gdb) bt
#0 0x00007fb6e9c36d62 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 0x00007fb6f9f44862 in ob_pthread_cond_timedwait ()
#2 0x00007fb6eee8d206 in oceanbase::common::ObThreadCond::wait_us(unsigned long) ()
#3 0x00007fb6f34b21c8 in oceanbase::observer::ObUniqTaskQueue<oceanbase::observer::ObServerSchemaTask, oceanbase::observer::ObServerSchemaUpdater>::run1() ()
#4 0x00007fb6f9f44259 in oceanbase::lib::Threads::run(long) ()
#5 0x00007fb6f9f40aca in oceanbase::lib::Thread::__th_start(void*) ()
```
看不到源码文件名和参数信息。
现在加载 debug-info 包。
```bash
(gdb) symbol-file usr/lib/debug/home/admin/oceanbase/bin/observer.debug
Reading symbols from usr/lib/debug/home/admin/oceanbase/bin/observer.debug...
```
> 使用debug-info文件的全路径更好
再次执行调试命令,就可以看到详细信息。
```bash
(gdb) bt
#0 0x00007fb6e9c36d62 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 0x00007fb6f9f44862 in ob_pthread_cond_timedwait (__cond=0x7fb6fb1d5340, __mutex=0x7fb6fb1d5318, __abstime=0x7fb6b3ed41d0)
at deps/oblib/src/lib/thread/ob_tenant_hook.cpp:124
#2 0x00007fb6eee8d206 in oceanbase::common::ObThreadCond::wait_us (this=<optimized out>, time_us=140422679606016)
at deps/oblib/src/lib/lock/ob_thread_cond.cpp:106
#3 0x00007fb6f34b21c8 in oceanbase::common::ObThreadCond::wait (this=0x7fb6fb1d5310, time_ms=200)
at deps/oblib/src/lib/lock/ob_thread_cond.h:69
#4 oceanbase::observer::ObUniqTaskQueue<oceanbase::observer::ObServerSchemaTask, oceanbase::observer::ObServerSchemaUpdater>::run1 (
this=<optimized out>) at src/observer/ob_uniq_task_queue.h:417
```
# 日志
日志是调试 OceanBase 最常用的方法,易于使用,适用于大多数场景。
在常见的场景中,可以在代码中添加日志并打印变量,然后重新编译和部署 oceanbase。
## 如何加日志
可以在源码中找到日志代码,比如
```cpp
LOG_DEBUG("insert sql generated", K(insert_sql));
```
`LOG_DEBUG` 是打印DEBUG级别的日志宏。
这跟其它的程序有点不太一样。第一个参数是一个字符串,其他的参数通常是 `K(variable_name)``K` 是一个宏,用来打印变量名和值。
## 如何搜索日志
日志文件在 OceanBase 运行目录的 `log` 目录下。你可以使用 `grep` 命令搜索日志。
一个日志的例子。
```
[2023-07-05 16:40:42.635136] INFO [SQL.EXE] explicit_start_trans (ob_sql_trans_control.cpp:194) [88022][T1003_ArbSer][T1003][YD9F97F000001-0005FFB71FCF95C7-0-0] [lt=42] start_trans(ret=0, tx_id={txid:2118151}, session={this:0x7ff2663d6188, id:1, tenant:"sys", tenant_id:1, effective_tenant:"sys", effective_tenant_id:1003, database:"oceanbase", user:"root@%", consistency_level:3, session_state:0, autocommit:true, tx:0x7ff26b0e4300}, read_only=false, ctx.get_execution_id()=18446744073709551615)
```
时间戳(`[2023-07-05 16:40:42.635136]`), 日志级别(`INFO`), 模块名称(`[SQL.EXE]`), 函数名称(`explicit_start_trans`), 代码文件(`ob_sql_trans_control.cpp`), 行号(`194`), 线程号(`88022`), 线程名称(`T1003_ArbSer`), trace id(`YD9F97F000001-0005FFB71FCF95C7-0-0`), 等等.
每个SQL请求都有一个唯一的 `trace id`。可以通过 `trace id` 来找到所有与指定SQL请求相关的日志。
## 日志的一些技巧
**Trace ID**
使用下面的SQL命令可以获取上一次执行SQL请求的 trace id。
```sql
select last_trace_id();
```
**日志级别**
使用下面的命令调整日志级别。
```sql
set ob_log_level=debug;
```
**Log 流量控制**
如果找不到想要的日志,可能是被限流了,可以使用下面的命令调整日志流量控制。
```sql
alter system set syslog_io_bandwidth_limit='1G';
alter system set diag_syslog_per_error_limit=1000;
```
**同步打印日志**
用下面的命令可以同步打印日志。
```sql
alter system set enable_async_syslog='False';
```
**打印调用栈**
在日志中可以这样打印调用栈
```cpp
LOG_DEBUG("insert sql generated", K(insert_sql), K(lbt()));
```
假设看到这样的信息:
```txt
lbt()="0x14371609 0xe4ce783 0x54fd9b6 0x54ebb1b 0x905e62e 0x92a4dc8 0x905df11 0x905dc94 0x13d2278e 0x13d22be3 0x6b10b81 0x6b0f0f7 0x62e2491 0x10ff6409 0x1475f87a 0x10ff6428 0x1475f1c2 0x1476ba83 0x14767fb5 0x14767ae8 0x7ff340250e25 0x7ff33fd0ff1d"
```
用这个命令查看调用栈信息:
```bash
addr2line -pCfe ./bin/observer 0x14371609 0xe4ce783 0x54fd9b6 0x54ebb1b 0x905e62e 0x92a4dc8 0x905df11 0x905dc94 0x13d2278e 0x13d22be3 0x6b10b81 0x6b0f0f7 0x62e2491 0x10ff6409 0x1475f87a 0x10ff6428 0x1475f1c2 0x1476ba83 0x14767fb5 0x14767ae8 0x7ff340250e25 0x7ff33fd0ff1d
```
我测试时看到的是这样的
```txt
oceanbase::common::lbt() at /home/distcc/tmp/./deps/oblib/src/lib/utility/ob_backtrace.cpp:130 (discriminator 2)
operator() at /home/distcc/tmp/./src/sql/session/ob_basic_session_info.cpp:599 (discriminator 2)
oceanbase::sql::ObBasicSessionInfo::switch_tenant(unsigned long) at /home/distcc/tmp/./src/sql/session/ob_basic_session_info.cpp:604
oceanbase::observer::ObInnerSQLConnection::switch_tenant(unsigned long) at /home/distcc/tmp/./src/observer/ob_inner_sql_connection.cpp:1813 (discriminator 2)
...
oceanbase::lib::Thread::run() at /home/distcc/tmp/./deps/oblib/src/lib/thread/thread.cpp:162
oceanbase::lib::Thread::__th_start(void*) at /home/distcc/tmp/./deps/oblib/src/lib/thread/thread.cpp:312
?? ??:0
?? ??:0
```
# SQL
一些SQL命令也可以帮助调试,首先运行下面的命令开启trace功能。
```sql
-- on 4.x
set ob_enable_show_trace=1;
```
然后运行SQL命令,比如:
```sql
select * from t, t1 where t.id=t1.id;
```
之后,你可以运行下面的命令获取trace信息。
```sql
show trace;
```
假设看到这样的信息
```txt
obclient> show trace;
+-------------------------------------------+----------------------------+------------+
| Operation | StartTime | ElapseTime |
+-------------------------------------------+----------------------------+------------+
| com_query_process | 2023-07-06 15:30:49.907532 | 9.547 ms |
| └── mpquery_single_stmt | 2023-07-06 15:30:49.907552 | 9.506 ms |
| ├── sql_compile | 2023-07-06 15:30:49.907615 | 6.605 ms |
| │ ├── pc_get_plan | 2023-07-06 15:30:49.907658 | 0.024 ms |
| │ └── hard_parse | 2023-07-06 15:30:49.907763 | 6.421 ms |
| │ ├── parse | 2023-07-06 15:30:49.907773 | 0.119 ms |
| │ ├── resolve | 2023-07-06 15:30:49.907952 | 0.780 ms |
| │ ├── rewrite | 2023-07-06 15:30:49.908857 | 1.320 ms |
| │ ├── optimize | 2023-07-06 15:30:49.910209 | 3.002 ms |
| │ ├── code_generate | 2023-07-06 15:30:49.913243 | 0.459 ms |
| │ └── pc_add_plan | 2023-07-06 15:30:49.914016 | 0.140 ms |
| └── sql_execute | 2023-07-06 15:30:49.914239 | 2.675 ms |
| ├── open | 2023-07-06 15:30:49.914246 | 0.217 ms |
| ├── response_result | 2023-07-06 15:30:49.914496 | 1.956 ms |
| │ └── do_local_das_task | 2023-07-06 15:30:49.914584 | 0.862 ms |
| └── close | 2023-07-06 15:30:49.916474 | 0.415 ms |
| ├── close_das_task | 2023-07-06 15:30:49.916486 | 0.037 ms |
| └── end_transaction | 2023-07-06 15:30:49.916796 | 0.064 ms |
+-------------------------------------------+----------------------------+------------+
18 rows in set (0.01 sec)
```
# Debug Sync
在使用 gdb 调试 OceanBase 的时候,可能会出现问题,因为 gdb 会挂起进程,而 OceanBase 依赖心跳来正常工作。所以我们提供了一个 debug sync 机制来解决这个问题。
在代码中增加一个 debug sync 点,特定的线程会在这个点挂起,然后你可以做一些事情来调试这个进程,比如使用 gdb attach 进程,或者执行一些 SQL 命令来获取一些信息。
> Debug Sync 在 release 模式下也可以用,所以它在生产环境中是开启的。
## 如何使用
**在代码中增加一个debug sync**
打开文件 `ob_debug_sync_point.h`,在宏 `OB_DEBUG_SYNC_POINT_DEF` 中增加 debug sync 定义。比如:
```cpp
#define OB_DEBUG_SYNC_POINT_DEF(ACT) \
ACT(INVALID_DEBUG_SYNC_POINT, = 0) \
ACT(NOW,) \
ACT(MAJOR_FREEZE_BEFORE_SYS_COORDINATE_COMMIT,) \
ACT(BEFORE_REBALANCE_TASK_EXECUTE,) \
ACT(REBALANCE_TASK_MGR_BEFORE_EXECUTE_OVER,) \
ACT(UNIT_BALANCE_BEFORE_PARTITION_BALANCE,)
```
要调试哪个函数,就在对应的函数加上 debug sync。比如:
```cpp
int ObRootService::do_restart()
{
int ret = OB_SUCCESS;
const int64_t tenant_id = OB_SYS_TENANT_ID;
SpinWLockGuard rs_list_guard(broadcast_rs_list_lock_);
...
DEBUG_SYNC(BEFORE_UNIT_MANAGER_LOAD);
...
}
```
可以在任何地方加 debug sync。
**开启 Debug Sync**
默认情况下,debug sync 是关闭的,通过下面的 SQL 命令开启它。
```sql
alter system set debug_sync_timeout='100000s';
```
`debug_sync_timeout` 设置为大于0的数字就可以开启。
> 注意:`debug_sync_timeout` 的单位是微秒。
**开启你自己的 debug sync point**
用这个命令开启自己加上的 debug sync。
```sql
set ob_global_debug_sync = 'BEFORE_UNIT_MANAGER_LOAD wait_for signal_name execute 10000';
```
`execute` 的意思是 debug sync 动作会在执行 10000 次后被禁用。
`signal_name` 是唤醒的名字。
当某个线程执行到debug sync时,就会停下来,这时候就可以执行一些调试操作。
**唤醒 debug sync point**
用下面的命令唤醒 debug sync。
```sql
set ob_global_debug_sync = 'now signal signal_name';
-- or
set ob_global_debug_sync = 'now broadcast signal_name';
```
`signal_name` 是你在开启 debug sync 点时设置的名字。
然后hang的线程会继续执行。
**清理 debug sync point**
调试完成后,需要清理掉 debug sync 点,可以使用下面的命令清理掉。
```sql
set ob_global_debug_sync = 'BEFORE_UNIT_MANAGER_LOAD clear';
```
**关闭 debug sync**
使用下面的命令关闭 debug sync。
```sql
alter system set debug_sync_timeout=0;
```
## debug sync 工作原理
当运行到指定的 debug sync 时,进程会使用 `condition_variable` 来等待信号,然后会挂起在 debug sync 点。当收到信号后,进程会继续执行。
可以查看代码 `ob_debug_sync.cpp/.h` 来了解更多关于 debug sync 机制的信息。

View File

@ -0,0 +1,162 @@
---
title: IDE 配置
---
# 背景
为了更好的阅读OceanBase的代码,我们建议使用一个可以方便的索引OceanBase代码的IDE。在Windows下,我们推荐使用`Source Insight`,在Mac或者Linux下,我们推荐使用`VSCode + ccls`来阅读OceanBase的代码。由于`Source Insight`使用起来非常简单,所以本文档不介绍如何使用`Source Insight`
本篇文档介绍如何设置 `VSCode + ccls`,这个组合非常适合阅读OceanBase的代码。[ccls](https://github.com/MaskRay/ccls) 是基于 [cquery](https://github.com/jacobdufault/cquery) 的,cquery是一个C/C++/Objective-C的 [LSP](https://en.wikipedia.org/wiki/Language_Server_Protocol) 之一(简单来说,LSP用于提供编程语言特定的功能,如代码补全、语法高亮、警告和错误标记,以及重构例程)。
OceanBase 的代码量非常大,而且OceanBase不能在Mac或者Windows下编译,所以我们建议在远程服务器上下载代码,然后在本地使用VSCode访问远程服务器上的代码。
# 在远程服务器上配置 ccls
**注意**
下面的 `/path/to` 只是一个路径示例,请替换成你的实际路径。
## 介绍
在 C/C++ LSP 领域,比较有名的工具有 clangd 和 ccls。这里我们推荐 ccls,因为:
1. ccls 构建索引的速度比 clangd 慢,但是构建完成后,ccls 访问索引的速度比 clangd 快。
2. clangd 不支持 unity 编译,而 OceanBase 是通过 unity 编译的,clangd 无法通过 compile_commands.json 构建索引。
# ccls 安装
## 在 CentOS 上安装 ccls
> 注意:如果没有权限执行 `yum`,请使用 `sudo yum ...`
```bash
yum install epel-release
yum install snapd # On centos8: yum install snapd --nobest
systemctl enable --now snapd.socket
ln -s /var/lib/snapd/snap /snap
snap install ccls --classic
```
然后把下面的命令添加到你的环境变量文件中,例如 '~/.bashrc' 或者 '~/.bash_profile'
```bash
export PATH=/var/lib/snapd/snap/bin:$PATH
```
刷新一下环境变量,例如:
```bash
source ~/.bashrc # or
source ~/.bash_profile
```
## 在 Ubuntu 上安装 ccls
```bash
apt-get -y install ccls
```
> 注意:如果没有权限执行 `apt-get`,请使用 `sudo apt-get ...`
## 检查安装是否成功
运行这个命令判断是否安装成功。
```bash
ccls --version
```
# VSCode 配置
## 远程插件
在远程主机上下载源代码后,可以很容易地在远程主机上设置调试环境。同时,由于远程主机更强大,应用程序可以更快地运行。即使网络出现问题,用户也可以很容易地访问远程主机上的源代码,只需在重新连接远程服务器后等待重新加载即可。
### 安装
在VSCode的扩展商店中下载并安装Remote插件。
![remote plugin](images/ide-settings-remote-plugin.png)
### 使用
**注意**:确保本地机器和远程机器之间的连接正常。
安装插件后,VSCode左下角会出现一个图标。
![remote plugin usage](images/ide-settings-remote-plugin-usage.png)
点击图标,选择 `Connect to Host`,或者按快捷键 `ctrl+shift+p`,选择 `Remote-SSH:Connect to Host`
![connec to remote ](images/ide-settings-connect-to-remote-server.png)
输入远程服务器的`用户名@IP地址`,然后输入密码,VSCode会连接到远程服务器,准备打开远程机器的文件或目录。
![input password](images/ide-settings-input-password.png)
输入密码之后,VSCode会连接到远程服务器,准备打开远程机器的文件或目录。
如果需要指定端口,请选择 `Add New SSH Host`,然后输入ssh命令,然后选择一个配置文件来存储ssh配置。
![ssh port](images/ide-settings-use-different-ssh-port.png)
![ssh config file](images/ide-settings-choose-ssh-config.png)
接着,配置好的机器可以在 `Connect to Host` 中找到。
每次都需要输入密码,如果想跳过这个步骤,可以配置SSH免密登录。
## C/C++ 插件
不推荐使用C/C++插件,因为无法为OceanBase提供良好的索引功能,并且与ccls插件不兼容。
如果有一些简单的场景,可以在VSCode的扩展商店中下载并安装C/C++插件。
![cpp plugins](images/ide-settings-cpp-plugins.png)
C/C++插件可以自动完成代码和语法高亮,但是这个插件无法为OceanBase构建索引,很难跳转到OceanBase的符号。
## ccls 插件
### 安装 ccls 插件
![ccls plugin](images/ide-settings-ccls-plugin.png)
> 要使用 ccls,就建议卸载 C/C++ 插件。
### 配置 ccls 插件
1. 插件设置按钮然后选择 **Extension Settings**
![ccls plugin settings](images/ide-settings-ccls-plugin-settings.png)
2. 设置 `ccls.index.threads`。ccls 默认使用系统80%的CPU核心作为默认的并行度。我们可以在VSCode的配置页面中搜索 `threads` 并设置如下数字。
> 默认情况下,OceanBase使用unity编译,比普通情况下消耗更多的内存。如果并行度太高,例如8核16G的系统,系统可能会挂起。
![ccls threads config](images/ide-settings-ccls-threads-config.png)
## 使用
1. 使用 git 下载源码 [https://github.com/oceanbase/oceanbase](https://github.com/oceanbase/oceanbase)
2. 生成 `compile_commands.json`
```bash
bash build.sh ccls --init
```
可以在 OceanBase 源码下看到 `compile_commands.json` 文件。
执行完上面的步骤后,需要重启 VSCode,然后在 VSCode 底部可以看到构建索引的过程:
![ccls-indexing](images/ide-settings-ccls-indexing.png)
索引构建完成后,可以很容易地找到任何打开文件的函数引用和类成员,如下面的示例:
![ccls index example](images/ide-settings-ccls-index-example.png)
一些快捷键设置
![ccls shortkey](images/ide-settings-ccls-keyboard-settings.png)
![ccls shortkey](images/ide-settings-ccls-keyboard-settings2.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

382
docs/docs/zh/logging.md Normal file
View File

@ -0,0 +1,382 @@
---
title: OceanBase 系统日志介绍
---
# OceanBase 系统日志介绍
## Introduction
本篇文档主要介绍OceanBase的系统日志,包括日志的分类、级别,如何在程序中输出日志,以及一些日志的实现细节。
## System Log Introduction
与常见的应用系统类似,系统日志是OceanBase开发人员排查问题时常用的重要手段之一。
OceanBase 的系统日志存储在observer安装路径下的log目录下面。系统日志主要分为两类:
1. 普通日志:log 后缀,打印某一模块的所有日志(包含警告日志)。
1. Ordinary logs: with log suffix, printed all logs (including warning logs) of a certain module.
2. 警告日志:log.wf 后缀,只打印某一模块的WARN级别及以上的日志。
2. Warning log: with log.wf suffix, only printed the warn level of a module and above.
| 日志文件名称 log file name | 记录的信息 record information |
| ------------------ | ----------------------------- |
| observer.log[.wf] | 一般日志(警告日志、一般查询日志、其他日志) General logs (warning logs, general query logs, other logs) |
| rootservice.log[.wf] | rootservice模块的日志(包含全局ddl日志)rootService module log (including global DDL log) |
| election.log[.wf] | 选举相关日志 Election related logs |
| trace.log | 全链路追踪日志 Full link tracking log |
> 比较特殊的是,trace.log 没有对应的".wf"日志。
> wf 日志除了输出普通的日志,还有一条特殊的INFO日志,就是每次日志文件创建时会记录一些当前系统、进程的信息。
### 日志参数 Log Parameters
目前syslog相关的有7个参数,都是动态生效,即可以在运行时动态调整。
| 配置项 | 数据类型 | 值域 | 默认值 | 描述 |
| --------------------------- | ---- | ----------------------------------------- | ------ | ------------------------------------- |
| enable_syslog_recycle | 布尔 | | False | 是否回收重启之前的旧日志(时间戳最小的日志文件) |
| enable_syslog_wf | 布尔 | | True | 是否打印WARN级别及以上的日志到单独的wf文件中 |
| enable_async_syslog | 布尔 | | True | 是否异步打印日志 |
| max_syslog_file_count | 整型 | \[0, +∞) | 0 | 每种只读日志文件最大保留数量 |
| syslog_io_bandwidth_limit | 字符串 | 0,其他合法大小 | "30MB" | 日志IO带宽限制 |
| syslog_level | 字符串 | DEBUG, TRACE, WDIAG, EDIAG, INFO, WARN, ERROR | WDIAG | 日志打印最低等级,该等级及以上的日志都打印 |
| diag_syslog_per_error_limit | 整型 | \[0, +∞) | 200 | 每个错误码每秒允许的 DIAG 系统日志数,超过该阈值后,日志将不再打印。 |
> 这里所有的参数都是集群级的,并且都是动态生效。
> 参数定义可以参考 ob_parameter_seed.ipp 文件。
## 日志回收
OceanBase的日志可以配置文件个数上限,以防止日志文件占用过大的磁盘空间。
如果 `enable_syslog_recycle = true``max_syslog_file_count > 0` ,每种日志文件的数量不能超过 `max_syslog_file_count`。日志内容刷新到磁盘中时触发旧日志文件回收。
每次执行 `flush_logs_to_file` 函数写日志时,如果某种文件写入了数据,就要检查是否需要将当前日志文件进行归档(有可能达到了大小上限)。
`max_syslog_file_count > 0` 的前提下,就可能会调用 `ObLogger::rotate_log` 函数,如果该种日志文件数量超过上限 `max_syslog_file_count`,就会删掉最旧的一个日志文件。
新日志文件都会在开头打印一个特殊日志,信息包含当前节点的IP和端口、版本号、以及一些系统信息,参考 `ObLogger::log_new_file_info`
```
[2023-12-26 13:15:58.612579] INFO New syslog file info: [address: "127.0.0.1:2882", observer version: OceanBase_CE 4.2.1.1, revision: 101010012023111012-2f6924cd5a576f09d6e7f212fac83f1a15ff531a, sysname: Linux, os release: 3.10.0-327.ali2019.alios7.x86_64, machine: x86_64, tz GMT offset: 08:00]
```
## 日志级别
与常见的系统打日志方法类似,OceanBase 也提供了日志宏来打印不同级别的日志:
| 级别 | 代码宏 | 说明 |
| ----- | --------- | ---- |
| DEBUG | LOG_DEBUG | 开发人员调试日志 |
| TRACE | LOG_TRACE | 事件跟踪日志,通常也是开发人员查看 |
| INFO | LOG_INFO | 系统状态变化日志 |
| WARN | LOG_DBA_WARN | 面向DBA的日志。出现非预期场景,observer能提供服务,但行为可能不符合预期,比如我们的写入限流 |
| ERROR | LOG_DBA_ERROR | 面向DBA的日志。observer不能提供正常服务的异常,如磁盘满监听端口被占用等。也可以是我们产品化后的一些内部检查报错,如我们的4377(dml defensive check error), 4103 (data checksum error)等,需DBA干预恢复 |
| WDIAG | LOG_WARN | Warning Diagnosis, 协助故障排查的诊断信息,预期内的错误,如函数返回失败。级别与WARN相同 |
| EDIAG | LOG_ERROR | Error Diagnosis, 协助故障排查的诊断信息,非预期的逻辑错误,如函数参数不符合预期等,通常为OceanBase程序BUG。级别与ERROR相同 |
> 这里仅介绍了最常用的日志级别,更详细的信息参考 `ob_parameter_seed.ipp` 中关于 `syslog_level` 的配置,以及`ob_log_module.h` 文件中 `LOG_ERROR` 等宏定义。
**如何设置日志级别?**
有三种方式调整日志级别:
- OceanBase 程序在启动时会读取配置文件或通过命令行参数输入系统日志级别配置,配置项名称是`syslog_level`
- 启动后也可以通过MySQL客户端连接,执行SQL命令 `alter system set syslog_level='DEBUG`;
- 通过SQL HINT修改执行请求时的日志级别,比如 `select /*+ log_level("ERROR") */ * from foo;`。这种方式只对当前SQL请求相关的日志有效。
动态修改日志的代码可以参考 `ObReloadConfig::reload_ob_logger_set`
```cpp
if (OB_FAIL(OB_LOGGER.parse_set(conf_->syslog_level,
static_cast<int32_t>(STRLEN(conf_->syslog_level)),
(conf_->syslog_level).version()))) {
OB_LOG(ERROR, "fail to parse_set syslog_level",
K(conf_->syslog_level.str()), K((conf_->syslog_level).version()), K(ret));
```
## 如何打印日志
常见的系统会使用C++ stream方式或C fprintf风格打印日志,但是OceanBase略有不同。接下来从示例入手看如何打印日志。
### 打印日志的例子
与fprintf不一样,OceanBase的系统日志没有formt string,而只有"info"参数,和各个参数信息。比如:
```cpp
LOG_INFO("start stmt", K(ret),
K(auto_commit),
K(session_id),
K(snapshot),
K(savepoint),
KPC(tx_desc),
K(plan_type),
K(stmt_type),
K(has_for_update),
K(query_start_time),
K(use_das),
K(nested_level),
KPC(session),
K(plan),
"consistency_level_in_plan_ctx", plan_ctx->get_consistency_level(),
K(trans_result));
```
其中 `"start stmt"` 就是INFO信息,而后面通常使用 `K` 宏与对象。
### 日志各字段介绍
上面示例代码的一个输出:
```textile
[2023-12-11 18:00:55.711877] INFO [SQL.EXE] start_stmt (ob_sql_trans_control.cpp:619)
[99178][T1004_TeRec][T1003][YD9F97F000001-00060C36119D4757-0-0] [lt=15]
start stmt(ret=0, auto_commit=true, session_id=1,
snapshot={this:0x7f3184fca0e8, valid:true, source:2,
core:{version:{val:1702288855549635029, v:0}, tx_id:{txid:167035},
scn:1702288855704049}, uncertain_bound:0, snapshot_lsid:{id:1},
snapshot_ls_role:0, parts:[{left:{id:1}, right:491146514786417}]},
savepoint=1702288855704049, tx_desc={this:0x7f31df697420,
tx_id:{txid:167035}, state:2, addr:"127.0.0.1:55801", tenant_id:1003,
session_id:1, assoc_session_id:1, xid:NULL, xa_mode:"",
xa_start_addr:"0.0.0.0:0", access_mode:0, tx_consistency_type:0,
isolation:1, snapshot_version:{val:18446744073709551615, v:3},
snapshot_scn:0, active_scn:1702288855704040, op_sn:6, alloc_ts:1702288855706134,
active_ts:1702288855706134, commit_ts:-1, finish_ts:-1, timeout_us:29999942,
lock_timeout_us:-1, expire_ts:1702288885706076, coord_id:{id:-1},
parts:[{id:{id:1}, addr:"127.0.0.1:55801", epoch:491146514786417,
first_scn:1702288855704043, last_scn:1702288855704048, last_touch_ts:1702288855704044}],
exec_info_reap_ts:1702288855704043, commit_version:{val:18446744073709551615, v:3},
commit_times:0, commit_cb:null, cluster_id:1, cluster_version:17180065792,
flags_.SHADOW:false, flags_.INTERRUPTED:false, flags_.BLOCK:false,
flags_.REPLICA:false, can_elr:true, cflict_txs:[], abort_cause:0,
commit_expire_ts:0, commit_task_.is_registered():false, ref:2},
plan_type=1, stmt_type=5, has_for_update=false, query_start_time=1702288855711692,
use_das=false, nested_level=0, session={this:0x7f31de2521a0, id:1,
deser:false, tenant:"sys", tenant_id:1, effective_tenant:"sys",
effective_tenant_id:1003, database:"oceanbase", user:"root@%",
consistency_level:3, session_state:0, autocommit:true, tx:0x7f31df697420},
plan=0x7f31565ba050, consistency_level_in_plan_ctx=3,
trans_result={incomplete:false, parts:[], touched_ls_list:[],
cflict_txs:[]})
```
> NOTE: 实际日志中一行日志并没有换行,这里为了方便查看增加了换行
一条日志中主要包含了以下几个部分:
| 内容 | 示例 | 描述 |
| -------- | ------------------------------------ | ----------------------------------- |
| 时间 | [2023-12-11 18:00:55.711877] | 日志打印时间 |
| 日志级别 | INFO | 日志级别 |
| 模块 | [SQL.EXE] | 日志打印模块 |
| 函数名称 | start_stmt | 打印日志的函数名称 |
| 代码位置信息 | (ob_sql_trans_control.cpp:619) | 打印日志的代码位置。包含文件名和行号 |
| 线程标识 | [99178][T1004_TeRec] | 打印日志的线程号、线程名称 |
| 租户信息 | [T1003] | 租户信息 |
| Trace ID | [YD9F97F000001-00060C36119D4757-0-0] | 某个请求的全局唯一标识。通常可以通过这个标识获取所有当前请求的所有日志 |
| 打印日志时间 | [lt=15] | 当前线程打印上一条日志花费的时间,单位微秒 |
| 日志信息 | start stmt(...) | 日志信息 |
### 日志参数常用宏介绍
对于开发者来说,我们只需要关心如何输出我们的对象信息。通常我们编写`K(obj)`即可将我们想要的信息输出到日志中。下面介绍一些细节。
OceanBase 为了避免format string的一些错误,使用自动识别类型然后序列化来解决这个问题。日志任意参数中会识别为多个Key Value对,其中Key是要打印的字段名称,Value是字段的值。比如上面的示例中的 `"consistency_level_in_plan_ctx", plan_ctx->get_consistency_level()` ,就是打印了一个字段的名称和值,OceanBase 自动识别 Value 的类型并转换为字符串。
因为大部分日志都是打印对象的某个字段,所以OceanBase提供了一些宏来简化打印日志的操作。最常用的就是 `K`,以上面的例子 `K(ret)`,其展开后在代码中是:
```cpp
"ret", ret
```
最终展示在日志中是:
```cpp
ret=-5595
```
OceanBase 还提供了一些其它的宏,在不同的场景下使用不同的宏。
> 日志参数宏定义可以在 `ob_log_module.h` 文件中找到。
| 宏 | 示例 | 说明 |
| --------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| K | K(ret) | 展开后就是 `"ret", ret`。参数可以是一个简单值,也可以是一个普通对象 |
| K_ | K_(consistency_level) | 展开后是 `"consistency_level", consistency_level_`。与 K 不同的是,会在展开的 Value 后自动增加 `_`后缀,用在类成员变量的打印 |
| KR | KR(ret) | 展开后是 `"ret", ret, "ret", common::ob_error_name(ret)`。这个宏是为了方便打印错误码与错误码名称。在 OceanBase 中,通常使用 `ret` 作为函数的返回值,而每个返回值会有一个对应的字符串描述。`ob_error_name` 就可以获得错误码对应的字符串描述。注意,这个宏只能用在非lib代码中 |
| KCSTRING/<br/>KCSTRING_ | KCSTRING(consistency_level_name) | 展开后是 `"consistency_level_name", consistency_level_name`。这个宏是为了打印 C 格式的字符串。由于`const char *` 类型的变量在C++中未必表示一个字符串,比如一个二进制buffer,那么打印这个变量的值时,当做C字符串来打印输出的话,会遇到访问非法内存的错误,所以增加了这个宏,用来明确表示打印C字符串 |
| KP/KP_ | KP(plan) | 展开后是 `"plan", plan`,其中 `plan` 是一个指针。这个宏将会打印出某个指针的十六进制值 |
| KPC/KPC_ | KPC(session) | 输入参数是对象指针。如果是NULL时会输出NULL,否则调用指针的to_string方法输出字符串 |
| KTIME | KTIME(cur_time) | 时间戳转换为字符串。时间戳单位微秒 |
| KTIMERANGE/<br/>KTIMERANGE_ | KTIMERANGE(cur_time, HOUR, SECOND) | 时间戳转换为字符串,仅获取指定范围,比如示例中的获取小时到秒这一段 |
| KPHEX/KPHEX_ | KPHEX(buf, 20) | 十六进制打印buf内容 |
| KERRMSG | KERRMSG | 输出系统错误码信息 |
| KERRNOMSG | KERRNOMSG(2) | 指定错误码输出系统错误信息 |
## 日志中的一些实现细节
### 值如何转换为字符串
OceanBase 会自动识别日志中想要打印的值的类型,然后将其转换为字符串。比如上面的例子中,`ret` 是一个 `int` 类型的变量,而 `plan_ctx->get_consistency_level()` 返回的是一个 `enum` 类型的变量,这两个变量都会被转换为字符串。
不过由于OceanBase不知道如何将一个普通对象转换为字符串,所以需要用户自己实现一个 `TO_STRING_KV` 函数,来将对象转换为字符串。比如上面的例子中,`snapshot` 是一个 `ObTxReadSnapshot` 类型的对象,这个对象实现了 `TO_STRING_KV` 函数,所以可以直接打印。
**普通值转换为字符串**
OceanBase 可以自动识别简单类型的值,比如`int``int64_t``double``bool``const char *` 等,将其转换为字符串。对于枚举类型,会当做数字来处理。对于指针,将会使用十六进制的方式输出指针值。
**类对象转换为字符串**
由于C++没有反射机制,所以无法自动识别一个类对象的成员变量,将其转换为字符串。所以需要用户自己实现一个 `TO_STRING_KV` 函数,来将对象转换为字符串。比如上面的例子中,`snapshot` 是一个 `ObTxReadSnapshot` 类型的对象,这个对象实现了 `TO_STRING_KV` 函数,可以参考实现代码如下:
```cpp
class ObTxReadSnapshot {
...
TO_STRING_KV(KP(this),
K_(valid),
K_(source),
K_(core),
K_(uncertain_bound),
K_(snapshot_lsid),
K_(parts));
};
```
可以看到,在 `TO_STRING_KV` 直接使用与打印日志类似的宏,"列出"想要输出的成员变量名称即可。
> NOTE: TO_STRING_KV 其实是一个宏定义,具体实现可以参考 ob_print_utils.h。 TO_STRING_KV 将输入参数转换为字符串输出到一个buffer中。
### 日志模块
OceanBase 的日志是区分模块的,而且可以支持子模块。比如上面的例子中,`[SQL.EXE]` 就是一个模块,`SQL` 是一个主模块,`EXE` 是一个子模块。日志模块的定义可以参考 `ob_log_module.h` 文件中 `LOG_MOD_BEGIN``DEFINE_LOG_SUB_MOD` 相关的代码。
**日志模块是如何输出到日志中的?**
通常情况下,我们仅仅使用像`LOG_WARN`这样的宏来打印日志,就会输出不同的模块,这也是通过宏定义来实现的。仍然以上面的日志为例,在 `ob_sql_trans_control.cpp` 文件的开始可以看到一个宏定义 `#define USING_LOG_PREFIX SQL_EXE`,这个宏定义了当前文件的日志模块,也就是说,当前文件中的所有日志都会打印 `[SQL.EXE]` 这个模块。
> 这里也有一个缺陷,就是当前实现文件中引入的头文件,默认也会使用这个模块来打印日志。
**如何明确的指定模块名称?**
使用上面的方式确实有些不够灵活,OceanBase 还有另一种方式来指定模块名称,即使用宏 `OB_MOD_LOG``OB_SUB_MOD_LOG`。这两个宏的使用方法与 `LOG_WARN` 类似,只是多了模块参数与日志级别:
```cpp
OB_MOD_LOG(parMod, level, info_string, args...)
OB_SUB_MOD_LOG(parMod, subMod, level, info_string, args...)
```
**设置模块的日志级别**
OceanBase 除了可以设置全局和当前线程的日志级别,还可以调整某个模块的日志级别。当前可以通过 SQL HINT 的方式修改执行请求时的某个模块的日志级别,比如:
```sql
select /*+ log_level("SHARE.SCHEMA:ERROR") */ * from foo;
```
其中 `SHARE` 是主模块,`SCHEMA` 是子模块,`ERROR` 是日志级别。这个 SQL HINT 的作用是将 `SHARE.SCHEMA` 模块的日志级别设置为 `ERROR`,并且只对当前请求有效。
### 日志时间
OceanBase 的日志时间是当前本地时间的微秒数。
由于将时间戳转换为字符串是一个相当耗时的事情,OceanBase 将时间戳转换缓存下来以加速该过程,具体可以参考 `ob_fast_localtime` 函数。
### 线程标识
当前会记录两个线程相关的信息:
- 线程号:系统调用 `__NR_gettid` 返回的信息(系统调用比较低效,这个值会缓存下来);
- 线程名称:线程名称字段中可能会包含租户ID、线程池类型和线程池中的编号。OceanBase的线程号是通过 `set_thread_name` 函数设置的,还会显示在 `top` 命令中。
> NOTE:线程名称是由创建的线程决定的,由于创建线程的租户可能与此线程后续运行的租户不同,所以线程名称中的租户可能不对。
### 日志限速
OceanBase 支持两种日志限速:一个普通系统日志磁盘IO带宽限制,一个是WARN系统日志限制。
**系统日志带宽限速**
OceanBase 会按照磁盘带宽来限制日志输出。日志带宽限速不会针对不同的日志级别限速。如果日志限速,可能会打印限速日志,关键字 `REACH SYSLOG RATE LIMIT `
限速日志示例:
```txt
[2023-12-26 09:46:04.621435] INFO [SHARE.LOCATION] fetch_vtable_location_ (ob_vtable_location_service.cpp:281) [35675][VTblLocAsyncUp0][T0][YB427F000001-00060D52A9614571-0-0] [lt=0] REACH SYSLOG RATE LIMIT [bandwidth]
```
可以通过配置项 `syslog_io_bandwidth_limit` 来调整限速。
限速的代码细节请参考 `check_tl_log_limiter` 函数。
**WDIAG 日志限速**
OceanBase 对WARN级别的日志做了限流,每个错误码每秒钟默认限制输出200条日志。超过限制会输出限流日志,关键字 `Throttled WDIAG logs in last second`。可以通过配置项 `diag_syslog_per_error_limit` 来调整限流阈值。
限流日志示例:
```txt
[2023-12-25 18:01:15.527519] WDIAG [SHARE] refresh (ob_task_define.cpp:402) [35585][LogLimiterRefre][T0][Y0-0000000000000000-0-0] [lt=8][errcode=0] Throttled WDIAG logs in last second(details {error code, dropped logs, earliest tid}=[{errcode:-4006, dropped:31438, tid:35585}])
```
限流代码参考 `ObSyslogPerErrLimiter::do_acquire`
## 一些其它细节
### DBA日志
OceanBase 中还有两类特殊的日志,LOG_DBA_WARN 和 LOG_DBA_ERROR,分别对应了WARN和ERROR日志。由于OceanBase日志量特别大,并且大部分只有研发人员才能理解,为DBA运维排查问题带来了一定的负担,因此增加这两种日志,希望让DBA仅关注少量的这两类日志即可排查系统问题。而使用 LOG_WARN 和 LOG_ERROR 输出的日志被转换成 WDIAG 和 EDIAG 日志,帮助开发人员排查问题。
### 输出提示信息到用户终端
有时候我们希望将错误信息直接输出到用户的终端,可以更方便的了解当前发生了什么错误。这时我们可以使用 `LOG_USER_ERROR``LOG_USER_WARN``LOG_USER_INFO`等宏来打印日志。每个错误码都有一个对应的`USER_ERROR_MSG`,如果这个`USER_ERROR_MSG`需要输入参数,那么我们在打印日志的时候也需要提供对应的参数。比如错误码`OB_NOT_SINGLE_RESOURCE_POOL`,有对应的 `OB_NOT_SINGLE_RESOURCE_POOL__USER_ERROR_MSG`,信息是 "create tenant only support single resource pool now, but pool list is %s",我们就需要提供一个字符串。
LOG_USER_ERROR 宏定义如下:
```cpp
#define LOG_USER_ERROR(errcode, args...)
```
其它宏的使用方法类似。
> 错误码的定义可以在 `src/share/ob_errno.h` 中找到。
由于`LOG_USER_XXX` 提供了固定的错误信息,如果我们想要输出一些自定义的信息,可以使用 `FORWARD_USER_XXX`,比如 `FORWARD_USER_ERROR``FORWARD_USER_WARN`等。以 `FORWARD_USER_ERROR`为例,其定义如下:
```cpp
#define FORWARD_USER_ERROR(errcode, args...)
```
### 健康日志
OceanBase 会周期性的输出一些内部状态信息,比如各模块、租户的内存信息,到日志中,方便查找问题。这种日志通常一条会输出多行数据,比如:
```txt
[2023-12-26 13:15:58.608131] INFO [LIB] print_usage (ob_tenant_ctx_allocator.cpp:176) [35582][MemDumpTimer][T0][Y0-0000000000000000-0-0] [lt=116]
[MEMORY] tenant_id= 500 ctx_id= GLIBC hold= 4,194,304 used= 1,209,328 limit= 9,223,372,036,854,775,807
[MEMORY] idle_size= 0 free_size= 0
[MEMORY] wash_related_chunks= 0 washed_blocks= 0 washed_size= 0
[MEMORY] hold= 858,240 used= 575,033 count= 3,043 avg_used= 188 block_cnt= 93 chunk_cnt= 2 mod=glibc_malloc
[MEMORY] hold= 351,088 used= 104,389 count= 3,290 avg_used= 31 block_cnt= 51 chunk_cnt= 1 mod=Buffer
[MEMORY] hold= 1,209,328 used= 679,422 count= 6,333 avg_used= 107 mod=SUMMARY
```
这种数据会查找历史问题很有帮助。
### ERROR 日志
对系统出现的一般错误,比如处理某个请求时,出现了异常,会以WARN级别输出日志。只有影响到OceanBase进程正常运行,或者认为有严重问题时,会以ERROR级别输出日志。因此如果遇到进程异常退出,或者无法启动时,搜索ERROR日志会更有效地查找问题原因。

134
docs/docs/zh/memory.md Normal file
View File

@ -0,0 +1,134 @@
---
title: 内存管理
---
# 简介
内存管理是所有大型C++工程中最重要的模块之一。由于OceanBase还需要处理多租户内存资源隔离问题,因此OceanBase相较于普通的C++工程,内存管理更加复杂。通常,一个良好的内存管理模块需要考虑以下几个问题:
- 易用。设计的接口比较容器理解和使用,否则代码会很难阅读和维护,也会更容易出现内存错误;
- 高效。高效的内存分配器对性能影响至关重大,尤其是在高并发场景下;
- 诊断。随着代码量的增长,BUG在所难免。常见的内存错误,比如内存泄露、内存越界、野指针等问题让开发和运维都很头疼,如何编写一个能够帮助我们避免或排查这些问题的功能,也是衡量内存管理模块优劣的重要指标。
对于多租户模式,内存管理设计的影响主要有以下几个方面:
- 透明的接口设计。如何让开发人员无感、或极少的需要关心不同租户的内存管理工作;
- 高效准确。内存充足应该必须申请成功,租户内存耗尽应该及时察觉,是多租户内存管理的最基础条件。
本篇文章将会介绍OceanBase 中常用的内存分配接口与内存管理相关的习惯用法,关于内存管理的技术细节,请参考[内存管理](https://open.oceanbase.com/blog/8501613072)(中文版)。
# OceanBase 内存管理常用接口与方式
OceanBase 针对不同场景,提供了不同的内存分配器。另外为了提高程序执行效率,有一些约定的实现,比如reset/reuse等。
## ob_malloc
OceanBase数据库自研了一套libc风格的接口函数ob_malloc/ob_free/ob_realloc,这套接口会根据tenant_id、ctx_id、label等属性动态申请大小为size的内存块,并且为内存块打上标记,确定归属。这不仅方便了多租户的资源管理,而且对诊断内存问题有很大帮助。
ob_malloc会根据tenant_id、ctx_id索引到相应的ObTenantCtxAllocator,ObTenantCtxAllocator会按照当前租户上下文环境分配内存。
ob_free通过偏移运算求出即将释放的内存所对应的对象分配器,再将内存放回内存池。
ob_realloc与libc的realloc不同,它不是在原有地址上扩容,而是先通过ob_malloc+memcpy将数据复制到另一块内存上,再调用ob_free释放原有内存。
```cpp
inline void *ob_malloc(const int64_t nbyte, const ObMemAttr &attr = default_memattr);
inline void ob_free(void *ptr);
inline void *ob_realloc(void *ptr, const int64_t nbyte, const ObMemAttr &attr);
```
## OB_NEWx
与 ob_malloc 类似,OB_NEW提供了一套"C++"的接口,在分配释放内存的同时会调用对象的构造析构函数。
## ObArenaAllocator
设计特点是多次申请一次释放,只有reset或者析构才真正释放内存,在这之前申请的内存即使主动调用free也不会有任何效用。
ObArenaAllocator 适用于很多小内存申请,短时间内存会释放的场景。比如一次SQL请求中,会频繁申请很多小内存,并且这些小内存的生命周期会持续整个请求期间。通常情况下,一次SQL的请求处理时间也非常短。这种内存分配方式对于小内存和避免内存泄露上非常有效。在OceanBase的代码中如果遇到只有申请内存却找不到释放内存的地方,不要惊讶。
> 代码参考 `page_arena.h`
## ObMemAttr 介绍
OceanBase 使用 `ObMemAttr` 来标记一段内存。
```cpp
struct ObMemAttr
{
uint64_t tenant_id_; // 租户
ObLabel label_; // 标签、模块
uint64_t ctx_id_; // 参考 ob_mod_define.h,每个ctx id都会分配一个ObTenantCtxAllocator
uint64_t sub_ctx_id_; // 忽略
ObAllocPrio prio_; // 优先级
};
```
> 参考文件 alloc_struct.h
**tenant_id**
内存分配管理会按照租户维护进行资源统计、限制。
**label**
在最开始,OceanBase 使用预定义的方式为各个模块创建内存标签。但是随着代码量的增长,预定义标签的方式不太适用,当前改用直接使用常量字符串的方式构造ObLabel。在使用ob_malloc时,也可以直接传入常量字符串当做ObLabel参数。
**ctx_id**
ctx id是预定义的,可以参考 `alloc_struct.h`。每个租户的每个ctx_id都会创建一个`ObTenantCtxAllocator` 对象,可以单独统计相关的内存分配使用情况。通常情况下使用`DEFAULT_CTX_ID`作为ctx id。一些特殊的模块,比如希望更方便的观察内存使用情况或者更方便的排查问题,我们为它定义特殊的ctx id,比如libeasy通讯库(LIBEASY)、Plan Cache缓存使用(PLAN_CACHE_CTX_ID)。我们可以在内存中看到周期性的内存统计信息,比如:
```txt
[2024-01-02 20:05:50.375549] INFO [LIB] operator() (ob_malloc_allocator.cpp:537) [47814][MemDumpTimer][T0][Y0-0000000000000000-0-0] [lt=10] [MEMORY] tenant: 500, limit: 9,223,372,036,854,775,807 hold: 800,768,000 rpc_hold: 0 cache_hold: 0 cache_used: 0 cache_item_count: 0
[MEMORY] ctx_id= DEFAULT_CTX_ID hold_bytes= 270,385,152 limit= 2,147,483,648
[MEMORY] ctx_id= GLIBC hold_bytes= 8,388,608 limit= 9,223,372,036,854,775,807
[MEMORY] ctx_id= CO_STACK hold_bytes= 106,954,752 limit= 9,223,372,036,854,775,807
[MEMORY] ctx_id= LIBEASY hold_bytes= 4,194,304 limit= 9,223,372,036,854,775,807
[MEMORY] ctx_id= LOGGER_CTX_ID hold_bytes= 12,582,912 limit= 9,223,372,036,854,775,807
[MEMORY] ctx_id= PKT_NIO hold_bytes= 17,969,152 limit= 9,223,372,036,854,775,807
[MEMORY] ctx_id= SCHEMA_SERVICE hold_bytes= 135,024,640 limit= 9,223,372,036,854,775,807
[MEMORY] ctx_id= UNEXPECTED_IN_500 hold_bytes= 245,268,480 limit= 9,223,372,036,854,775,807
```
**prio**
当前支持两个内存分配优先级,Normal和High,默认为Normal,定义可以参考 `enum ObAllocPrio` 文件`alloc_struct.h`。高优先级内存可以从urgent(memory_reserved)内存中分配内存,否则不可以。参考 `AChunkMgr::update_hold`实现。
> 可以使用配置项 `memory_reserved` 查看预留内存大小。
## init/destroy/reset/reuse
缓存是提升程序性能的重要手段之一,对象重用也是缓存的一种方式,一方面减少内存申请释放的频率,另一方面可以减少一些构造析构的开销。OceanBase 中有大量的对象重用,并且形成了一些约定,比如reset和reuse函数。
**reset**
用于重置对象。把对象的状态恢复成构造函数或者init函数执行后的状态。比如 `ObNewRow::reset`
**reuse**
相较于reset,更加轻量。尽量不去释放一些开销较大的资源,比如 `PageArena::reuse`
OceanBase 中还有两个常见的接口是`init``destroy`。在构造函数中仅做一些非常轻量级的初始化工作,比如指针初始化为`nullptr`
## SMART_VAR/HEAP_VAR
SMART_VAR是定义局部变量的辅助接口,使用该接口的变量总是优先从栈上分配,当栈内存不足时退化为从堆上分配。对于那些不易优化的大型局部变量(>8K),该接口即保证了常规场景的性能,又能将栈容量安全地降下来。接口定义如下:
```cpp
SMART_VAR(Type, Name, Args...) {
// do...
}
```
满足以下条件时从栈上分配,否则从堆上分配
```cpp
sizeof(T) < 8K || (stack_used < 256K && stack_free > sizeof(T) + 64K)
```
> SMART_VAR 的出现是为了解决历史问题。尽量减少大内存对象占用太多的栈内存。
HEAP_VAR 类似于 SMART_VAR,只是它一定会在堆上申请内存。
## SMART_CALL
SMART_CALL用于"准透明化"的解决那些在栈非常小的线程上可能会爆栈的递归函数调用。该接口接受一个函数调用为参数,函数调用前会自动检查当前栈的使用情况,一旦发现栈可用空间不足立即在本线程上新建一个栈执行函数,函数结束后继续回到原始栈。即保证了栈足够时的性能,也可以兜底爆栈场景。
```cpp
SMART_CALL(func(args...))
```
注意:
1. func返回值必须是表征错误码的int类型
2. SMART_CALL会返回错误码,这个可能是内部机制的也可能是func调用的
3. 支持栈级联扩展,每次扩展出一个2M栈(有一个写死的总上限,10M)
SMART_CALL 相对于直接调用多了 `check_stack_overflow` 栈移除检查。

112
docs/docs/zh/mysqltest.md Normal file
View File

@ -0,0 +1,112 @@
# 使用 obd.sh 运行 mysqltest
要使用 obd.sh 运行 mysqltest 测试,需要通过 obd.sh 部署 OceanBase 数据库。本文从编译源代码开始,使用示例介绍如何使用 obd.sh 部署 OceanBase 数据库并运行 mysqltest 测试。
## 背景
为了简化开发者的操作步骤,降低其理解成本,我们将一些 OBD 命令封装到 obd.sh 脚本中,并将脚本存放在 OceanBase 源代码的 oceanbase/tools/deploy 目录下。本文通过在 OBD 中调用 [obd test mysqltest](https://www.oceanbase.com/docs/community-obd-cn-10000000002048173) 命令,运行 mysqltest 测试。
## 相关概念
mysqltest 是OceanBase数据库中的一种准入测试,简单来说,它以编写的 case 文件为输入,将数据库的输出与预期输出进行比较。OceanBase数据库中 mysqltest 测试的 case 都位于 `tools/deploy/mysql_test` 目录下。
`case` 是 mysqltest 的最小执行单元,一个 `case` 至少包含一个 test 文件和一个 result 文件。对 case 进行分类形成 `suite``suite` 是 case 的集合。
mysqltest 有多种运行模式,取决于选择的节点,常见的 mysqltest 模式如下。
* c 模式:连接到 Primary Zone 所在的服务器运行 mysqltest。例如,使用配置文件 distributed.yaml 部署集群,然后连接到 server1 运行测试。
```shell
./obd.sh mysqltest -n <name> --suite acs --test-server=server1
```
* Slave 模式:连接到 Primary Zone 以外的服务器运行 mysqltest。例如,使用配置文件 distributed.yaml 部署集群,然后连接到 server2 运行测试。
```shell
./obd.sh mysqltest -n <name> --suite acs --test-server=server2
```
* Proxy 模式:通过 ODP 连接到集群进行 mysqltest 测试。例如,使用配置文件 distributed-with-proxy.yaml 部署集群,然后运行测试。
```shell
./obd.sh mysqltest -n <name> --all
```
## 执行步骤
### 步骤 1: 编译源代码
查看[编译运行](./build-and-run.md)文档,编译源代码。
### 步骤 2: 运行 mysqltest 测试
可以全部运行测试,也可以指定测试的 `case``suite`。具体执行 obd.sh 脚本时使用的参数含义,请参考[附录](#附录)。
* 全部测试,即运行 `mysql_test/test_suite` 目录下的所有 suite,请参考以下命令。
```shell
[admin@obtest ~]$ cd oceanbase/tools/deploy
[admin@obtest deploy]$ ./obd.sh mysqltest -n test --all
```
* 指定 case 测试,例如指定 `mysql_test/test_suite/alter/t/alter_log_archive_option.test`。请参考以下命令。
```shell
[admin@obtest ~]$ cd oceanbase/tools/deploy
[admin@obtest deploy]$ ./obd.sh mysqltest -n test --test-dir ./mysql_test/test_suite/alter/t --result-dir ./mysql_test/test_suite/alter/r --test-set alter_log_archive_option
```
* 指定一个 suite 测试,例如在 `mysql_test/test_suite` 目录下指定一个 suite 进行测试,请参考以下命令。
```shell
[admin@obtest ~]$ cd oceanbase/tools/deploy
[admin@obtest deploy]$ ./obd.sh mysqltest -n test --suite acs
```
## 附录
在执行 mysqltest 测试时,可以根据实际情况配置一些参数。参数说明如下表:
| 参数名称 | 是否必须 | 类型 | 默认值 | 说明 |
|--------|---------|----------|-------|------|
| -n | Y | 字符串 | null | 集群名称 |
| --component | N | 字符串 | null | T要测试的组件名称。可选值:obproxy、obproxy-ce、oceanbase和oceanbase-ce。如果是空,按照 obproxy、obproxy-ce、oceanbase、oceanbase-ce的顺序检查。如果检测到某个组件存在,就会停止遍历,然后执行相应的测试。 |
| --test-server | N | 字符串 | 默认为指定组件下的服务器中的第一个节点 | 要测试的机器,可以设置为 yaml 文件中 servers 下指定组件下的 name 值对应的服务器。如果 servers 下没有配置 name 值,将使用 ip 值,必须是指定组件下的某个节点的 name 值。 |
| --user | N | 字符串 | admin | 执行测试的用户名,一般不需要修改。 |
| --password | N | 字符串 | admin | 密码 |
| --database | N | 字符串 | test | database |
| --mysqltest-bin | N | 字符串 | /u01/obclient/bin/mysqltest | mysqltest 二进制文件路径 |
| --obclient-bin | N | 字符串 | obclient | obclient 二进制文件路径 |
| --test-dir | N | 字符串 | ./mysql_test/t | test-file 的目录。如果找不到 test-file,将尝试在 OBD 内置中查找。 |
| --test-file-suffix | N | 字符串 | .test | mysqltest 所需的 test-file 的后缀。 |
| --result-dir | N | 字符串 | ./mysql_test/r | result-file 的目录。如果找不到 result-file,将尝试在 OBD 内置中查找。 |
| --result-file-suffix | N | 字符串 | .result | result-file 文件的后缀。 |
| --record | N | bool | false | 仅记录执行结果,不执行测试。 |
| --record-dir | N | 字符串 | ./record | 执行结果记录的目录。 |
| --record-file-suffix | N | 字符串 | .record | 执行结果记录的文件后缀。 |
| --tmp-dir | N | 字符串 | ./tmp | mysqltest 的 tmpdir 选项。 |
| --var-dir | N | 字符串 | ./var | mysqltest 的 logdir 选项。 |
| --test-set | N | 字符串 | no | 测试用例数组。使用逗号(`,`)分隔多个用例。 |
| --exclude | N | 字符串 | no | 需要排除的测试用例数组。使用逗号(`,`)分隔多个用例。 |
| --test-pattern | N | 字符串 | no | 测试文件匹配的正则表达式。所有匹配表达式的用例将覆盖 test-set 选项。 |
| --suite | N | 字符串 | no | suite 数组。一个 suite 包含多个测试,可以用逗号(`,`)分隔。 |
| --suite-dir | N | 字符串 | ./mysql_test/test_suite | suite 目录。如果找不到 suite 目录,将尝试在 OBD 内置中查找。 |
| --all | N | bool | false | 执行所有 --suite-dir 下的 case。suite 目录所在的目录下的所有 case。 |
| --need-init | N | bool | false | 执行初始化SQL文件。新集群可能需要在执行 mysqltest 之前执行一些初始化文件,例如创建用于 case 的账户和租户。 |
| --init-sql-dir | N | 字符串 | ./ | 初始化SQL文件所在的目录。当找不到 sql 文件时,将尝试在 OBD 内置中查找。 |
| --init-sql-files | N | 字符串 | Default is empty | 初始化SQL文件数组,当需要初始化时执行。使用逗号(`,`)分隔。如果未填写,如果需要初始化,OBD 将根据集群配置执行内置初始化。 |
| --auto-retry | N | bool | false | 测试失败时,自动重新部署集群并重试。 |
| --psmall | N | bool | false | 以psmall模式执行case。 |
| --slices | N | int | null | 测试用例拆分为多少组。 |
| --slice-idx | N | int | null | 指定当前组 ID。 |
| --slb-host | N | 字符串 | null | 指定软负载均衡中心。 |
| --exec-id | N | 字符串 | null | 指定执行 ID。 |
| --case-filter | N | 字符串 | ./mysql_test/filter.py | filter.py 文件维护需要过滤的 case。 |
| --reboot-timeout | N | int | 0 | 重启超时时间。 |
| --reboot-retries | N | int | 5 | 失败重启的重试次数。 |
| --collect-all | N | bool | false | 是否收集组件日志。 |
| --log-dir | N | 字符串 | 日志默认在tmp_dir下 | mysqltest 的日志存储路径。 |
| --log-pattern | N | 字符串 | *.log | 收集与正则表达式匹配的日志文件名称,命中的文件将被收集。 |
| --case-timeout | N | int | 3600 | 单个测试用例的超时时间。 |
| --disable-reboot | N | bool | false | 测试执行期间不再重启。 |
| --collect-components | N | 字符串 | null | 收集哪些组件的日志。多个组件用逗号(`,`)分隔。 |
| --init-only | N | bool | false | 仅指定初始化SQL文件。 |

59
docs/docs/zh/toolchain.md Normal file
View File

@ -0,0 +1,59 @@
# 安装工具链
在编译OceanBase源码之前,需要先在开发环境中安装C++工具链。如果你的开发环境中还没有安装C++工具链,可以按照本文档中的指导进行安装。
## 支持的操作系统
OceanBase 并不支持所有的操作系统,特别是 Windows 和 Mac OS X。
这是当前兼容的操作系统列表:
| 操作系统 | 版本 | 架构 | 是否兼容 | 安装包是否可部署 | 编译的二进制文件是否可部署 | 是否测试过 MYSQLTEST |
| ------------------- | --------------------- | ------ | ---------- | ------------------ | -------------------------- | ---------------- |
| Alibaba Cloud Linux | 2.1903 | x86_64 | Yes | Yes | Yes | Yes |
| CentOS | 7.2 / 8.3 | x86_64 | Yes | Yes | Yes | Yes |
| Debian | 9.8 / 10.9 | x86_84 | Yes | Yes | Yes | Yes |
| Fedora | 33 | x86_84 | Yes | Yes | Yes | Yes |
| openSUSE | 15.2 | x86_84 | Yes | Yes | Yes | Yes |
| OpenAnolis | 8.2 | x86_84 | Yes | Yes | Yes | Yes |
| StreamOS | 3.4.8 | x86_84 | Unknown | Yes | Yes | Unknown |
| SUSE | 15.2 | x86_84 | Yes | Yes | Yes | Yes |
| Ubuntu | 16.04 / 18.04 / 20.04 | x86_84 | Yes | Yes | Yes | Yes |
> **注意**:
>
> 其它的 Linux 发行版可能也可以工作。如果你验证了 OceanBase 可以在除了上面列出的发行版之外的发行版上编译和部署,请随时提交一个拉取请求来添加它。
## 支持的 GLIBC
OceanBase 和它的依赖项动态链接到 GNU C Library (GLIBC)。GLIBC 共享库的版本限制为小于或等于 2.34。
请查看[ISSUE-1337](https://github.com/oceanbase/oceanbase/issues/1337)了解详情。
## 安装
这个安装指导因操作系统和包管理器的不同而有所不同。以下是一些流行环境的安装指导:
### Fedora 系统
包括 CentOS, Fedora, OpenAnolis, RedHat, UOS 等等。
```shell
yum install git wget rpm* cpio make glibc-devel glibc-headers binutils m4 libtool libaio
```
### Debian 系统
包括 Debian, Ubuntu 等等。
```shell
apt-get install git wget rpm rpm2cpio cpio make build-essential binutils m4
```
### SUSE 系统
包括 SUSE, openSUSE 等等。
```shell
zypper install git wget rpm cpio make glibc-devel binutils m4
```

99
docs/docs/zh/unittest.md Normal file
View File

@ -0,0 +1,99 @@
# 编写以及运行单测
## 如何编译及运行所有单元测试
[OceanBase](https://github.com/oceanbase/oceanbase) 有两个单元测试目录。
- `unittest` :这是主要的单元测试用例,它测试 `src` 目录中的代码。
- `deps/oblib/unittest`: oblib 的测试用例。
首先,你需要编译 `unittest`。你需要进入构建目录中的 `unittest` 目录并显式编译。当你构建 oceanbase 项目时,默认不会构建 unittest。例如:
```bash
bash build.sh --init --make # init and build a debug mode project
cd build_debug/unittest # 或者 cd build_debug/deps/oblib/unittest
make -j4 # build unittest
```
接着可以运行 `run_tests.sh` 脚本来运行所有测试用例。
## 编译并运行单个测试
可以编译并运行单个测试用例。你可以进入 `build_debug` 目录,执行 `make case-name` 来编译特定的测试用例并运行生成的二进制文件。例如:
```bash
cd build_debug
# **注意**: 不要进入unittest目录
make -j4 test_chunk_row_store
find . -name "test_chunk_row_store"
# 可以看到 ./unittest/sql/engine/basic/test_chunk_row_store
./unittest/sql/engine/basic/test_chunk_row_store
```
## 编写单元测试
作为一个 C++ 项目,[OceanBase](https://github.com/oceanbase/oceanbase)使用[google test](https://github.com/google/googletest)作为单元测试框架。
OceanBase 使用 `test_xxx.cpp` 作为单元测试文件名。你可以创建一个 `test_xxx.cpp` 文件,并将文件名添加到特定的 `CMakeLists.txt` 文件中。
`test_xxx.cpp` 文件中,你需要添加头文件 `#include <gtest/gtest.h>` 和下面的 main 函数。
```cpp
int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
```
接着可以添加一些函数来测试不同的场景。下面是 `test_ra_row_store_projector.cpp` 的一个例子。
```cpp
///
/// TEST 是 google test 的一个宏。
/// 用来添加一个新的测试函数。
///
/// RARowStore 是一个测试套件的名字,alloc_project_fail 是测试的名字。
///
TEST(RARowStore, alloc_project_fail)
{
ObEmptyAlloc alloc;
ObRARowStore rs(&alloc, true);
///
/// ASSERT_XXX 是一些测试宏,可以帮助我们判断结果是否符合预期,如果失败会终止测试。
///
/// 还有一些其它的测试宏,以 `EXPECT_` 开头,如果失败不会终止测试。
///
ASSERT_EQ(OB_SUCCESS, rs.init(100 << 20));
const int64_t OBJ_CNT = 3;
ObObj objs[OBJ_CNT];
ObNewRow r;
r.cells_ = objs;
r.count_ = OBJ_CNT;
int64_t val = 0;
for (int64_t i = 0; i < OBJ_CNT; i++) {
objs[i].set_int(val);
val++;
}
int32_t projector[] = {0, 2};
r.projector_ = projector;
r.projector_size_ = ARRAYSIZEOF(projector);
ASSERT_EQ(OB_ALLOCATE_MEMORY_FAILED, rs.add_row(r));
}
```
可以查看文档[google test document](https://google.github.io/googletest/)获取更多关于 `TEST`, `ASSERT``EXPECT` 的细节。
## GitHub CI的单测
在合并拉取请求之前,CI将测试您的拉取请求。`Farm`将测试`mysql test``unittest`。您可以查看下面的`Details`链接的详细信息。
![github ci](images/unittest-github-ci.png)
![github ci farm 详情](images/unittest-ci-details.png)
![Farm unittest](images/unittest-unittest.png)

57
docs/mkdocs.yml Normal file
View File

@ -0,0 +1,57 @@
site_name: OceanBase Developer Guide
site_url: https://oceanbase.github.io/oceanbase
repo_url: https://github.com/oceanbase/oceanbase
repo_name: oceanbase/oceanbase
plugins:
- search
#- material-relative-language-selector
#- mdpo
- i18n:
docs_structure: folder
fallback_to_default: true
reconfigure_material: true
reconfigure_search: true
languages:
- locale: en
default: true
name: English
build: true
- locale: zh
name: 中文
build: true
link: /zh/
- git-authors
nav:
- README.md
- toolchain.md
- build-and-run.md
- ide-settings.md
- coding-convension.md
- unittest.md
- mysqltest.md
- debug.md
- logging.md
- memory.md
- container.md
- architecture.md
- coding_standard.md
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
theme:
name: material
language: en
features:
- search.highlight
- navigation.sections
- navigation.path
- navigation.top
logo: assets/logo.png
favicon: assets/favicon.ico
icon:
repo: fontawesome/brands/github

3
docs/requirements.txt Normal file
View File

@ -0,0 +1,3 @@
mkdocs-material
mkdocs-static-i18n
mkdocs-git-authors-plugin

View File

@ -35,7 +35,7 @@ class IObLogRpc
public:
virtual ~IObLogRpc() { }
// Reuest start LSN by timestamp
// Request start LSN by timestamp
virtual int req_start_lsn_by_tstamp(const uint64_t tenant_id,
const common::ObAddr &svr,
obrpc::ObCdcReqStartLSNByTsReq &req,

View File

@ -36,7 +36,7 @@ class IObLogRpc
public:
virtual ~IObLogRpc() { }
// Reuest start LSN by timestamp
// Request start LSN by timestamp
virtual int req_start_lsn_by_tstamp(const uint64_t tenant_id,
const common::ObAddr &svr,
obrpc::ObCdcReqStartLSNByTsReq &req,

View File

@ -29,8 +29,15 @@ LogMinerLogger &LogMinerLogger::get_logminer_logger_instance()
static LogMinerLogger logger_instance;
return logger_instance;
}
LogMinerLogger::LogMinerLogger():
verbose_(false) { memset(pb_str_, '>', sizeof(pb_str_)); }
verbose_(false),
begin_ts_(0),
last_ts_(0),
last_record_num_(0)
{
memset(pb_str_, '>', sizeof(pb_str_));
}
void LogMinerLogger::log_stdout(const char *format, ...)
{
@ -79,8 +86,29 @@ int LogMinerLogger::log_progress(int64_t record_num, int64_t current_ts, int64_t
nls_format, 0, time_buf, sizeof(time_buf), pos))) {
LOG_WARN("datetime to string failed", K(current_ts), K(LOGMINER_TZ.get_tz_info()));
} else {
fprintf(stdout, "\r%s %s %5.1lf%%, written records: %-20jd", time_buf, pb_buf,
progress, record_num);
int64_t current_time = ObTimeUtility::current_time();
int64_t average_rps = 0;
int64_t current_rps = 0;
int64_t inc_record_num = 0;
if (begin_ts_ == 0) {
begin_ts_ = current_time;
}
if (last_ts_ == 0) {
last_ts_ = current_time;
}
if (current_time - begin_ts_ > 0) {
// calculated in seconds
average_rps = record_num * 1000 * 1000 / (current_time - begin_ts_);
}
if (current_time - last_ts_ > 0) {
inc_record_num = record_num - last_record_num_;
// calculated in seconds
current_rps = inc_record_num * 1000 * 1000 / (current_time - last_ts_);
last_ts_ = current_time;
last_record_num_ = record_num;
}
fprintf(stdout, "\r%s %s %5.1lf%%, written records: %5.1jd, current rps: %5.1jd, average rps: %5.1jd",
time_buf, pb_buf, progress, record_num, current_rps, average_rps);
fflush(stdout);
}
return ret;

View File

@ -43,6 +43,9 @@ private:
static const int MAX_SCREEN_WIDTH = 4096;
bool verbose_;
char pb_str_[MAX_SCREEN_WIDTH];
int64_t begin_ts_;
int64_t last_ts_;
int64_t last_record_num_;
};
}

View File

@ -326,8 +326,8 @@ private:
class ObDeviceChannel;
/**
* worker to process sync io reuest and get result of async io request from file system
* io channel has two independent threads, one for sync io operation, anather for polling events from file system
* worker to process sync io request and get result of async io request from file system
* io channel has two independent threads, one for sync io operation, another for polling events from file system
*/
class ObIOChannel : public lib::TGRunnable
{

View File

@ -75,7 +75,7 @@ function variables_prepare {
function copy_sh {
if [[ -f copy.sh ]]
then
sh copy.sh $BUILD_PATH
bash copy.sh $BUILD_PATH
else
echo 'can not find copy.sh'
fi