之前的一篇文章《OceanBase 4.0 我回来给你点个赞》里面提到了关于OceanBase驱动/接口和扩展性的一些问题,其实无论是接口、驱动、扩展性,都是以不同的形式为客户提供访问/操作数据库的手段,手段是否丰富也可以侧面反应数据库生态的完备程度。但生态工具其实并不限于上篇文章提到的接口/驱动和插件扩展性,OceanBase其实还为用户提供了很多生态工具,能让用户在其基础上自行封装一些增值服务,比如本文下面要介绍的TableAPI。
早在OceanBase 社区版 3.1.1中开放了一组新的 API 叫做 TableAPI,TableAPI 以 API而非 SQL 的方式提供了一种新的访问 OceanBase 数据的接口。它把 OceanBase 可靠和可扩展的分布式存储层能力直接提供给应用程序,提供了灵活(非关系模型)和轻量的数据访问接口(无连接状态),应用程序可以把 TableAPI 当做 key-value , Table-store , Hbase等多种数据模型的数据库来使用。在简单读写场景下,TableAPI 比 SQL 也有一定的性能优势。
TableAPI 提供了对表模型数据的操作接口。同时,在内部,TableAPI 定义了客户端和数据库服务端之间的一组通用的交互协议。
关于TableAPI的详细介绍可以参考OceanBase官方文档:
总体上看,应用层可通过 TableAPI 或 SQL 两种方式访问到 OBServer,分别对应 NOSQL 模式和 SQL 模式。
NOSQL 模式下,客户端接口 JavaClient 通过 RPC 协议与 OBServer 通信,同时客户端支持丰富多样的操作函数,功能上基本可以满足大部分场景的需求。
相比于 SQL 模式,整体流程上,TableAPI 并没有复杂的 SQL 语法语义等繁琐的解析以及生成执行计划等过程,但却具有与 SQL 模式几乎同等的事务以及存储能力,使用起来相对简单。整体流程图如下:

OceanBase TableAPI 提供的表模型 API 可以和 OceanBase SQL 无缝集成,甚至共同使用。下面分别介绍 TableAPI 和 SQL 模式相比的优缺点。
本次实践使用普通个人电脑(12C8G)、操作系统:windows 11自带WSL (Ubuntu 22.04.1 LTS)、开发环境:IntelliJ IDEA Community Edition。
frank@DESKTOP-6NF3B9K:~$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 12
On-line CPU(s) list: 0-11
Vendor ID: GenuineIntel
Model name: 11th Gen Intel(R) Core(TM) i5-11400F @ 2.60GHz
CPU family: 6
Model: 167
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
Stepping: 1
BogoMIPS: 5183.99
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constan
t_tsc rep_good nopl xtopology tsc_reliable nonstop_tsc cpuid pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_dea
dline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow vnmi
ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid avx512f avx512dq rdseed adx smap avx512ifma clflushopt avx512cd sha_ni
avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves avx512vbmi umip avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcnt
dq rdpid fsrm flush_l1d arch_capabilities
Virtualization features:
Virtualization: VT-x
Hypervisor vendor: Microsoft
Virtualization type: full
Caches (sum of all):
L1d: 288 KiB (6 instances)
L1i: 192 KiB (6 instances)
L2: 3 MiB (6 instances)
L3: 12 MiB (1 instance)
Vulnerabilities:
Itlb multihit: Not affected
L1tf: Not affected
Mds: Not affected
Meltdown: Not affected
Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl and seccomp
Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Spectre v2: Mitigation; Enhanced IBRS, IBPB conditional, RSB filling
Srbds: Not affected
Tsx async abort: Not affected
frank@DESKTOP-6NF3B9K:~$ free -h
total used free shared buff/cache available
Mem: 7.7Gi 98Mi 7.3Gi 0.0Ki 225Mi 7.3Gi
Swap: 2.0Gi 0B 2.0Gi

sudo docker run -p 2881:2881 -p 2882:2882 --name obstandalone -e MINI_MODE=1 -d oceanbase/oceanbase-ce:3.1.4
注意:这里需要注意的是,tag要加上3.1.4,否则默认latest,则是4.0.0版本,目前4.0.0版本TableAPI功能尚未开发。


