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

https://github.com/OpenByteDev/dlopen2

posted on 2025-08-08 08:00  荣锋亮  阅读(23)  评论(0)    收藏  举报

导航