Rust 和 Java 混合编程的经验:快速、安全且实用
如何使用 Rust 增强你的 Java 项目 —— 带有完整示例的 JNI 集成实用指南
Greptime
Follow
5 min read
·
4 days ago
Listen
Share
简介
Rust 和 Java 都是广泛使用的语言,每种语言都在不同的领域表现出色。在实际场景中,将它们结合起来以实现更有效的系统级和应用级编程通常是有益的:
- 在 Java 应用程序中,你可能希望绕过垃圾回收器(GC),并在对性能要求高的区域手动管理内存。
- 你可能会将对性能敏感的算法移植到 Rust,以提高速度和隐藏实现细节。
- 或者,在 Rust 应用程序中,你可能希望通过将其打包为 JAR 来向 Java 生态系统公开功能。
在本文中,我们将逐步介绍如何在同一项目中组织和集成 Rust 和 Java。重点是实践,提供具体的代码示例和逐步说明。最后,你将能够创建一个跨语言应用程序,其中 Rust 和 Java 可以顺畅地交互。
背景:理解 JNI 和 Java 内存管理
Java Native Interface (JNI) 是 Java 与用 C/C++ 或 Rust 编写的本机代码之间的桥梁。虽然它的语法相对简单,但 JNI 在实践中却因其隐式的内存和线程管理规则而臭名昭著。
Java 运行时中的内存段
Java 应用程序跨多个内存段运行:
- Java Heap:Java 对象所在的位置,由垃圾回收器自动管理。
- Native Memory:由本机代码(例如,Rust)分配的内存,不由 GC 直接管理 - 需要特别注意以避免内存泄漏。
- Others:各种内存段,例如代码缓存和已编译类的元数据。
理解这些边界是编写高性能、内存安全的跨语言代码的关键。
一个实际的集成:rust-java-demo
项目
让我们来看一个真实的例子:我们的开源 rust-java-demo
存储库演示了如何将 Rust 无缝集成到 Java 应用程序中。
将特定于平台的 Rust 库打包到单个 JAR 中
Java 字节码是平台独立的,但 Rust 二进制文件不是。将 Rust 动态库嵌入 JAR 中会引入平台依赖性。虽然为每个架构构建单独的 JAR 是可能的,但这会使分发和部署复杂化。
更好的解决方案是将特定于平台的 Rust 库打包到单个 JAR 中的不同文件夹中,并在运行时动态加载正确的库。
在提取我们的多平台 JAR (jar xf rust-java-demo-2c59460-multi-platform.jar
) 之后,你将找到如下结构:
我们使用一个简单的实用程序来根据主机平台加载正确的库:
static { JarJniLoader.loadLib( RustJavaDemo.class, "/io/greptime/demo/rust/libs", "demo" );}
这种方法确保了平台灵活性,而不会牺牲开发人员或运营的便利性。
统一 Rust 和 Java 的日志
如果没有统一的日志记录,跨语言项目可能会很快变成调试的噩梦。我们通过将所有日志(Rust 和 Java)通过相同的 SLF4J 后端进行处理来解决这个问题。
在 Java 端,我们定义了一个简单的 Logger
包装器:
public class Logger { private final org.slf4j.Logger inner; public Logger(org.slf4j.Logger inner) { this.inner = inner; } public void error(String msg) { inner.error(msg); } public void info(String msg) { inner.info(msg); } public void debug(String msg) { inner.debug(msg); } // ...}
然后,Rust 使用 JNI 调用此记录器。这是一个简化的 Rust 实现:
impl log::Log for Logger { fn log(&self, record: &log::Record) { let env = ...; // Obtain the JNI environment let java_logger = find_java_side_logger(); let logger_method = java_logger.methods.find_method(record.level()); unsafe { env.call_method_unchecked( java_logger, logger_method, ReturnType::Primitive(Primitive::Void), &[JValue::from(format_msg(record)).as_jni()] ); } }}
我们将其注册为全局记录器:
log::set_logger(&LOGGER).expect("Failed to set global logger");
现在,来自两种语言的日志都可以在相同的输出流中看到,从而简化了诊断和监视。
从 Java 调用 Rust 异步函数
Rust 的突出特点之一是其强大的异步运行时。不幸的是,JNI 方法不能声明为 async
,因此直接从 Java 调用异步 Rust 代码并不简单:
#[no_mangle]pub extern "system" fn Java_io_greptime_demo_RustJavaDemo_hello(...) { // ❌ This won't compile foo().await;}async fn foo() { ... }
为了弥合这一点,我们必须手动创建一个运行时(例如,使用 Tokio)并自己管理异步执行:
async fn async_add_one(x: i32) -> i32 { x + 1}fn sync_add_one(x: i32) -> i32 { let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); let handle = rt.spawn(async_add_one(x)); rt.block_on(handle).unwrap()}
但是 block_on()
会阻塞当前线程(包括 Java 的线程)。相反,我们利用一种更惯用的方法:异步任务生成与 Java 端的 CompletableFuture
相结合,从而实现非阻塞集成。
在 Java 端:
public class AsyncRegistry { private static final AtomicLong FUTURE_ID = new AtomicLong(); private static final Map<Long, CompletableFuture<?>> FUTURE_REGISTRY = new ConcurrentHashMap<>();}public CompletableFuture<Integer> add_one(int x) { long futureId = native_add_one(x); // Call Rust return AsyncRegistry.take(futureId); // Get CompletableFuture}
这种模式(在 Apache OpenDAL 中使用)让 Java 开发人员可以决定何时以及是否阻塞,从而使集成更加灵活。
将 Rust 错误映射到 Java 异常
为了统一跨语言的异常处理,我们将 Rust 的 Result::Err
转换为 Java 的 RuntimeException
:
fn throw_runtime_exception(env: &mut JNIEnv, msg: String) { let msg = if let Some(ex) = env.exception_occurred() { env.exception_clear(); let exception_info = ...; // Extract exception class + message format!("{}. Java exception occurred: {}", msg, exception_info) } else { msg }; env.throw_new("java/lang/RuntimeException", &msg);}
这确保了 Java 代码可以统一处理所有异常,无论它们是来自 Rust 还是 Java。
结论
在本文中,我们探讨了 Rust-Java 互操作性的关键方面:
- 将特定于平台的本机库打包到单个 JAR 中。
- 统一 Rust 和 Java 的日志记录。
- 将异步 Rust 函数与 Java 的
CompletableFuture
连接起来。 - 将 Rust 错误映射到 Java 异常。
有关更多详细信息和完整的演示,请查看我们的开源存储库。
我们欢迎来自社区的反馈、问题和 PR!
关于我们
GreptimeDB 是一个开源的、云原生的数据库,专为实时可观测性而构建。它用 Rust 构建并针对云原生环境进行了优化,为指标、日志和跟踪提供统一的存储和处理 - 从边缘到云,以任何规模提供亚秒级的洞察力。
- GreptimeDB OSS 为你提供了一个快速、灵活的平台,用于轻量级可观测性和原型设计。
- GreptimeDB Enterprise 提供高级安全性、高可用性和 SLA 支持,适用于生产级使用。
- GreptimeCloud 以托管服务的形式提供 GreptimeDB 的全部功能 - 无服务器、可扩展且零维护。
🧑💻 想贡献吗?从 good first issue
开始,加入我们不断壮大的开源社区。
⭐ GitHub: GreptimeDB
🌐 greptime.cn | 📚 docs.greptime.cn