OB server端需要完成一些配置,如下:
alter system set _ob_enable_prepared_statement= true;
alter system set obconfig_url = 'http://127.0.0.1:8080/services?Action=ObRootServiceInfo&ObCluster=obcluster';
create user frank identified by "frank";
GRANT ALL ON *.* TO frank;
CREATE TABLE IF NOT EXISTS `test_table` (
`c1` bigint NOT NULL,
`c2` int NOT NULL,
`c3` varchar(20) DEFAULT NULL,
PRIMARY KEY (`c1`)
);
TableAPI的client需要获取OceanBase的rootservice list,客户端会先根据 table name、rowkey 算出具体的分区号 partition id,进而根据分区号计算得出执行该指令的 observer 具体位置,然后再把 request 请求发送到对应 observer 节点。另外,客户端除了要保证基本的读写功能外,还承担着路由计算的重大使命。
能够提供rootservice list的有OCP和ob-configserver两个工具,考虑资源问题,这里使用资源占用较小的ob-configserver。
ob-configserver的源码在OceanBase源码目录下:${OB_SRC}/tools/ob-configserver
git clone ``https://github.com/oceanbase/oceanbase.gitsudo apt install golang$ go version
go version go1.18.1 linux/amd64
$ git checkout 3.1
$ make build-release
## log config
log:
# level: debug
level: info
filename: ./log/ob-configserver.log
maxsize: 30
maxage: 7
maxbackups: 10
localtime: true
compress: true
## server config
server:
address: "0.0.0.0:8080"
run_dir: run
## vip config, configserver will generate url with vip address and port and return it to the client
## if you don't hava a vip, use the server address and port is ok, but do not use some random value that can't be connected
vip:
address: "127.0.0.1"
port: 8080
## storage config
storage:
## database type, support sqlite3 or mysql
database_type: mysql
# database_type: sqlite3
## database connection config, should match database_type above
#connection_url: "user:password@tcp(127.0.0.1:3306)/oceanbase?parseTime=true"
connection_url: "frank:frank@tcp(127.0.0.1:2881)/test?parseTime=true"
# connection_url: "/tmp/data.db?cache=shared&_fk=1"
# connection_url: "file:ent?mode=memory&cache=shared&_fk=1"
注意:关键是配置connection_url: “frank:frank@tcp(127.0.0.1:2881)/test?parseTime=true”
bin/ob-configserver -c etc/config.yaml

curl 'http://127.0.0.1:8080/services?Action=ObRootServiceInfo&ObCluster=obcluster&database=test' | jq

