首批通过分布式安全可靠测评,为关键业务系统打造
obcdc 开发说明
更新时间:2026-06-19 10:13:53
本文介绍如何将社区版 obcdc 接入自己的数据消费链路。
功能适用性
该内容仅适用于社区版 obcdc。
使用 obcdc 动态库开发自己的 OceanBase 数据消费工具
obcdc 是 C++ 编写,编译产出物为动态库,下游消费程序开发时需要依赖动态库和头文件(libobcdc.h、ob_errno.h)。
引入
社区版 obcdc 依赖 oceanbase-ce-libs 包,可以通过 软件中心 进行下载安装。
rpm -ivh oceanbase-ce-libs-****.rpm
安装完成后执行 ldd ./libobcdc.so 命令,检查本地是否有缺少的动态库,如果有缺少,请保证所有依赖库都在本地,并为使用 libobcdc 的程序配置 LD_LIBRARY_PATH 保证 obcdc 可以正常连接,例如 oceanbase-ce-libs 中的 libmariadb.so.3。
头文件
libobcdc.h 中有详细的接口说明,您可以参考代码中的接口说明,本文 附录 中简单列出常用接口。
构建 obcdc 实例
您可通过 ObCDCFactory::construct_obcdc() 方法构造 obcdc。
使用 obcdc
初始化接口:需要通过
init/init_with_start_tstamp_usec接口通知 obcdc 配置信息和启动时间戳信息。- 配置信息:支持传入配置文件路径、配置文件的 map。
- 指定启动时间(拉日志的起始时间):时间单位可以是秒,也可以是毫秒。
启动接口:通过 launch 接口通知 obcdc 开始工作。
获取 LogRecord 的接口:通过
next_record接口不断的从 obcdc 中拿到 OceanBase 的增量数据,这个接口可以指定获取超时时间,可以指定租户的数据。数据被封装为 LogRecord 格式,LogRecord 内存由 obcdc 分配。归还 LogRecord 的接口:通过
release_record接口将完成消费的 LogRecord 归还给 obcdc,obcdc 有后台的 GC 线程回收内存(异步回收)。获取当前 obcdc 服务的所有租户 ID:通过
get_tenant_ids获取当前 obcdc 服务中的所有租户列表。
销毁 obcdc 实例
销毁 obcdc 要先停止 obcdc 实例,然后销毁 obcdc。
停止 obcdc:
- 通过
stop接口通知 obcdc 停止各模块的运行。 - 通过
destroy接口析构 obcdc 的各模块,释放相关资源。
- 通过
销毁 obcdc 实例:通过
ObCDCFactory::deconstruct(IObCDCInstance *instance)销毁 obcdc 实例,此后,不能再访问第一步拿到的 obcdc 实例的指针。
注意事项
从 obcdc 中取到的数据都是在 obcdc 进程内分配的内存,所以请务必保证
next_record和release_record接口成对调用,避免内存无法释放。允许多次next_record后执行一起为对应的 LogRecord 调用release_record。调用 stop 和 launch 后不能再调用 launch/init 接口。
您需要对 obcdc 接口返回的所有错误码按需适当处理:
- 返回错误码
OB_SUCCESS表示取数据成功,返回的数据指针一定不为 NULL。 - 返回错误码
OB_TIMEOUT表示目前在 obcdc 中拉不到数据,收到该错误码后可以重试获取数据。如果数据位点实时表示正常,如果数据位点不实时则可能 obcdc 内部存在问题,需要进一步排查。 - 返回
OB_IN_STOP_STATE,说明 obcdc 停止了,可能是调用方主动触发了 obcdc 的 stop/destroy 接口,也可能是 obcdc 内部无法处理的异常,各模块标记停止工作。收到这个错误码可以记录安全位点等必要信息后退出进程。 - 收到其它类型错误码都是非预期的,可以记录安全位点等必要信息后退出进程。
- 返回错误码
示例
这里提供一个简单的 demo 展示如何使用 obcdc,详细信息,请参考 obcdc_tailf 以及 obcdc_tailf 源码。
注意
这个 demo 仅用于提供开发指引,不能执行编译或运行。
/**
* Copyright (c) 2021 OceanBase
* OceanBase CE is licensed under Mulan PubL v2.
* You can use this software according to the terms and conditions of the Mulan PubL v2.
* You may obtain a copy of Mulan PubL v2 at:
* http://license.coscl.org.cn/MulanPubL-2.0
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PubL v2 for more details.
*
* obcdc_demo
*/
#include <iostream>
#include "include/libobcdc/libobcdc.h"
#include "include/libobcdc/ob_errno.h"
using namespace std;
using namespace oceanbase::libobcdc;
using namespace oceanbase::common;
typedef IBinlogRecord Record;
#define LOG(msg) \
do { \
std::cout << msg << std::endl; \
} while (0)
int create_obcdc_instance(ObCDCFactory &cdc_factory, IObCDCInstance *&obcdc_instance)
{
int ret = OB_SUCCESS;
if (NULL == (obcdc_instance = cdc_factory.construct_obcdc())) {
ret = OB_NOT_INIT;
LOG("[ERROR] construct_obcdc failed");
}
return ret;
}
void destroy_obcdc_instance(ObCDCFactory &cdc_factory, IObCDCInstance *obcdc_instance)
{
obcdc_instance->stop();
cdc_factory.deconstruct(obcdc_instance);
}
int init_obcdc_instance(IObCDCInstance &obcdc_instance)
{
int ret = OB_SUCCESS;
const char *config_path = "conf/libobcdc.conf";
if (OB_SUCCESS != (ret = obcdc_instance.init(config_path, 0))) {
LOG("obcdc_instance init failed");
} else if (OB_SUCCESS != (ret = obcdc_instance.launch())) {
LOG("obcdc_instance launch failed");
}
return ret;
}
int fetch_next_cdc_record(IObCDCInstance &obcdc_instance, Record *record)
{
int ret = OB_SUCCESS;
const int64_t timeout = 10000; // usec
if (OB_SUCCESS != (ret = obcdc_instance.next_record(&record, timeout))) {
if (OB_TIMEOUT != ret) {
LOG("[WARN] next_record failed");
}
} else if (NULL == record) {
ret = OB_ERR_UNEXPECTED;
LOG("invalid record");
}
return ret;
}
int release_cdc_record(IObCDCInstance &obcdc_instance, Record *record)
{
int ret = OB_SUCCESS;
obcdc_instance.release_record(record);
return ret;
}
int handle_cdc_record(Record *record)
{
int ret = OB_SUCCESS;
return ret;
}
int main(int argc, char **argv)
{
int ret = OB_SUCCESS;
ObCDCFactory cdc_factory;
IObCDCInstance *obcdc_instance = NULL;
if (OB_SUCCESS != create_obcdc_instance(cdc_factory, obcdc_instance)) {
LOG("[ERROR] construct_obcdc_instance failed");
} else if (NULL == obcdc_instance) {
ret = OB_ERR_UNEXPECTED;
LOG("[ERROR] obcdc_instance should not be null!");
} else {
if (OB_SUCCESS != init_obcdc_instance(*obcdc_instance)) {
LOG("[ERROR] obcdc_instance init failed");
} else {
while(OB_SUCCESS == ret) {
Record *record = NULL;
if (OB_SUCCESS != (ret = fetch_next_cdc_record(*obcdc_instance, record))) {
if (OB_TIMEOUT == ret) {
ret = OB_SUCCESS;
} else {
LOG("[ERROR] fetch_next_cdc_record failed");
}
} else if (OB_SUCCESS != (ret = handle_cdc_record(record))) {
LOG("[ERROR] handle_cdc_record failed");
} else if (OB_SUCCESS != (ret = release_cdc_record(*obcdc_instance, record))) {
LOG("[ERROR] release_cdc_record failed");
}
}
}
destroy_obcdc_instance(cdc_factory, obcdc_instance);
}
return 0;
}
附录
该章节主要展示部分 obcdc 头文件代码。
ObCDCFactory
ObCDCFactory 是 obcdc 实例工厂,负责构建或销毁 obcdc 实例。
注意
一个进程只能构造一个 obcdc 实例。
// libobcdc.h: ObCDCFactory
namespace oceanbase{
namespace liboblog{
class ObCDCFactory
{
public:
ObCDCFactory();
~ObCDCFactory();
public:
IObCDCInstance *construct_obcdc();
void deconstruct(IObCDCInstance *log);
};
}
}
IObCDCInstance
IObCDCInstance 是 obcdc 的对外接口,提供初始化、启动、停止、销毁、取数据、归还数据的能力,这里列出了一些常用的接口定义。
// libobcdc.h: IObCDCInstance
namespace oceanbase{
namespace liboblog{
// IObCDCInstance 是 liboblog 对外提供的接口
// 注意:这里忽略接口中需要传入的参数
class IObCDCInstance
{
public:
virtual ~IObCDCInstance() {};
public:
/*
* init libobcdc
* @param config_file config file name
* @param start_timestamp start timestamp (by second)
* @param err_cb error callback function pointer
*/
virtual int init(const char *config_file,
const uint64_t start_timestamp_sec,
ERROR_CALLBACK err_cb = NULL) = 0;
/*
* init libobcdc
* @param configs config by map
* @param start_timestamp start timestamp (by second)
* @param err_cb error callback function pointer
*/
virtual int init(const std::map<std::string, std::string> &configs,
const uint64_t start_timestamp_sec,
ERROR_CALLBACK err_cb = NULL) = 0;
/*
* init libobcdc
* @param configs config by map
* @param start_timestamp start timestamp by microsecond
* @param err_cb error callback function pointer
*/
virtual int init_with_start_tstamp_usec(const std::map<std::string, std::string> &configs,
const uint64_t start_timestamp_usec,
ERROR_CALLBACK err_cb = NULL) = 0;
virtual void destroy() = 0;
/*
* fetch next binlog record from OB cluster
* @param record binlog record, memory allocated by oblog, support release_record(corresponding times) after mutli next_record
* @param OB_SUCCESS success
* @param OB_TIMEOUT timeout
* @param other errorcode fail
*/
virtual int next_record(ICDCRecord **record, const int64_t timeout_us) = 0;
/*
* fetch next binlog record from OB cluster
* @param [out] record binlog record, memory allocated by oblog, support release_record(corresponding tiems) after mutli next_record
* @param [out] major_version major version of ICDCRecord
* @param [out] tenant_id tenant id of ICDCRecord
*
* @param OB_SUCCESS success
* @param OB_TIMEOUT timeout
* @param other error code fail
*/
virtual int next_record(ICDCRecord **record,
int32_t &major_version,
uint64_t &tenant_id,
const int64_t timeout_us) = 0;
/*
* release recorcd for EACH ICDCRecord
* @param record
*/
virtual void release_record(ICDCRecord *record) = 0;
/*
* Launch libobcdc
* @retval OB_SUCCESS on success
* @retval ! OB_SUCCESS on fail
*/
virtual int launch() = 0;
/*
* Stop libobcdc
*/
virtual void stop() = 0;
/// get all serving tenant id list after oblog inited
///
/// @param [out] tenant_ids tenant ids that oblog serving
///
/// @retval OB_SUCCESS success
/// @retval other value fail
virtual int get_tenant_ids(std::vector<uint64_t> &tenant_ids) = 0;
};
}
}