基于湖库一体架构,统一管理结构化、半结构化与非结构化等多模态数据,一个系统承载事务处理、实时分析与 AI 工作负载。
obcdc 开发说明
更新时间:2025-01-20 15:35:17
本文介绍如何将 obcdc 接入自己的数据消费链路。
使用 obcdc 动态库开发自己的 OceanBase 数据消费工具
obcdc 是 C++ 编写,编译产出物为动态库,下游消费程序开发时需要依赖动态库和头文件(libobcdc.h、ob_errno.h)。
引入
执行 ldd ./libobcdc.so 看本地是否有缺少的动态库,如果有缺少,请保证所有依赖库(比如 libdrcmessage.so(商业版))都在本地,并配置 LD_LIBRARY_PATH 保证 obcdc 能链接到。
头文件
libobcdc.h 中有详细的接口说明,您可以参考代码中的接口说明,本文 附录 中简单列出常用接口。
构建 obcdc 实例
您可通过 ObLogFactory::construct_oblog() 方法构造 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 实例:通过
ObLogFactory::deconstruct(IObLog *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 仅用于提供开发指引,不能执行编译或运行。
#include "libobcdc.h"
#include "ob_errno.h"
using namespace oceanbase::common;
using namespace oceanbase::liboblog;
namespace oceanbase
{
namespace liboblog
{
class ObLogDemo
{
public:
static ObLogDemo& get_instance();
static const int64_t NEXT_RECORD_TIMEOUT = 1000000;
public:
ObLogDemo();
~ObLogDemo();
public:
int init(const char* config_file_path, const uint64_t start_tstamp);
void destroy();
int start();
void stop();
void run();
private:
int handle_log_record(ILogRecord *log_record, volatile bool stop_flag);
private:
ObLogFactory oblog_factory_;
IObLog* oblog_;
bool inited_;
volatile bool stop_flag_;
};
}
}
oblog_demo.cpp
#include "oblog_demo.h"
#include "ob_log_module.h" // ONLY FOR OB LOG MODULE, DEVELOPER CAN REPLACE IT AND LOG_XXX BELOW WITY YOUR OWN LOG MODULE
#define USING_LOG_PREFIX OBLOG_TAILF // ONLY FOR LOG
namespace oceanbase
{
namespace liboblog
{
ObLogDemo& ObLogDemo::get_instance()
{
static ObLogDemo oblog_instance;
return oblog_instance;
}
ObLogDemo::ObLogDemo() :
oblog_factory_(),
oblog_(NULL),
inited_(false),
stop_flag_(true)
{}
ObLogDemo::~ObLogDemo()
{
destroy();
}
int ObLogDemo::init(const char* config_file_path, const uint64_t start_tstamp)
{
int ret = OB_SUCCESS;
if (NULL == (oblog_ = oblog_factory_.construct_oblog())) {
LOG_ERROR("failed to construct liboblog", KR(ret));
} else if (OB_SUCCESS != oblog_->init(config_file_path, start_tstamp)) {
LOG_ERROR("failed to init liboblog instance", KR(ret), K(config_file_path, K(start_tstamp)));
} else {
inited_ = true;
}
return ret;
}
void ObLogDemo::destroy()
{
stop();
inited_ = false;
if (NULL != oblog_) {
oblog_->destroy(); // will call ObLog::stop for another time
oblog_factory_.deconstruct(oblog_);
oblog_ = NULL;
}
}
int ObLogDemo::start()
{
int ret = OB_SUCCESS;
if (!inited_) {
ret = OB_NOT_INIT;
LOG_ERROR("ObLogDemo not init, please call init function first!", KR(ret));
} else if (!stop_flag_){
ret = OB_ERR_UNEXPECTED;
LOG_ERROR("ObLogDemo already start!", KR(ret), K_(inited), K_(stop_flag));
} else {
if (NULL == oblog_) {
ret = OB_ERR_UNEXPECTED;
LOG_ERROR("oblog instance should not be null while start ObLogDemo", KR(ret), K_(inited), K_(stop_flag));
} else if (OB_SUCCESS != oblog_->launch()) {
LOG_ERROR("liboblog instance launch failed", KR(ret));
} else {
LOG_INFO("liboblog instance launch success");
stop_flag_ = false;
}
}
return ret;
}
void ObLogDemo::stop()
{
stop_flag_ = true;
if (NULL != oblog_) {
oblog_->stop();
}
}
void ObLogDemo::run()
{
int ret = OB_SUCCESS;
if (inited_ && NULL != oblog_) {
while (OB_SUCCESS == ret && ! stop_flag_) {
ILogRecord* record = NULL;
if (OB_SUCCESS == oblog_->next_record(&record, NEXT_RECORD_TIMEOUT)) {
if (OB_SUCCESS != handle_log_record(record, stop_flag_)) {
LOG_ERROR("failed to handle LogRecord", KR(ret), K(record));
}
// must call release_record
oblog_->release_record(record);
record = NULL;
} else if (OB_TIMEOUT == ret) {
LOG_WARN("fetch record timeout, may don't have any record", KR(ret));
// fetch record timeout may cause by no data produced by OB
// process this situation with your own logic
// e.g. retry or exit progress, etc.
} else if (OB_IN_STOP_STATE == ret) {
stop_flag_ = true;
ret = OB_SUCCESS;
} else {
LOG_ERROR("next_record fail", KR(ret));
stop_flag_ = true;
}
}
} else {
LOG_ERROR("should not run ObLogDemo while not inid or oblog instance is NULL, will exit");
}
}
int ObLogDemo::handle_log_record(ILogRecord *log_record, volatile bool stop_flag)
{
int ret = OB_SUCCESS;
// do your process for log_record
// u can get stop signal by volatile variable stop_flag
if (!stop_flag_) {
LOG_DEBUG("handle log record", K(log_record));
}
return ret;
}
}
}
附录
该章节主要给出部分 obcdc 头文件代码。
ObLogFactory
ObLogFactory 是 obcdc 实例工厂,负责构建或销毁 obcdc 实例。
注意
一个进程只能构造一个 obcdc 实例。
// libobcdc.h: ObLogFactory
namespace oceanbase{
namespace liboblog{
class ObLogFactory
{
public:
ObLogFactory();
~ObLogFactory();
public:
// 构建 liboblog 实例
IObLog *construct_oblog();
// 销毁 liboblog 实例
void deconstruct(IObLog *log);
};
}
}
IObLog
IObLog 是 obcdc 的对外接口,提供初始化、启动、停止、销毁、取数据、归还数据的能力,这里列出了一些常用的接口定义。
// libobcdc.h: IObLog
namespace oceanbase{
namespace liboblog{
// IObLog 是 liboblog 对外提供的接口
// 注意:这里忽略接口中需要传入的参数
class IObLog
{
public:
virtual ~IObLog() {};
public:
/*
* init liboblog
* @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,
ERROR_CALLBACK err_cb = NULL) = 0;
/*
* init liboblog
* @param configs config by map
* @param start_timestamp start timestamp (by secon)
* @param err_cb error callback function pointer
*/
virtual int init(const std::map<std::string, std::string>& configs,
const uint64_t start_timestamp,
ERROR_CALLBACK err_cb = NULL) = 0;
/*
* init liboblog
* @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(ILogRecord **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 ILogRecord
* @param [out] tenant_id tenant id of ILogRecord
*
* @param OB_SUCCESS success
* @param OB_TIMEOUT timeout
* @param other error code fail
*/
virtual int next_record(ILogRecord **record,
int32_t &major_version,
uint64_t &tenant_id,
const int64_t timeout_us) = 0;
/*
* release recorcd for EACH ILogRecord
* @param record
*/
virtual void release_record(ILogRecord *record) = 0;
/*
* Launch liboblog
* @retval OB_SUCCESS on success
* @retval ! OB_SUCCESS on fail
*/
virtual int launch() = 0;
/*
* Stop liboblog
*/
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;
};
}
}