iggy dlopen2 插件使用简单说明
iggy 基于dlopen2 实现sink,source的插件化,机制上比较简单,核心是先基于配置,然后是使用dlopen2 约定加载插件
插件定义
对于sink 以及source 都定义了明确的接口
- source定义
为了方便处理,同时基于了macro
#[derive(WrapperApi)]
struct SourceApi {
open: extern "C" fn(id: u32, config_ptr: *const u8, config_len: usize) -> i32,
handle: extern "C" fn(id: u32, callback: SendCallback) -> i32,
close: extern "C" fn(id: u32) -> i32,
}
macro 定义
#[macro_export]
macro_rules! source_connector {
($type:ty) => {
const _: fn() = || {
fn assert_trait<T: $crate::Source>() {}
assert_trait::<$type>();
};
use dashmap::DashMap;
use once_cell::sync::Lazy;
use $crate::source::SendCallback;
use $crate::source::SourceContainer;
static INSTANCES: Lazy<DashMap<u32, SourceContainer<$type>>> = Lazy::new(DashMap::new);
#[cfg(not(test))]
#[unsafe(no_mangle)]
unsafe extern "C" fn open(id: u32, config_ptr: *const u8, config_len: usize) -> i32 {
let mut container = SourceContainer::new(id);
let result = container.open(id, config_ptr, config_len, <$type>::new);
INSTANCES.insert(id, container);
result
}
#[cfg(not(test))]
#[unsafe(no_mangle)]
unsafe extern "C" fn handle(id: u32, callback: SendCallback) -> i32 {
let Some(mut instance) = INSTANCES.get_mut(&id) else {
tracing::error!(
"Source connector with ID: {id} was not found and cannot be handled."
);
return -1;
};
instance.handle(callback)
}
#[cfg(not(test))]
#[unsafe(no_mangle)]
unsafe extern "C" fn close(id: u32) -> i32 {
let Some(mut instance) = INSTANCES.remove(&id) else {
tracing::error!(
"Source connector with ID: {id} was not found and cannot be closed."
);
return -1;
};
instance.1.close()
}
};
}
- sink 定义
#[derive(WrapperApi)]
struct SinkApi {
open: extern "C" fn(id: u32, config_ptr: *const u8, config_len: usize) -> i32,
#[allow(clippy::too_many_arguments)]
consume: extern "C" fn(
id: u32,
topic_meta_ptr: *const u8,
topic_meta_len: usize,
messages_meta_ptr: *const u8,
messages_meta_len: usize,
messages_ptr: *const u8,
messages_len: usize,
) -> i32,
close: extern "C" fn(id: u32) -> i32,
}
macro 定义, 实际上就是具体的包装
#[macro_export]
macro_rules! sink_connector {
($type:ty) => {
const _: fn() = || {
fn assert_trait<T: $crate::Sink>() {}
assert_trait::<$type>();
};
use dashmap::DashMap;
use once_cell::sync::Lazy;
use $crate::sink::SinkContainer;
static INSTANCES: Lazy<DashMap<u32, SinkContainer<$type>>> = Lazy::new(DashMap::new);
#[cfg(not(test))]
#[unsafe(no_mangle)]
unsafe extern "C" fn open(id: u32, config_ptr: *const u8, config_len: usize) -> i32 {
let mut container = SinkContainer::new(id);
let result = container.open(id, config_ptr, config_len, <$type>::new);
INSTANCES.insert(id, container);
result
}
#[cfg(not(test))]
#[unsafe(no_mangle)]
unsafe extern "C" fn consume(
id: u32,
topic_meta_ptr: *const u8,
topic_meta_len: usize,
messages_meta_ptr: *const u8,
messages_meta_len: usize,
messages_ptr: *const u8,
messages_len: usize,
) -> i32 {
let Some(instance) = INSTANCES.get(&id) else {
tracing::error!(
"Sink connector with ID: {id} was not found and consume messages cannot be invoked."
);
return -1;
};
instance.consume(
topic_meta_ptr,
topic_meta_len,
messages_meta_ptr,
messages_meta_len,
messages_ptr,
messages_len,
)
}
#[cfg(not(test))]
#[unsafe(no_mangle)]
unsafe extern "C" fn close(id: u32) -> i32 {
let Some(mut instance) = INSTANCES.remove(&id) else {
tracing::error!("Sink connector with ID: {id} was not found and cannot be closed.");
return -1;
};
instance.1.close()
}
};
}
插件加载
对于sink 以及source 都定义了帮助方法
let sources = source::init(config.sources.clone(), &producer_client).await?;
let sinks = sink::init(config.sinks.clone(), &consumer_client).await?;
在source::init 以及sink::init 中会基于配置进行插件的加载,具体加载就是dlopen2的标准玩法
source::init 参考处理(只粘贴了load ,其他部分省略了)
......
} else {
let container: Container<SourceApi> =
unsafe { Container::load(&path).expect("Failed to load source container") };
info!("Source container for plugin: {path} loaded successfully.",);
init_source(&container, &config.config.unwrap_or_default(), plugin_id);
source_connectors.insert(
path.to_owned(),
SourceConnector {
container,
plugins: vec![SourceConnectorPlugin {
id: plugin_id,
key: key.to_owned(),
name: name.to_owned(),
path: path.to_owned(),
config_format: config.config_format,
producer: None,
transforms: vec![],
}],
},
);
}
.....
说明
从iggy 对于dlopen2 的使用来看,通过dlopen2 开发插件化系统还是比较快速方便的,相比libloading 方便不少,值得尝试下
参考资料
https://github.com/apache/iggy/tree/master/core/connectors/runtime