这里使用的是OceanBase社区开源项目oceanbase / obkv-table-client-java 中提供的example。

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.oceanbase.examplegroupId>
<artifactId>simple-kv-demoartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>com.oceanbasegroupId>
<artifactId>obkv-table-clientartifactId>
<version>0.1.3version>
dependency>
dependencies>
project>
private String tableName = "test_table";
public boolean initial() {
try {
obTableClient = new ObTableClient();
obTableClient.setFullUserName("frank@sys#obcluster"); // e.g. root@sys#ocp
obTableClient.setParamURL("http://172.19.227.101:8080/services?Action=ObRootServiceInfo&ObCluster=obcluster&database=test"); // e.g. http://ip:port/services?Action=ObRootServiceInfo&ObRegion=ocp&database=test
obTableClient.setPassword("frank");
obTableClient.setSysUserName("root@sys"); // e.g. proxyro@sys
obTableClient.setSysPassword("");
obTableClient.init();
} catch (Exception e) {
注1:setParamURL使用的是ob-configserver的IP:PORT,Action是ObRootServiceInfo。
注2:URL中如果使用127.0.0.1,可能会报错,建议使用真是IP。
注3:建议不要使用sys租户下的用,我这里只是为了测试方便。
initial table client success
Sat Nov 12 13:42:25 GMT+08:00 2022 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
del table data success, row = 1
del table data success, row = 1
del table data success, row = 1
get after delete ...
fail to get table data
key_1 -> null
fail to get table data
key_2 -> null
fail to get table data
key_3 -> null
put table data success
put table data success
put table data success
get after insert ...
key_1 -> c2:11, c3:c3_1
key_2 -> c2:22, c3:c3_2
key_3 -> c2:33, c3:c3_3
update table data success
key_1 -> c2:111, c3:update c3_1
insertOrUpdate table data success, row = 1
insertOrUpdate table data success, row = 1
get after insertOrUpdate ...
key_3 -> c2:333, c3:update old c3_3
key_4 -> c2:44, c3:insert c3_4
replace table data success, row = 0
key_2 -> c2:222, c3:replace c3_2
get after increment ...
key_4 -> c2:45, c3:insert c3_4
get after append ...
key_4 -> c2:45, c3:insert c3_4_append
batch Ops success.
get after batch ops ...
key_5 -> c2:55, c3:batch new c3_5
key_6 -> c2:7, c3:batch new c3_6
query result size: 5
c3,update c3_1
c1,1
c2,111
c3,replace c3_2
c1,2
c2,222
c3,update old c3_3
c1,3
c2,333
c3,insert c3_4_append
c1,4
c2,45
c3,batch new c3_5
c1,5
c2,55
query table success.
Process finished with exit code 0
您可使用该接口插入一行记录,但如果主键冲突(即行已存在),则插入失败。
您可使用该接口取回一行记录,如果行存在,则取回行,否则返回 empty。
您可使用该接口删除一行记录,如果该行不存在,返回 affectrows = 0,否则返回具体删除的行数量(1)。
您可使用该接口更新一行记录,如果目标行不存在,返回 affectrows = 0,否则返回具体更新的行数量(1)。
您可使用该接口替换一行记录,使用该接口会出现以下三种情况:
您可使用该接口插入或修改一行记录,使用该接口会出现以下三种情况:
您可使用该接口把指定列的值原子地增加某个增量值(可以为负数),使用该接口会出现以下三种情况:
您可使用该接口把指定列的值原子地追加某个串,使用该接口会出现以下三种情况:
您可使用该接口进行范围查找,支持根据主键或主键的前缀进行范围扫描,也可以不指定主键,根据索引或者索引前缀范围进行扫描查询,接口支持多 range 扫描和逆序扫描。
您可使用该接口批量执行多种操作组合。

在3.1.4的代码中看到了libobtable的客户端类型注释,但没有找到对应的实现 uint8_t client_type_; // 1: libobtable; 2: java client。
class ObTableLoginRequest final
{
OB_UNIS_VERSION(1);
public:
uint8_t auth_method_; // always 1 for now
uint8_t client_type_; // 1: libobtable; 2: java client
uint8_t client_version_; // always 1 for now
uint8_t reserved1_;
uint32_t client_capabilities_;
uint32_t max_packet_size_; // for stream result
uint32_t reserved2_; // always 0 for now
uint64_t reserved3_; // always 0 for now
ObString tenant_name_;
ObString user_name_;
ObString pass_secret_;
ObString pass_scramble_; // 20 bytes random string
ObString database_name_;
int64_t ttl_us_; // 0 means no TTL
TableAPI给用户提供了更多的选择,对OceanBase周边的应用扩展和基于TableAPI的增值产品开发提供了更多的可能行。比如,蚂蚁集团内部的时序数据库 CeresDB 就是这么做的。也可以利用TableAPI性能的优势实现异构数据库的数据迁移。另外,因为TableAPI是基于RPC接口,因此可以扩展到更多的语言,处理本文的obkv-table-client-java Java语言客户端外,目前社区里面还提供了obkv-table-client-rs 基于rust语言的客户端。
写这篇文章之前检索了一下关于TableAPI的文章,确实比较少。一篇是竹翁(杨志丰)老师的,另外就是官网文档,但都对实践操作描述较少,因此这篇文章主要从实操的角度介绍TableAPI使用,抛砖引玉,希望感兴趣的小伙伴利用TableAPI实现更多的扩展应用。