后端业务服务使用 Java + Dubbo,管理后台、任务程序或测试工具使用 .NET,这样的技术组合并不少见。两边需要协作时,我们希望 C# 应用能够直接调用已有 Dubbo 接口,而不是为每个接口再维护一层 HTTP 适配代码。
DubboNet 就是为这个场景提供的 .NET Dubbo 客户端库。 它可以发现服务、读取方法签名、选择 Provider,再通过 Dubbo TCP/Hessian2 或已支持的 Triple HTTP/JSON 方式完成调用,.NET 调用方不需要引用 Java 接口 JAR。
先看怎么用,再展开介绍它能做什么。
本文示例基于 DubboNet 3.6.2。当前包目标框架为
net6.0,可被 .NET 6 及以上应用引用。
一、最简单的使用方式
1. 安装 NuGet 包
在已有 .NET 项目目录中执行:
bash
dotnet add package DubboNet --version 3.6.2
2. 几行 C# 调用一个 Dubbo 方法
假设 Java 端已经发布接口 com.example.api.HelloService,其中有一个方法:
java
String sayHello(String name);
服务注册在 ZooKeeper,默认根目录为 /dubbo。C# 调用代码如下:
csharp
using DubboNet.Clients;
using var client = new DubboClient("127.0.0.1:2181");
var result = await client.QueryAsync(
"com.example.api.HelloService#sayHello",
"DubboNet");
if (!result.QuerySuccess)
{
Console.WriteLine(result.ErrorMeaasge);
return;
}
Console.WriteLine(result.Result);
这里的入口由"完整 Java 接口名 + # + 方法名"组成,后面直接传业务参数。DubboNet 会尝试从方法元数据中选择对应签名,不需要每次调用都手写参数类型。
带参数调用的自动类型推断还需要可用的方法元数据(这个dubbo服务默认都是带的);如果没有元数据,后面介绍的"显式 Java 类型"方式同样可以调用。
文中的接口、方法、地址和 DTO 都是示例,需要替换成你自己的服务。
3. 无参数、多参数也使用同一套 API
csharp
// 无参数方法。
var health = await client.QueryAsync(
"com.example.api.HealthService#check");
// 多参数方法,参数按 Java 方法的声明顺序传入。
var customer = await client.QueryAsync(
"com.example.api.CustomerService#getCustomer",
"customer-001",
1234L);
公共调用入口统一为 QueryAsync。单个参数本身是数组或集合时,也会作为一个参数处理,不会误拆成多个 Java 方法参数。
实际应用中请复用长生命周期的 DubboClient,不要每个请求都创建和释放一次。 上面的 using 用来展示完整生命周期;在 Web 应用或后台服务中,可以由 DI 容器或宿主统一管理,关闭应用时再释放。
二、DubboNet 的能力和优势
| 能力 | 对 .NET 调用方的价值 |
|---|---|
| 不依赖 Java 接口 JAR | 用接口名、方法名和参数完成调用,适合跨语言接入与通用工具 |
| ZooKeeper / Nacos 服务发现 | 不必在业务代码中维护每个 Provider 的 IP,并能订阅节点变化 |
| 原生 Dubbo TCP + Hessian2 | 通过 $invoke 泛化调用已有 Dubbo 服务,不依赖 Telnet 执行业务请求 |
| Triple Java Interface Unary HTTP/JSON | 通过统一客户端 API 调用已支持的 Dubbo 3 Triple 接口 |
| 元数据读取与类型推断 | 获取方法签名、识别重载,减少手工填写类型的工作 |
| 显式签名与强类型响应 | 兼顾调用确定性与 C# 业务代码的易用性 |
| 六种负载均衡模式 | 根据流量特点选择随机、轮询、最少活跃、最短响应等策略 |
| 连接复用与缓存 | 复用注册中心资源、Provider 连接和方法元数据,减少重复初始化 |
| 可选消费者注册 | 让注册中心看到应用引用的接口,并支持运行中开关 |
| MIT 开源 | 可以查看实现、提交问题,也可以按许可证约定用于自己的项目 |
原生执行器支持在单个 TCP 连接上使用 Request ID 复用并发请求,不需要为每个请求新建一条连接。LeastActive、ShortestResponse 等策略使用消费者本地统计,也不要求开放 Provider 的 Telnet 诊断端口(Telnet的操作方式在1.3.0以后的版本逐步被弃用)。
尽可能的资源复用使得DubboNet有非常强的性能表现,即便在性能测试过程中同时启动上千个异步请求也只需要一个client。
三、让调用更明确:Java 类型、强类型响应与复杂参数
1. 有重载或 null 参数时,显式指定 Java 类型
自动推断适合简单、签名明确的接口。但目标Dubbo服务可能没有开启元数据中心,或因歧义及重载导致参数推断失败。
遇到这些情况,可以直接传入准确的 Java 签名:
csharp
var result = await client.QueryAsync(
"com.example.api.CustomerService#getCustomer",
new[] { "java.lang.String", "java.lang.Long" },
new object[] { "customer-001", 1234L });
类型数组和参数数组一一对应。常见类型可以写成 int、long、java.lang.Integer、java.lang.String、java.util.List、java.util.Map,以及 Java DTO 的全限定名。
显式签名不依赖元数据获取成功。对于关键接口、复杂重载或元数据不完整的环境,这是更确定的接入方式。
2. 直接读取 C# 响应模型
不想在业务代码中手动解析 JSON,可以指定响应类型:
csharp
var result = await client.QueryAsync<CustomerDto>(
"com.example.api.CustomerService#getCustomer",
new[] { "java.lang.Long" },
new object[] { 1234L });
if (result.QuerySuccess && result.ResultModle is CustomerDto customer)
{
Console.WriteLine($"{customer.Id}: {customer.Name}");
}
else
{
Console.WriteLine(result.ErrorMeaasge);
}
对应模型可以定义为:
csharp
using System.Text.Json.Serialization;
public sealed class CustomerDto
{
[JsonPropertyName("id")]
public long Id { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
}
ResultModle 通过 System.Text.Json 反序列化,原始 JSON 仍保留在 Result 中。属性名称或大小写不一致时,可以像上面一样使用 JsonPropertyName 映射。
ResultModle和ErrorMeaasge是当前版本的实际 API 拼写。模型反序列化错误记录在ErrorMeaasge中;QuerySuccess本身也不代表业务响应里的code或success一定成功。
3. POJO、版本、分组和 attachments
需要传复杂对象,或者指定服务版本、分组与附加信息时,可以使用 DubboInvocation:
csharp
using DubboNet.DubboService.DataModle;
var invocation = new DubboInvocation(
"com.example.api.CustomerService",
"save",
new[] { "com.example.api.dto.CustomerRequest" },
new object[]
{
new
{
name = "张三",
age = 28
}
})
{
Version = "1.0.0",
Group = "prod",
Attachments = new Dictionary<string, string>
{
["traceId"] = Guid.NewGuid().ToString("N")
}
};
var result = await client.QueryAsync(invocation);
原生 Dubbo 泛化调用会根据 POJO 参数类型补充需要的 class 信息。对象字段应与 Java DTO 对应,复杂嵌套结构应先在真实 Provider 上验证。
Version、Group 表示服务版本与服务分组,不是 Dubbo 框架版本,也不是 Nacos 的 Naming 分组。traceId 这类 attachment 是否参与链路追踪,需要服务端配合读取和处理。
四、从 ZooKeeper 切换到 Nacos
使用 Nacos 时,以 nacos:// 开头指定注册中心即可:
csharp
using var nacosClient = new DubboClient(
"nacos://127.0.0.1:8848",
new DubboClient.DubboClientConf
{
// Nacos Naming 分组,与 Provider 的注册配置保持一致。
DubboRootPath = "DEFAULT_GROUP",
// Nacos Config 中存放 Dubbo 元数据的分组,独立配置。
MetadataRootPath = "dubbo",
DubboRequestTimeout = 5_000
});
var result = await nacosClient.QueryAsync(
"com.example.api.HelloService#sayHello",
"hello-nacos");
Nacos 还可以通过连接字符串查询参数指定 namespace ID、认证等信息。实际项目中请从配置或密钥管理服务读取凭据,不要硬编码进源码。
两种注册中心使用相同的 QueryAsync 调用方式;DubboClient 会根据 Provider URL 选择执行器:
dubbo://:经典 Dubbo2 TCP 帧 + Hessian2 泛化调用。tri://:当前支持的 Triple Java Interface 单次请求/响应 HTTP/JSON 调用。
需要注意,支持 Dubbo 3 Provider 不等于支持 Dubbo 3 的全部能力。目前服务发现仍面向接口级注册,Triple 也有明确边界,文末会单独说明。
五、按业务选择负载均衡
创建客户端时可以指定策略:
csharp
using var balancedClient = new DubboClient(
"127.0.0.1:2181",
new DubboClient.DubboClientConf
{
NowLoadBalanceMode = DubboClient.LoadBalanceMode.LeastActive,
DubboRequestTimeout = 5_000
});
当前支持以下六种模式:
| 模式 | 主要行为 |
|---|---|
Random |
默认策略,按 Provider 权重随机选择 |
RoundRobin |
平滑加权轮询 |
LeastActive |
优先选择本客户端观察到的在途请求数最少的节点,同分时加权随机 |
P2CLoadBalance |
按权重抽取两个不同节点,再比较本地在途请求数 |
ShortestResponse |
综合最近 30 秒的成功响应平均耗时、在途请求数及失败惩罚选址 |
ConsistentHash |
相同参数 key 尽量命中同一节点,适合请求亲和场景 |
这里的活跃数和耗时是当前消费者本地的观测值,不是 Provider 的全局负载。最短响应模式也不是简单地选择"最后一次请求最快"的节点。
一致性 Hash 尤其容易在压测时被误解:当前 key 是完整参数数组序列化后的 JSON。所有请求都传 [0,0] 时,落到同一个节点是预期行为;如果要测试集群总体吞吐,应选择能分散重复请求的策略,或者准备具有合理分布的业务 key。
当前一致性 Hash 使用 UTF-8、稳定 MurmurHash2 和每节点 160 个虚拟节点,不保证与 Java Dubbo 的 Hash 实现得到相同映射,也不按正权重比例分配虚拟节点。
六、不只是调用:也可以读取接口和类型元数据
如果你正在开发 Dubbo 调试工具、接口浏览器或动态参数编辑器,往往还需要知道"接口有哪些方法、参数是什么类型"。
DubboNet 提供了对应 API:
csharp
// 查看一个方法的全部重载。
var overloads = await client.GetMethodMetadataAsync(
"com.example.api.CustomerService",
"getCustomer");
foreach (var method in overloads)
{
Console.WriteLine(
$"{method.ReturnType} {method.Name}" +
$"({string.Join(", ", method.ParameterTypes)})");
}
// 查看服务的全部方法签名。
var methods = await client.GetServiceMethodsMetadataAsync(
"com.example.api.CustomerService");
// 从元数据中心读取 POJO、集合、枚举等类型定义。
var types = await client.GetServiceTypesMetadataAsync(
"com.example.api.CustomerService");
方法签名优先按 Provider 版本选择元数据来源,必要时使用一次性 Telnet ls -l 回退;元数据成功读取后会缓存,并监听相关变化。
GetServiceTypesMetadataAsync 则依赖元数据中心的完整类型定义,不会退回 Telnet,因为 ls -l 没有完整 POJO 结构。它提供的是 Provider 已上报的信息,不会凭空补全缺失的业务定义。
这使 DubboNet 不仅可以嵌入业务应用,也适合作为 .NET Dubbo 测试和管理工具的调用基础。
七、按需开启消费者注册,并支持热切换
如果希望注册中心及兼容管理平台能够看到"哪个应用引用了哪些接口",可以开启消费者注册。默认情况下它是关闭的。
csharp
using DubboNet.Clients.DataModle;
DubboClient.ConfigureConsumerRegistration(new ConsumerRegistrationOptions
{
Enabled = true,
ApplicationName = "customer-admin-dotnet"
});
// 后续 QueryAsync 使用到的接口会在后台登记消费者记录。
// 运行期间关闭,不需要重建 DubboClient。
DubboClient.ConfigureConsumerRegistration(new ConsumerRegistrationOptions
{
Enabled = false
});
// 再次开启,继承之前的应用身份,并补登记仍存活客户端已引用的接口。
DubboClient.ConfigureConsumerRegistration(new ConsumerRegistrationOptions
{
Enabled = true
});
上面演示的是三次独立配置操作,实际使用时在对应业务时机执行,不需要启动后立即连续切换。
这个配置是进程级的,不是某个 client 独享的开关。配置更新后,后台按照最新期望状态执行注册或注销,不会等待网络操作完成才返回,也不会因为注册失败而阻断 RPC。
ZooKeeper 使用临时节点和现有会话机制;Nacos 使用临时实例及 SDK 的连接维护机制。同时提供失败日志、退避重试和会话恢复处理。应用仍应正常释放 client,不能把心跳当作"自动识别业务是否还在使用"的替代品。
八、调试时也可以绕过注册中心直连
已知 Provider 的 IP、端口及 Java 方法签名时,可以直接使用原生执行器:
csharp
using DubboNet.DubboService;
using DubboNet.DubboService.DataModle;
using var suite = new NativeDubboActuatorSuite(
"127.0.0.1",
20880,
new DubboActuatorSuiteConf
{
DubboRequestTimeout = 5_000
});
var invocation = new DubboInvocation(
"com.example.api.HelloService",
"sayHello",
new[] { "java.lang.String" },
new object[] { "hello-direct" });
var result = await suite.SendQuery(invocation);
Console.WriteLine(result.Result);
直连适合联调和定点排障,但不会提供服务发现、负载均衡或方法签名推断。已有 Telnet 开放条件的环境,还可以使用相关诊断 API 读取 ls、status 等信息。
九、可能需要注意的点
- 已验证的组合 :原生 Dubbo 路径包括 Dubbo 2.7.3,以及 Dubbo 3.2.20 / 3.3.6 的经典
dubbo://兼容模式;Triple 路径已验证 Dubbo 3.3.6 Java Interface Unary HTTP/JSON。其他版本需结合项目兼容说明实测。 - 运行平台:当前包目标为 .NET 6,未提供 .NET Standard 或 .NET Framework 目标。
- 服务发现:支持 ZooKeeper / Nacos 接口级注册,尚未实现 Dubbo 3 应用级服务发现。
- 原生序列化:目前使用 Hessian2,不会根据 Provider 配置自动切换到其他序列化器。
- Triple 范围:尚未实现 Protobuf/IDL、流式调用和完整 gRPC framing,不能将当前实现视作通用 gRPC 客户端。
- Nacos 多环境工具场景:受当前底层 SDK 生命周期限制,同一进程最多保留 32 种不同连接配置,详情见github README。
接入后也要分别检查"调用层成功"和"业务成功":先看 QuerySuccess,再看响应中的业务状态。不要在非幂等写操作外层直接套无限重试。
十、适合哪些项目?
如果你有以下需求,可以考虑试用 DubboNet:
- 用 .NET 管理后台调用已有 Java Dubbo 业务服务。
- 在 C# 后台任务、集成程序中复用现有 Dubbo 接口。
- 开发 Dubbo 接口调试、元数据浏览或压测工具。
- 希望直接接入注册中心,而不是在调用方维护 Provider 地址列表。
它把服务发现、签名解析、连接复用、负载均衡和调用结果处理封装进一个 .NET 客户端,让跨语言调用不必从底层协议开始写起。
先从一个简单的查询接口开始,确认注册中心、协议和类型签名,再逐步接入真实业务。 欢迎试用、反馈兼容场景,也欢迎通过 Issue 和 PR 一起完善。
<>()