目前,Dubbo的SPI扩展只能使用Java语言编写,dubbo-wasm模块旨在克服这一限制。
WASM(WebAssembly)字节码设计为以大小和高效加载时间的二进制格式编码。WASM旨在利用各种平台上可用的通用硬件特性,在浏览器中以机器码速度执行。
WASI(WebAssembly系统接口)为在受限沙箱环境中运行的应用程序提供了一个可移植接口,这使得WASM可以在非浏览器环境(如Linux)中运行。它具有可移植性和安全性。
我们使用 wasmtime-java 在Java中运行WASI,只要语言可以编译成WASM字节码,就可以用于编写dubbo的SPI扩展。
由于WASM对参数类型有严格要求,并且为了简单起见,除了 java.lang.Long/java.lang.Integer
之外的类型不用作参数或返回值。
以下是在rust中实现 org.apache.dubbo.rpc.cluster.LoadBalance
SPI的示例。
cargo new --lib your_subproject_name
// Adding `#[no_mangle]` to prevent the Rust compiler from modifying method names is mandatory #[no_mangle] pub unsafe extern "C" fn doSelect(_arg_id: i64) -> i32 { 1 }
Cargo.toml
中添加 [lib]
并将 crate-type
更改为 ["cdylib"]
。最终,你的 Cargo.toml
应如下所示:[package] name = "your_subproject_name" version = "0.1.0" edition = "2021" [dependencies] # ...... [lib] crate-type = ["cdylib"]
cargo build --target wasm32-wasi --release
你可以看到 {your_subproject_name}/target/wasm32-wasi/release/{your_subproject_name}.wasm
.
pom.xml
<dependency> <groupId>org.apache.dubbo.extensions</groupId> <artifactId>dubbo-wasm-cluster-api</artifactId> <version>${revision}</version> </dependency>
x.y.z.RustLoadBalance.java
package x.y.z; public class RustLoadBalance extends AbstractWasmLoadBalance { public static final String NAME = "rust"; }
resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance
rust=x.y.z.RustLoadBalance
由于类 x.y.z.RustLoadBalance.java
,最终的wasm文件名应该是 x.y.z.RustLoadBalance.wasm
,最后将 wasm 文件放在你模块的 resources
文件夹中。
以下是使用rust的示例。
public class RustLoadBalance extends AbstractWasmLoadBalance { //...... @Override protected Map<String, Func> initWasmCallJavaFunc(Store<Void> store, Supplier<ByteBuffer> supplier) { Map<String, Func> funcMap = new HashMap<>(); //...... funcMap.put("get_args", WasmFunctions.wrap(store, WasmValType.I64, WasmValType.I64, WasmValType.I32, WasmValType.I32, (argId, addr, len) -> { String config = "hello from java " + argId; System.out.println("java side->" + config); assertEquals("hello from java 0", config); ByteBuffer buf = supplier.get(); for (int i = 0; i < len && i < config.length(); i++) { buf.put(addr.intValue() + i, (byte) config.charAt(i)); } return Math.min(config.length(), len); })); //...... return funcMap; } }
#[link(wasm_import_module = "dubbo")] extern "C" { fn get_args(arg_id: i64, addr: i64, len: i32) -> i32; }
#[no_mangle] pub unsafe extern "C" fn doSelect(arg_id: i64) -> i32 { //...... let mut buf = [0u8; 32]; let buf_ptr = buf.as_mut_ptr() as i64; eprintln!("rust side-> buffer base address: {}", buf_ptr); // get arg from java let len = get_args(arg_id, buf_ptr, buf.len() as i32); let java_arg = std::str::from_utf8(&buf[..len as usize]).unwrap(); eprintln!("rust side-> recv:{}", java_arg); //...... }
以下是使用rust的示例。
public class RustLoadBalance extends AbstractWasmLoadBalance { //...... private static final Map<Long, String> RESULTS = new ConcurrentHashMap<>(); @Override protected Map<String, Func> initWasmCallJavaFunc(Store<Void> store, Supplier<ByteBuffer> supplier) { Map<String, Func> funcMap = new HashMap<>(); //...... funcMap.put("put_result", WasmFunctions.wrap(store, WasmValType.I64, WasmValType.I64, WasmValType.I32, WasmValType.I32, (argId, addr, len) -> { ByteBuffer buf = supplier.get(); byte[] bytes = new byte[len]; for (int i = 0; i < len; i++) { bytes[i] = buf.get(addr.intValue() + i); } String result = new String(bytes, StandardCharsets.UTF_8); assertEquals("rust result", result); RESULTS.put(argId, result); System.out.println("java side->" + result); return 0; })); //...... return funcMap; } }
#[link(wasm_import_module = "dubbo")] extern "C" { fn put_result(arg_id: i64, addr: i64, len: i32) -> i32; }
#[no_mangle] pub unsafe extern "C" fn doSelect(arg_id: i64) -> i32 { //...... let rust_result = "rust result".as_bytes(); let result_ptr = rust_result.as_ptr() as i64; _ = put_result(arg_id, result_ptr, rust_result.len() as i32); //...... }
public class RustLoadBalance extends AbstractWasmLoadBalance { //...... private static final Map<Long, String> RESULTS = new ConcurrentHashMap<>(); @Override protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) { return wasmLoader.getWasmExtern(DO_SELECT_METHOD_NAME) .map(extern -> { // call WASI function final Long argumentId = getArgumentId(invokers, url, invocation); ARGUMENTS.put(argumentId, new Argument<>(invokers, url, invocation)); // WASI cannot easily pass Java objects like JNI, here we pass Long // then we can get the argument by Long final Integer index = WasmFunctions.func(wasmLoader.getStore(), extern.func(), WasmValType.I64, WasmValType.I32) .call(argumentId); ARGUMENTS.remove(argumentId); // For demonstration purposes, the doSelect method has been overwritten final String result = RESULTS.get(argumentId); assertEquals("rust result", result); return invokers.get(index); }) .orElseThrow(() -> new DubboWasmException( DO_SELECT_METHOD_NAME + " function not found in " + wasmLoader.getWasmName())); } }