---
title: "租户间的资源隔离 - OceanBase 数据库 V4.1.0 | OceanBase 文档中心"
description: 租户间的资源隔离 OceanBase 数据库是多租户的数据库系统，为了确保租户间不出现资源争抢保障业务稳定运行， OceanBase 数据库针对租户间的资源进行了隔离。 OceanBase 数据库中把 Unit 当作给租户分配资源的基本单位，一个 Unit 可以类比于一个 Docker 容器。一个节点上可以创建多个 …
---
切换语言

- 中文站 - 简体中文
- International - English
- 日本站 - 日本語

文档反馈![](https://mdn.alipayobjects.com/huamei_22khvb/afts/img/A*P8CuR4UJ_FkAAAAAAAAAAAAADiGDAQ/original) OceanBase 数据库分布式版 - V 4.1.0

# 租户间的资源隔离

更新时间：2025-12-03 18:02:00

[编辑](https://github.com/oceanbase/oceanbase-doc/edit/V4.1.0/zh-CN/700.reference/100.oceanbase-database-concepts/300.multi-tenant-architecture/500.tenants-and-resource-management/200.resource-isolation-between-tenants.md)  

OceanBase 数据库是多租户的数据库系统，为了确保租户间不出现资源争抢保障业务稳定运行， OceanBase 数据库针对租户间的资源进行了隔离。

OceanBase 数据库中把 Unit 当作给租户分配资源的基本单位，一个 Unit 可以类比于一个 Docker 容器。一个节点上可以创建多个 Unit，在节点上每创建一个 Unit 都会占用一部分该节点的 CPU、内存等物理资源，节点的资源分配情况会记录在内部表中以便 DBA 查看。

一个租户可以在多个节点上放置多个 Unit，但一个特定的租户在某个节点上只能有一个 Unit。一个租户的多个 Unit 相互独立，OceanBase 数据库目前没有汇总多个 Unit 的资源占用进行全局的资源控制, 具体来讲，不会因为一个租户在某个节点上的资源没得到满足，就让它在另一个节点上去抢其它租户的资源。

所谓资源隔离，就是节点控制本地多个 Unit 间的资源分配的行为, 它是节点本地的行为。类似的技术是 Docker 和虚拟机，但 OceanBase 数据库并没有依赖 Docker 或虚拟机技术，而是在数据库内部实现资源隔离。

## OceanBase 数据库租户隔离的优势

相比 Docker 和虚拟机，OceanBase 数据库的租户隔离更加轻量，并且便于实现优先级等高级特性。 从 OceanBase 数据库的需求来看，Docker 或虚拟机的主要有以下几个问题：

- Docker 或虚拟机运行环境的开销太重，OceanBase 数据库需要支持轻量级租户。
 - Docker 或虚拟机规格变化以及迁移开销比较大，OceanBase 数据库希望租户的规格变化和迁移尽量快。
 - Docker 或虚拟机不便于租户间的资源共享, 例如，对象池的共享。
 - Docker 或虚拟机的资源隔离很难定制，例如，租户内的优先级支持。

除此之外，Docker 或虚拟机的实现不便于暴露统一视图。

## 普通用户的视角看隔离效果

从普通用户的角度，我们可以看到的隔离效果如下：

- 内存完全隔离。

  具体包括：

     - SQL 执行过程各种算子使用的内存是分离的，一个租户的内存耗尽不会影响到另一个租户。

      SQL 执行过程的各种算子的详细信息请参见 [用户接口和查询语言](https://www.oceanbase.com/docs/common-oceanbase-database-cn-10000000001699600) 章节。
     - Block Cache 和 MemTable 是分离的，一个租户的内存耗尽不会影响到另一个租户的写入和读取。

      Block Cache 的详细介绍信息请参见 [多级缓存](https://www.oceanbase.com/docs/common-oceanbase-database-cn-10000000001698941)。

      MemTable 的详细介绍信息请参见 [MemTable](https://www.oceanbase.com/docs/common-oceanbase-database-cn-10000000001702150)。
 - CPU 通过用户态调度实现隔离。

  一个租户能使用的 CPU 资源是由 Unit 规格决定的。

  为了达到更好的隔离效果，从 V4.0.0 版本开始，支持配置 cgroup 来进行 CPU 隔离。
 - 不同租户的 SQL 模块是不会互相影响的。

  具体包括：

     - SQL 的 Plan Cache 是租户分离的，一个租户的 Plan Cache 淘汰不会影响另一个租户。
     - SQL 的 Audit 表是分离的，一个租户的 QPS 太高，不会冲洗掉另一个租户的 Audit 信息。
 - 不同租户的事务模块是不会互相影响的。

  具体包括：

     - 一个租户的行锁挂起，不会影响到其他租户。
     - 一个租户的事务挂起，不会影响到其他租户。
     - 一个租户的回放出问题，不会影响到其它租户。
 - 不同租户的 Clog 模块是不会互相影响的。

  日志流的目录以租户为单位，最终在磁盘上的实现表现为如下：

  ```javascript
    tenant_id
  ├── unit_id_0
  │   ├── log
  │   └── meta
  ├── unit_id_1
  │   ├── log
  │   └── meta
  └── unit_id_2
      ├── log
      └── meta

  ```

## 资源分类

从设计上，OceanBase 数据库从 V4.0.0 版本支持租户间 CPU、Memory、IOPS 隔离，一个 Unit 可以指定 CPU、Memory、IOPS、log_disk_size 这 4 种资源。

```sql
obclient> CREATE RESOURCE UNIT name
    MAX_CPU = 1, [MIN_CPU = 1,]
    MEMORY_SIZE = '5G',
    [MAX_IOPS = 1024, MIN_IOPS = 1024, IOPS_WEIGHT=0,]
    [LOG_DISK_SIZE = '2G'];

```

### 节点的可用 CPU

节点在启动时会探测物理机或容器的在线 CPU 个数，如果节点探测得不准确（例如，在容器化环境里)，您也可以通过 `cpu_count` 配置项来指定。

由于节点会为后台线程预留两个 CPU，故实际可以分给租户的 CPU 总数会少两个。

### 节点的可用 Memory

节点在启动时会探测物理机或容器的内存，由于节点需要为其它进程预留一部分内存，故 observer 进程的可用内存等于 `物理内存 * memory_limit_percentage`。您也可以通过配置项 `memory_limit` 直接配置 observer 进程可用的总内存大小。

observer 进程可用的内存需要进一步扣除掉内部共用模块的那一部分，这部分内存大小由配置项 `system_memory` 指定，剩下的内存才是租户可用的总内存。

关于内存配置的更多信息，请参见 [内存管理](https://www.oceanbase.com/docs/common-oceanbase-database-cn-10000000001702183) 章节。

### 查看每个节点的可用资源

您可以通过 `oceanbase.GV$OB_SERVERS` 视图查看每个节点的可用资源，示例如下：

```sql
obclient> SELECT * FROM oceanbase.GV$OB_SERVERS\G
*************************** 1. row ***************************
                 SVR_IP: xx.xx.xx.xx
               SVR_PORT: 57234
                   ZONE: zone1
               SQL_PORT: 57235
           CPU_CAPACITY: 14
       CPU_CAPACITY_MAX: 14
           CPU_ASSIGNED: 6.5
       CPU_ASSIGNED_MAX: 6.5
           MEM_CAPACITY: 10737418240
           MEM_ASSIGNED: 6442450944
      LOG_DISK_CAPACITY: 316955164672
      LOG_DISK_ASSIGNED: 15569256438
        LOG_DISK_IN_USE: 939524096
     DATA_DISK_CAPACITY: 10737418240
       DATA_DISK_IN_USE: 624951296
DATA_DISK_HEALTH_STATUS: NORMAL
DATA_DISK_ABNORMAL_TIME: NULL
1 row in set

```

## 资源隔离

### 内存隔离

节点的内存资源实际上不支持超卖，引入内存超卖反而会导致租户工作不稳定，从 V4.0.0 版本开始，OceanBase 数据库不再支持内存超卖。废弃 `MIN_MEMORY` 和 `MAX_MEMORY` 配置，采用 `MEMORY_SIZE` 参数代替。

### CPU 隔离

在 OceanBase 数据库 V3.1.x 版本之前，主要通过控制线程数来控制 CPU 的占用；在 OceanBase 数据库 V3.1.x 及之后的版本，允许配置 cgroup 来控制 CPU 的占用。

**线程分类**

节点会启动很多不同功能的线程，详细的分类请参见 [observer 线程](https://www.oceanbase.com/docs/common-oceanbase-database-cn-10000000001700997)，本节按照最粗略的标准可以分为以下两类：

- 一类是处理 SQL 和事务提交的线程，统称为租户工作线程。
 - 其余的是处理网络 IO、磁盘 IO、Compaction 以及定时任务的线程，统称为后台线程。

在当前版本，租户工作线程和大部分后台线程是分租户的，网络线程是共享线程的。

**基于线程数的租户工作线程的 CPU 隔离**

Unit 的 CPU 隔离是通过一个 Unit 的活跃租户工作线程数实现的。

由于 SQL 执行过程中可能会有 IO 等待、锁等待等，所以一个线程无法用满一个物理 CPU，故在缺省配置下，OBServer 节点会给每个 CPU 启动 4 个线程，4 这个倍数可以通过配置 `cpu_quota_concurrency` 来控制。这就意味着如果一个 Unit 的 `MAX_CPU` 是 10，那么它能同时运行的活跃线程是 40，最大物理 CPU 的占用是 400%。

**基于 cgroup 的租户工作线程的 CPU 隔离**

开启 cgroup 后最大的变化是不同租户的工作线程放到不同的 cgroup 目录内，租户间的 CPU 隔离效果会更好。最后的隔离效果如下：

- 如果一个 OBServer 节点上只有一个租户负载很高，其余租户比较空闲，那么这个负载高的租户的工作线程可以用满与线程数相同数目的物理 CPU。
 - 延续上面的场景，如果有多个空闲的租户的负载上升了，导致物理 CPU 不够了，cgroup 会按照权重分配时间片。

**后台线程的 CPU 隔离**

- V4.0.0 版本的后台线程拆分了租户，每个租户下由对应的线程控制。
 - 为了更好的隔离效果，后台线程也会放到不同的 cgroup 目录内，实现租户间隔离，租户内和工作线程隔离。

**大查询处理**

我们认为相比于大查询，让短查询尽快返回对用户更有意义，即大查询的查询优先级更低，当大查询和短查询同时争抢 CPU 时，系统会限制大查询的 CPU 使用。

当一个线程执行的 SQL 查询耗时太长，这条查询就会被判定为大查询, 一旦判定为大查询，执行大查询的线程会等在一个 Pthread Condition 上，这样就为其它的租户工作线程让出了 CPU。

具体实现上，OBServer 节点在代码中插入了很多检查点，租户工作线程在运行过程中会通过检查点定期检查自己的状态，如果判断应该挂起，那么线程就会等待在一个 Pthread Condition 上，等到合适的时机再被唤醒。

如果同时有大查询和小查询，大查询最多占用 30% 的租户工作线程，30% 这个百分比值可以通过配置项 `large_query_worker_percentage` 来设置。

有两点需要说明：

- 当没有小查询的时候，大查询可以用到 100% 的租户工作线程。只有当同时有大查询和小查询时，30% 的比例才生效。
 - 一个租户工作线程因为执行大查询被挂起时，作为补偿，系统可能会新创建一个租户工作线程，但是总的租户工作线程数不能超过 `MAX_CPU` 的 10 倍，10 这个倍数可以通过配置项 `workers_per_cpu_quota` 来设置。

**提前识别大查询**

由于节点挂起一个大查询线程，就会启动一个新的租户工作线程, 但是如果有大量大查询涌入，OBServer 节点新创建的线程还是被用来处理大查询，很快达到租户工作线程数上限，在这批大查询消耗完之前就没有机会再处理短查询了。

为了优化这个场景，OBServer 节点会在 SQL 开始执行之前预判它是不是大查询，预判的本质就是估计 SQL 的执行时间。预判主要依据以下假设场景：如果两条 SQL 的执行 Plan 是一样的，可以猜测它们的执行时间也是相似的，这样就可以用 Plan 最近的执行时间来判断 SQL 会不会是大查询。

如果某条 SQL 被预判为大查询，那么该查询就会被放入一个特殊的大查询队列，其线程会被释放，系统就会接着执行后面的请求了。

### 监控项和日志

在日志中搜索 `dump tenant info` 关键字，可看到租户的规格、线程、队列及请求统计等信息，且这条日志每个租户每 30s 打印一次。

日志示例：

```javascript
grep 'dump tenant info.tenant={id:1002' log/observer.log | sed 's/,/,\n/g'

[2022-07-20 14:55:40.774143] INFO  [SERVER.OMT] run1 (ob_multi_tenant.cpp:1993) [80700][MultiTenant][T0][Y0-0000000000000000-0-0] [lt=621]
dump tenant info(tenant={id:1002, //租户 ID
tenant_meta:{unit:{tenant_id:1002, //tenant_meta 是租户元数据包括 Unit 配置信息和SuperBlcok 以及 create_status
unit_id:1001, //Unit ID
has_memstore:true, //是否有 MemTable
unit_status:"NORMAL", //Unit 的状态: UNIT_NORMAL/UNIT_MIGRATE_IN/UNIT_MIGRATE_OUT/UNIT_MARK_DELETING/UNIT_WAIT_GC_IN_OBSERVER/UNIT_DELETING_IN_OBSERVER/UNIT_ERROR_STAT
config:{unit_config_id:1003, //Unit 的配置 ID
name:"2c2g", //Unit 配置的名字
resource:{min_cpu:2, //Unit 配置的最小 CPU 数
max_cpu:2, //Unit 配置的最大 CPU 数
memory_size:"1.5GB", //Unit 配置的内存大小
log_disk_size:"5.4GB", //Unit 配置的日志盘大小
min_iops:20000, //Unit 配置的最小磁盘 IOPS
max_iops:20000, //Unit 配置的最大磁盘 IOPS
iops_weight:2}}, //Unit 配置的 IOPS 权重
mode:1, //CompatMode 0:MYSQL 1:ORACLE
create_timestamp:1658298418435426, //Unit 的创建时间
is_removed:false}, //该 Unit 是否被标记删除
super_block:{tenant_id:1002, //ObTenantSuperBlock 信息
replay_start_point:ObLogCursor{file_id=1, //租户 slog 的日志回放点
log_id=440,
offset=343322},
ls_meta_entry:[140](ver=0, //租户 ls meta 的 slog checkpoint 入口点
mode=0,
seq=139),
tablet_meta_entry:[141](ver=0, // 租户tablet meta的slog checkpoint的入口点
mode=0,
seq=140)},
create_status:1}, // 租户的创建状态: CREATING = 0,CREATE_COMMIT=1,CREATE_ABORT=2,DELETING=3,DELETE_COMMIT=4
unit_min_cpu:"2.000000000000000000e+00", //最小cpu核数，保证提供
unit_max_cpu:"2.000000000000000000e+00", //最大cpu核数，限制上限
slice:"0.000000000000000000e+00",
slice_remain:"0.000000000000000000e+00",
token_cnt:8, //调度器分配的token数，一个token会转换为一个工作线程
sug_token_cnt:8,
ass_token_cnt:8, //租户当前确认的token数（根据token_cnt确认，一般两者相等）
lq_tokens:2, //Large Query token个数，根据token_cnt乘以大请求比例设置
used_lq_tokens:0, //当前持有LQ Token的Worker数
stopped:false, //租户unit是否正在删除
idle_us:6076629, //一轮（10秒）中工作线程空闲的总时间和, 所谓空闲实际只统计了等待队列的时间
recv_hp_rpc_cnt:1, //租户累计收到不同级别的rpc请求数，hp(High), np(Normal), lp(Low)
recv_np_rpc_cnt:5,
recv_lp_rpc_cnt:0,
recv_mysql_cnt:24, //租户累计收到的mysql请求数
recv_task_cnt:1, //租户累计收到的内部任务数
recv_large_req_cnt:0, //租户累计预判的大请求数，只会递增，不会清零。实际是重试的时候递增的。
tt_large_quries:0, //租户累计处理的大请求数, 只会递增，不会清零。实际是打点check的时候递增的。
pop_normal_cnt:1024555,
actives:8, //活跃工作线程数，一般和workers相等，它们的差包含：租户工作线程缓存+带工作线程的大请求缓存
workers:8, //租户持有的工作线程数, 实际就是workers_这个list的size。
nesting workers:7, //租户持有的嵌套请求专用线程数，共7个线程对应7个嵌套层级。
lq waiting workers:0, //处于被判定为处理大查询，并被挂起等待调度的工作线程
req_queue:total_size=0 queue[0]=0 queue[1]=0 queue[2]=0 queue[3]=0 queue[4]=0 queue[5]=0 , //不同优先级的工作队列，数字越小优先级越高
large queued:0, //当前预判出的大请求个数
multi_level_queue:total_size=0 queue[0]=0 queue[1]=0 queue[2]=0 queue[3]=0 queue[4]=0 queue[5]=0 queue[6]=0 queue[7]=0 , //存放嵌套请求的工作队列，1～7对应7个嵌套层级（queue[0]暂时不用，queue[5]也存放innersql请求）。
recv_level_rpc_cnt:cnt[0]=0 cnt[1]=0 cnt[2]=0 cnt[3]=0 cnt[4]=0 cnt[5]=0 cnt[6]=0 cnt[7]=0 ,
group_map:group_id = 1, //group_map后面跟着每个group_id对应group的线程和排队情况
queue_size = 0, //group队列排队情况
recv_req_cnt = 13526, //group累计push到队列请求数
pop_req_cnt = 13526, //group线程累计pop出的请求数
token_cnt = 2, //group分配的token数，一个token会转换为一个工作线程
min_token_cnt = 2,
max_token_cnt = 2,
ass_token_cnt = 2 , //group当前确认的token数（根据token_cnt确认，一般两者相等）
rpc_stat_info: pcode=0x150a:cnt=1489 pcode=0x150b:cnt=1091}) //租户一段时间内收到的最多的rpc pcode，统计周期10秒，最多打印前五

```

 上一篇 下一篇 ![有帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*y6ocSqN8cqsAAAAAAAAAAAAAARQnAQ)![无帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*BG9IQJyLHF8AAAAAAAAAAAAAARQnAQ)![反馈](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*eTWdQKCRKHwAAAAAAAAAAAAAARQnAQ)[AI](https://www.oceanbase.com/obi) 咨询热线
