Dubbo 3.2.x 简介、注册中心、tripe 流式调用以及跨语言支持演示

Dubbo 3.2.x 介绍

是什么?

Dubbo 是一款高性能、轻量级的开源 RPC(远程过程调用)服务框架,它最核心的功能是让分布式系统中的微服务能够像调用本地方法一样相互调用,并提供完善的服务治理能力。 它是一个为了解决分布式系统 “多节点连接难、调用慢、管理乱” 而诞生的全栈服务治理框架。 它的核心价值,就是用最少的性能损耗和最纯粹的代码体验,把一堆散落在不同机器上的服务,揉成一个有机的整体。


核心架构:三驾马车

Dubbo 的运行机制完全围绕着一个经典的“生态三角”展开,它解决了微服务在分布式环境下的生存问题:

  • Provider(生产者):暴露服务的服务提供方,启动时将自己的 IP 和端口注册到 Registry。
  • Consumer(消费者):调用远程服务的服务消费方,启动时向 Registry 订阅自己需要的服务,获取 Provider 的地址列表,并在本地缓存。
  • Registry(注册中心):类似于分布式系统中的114查号台(常用的有 Nacos、ZooKeeper)。它不参与服务之间的直接通信,只负责地址的登记与动态通知。当 Provider 宕机或扩容时,注册中心会实时把最新的地址列表推送给 Consumer。


面向接口代理:代码无侵入

在写代码时,Dubbo 给开发者的体验极其丝滑。你不需要手动去写 HTTP 请求、不需要自己拼接 URL,也不需要手动解析 JSON。

  • 在服务提供端:你只需要在一个普通的 Java 实现类上加一个 @DubboService 注解。
  • 在服务消费端:你只需要通过 @DubboReference 注入一个接口。

对于业务代码来说,远程调用被完全抽象成了一个普通的本地 Interface 调用,底层的网络传输、序列化、超时重试等复杂的分布式细节,全部被 Dubbo 框架在底层屏蔽了。


核心优势:极致的通信性能

这是 Dubbo 区别于传统 Spring Cloud(基于 HTTP 协议)最核心的杀手锏。在大规模高并发的电商或金融场景下,Dubbo 的性能优势非常明显:

  • 长连接复用:Dubbo 默认的 Dubbo 协议基于 Netty 框架,在 Consumer 和 Provider 之间建立 TCP 长连接。所有的 RPC 请求都复用这一条通道,避免了每次调用都要进行 TCP 三次握手和四次挥手的巨大开销。
  • 高效二进制序列化:不同于 HTTP 默认传输体积庞大的 JSON 文本,Dubbo 支持 Hessian2、Fastjson2、Kryo 等二进制序列化协议,将对象压缩成极小的二进制流进行网络传输,速度极快,带宽占用极低。


解决的问题

在传统的单体应用或简单的 HTTP 调用(如配置很多 RestTemplate)演进到大规模分布式架构时,会遇到三个核心痛点,Dubbo 正是为此而生的。

  • 服务的动态发现与自动寻址(服务治理问题)
    • 痛点:在微服务集群中,服务的 IP 和端口是动态变化的(由于扩容、缩容、机器宕机重启等)。如果采用硬编码或 nginx 配置硬指向,维护成本是灾难性的。
    • Dubbo 的解决方式:引入了注册中心机制。Provider 启动时自动注册服务,Consumer 启动时订阅服务。当 Provider 节点发生变更时,注册中心能动态推送给 Consumer,实现了服务的自动发现。
  • 跨网络的强类型高性能调用(远程通信问题)
    • 痛点:传统的 HTTP/REST 协议通常使用 JSON 传输,序列化文本效率较低,且写代码时需要频繁拼接 URL、手动解析返回值,缺乏强类型约束,容易出错。
    • Dubbo 的解决方式:提供了 RPC(远程过程调用)能力。通过动态代理技术,让开发者能够 “像调用本地方法一样调用远程服务”,屏蔽了底层的网络传输、序列化/反序列化细节。同时,其默认的 Dubbo 协议基于 Netty 长连接,传输效率极高。
  • 软负载均衡与容错(高可用问题)
    • 痛点:当下游服务部署多台机器,如何把流量均匀分发过去?如果某台机器挂了,怎么自动避让并重试?
    • Dubbo 的解决方式:在 Consumer 端内置了软负载均衡算法(如随机、轮询、一致性 Hash 等)以及集群容错策略(如 Failover 失败自动切换、Failfast 快速失败等),保障了服务的高可用。


核心好处

为什么要选 Dubbo,而不是 Spring Cloud(HTTP)?

  • 面向接口代理,代码无侵入。
    • Dubbo 的设计让 Consumer 只需要依赖一个通用的 API 接口(Interface),在代码中通过 @DubboReference 注入即可直接使用。服务消费者完全不需要感知网络层和业务提供方的具体实现,这使得业务代码非常纯粹,解耦极其彻底。
  • 极致的性能(对比 Spring Cloud OpenFeign)
    • 协议层:Spring Cloud 默认基于 HTTP/1.1,每次请求都需要三握四挥,且 HTTP 头部(Header)信息冗余。而 Dubbo 默认的 Dubbo 协议基于 TCP 长连接复用,配合 NIO 异步通信,极大地减少了网络开销。
    • 序列化层:Dubbo 支持 Hessian2、Fastjson2、Kryo、Protobuf 等多种高效二进制序列化协议,比传统的 HTTP JSON 文本序列化体积更小、速度更快。
  • 强大的服务治理与生态(Dubbo 3.x 升级)
    • 三中心架构:现在的 Dubbo 已经不仅仅是一个 RPC 框架,而是一个全栈的三中心架构服务治理框架,将注册中心元数据中心和配置中心分离,极大地减轻了注册中心(如 ZooKeeper/Nacos)的同步压力,能够支撑超大规模的微服务集群。
    • 云原生支持:Dubbo 3 彻底打通了云原生生态,支持应用级服务发现,完美兼容 Kubernetes Pod 生命周期,并且支持 Triple 协议(基于 HTTP/2,原生兼容 gRPC),具备跨语言调用能力。
    • 丰富的流量控制:自带控制台(Dubbo Admin),支持动态修改权重、路由规则、黑白名单、服务降级、标签路由(用于蓝绿发布/灰度发布)等。


新的 Triple 协议

目前 Dobbo 已经演变到的 3.2.x 版本,已经从当年单一的 Dubbo 协议(Dubbo 2.x 时代),演进出了一款全新的、面向云原生时代的王牌协议 —— Triple (三联) 协议,它虽然依然兼容老一代的 Dubbo 协议,但 Triple 协议已经成为了官方首推的核心新协议。

为什么要演进出 Triple 协议?在微服务刚兴起的时代,老一代的 Dubbo 协议(基于 TCP 长连接 + Hessian2 序列化)凭借极致的性能打响了名号。但随着云原生(Kubernetes/Service Mesh)和多语言微服务的普及,它遇到了两个致命的瓶颈:

  • 跨语言极度困难:老 Dubbo 协议跟 Java 深度绑定(比如使用了 Hessian、Java 序列化)。如果前端想用 Node.js,或者大数据/AI 团队想用 Python、Go,它们很难直接调用 Java 的 Dubbo 服务,必须加一层繁琐的网关做转换。
  • 对网关/云原生不友好:老 Dubbo 协议是完全自定义的二进制协议。这意味着像 Nginx、Envoy 或者是 K8s 的 Ingress 这种标准网关,根本看不懂老 Dubbo 协议的包头。网关想做个根据路径(Path)路由流量、或者做个负载均衡,都必须深度定制,代价极大。

为了彻底打破这些限制,Dubbo 3 顺应时代,推出了基于 HTTP/2 标准打造的 Triple 协议。它的设计核心非常聪明:不再自己闭门造车,而是选择站在巨人的肩膀上——全面兼容 gRPC。Triple 协议的 3 大杀手锏:

  • 100% 行业标准(网关直接识别):因为 Triple 协议的底层就是 HTTP/2,这就意味着天生自带流量透明的属性。任何市面上的标准网关(如 Envoy、Nginx、K8s Ingress)不需要做任何二次开发,就能直接对 Triple 协议的流量进行拦截、转发、限流和路由。这让 Dubbo 完美融入了云原生 Service Mesh 生态。
  • 真正的原生跨语言调用:Triple 协议默认推荐使用 Protobuf (Protocol Buffers) 作为序列化方式。你只需要编写一个 .proto 接口定义文件,就可以通过工具自动生成 Java、Go、Python、Node.js、C++ 等各种语言的代码。任何语言的客户端,都可以像调用本地服务一样,直接请求 Java 端暴露的 Triple 服务。
  • 支持完美的流式通信 (Streaming):得益于 HTTP/2 的多路复用和数据流特性,Triple 协议原生支持三种通信模式:
    • UNARY:传统的 “一问一答” RPC 请求。
    • BI_STREAM (双向流):客户端和服务端可以同时源源不断地向对方发送数据流(非常适合大文件传输、实时聊天、AI 流式响应/大模型 Token 吐出等场景)。
    • SERVER_STREAM / CLIENT_STREAM:单向的流式传输。


Dubbo hello world

这里只展示最核心的构建代码,其他关于构建 Spring Boot 应用程序:请参考《构建一个最基础的应用框架》


项目POM

父项目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<modules>
<module>janus-user</module>
<module>janus-order</module>
<module>janus-common</module>
</modules>

<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.5.13</spring-boot.version>
<dubbo.verison>3.2.19</dubbo.verison>
<logback.version>1.5.25</logback.version>
<lombok.version>1.18.42</lombok.version>
...
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.verison}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-fastjson2</artifactId>
<version>${dubbo.verison}</version>
</dependency>
...
</dependencies>
</dependencyManagement>

common:

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

order:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
<dependency>
<groupId>com.owlias.janus</groupId>
<artifactId>janus-common</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-fastjson2</artifactId>
</dependency>
</dependencies>

user:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
<dependency>
<groupId>com.owlias.janus</groupId>
<artifactId>janus-common</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-fastjson2</artifactId>
</dependency>
</dependencies>


common 模块(公共)

只存放公共类、公共接口等。

OrderDTO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
public class OrderDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;

private String orderId;
private String userId;
private Double price;

public OrderDTO() {}
public OrderDTO(String orderId, String userId, Double price) {
this.orderId = orderId;
this.userId = userId;
this.price = price;
}
}

OrderService:

1
2
3
public interface OrderService {
List<OrderDTO> getOrdersByUserId(String userId);
}


order 模块(生产者)

OrderServiceImpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import com.owlias.janus.model.OrderDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import java.util.Arrays;
import java.util.List;

@Slf4j
@DubboService // 将该服务暴露为 Dubbo RPC 服务
public class OrderServiceImpl implements OrderService {

@Override
public List<OrderDTO> getOrdersByUserId(String userId) {
log.info("userId:{}", userId);
return Arrays.asList(
new OrderDTO("RPC_ORD_001", userId, 88.5),
new OrderDTO("RPC_ORD_002", userId, 188.0)
);
}
}

OrderApp:

1
2
3
4
5
6
@SpringBootApplication
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}
}

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server:
port: 8081

spring:
application:
name: service-order

# Dubbo 配置
dubbo:
application:
name: service-order
qos-enable: false
registry:
address: N/A # 将注册中心设置为 N/A,不向任何远程注册中心注册
protocol:
name: dubbo
port: 20880
serialization: fastjson2
prefer-serialization: fastjson2
scan:
base-packages: com.owlias.janus.service


user 模块(消费者)

UserController:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.owlias.janus.model.OrderDTO;
import com.owlias.janus.service.OrderService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {

// 使用 @DubboReference 注入远程服务,url 属性指定了 service-order 的本地 Dubbo 协议地址
@DubboReference(url = "dubbo://localhost:20880")
private OrderService orderService;

@GetMapping("/orders/{userId}")
public List<OrderDTO> getUserOrders(@PathVariable("userId") String userId) {
// 像调用本地方法一样,直接调用远程的 Service
return orderService.getOrdersByUserId(userId);
}
}

UserApp:

1
2
3
4
5
6
@SpringBootApplication
public class UserApp {
public static void main(String[] args) {
SpringApplication.run(UserApp.class,args);
}
}

application.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 8082

spring:
application:
name: service-user

dubbo:
application:
name: service-user
qos-enable: false
registry:
address: N/A
protocol:
serialization: fastjson2
prefer-serialization: fastjson2

另外,如果你使用的是 JDK9+,由于引入的强封装特性,为了不让应用在运行的时候报 “java.lang.reflect.InaccessibleObjectException: Unable to make field final int java.math.BigInteger.signum accessible” 的问题,需要在启动服务(包括 Provider 和 Consumer)时,显式地告诉 JVM:允许第三方组件对特定的核心包进行反射。请在你的 IDE(IntelliJ IDEA)启动配置,或者打包后的启动脚本中,添加以下 VM options参数:

1
2
3
--add-opens java.base/java.math=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED


日志和监控上的优化

在 order 和 user 模块的配置文件中,增加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dubbo:
# 彻底关闭 Dubbo 针对整个应用的耗时性能监控(生产环境推荐,因为这个 Filter 会计算纳秒级时间差,高并发下很吃 CPU)
provider:
filter: -profiler
# 如果不需要通过 Prometheus 收集 Dubbo 的底层吞吐量监控指标,直接关闭 Dubbo 的 Metrics 收集
metrics:
# 显式关闭内置的 JVM、线程池、RPC 等指标收集器
enable-rpc: false
enable-jvm: false
enable-threadpool: false
enable-registry: false

logging:
level:
org.apache.dubbo: INFO
org.apache.dubbo.rpc.filter: ERROR
org.apache.dubbo.config.deploy.DefaultMetricsServiceExporter: ERROR


服务质量参数设置

你可能已经注意到,上述的配置文件中有一个 dubbo.qos.enable 配置,QoS 的全称是 Quality of Service(服务质量)。在 Dubbo 中,它特指在线运维命令空间。简单来说,开启这个参数就像是给你的微服务实例开了一个“后门控制台”。允许你在不重启服务、不依赖任何可视化页面的情况下,直接通过命令行(Telnet)连接到这个服务实例内部,去查看它的状态、甚至动态修改它的行为。

当 qos.enable=true 开启后,Dubbo 就会在当前服务中启动一个隐藏的 Netty 服务器(默认端口是 22222),并支持以下核心运维命令:

  • 动态服务上下线(核心高频场景)
    • offline(下线服务):让当前节点通知注册中心自己要下线,这样新的流量就不会再打到这台机器上,但现有的请求可以优雅地执行完。这个命令常用于滚动发布/无损发布。
    • online(上线服务):让当前节点重新向注册中心注册,开始接收流量。
  • 状态与依赖检查
    • ls:列出当前 Provide 的所有接口和消费 Consume 的所有接口,以及它们目前在注册中心的状态。
    • ready:检查当前 Dubbo 实例是否已经初始化完毕、可对外提供服务。常用于 K8s 的 Readiness Probe 就绪检查。
  • 查看与切换配置
    • change:动态修改某个服务的路由规则、权重等参数。

在开发环境可以视情况开启或者关闭,如果开启的话,需要注意不同的应用端口错开。

1
2
3
4
dubbo.qos.enable=true
dubbo.qos.port=22222
# 允许非本机 IP 连接(默认只允许 localhost 连接,如果需要远程运维,可设置为 false,但需注意安全)
dubbo.qos.accept.foreign.ip=false

生产/测试环境强烈建议开启,因为它是:

  • 无损发布的基石:在 CI/CD 自动化流水线发布新版本时,脚本会先通过 telnet 127.0.0.1 22222 执行 offline,这条命令会主动向注册中心如 Nacos 或 ZooKeeper 发送一个注销请求。注册中心收到后,会立即主动把这个变更通知推送给所有相关的消费者。消费者收到通知后,马上把自己本地缓存的负载均衡列表里踢掉这台机器。等这台机器的流量归零后,再进行停机升级。这样可以做到用户完全无感知的丝滑发布。
  • 使用 offline 的核心目的,就是利用 “主动通知” 代替 “被动等待超时*,并利用 “容器不退出” 留出缓冲时间,把原先不可控的“盲区报错时间”,变成了完全可控的“平滑过渡期”。这就是现代微服务架构能够做到 7x24 小时不停机滚动的底层秘密。
  • 容器探针利器:在 Kubernetes 中,你可以直接用 ready 命令来作为 Pod 的就绪和存活检查,非常原生。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ telnet localhost 22222
Trying ::1...
Connected to localhost.
Escape character is '^]'.
___ __ __ ___ ___ ____
/ _ \ / / / // _ ) / _ ) / __ \
/ // // /_/ // _ |/ _ |/ /_/ /
/____/ \____//____//____/ \____/
dubbo>ls
As Provider side:
+-----------------------------------------------------------------------------+---+
| Provider Service Name |PUB|
+-----------------------------------------------------------------------------+---+
|DubboInternal - service-order/org.apache.dubbo.metadata.MetadataService:1.0.0| |
+-----------------------------------------------------------------------------+---+
| com.owlias.janus.service.OrderService | |
+-----------------------------------------------------------------------------+---+
As Consumer side:
+---------------------+---+
|Consumer Service Name|NUM|
+---------------------+---+
dubbo>
dubbo>help


使用 zk 充当服务注册中心

使用 zk 带来的好处

上面的示例,我们将服务注册在了本地内存。现在我们将其注册在 zookeeper 集群中。关于 zookeeper 集群的搭建,请参考 《ZK的安装配置和常用命令》。换成真正的注册中心集群后,整个系统的可用性发生了质的飞跃:

  • 分布式高可用(CP模型):ZooKeeper 集群通过 ZAB 协议保证数据的一致性。只要集群中半数以上的机器存活(如3台挂1台),注册中心就能正常提供服务,告别单点故障。
  • 解耦动态扩容:如果你要给 service-order 扩容到 5 台机器,只需带着相同的 ZK 配置直接启动 5 个实例即可。service-user 会在毫秒级内自动收到 ZK 的 NodeChildrenChanged 事件通知,无需重启消费者。


应用程序的改动

首先,确保两个微服务(service-order 和 service-user)的 pom.xml 中都引入了 Dubbo 官方推荐的 ZooKeeper 注册中心客户端依赖(基于 Apache Curator):

1
2
3
4
5
6
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
<type>pom</type>
<version>3.2.19</version>
</dependency>

提供者端:service-order 的 application.yaml

我们需要把原来的 dubbo.registry.address=N/A 替换为集群地址,并显式指定元数据中心。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Dubbo 配置
dubbo:
application:
name: service-order
qos-enable: true
qos-port: 22222
registry:
address: zookeeper://192.168.1.149:2181?backup=192.168.1.7:2181,192.168.1.224:2181
protocol:
name: dubbo
port: 20880
serialization: fastjson2
prefer-serialization: fastjson2
scan:
base-packages: com.owlias.janus.service

消费者端:service-user 的 application.yaml

消费者端不再需要写死 @DubboReference(url = “dubbo://…”) 的直连方式,改由去 ZK 集群订阅。

1
2
3
4
5
6
7
8
9
10
dubbo:
application:
name: service-user
qos-enable: true
qos-port: 22223 # telnet localhost 22223
registry:
address: zookeeper://192.168.1.149:2181?backup=192.168.1.7:2181,192.168.1.224:2181
protocol:
serialization: fastjson2
prefer-serialization: fastjson2

同时,记得把 service-user 代码中注入 OrderService 的注解恢复为最纯粹的形态:

1
2
3
4
// 使用 @DubboReference 注入远程服务
// 删掉原先的 url 直连参数,现在它会自动去 ZK 里找服务
@DubboReference
private OrderService orderService;


注册数据在zk的存储目录

ZooKeeper 节点是如何存储的?当你把服务注册到 ZooKeeper 集群后,Dubbo 3.x 会在 ZK 内部建立清晰的目录树。理解这个结构有助于在面试中深入阐述 ZK 的底层机制。Dubbo 在 ZooKeeper 中主要利用了以下两种节点特性:

  • Persistent(持久节点):用于存放服务名、元数据信息、配置规则等不会随服务下线而消失的数据。
  • Ephemeral(临时节点):用于存放具体的服务实例运行数据(IP、Port、权重)。一旦某个服务节点宕机,ZK 会在心跳断开后自动将该临时节点删除,从而触发消费者的动态感知。

在 ZK 中常见的路径结构:

  • /dubbo/mapping(接口与应用的映射中心):
    • /dubbo/mapping/com.owlias.janus.service.OrderService:它的文本内容是 service-order,节点类型是持久节点。这是 Dubbo 3.x 应用级服务发现的敲门砖。当消费者(service-user)启动时,它手里只有接口名 OrderService。它会首先查这个 mapping 节点,得知:“哦!原来提供这个接口的应用名叫 service-order”。然后消费者才会去 /services/service-order 下面找具体的机器 IP。
  • /services(新版:应用级实例注册中心):看最下方的目录 /services/service-order/192.168.1.3:20880,是一个临时节点,存储的内容是这台机器的轻量级元数据(如当前节点的权重、应用版本等),它是真正的分布式数据寻址中心。一旦 service-order 实例挂了、断开了与 ZK 集群的心跳,这个节点会被 ZK 立刻自动删除,从而触发消费者端的动态下线。
  • /dubbo/metadata(新版:元数据报告中心):对应的目录是 /dubbo/metadata/com.owlias.janus.service.OrderService,它的下面所挂节点分为了 /provider/service-order 和 /consumer/service-user。
    • 作用:由于应用级注册(上述 /services)只在 ZK 里存了极简的 IP:Port,那接口里有哪些方法?方法的返回值和参数是什么?这些沉重的 “接口全量信息” 就被剥离了出来,统一存放在这个 metadata(元数据中心)里。
    • 好处:极大地减轻了注册中心集群在面对几万个服务实例频繁上下线时的同步通知压力(这也是 Dubbo 3.x 支撑超大规模集群的秘密武器)。
  • /dubbo/com.owlias...(老版:接口级注册中心 - 兼容模式)
    • 作用:这是标准的 Dubbo 2.x 老版存储结构。Dubbo 3.x 默认启动时会采用 双注册(Dual-Register) 策略。即它既往新版的 /services 里注册应用,也会往老版的接口目录下注册具体的 URL 协议。
    • 意义:这样能保证你公司里如果还有老旧的 Dubbo 2.x 服务,它们依然能通过这个老目录和你的新系统正常互通。在完全升级到 Dubbo 3 后,这个双注册行为可以通过配置关闭以节省 ZK 空间。


关闭双注册的方法

在 Dubbo 3.x 中,默认采用的双注册(Dual-Register)策略是为了平滑迁移老项目。如果你的项目全部已经迁移完毕,那么就可以关掉老版的接口级注册可以极大地减少 ZooKeeper 的存储压力,并提升注册中心的性能。关闭双注册,只要在提供者端(不往老目录写)更改即可,新版的消费者端默认优先尝试新模式,失败则降级走老模式。

1
2
3
# 指定注册行为:只注册应用级(新),不注册接口级(老)
# 默认值是 ALL(双注册),可选值有:INSTANCE, INTERFACE, ALL
dubbo.application.register-mode=INSTANCE


使用 nacos 充当注册中心

从 ZooKeeper 切换到 Nacos 充当注册中心,是目前国内微服务生态(尤其是结合 Spring Cloud Alibaba 或 Dubbo 3)最主流的架构选择。Nacos 在服务治理上属于 AP 模型(ZooKeeper 是 CP 模型),能承受更高并发的实例上下线,且自带一个非常漂亮直观的 Web 控制台。


Nacos 服务端准备

如果你是在本地或开发服务器部署,可以直接下载官方编译好的压缩包:

1
2
3
4
# 以 Nacos 2.3.2 为例(根据你的实际版本选择)
wget https://github.com/alibaba/nacos/releases/download/2.3.2/nacos-server-2.3.2.tar.gz
tar -zxvf nacos-server-2.3.2.tar.gz
cd nacos/bin

在 Nacos 2.x/3.x 中,默认情况下出于开发便利是不开启用户鉴权(Authentication)的,这意味着任何知道你 Nacos IP 和端口的人,都可以随意查看、修改、删除你的核心微服务配置和注册数据。在生产或正式测试环境中,必须开启鉴权。开启鉴权不需要重新下载 Nacos,只需要修改服务端配置文件即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ vim conf/application.properties

# 1. 开启鉴权功能(核心开关,默认是 false)
nacos.core.auth.enabled=true

# 2. 配置自定义的加密 Key(防坑:必须是 Base64 编码,且长度不能低于 32 位!)
# 推荐自己生成一个随机串。这里给出一个示例串( owliasjanussecretkey2026bestnacoskey )对应的 Base64:
nacos.core.auth.plugin.nacos.token.secret.key=b3dsaWFzamFudXNzZWNyZXRrZXkyMDI2YmVzdG5hY29za2V5

# 3. 鉴权体系对应的 Token 有效期(单位:秒,默认 5 小时)
nacos.core.auth.plugin.nacos.token.expire.seconds=18000

# 4. 设置服务端内部通信的 Identity Key
nacos.core.auth.server.identity.key=owliasJanusKey

# 5. 设置服务端内部通信的 Identity Value
nacos.core.auth.server.identity.value=owliasJanusValue

启动 Nacos(单机运行模式),本地测试不需要搭建复杂的集群,直接以单机(Standalone)模式启动:

1
sh startup.sh -m standalone

打开浏览器,访问 Nacos 自带的控制台:

  • URL: http://192.168.1.149:8848/nacos
  • 默认账号密码: nacos / nacos (新版本首次登录可能会强制要求修改密码)。
  • 核心防坑端口:确保服务器的 8848(HTTP 端口)、9848 和 9849(gRPC 内部通信端口,由 8848 偏移而来)这三个端口在防火墙中都是放行的。

刚才我们的开发 nacos 没有配置外部数据库(如 MySQL)的情况下,Nacos 依然能正常运行并保存你的账号、密码和配置数据,是因为它内置了一个轻量级的嵌入式数据库 —— Derby(Apache Derby)。它默认的数据存放目录如下。这个 derby-data 目录里存放的就是 Derby 数据库的二进制文件,Nacos 鉴权账号、密码(加密后的密文)、以及你在控制台创建的所有配置,全部被持久化在这些文件里。即使你重启电脑或重启 Nacos,数据也不会丢失。正式的测试环境和生产环境中,请自行改成 mysql 等数据库存储。

1
2
3
4
5
6
nacos/
├── bin/
├── conf/
├── target/
└── data/ <─── 就在这里!
└──derby-data/ <─── 内置数据库的物理文件

切换mysql数据库步骤:

  • 初始化表结构: 在你的 MySQL 中创建一个名为 nacos 的数据库。然后把 nacos/conf/mysql-schema.sql 这个官方自带的脚本导入进去,它会帮你建好用户表、配置表等十几张核心表。

  • 修改配置文件:nacos/conf/application.properties,取消掉关于 MySQL 的注释,改成你的数据库信息:

    1
    2
    3
    4
    5
    6
    spring.datasource.platform=mysql

    db.num=1
    db.url.0=jdbc:mysql://xxx:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
    db.user.0=root
    db.password.0=你的数据库密码
  • 重启 Nacos:再次启动 Nacos,它就会自动放弃内置的 Derby,转而全量依赖你的 MySQL。


应用调整

我们需要把原先的 ZooKeeper 客户端依赖删掉,换成 Dubbo 官方提供的 Nacos 注册中心适配器。

打开 service-order 和 service-user 的 pom.xml:

1
2
3
4
5
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>3.2.19</version>
</dependency>

配置文件的改造。现在我们利用 Nacos 统一接管 Dubbo 3.x 的 注册中心元数据中心。由于我们要彻底关闭双注册、直接走高性能的应用级发现,配合 Dubbo 3.2.x 的参数特性,改造如下。

提供者端:service-order 的 application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
server:
port: 8081

spring:
application:
name: service-order

# Dubbo 配置
dubbo:
application:
name: service-order
qos-enable: true
qos-port: 22222
# 彻底关闭老版接口级注册,只往 Nacos 注册应用级实例
register-mode: INSTANCE
registry:
# registry(注册中心)对应 Nacos 的服务管理(对应zk的临时节点)。
# 单机/集群皆可,多台用逗号隔开
address: nacos://192.168.1.149:8848?username=nacos&password=nacos
metadata-report:
# metadata-report(元数据中心)和 config-center(配置中心)本质上都体现在 Nacos 的配置管理中(对应zk的持久节点)。
address: nacos://192.168.1.149:8848?username=nacos&password=nacos
config-center:
address: nacos://192.168.1.149:8848?username=nacos&password=nacos
protocol:
name: dubbo
port: 20880
serialization: fastjson2
prefer-serialization: fastjson2
scan:
base-packages: com.owlias.janus.service

消费者端:service-user 的 application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server:
port: 8082

spring:
application:
name: service-user

dubbo:
application:
name: service-user
# 关闭本地消费者的 QoS 端口,避免本地启动冲突
qos-enable: false
registry:
address: nacos://192.168.1.149:8848?username=nacos&password=nacos
metadata-report:
address: nacos://192.168.1.149:8848?username=nacos&password=nacos
config-center:
address: nacos://192.168.1.149:8848?username=nacos&password=nacos
protocol:
serialization: fastjson2
prefer-serialization: fastjson2


启动与控制台验证

完成上述改造后,依次启动 service-order 和 service-user。

  • 第一,检查 Nacos 服务列表。刷新 Nacos Web 控制台(http://192.168.1.149:8848/nacos), 点击左侧菜单栏 服务管理 -> 服务列表。在列表中,你应该能清晰地看到注册上来的应用名 service-order,点击详情可以看到具体的机器 IP(例如 192.168.1.3:20880),这证明应用级服务发现已经完全在 Nacos 落地。
  • 第二,检查 Dubbo 元数据。点击左侧菜单栏 配置管理 -> 配置列表(或者在 Nacos 的元数据菜单中,取决于具体集成的控制台插件),由于我们配置了 metadata-report,Dubbo 会把 OrderService 的接口定义、方法名、入参出参等全量元数据以 JSON 格式持久化在 Nacos 中,保持了注册中心服务列表的绝对轻量。


Nacos 服务管理:

Nacos 元数据和配置管理:


dubbo 协议升级为 Triple 协议

如何升级?

上述在 application.properties 里配的:

1
dubbo.protocol.name=dubbo

使用的正是 Dubbo 框架最经典、沿用了十几年的 Dubbo2 协议(基于 TCP 长连接和自定义二进制序列化协议)。而在 Dubbo 3.x 时代,官方最为推崇、代表未来的则是 Triple 协议。Triple 协议是 Dubbo 3 创新的下一代云原生 RPC 协议。它在底层完全基于 HTTP/2,并 100% 兼容 gRPC 核心概念。换成 Triple 协议后,你会获得以下突破性的优势:

  • 跨语言互通:经典的 Dubbo 协议强绑定 Java(很难用前端 JS、Go 或 Python 直接调用)。而 Triple 基于 HTTP/2 和 Protobuf,前端浏览器、移动端 App、或者其它任何语言写的微服务,都能像调用普通 HTTP 接口一样直接调用你的 Dubbo 服务。
  • 原生支持流式调用(Streaming):支持像微信聊天、大模型打字机输出(SSE)那样的 “客户端流”、“服务端流” 和 “双向流” 通信。
  • 完美的网关穿透性:因为本质上就是 HTTP/2,普通的标准网关(如 Nginx、Envoy、Kong)不需要任何特殊插件就能直接对 Triple 流量进行路由、限流和安全审计。

从经典 Dubbo 协议切换到 Triple 协议非常简单,不需要修改任何业务代码,只需要加入依赖并动一动配置文件即可。

提供者端:service-order 的改造

加入依赖:

1
2
3
4
5
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-triple</artifactId>
<version>3.2.19</version>
</dependency>

配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dubbo:
application:
name: service-order
qos-enable: true
qos-port: 22222
# 彻底关闭老版接口级注册,只往 Nacos 注册应用级实例
register-mode: INSTANCE
# 明确告诉注册中心,首选注册协议是 triple
protocol: tri
protocol:
# 将协议名称改为 tri
name: tri
# 修改端口(虽然可以用原端口,但习惯上 Triple 推荐使用 50051 等标准 gRPC 端口区分)
port: 50051
serialization: fastjson2
prefer-serialization: fastjson2

消费者端:service-user 的改造

如果你的消费者和提供者用的是同一个注册中心(Nacos/ZK),消费者端甚至不需要做任何协议配置! 因为当 service-order 启动并把自己的协议(tri://…)注册到 Nacos 之后,service-user 在拉取通讯录时会自动识别到对方支持 Triple,并自动在底层切换为 Triple 协议发起调用。

唯一需要注意的场景(直连模式):如果你在本地开发时使用了直连模式(即绕过注册中心,在代码里写了 @DubboReference(url = “…”)),你需要把 URL 的前缀改掉:

  • 旧直连:dubbo://127.0.0.1:20880
  • 新直连:tri://127.0.0.1:50051

不过为了防止客户端的一些警告,可以在配置中加入如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dubbo:
consumer:
# 强制宣告当前应用是一个“纯客户端”,禁止 Dubbo 在底层为任何协议创建和绑定本地的监听端口(Server Port)
client: true
# 强制让消费者在不指定协议时,默认优先去 Nacos 订阅 triple 协议的服务地址
protocol: tri
application:
name: service-user
# 关闭本地消费者的 QoS 端口,避免本地启动冲突
qos-enable: false
# 明确告诉注册中心,首选注册协议是 triple
protocol: tri
# 显式规定应用以应用级(Instance)模式进行服务发现与注册
register-mode: instance

由于 Triple 协议具有极强的兼容性,最直观的验证方法就是直接用浏览器或 cURL 命令行去肉身测试你的 RPC 接口。在 Triple 协议下,Dubbo 默认开启了 HTTP/1.1 路由映射。这意味着你原先只能通过 RPC 调用的接口,现在变成了一个标准的 RESTful API。打开终端,直接执行 cURL(假设你的接口叫 OrderService,方法叫 createOrder,接收一个 String 参数):

1
2
3
curl -X POST http://localhost:50051/com.owlias.janus.service.OrderService/getOrdersByUserId \
-H "Content-Type: application/json" \
-d '"11"'

如果能直接收到后端返回的 JSON 响应,说明你的分布式架构已经正式跨入了云原生 Triple(HTTP/2)时代。在 nacos 服务详情,我们也可以看到 service-order 的服务协议改为了 tri。


tripe 协议的流式调用

定义 Stream 接口

通常我们的接口需要定义在 common 模块,因为接口定义需要使用到 StreamObserver 类,所以现在我们需要在 common 中引入 dubbo-rpc-triple 包,将 service-order 中的这个包转移到 common 中。dubbo-rpc-triple 这个包非常轻量,它只包含了流式调用所需的接口规范(如 StreamObserver 及其基础实现),它会让我们的公共 API 模块保持绝对的干净和轻量。

1
2
3
4
5
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-triple</artifactId>
<version>3.2.19</version>
</dependency>

在 common 模块中定义 Stream 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.owlias.janus.service;

import com.owlias.janus.model.OrderDTO;
import org.apache.dubbo.common.stream.StreamObserver;

public interface OrderStreamService {

/**
* 1. 服务端流式(Server Stream)
* 客户端发送一个请求(userId),服务端通过流分批、源源不断地返回多个订单
*/
void getOrdersServerStream(String userId, StreamObserver<OrderDTO> responseObserver);

/**
* 2. 客户端流式(Client Stream)
* 客户端源源不断地发送多个订单(比如批量同步),服务端最后只返回一个汇总结果(String)
*/
StreamObserver<OrderDTO> syncOrdersClientStream(StreamObserver<String> responseObserver);

/**
* 3. 双向流式(Bidirectional Stream / 混用)
* 客户端源源不断地发,服务端也源源不断地回,两边像聊天一样异步通信
*/
StreamObserver<OrderDTO> biStream(StreamObserver<OrderDTO> responseObserver);
}


服务端流式案例

客户端 “说一句话”(发一个请求),服务端就像 “打字机”一样,源源不断地把数据吐给客户端,直到全部吐完。典型的场景比如大模型打字机、批量数据分批下载。

服务端(Provider)的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import com.owlias.janus.model.OrderDTO;
import org.apache.dubbo.common.stream.StreamObserver;
import org.apache.dubbo.config.annotation.DubboService;

@DubboService(timeout = 300000) // 针对流式服务,我们应该在服务端(Provider)明确调大或取消该接口/方法的超时时间限制
public class OrderStreamServiceImpl implements OrderStreamService {

@Override
public void getOrdersServerStream(String userId, StreamObserver<OrderDTO> responseObserver) {
System.out.println("收到 ServerStream 请求,用户 ID: " + userId);

// 模拟源源不断地产生 3 个订单并推回给客户端
for (int i = 1; i <= 3; i++) {
OrderDTO order = new OrderDTO("ORDER_" + i, "USER_" + i, 100.0 * i);

// 通过 onNext 一笔一笔推过去
responseObserver.onNext(order);

try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
}

// 吐完了,告诉客户端:我发完了
responseObserver.onCompleted();
}

// 另外两个方法先略过...
}

客户端(Consumer)调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Component
public class StreamClientTest {

@DubboReference
private OrderStreamService orderStreamService;

public void testServerStream() {
orderStreamService.getOrdersServerStream("zhangsan", new StreamObserver<OrderDTO>() {
@Override
public void onNext(OrderDTO data) {
// 每当服务端吐出一个订单,这里就会被触发一次
System.out.println("【ServerStream 接收】: " + data.getOrderId());
}

@Override
public void onError(Throwable throwable) {
System.err.println("流发生异常: " + throwable.getMessage());
}

@Override
public void onCompleted() {
System.out.println("【ServerStream 结束】服务端全部发完了!");
}
});
}
}


客户端流式案例

客户端源源不断地把数据塞给管道,服务端一直憋着不说话,等到客户端高喊“我发完了!”之后,服务端才最后给出一个最终答复。典型的场景例如物联网数据上报、大文件分片上传。

服务端(Provider)实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
public StreamObserver<OrderDTO> syncOrdersClientStream(StreamObserver<String> responseObserver) {
// 返回一个 StreamObserver 给客户端,用来接收客户端源源不断发来的订单
return new StreamObserver<OrderDTO>() {
private int count = 0;

@Override
public void onNext(OrderDTO data) {
// 客户端每发过来一个订单,这里就会触发一次
count++;
System.out.println("【ClientStream 收到来自客户端的订单】: " + data.getOrderId());
}

@Override
public void onError(Throwable throwable) {
System.err.println("客户端流异常");
}

@Override
public void onCompleted() {
// 关键点:当客户端高喊“我发完了”(调用了 onCompleted),服务端把最终结果一次性返回
responseObserver.onNext("同步成功!共收到 " + count + " 个订单。");
responseObserver.onCompleted(); // 结束服务端的生命周期
}
};
}

客户端(Consumer)调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void testClientStream() throws InterruptedException {
// 1. 传入一个接收器,用来听取服务端的“最终宣判”
StreamObserver<OrderDTO> requestObserver = orderStreamService.syncOrdersClientStream(new StreamObserver<String>() {
@Override
public void onNext(String data) {
System.out.println("【ClientStream 最终结果】: " + data);
}

@Override
public void onError(Throwable throwable) {}

@Override
public void onCompleted() {
System.out.println("整个客户端流调用完美闭环。");
}
});

// 2. 客户端开始源源不断地狂塞数据
requestObserver.onNext(new OrderDTO("ORDER_1", "USER_1", 50.0));
Thread.sleep(500);
requestObserver.onNext(new OrderDTO("ORDER_2", "USER_2", 75.0));
Thread.sleep(500);

// 3. 告诉服务端:我发射完毕了!
requestObserver.onCompleted();
}


双向流 Bidirectional Stream

在分布式和微服务架构中,把 ServerStream 和 ClientStream 融合在一起的模式,官方术语叫做 “双向流式调用(Bidirectional Streaming)”。它完全打破了传统 RPC “请求-响应”的死板束缚。它的底层是一条完全打通的、基于 HTTP/2 的全双工长连接通道。客户端和服务端可以完全处于异步状态,你发你的,我回我的,甚至服务端可以根据客户端发过来的前两个包,实时算好先吐回三个包给客户端。 像极了双向网络聊天室或股票K线实时量价同步。

混用(双向流)案例:实时订单对账流水。客户端每向服务端上报一个 “本地订单”,服务端收到后立刻去数据库查验,并实时把对账后的 “带状态的订单流水” 吐回给客户端。

服务端(Provider)实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public StreamObserver<OrderDTO> biStream(StreamObserver<OrderDTO> responseObserver) {
// 返回客户端一个接收器
return new StreamObserver<OrderDTO>() {
@Override
public void onNext(OrderDTO clientOrder) {
System.out.println("【双向流】收到客户端上报: " + clientOrder.getOrderId());

// 收到一个,立刻原地计算,并实时推回一个结果给客户端!
OrderDTO processedOrder = new OrderDTO(
clientOrder.getOrderId(),
clientOrder.getUserId(),
clientOrder.getPrice()
);
processedOrder.setRemark("服务端已清洗 -> 状态:[合法]");

// 实时吐回
responseObserver.onNext(processedOrder);
}

@Override
public void onError(Throwable throwable) {
System.err.println("双向流中断");
}

@Override
public void onCompleted() {
System.out.println("客户端全部上报完毕,服务端关闭通道。");
responseObserver.onCompleted();
}
};
}

客户端(Consumer)调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public void testBiStream() throws InterruptedException {
// 1. 先定义如何接收服务端的实时回弹数据
StreamObserver<OrderDTO> requestObserver = orderStreamService.biStream(new StreamObserver<OrderDTO>() {
@Override
public void onNext(OrderDTO serverProcessedData) {
System.out.println("【双向流 实时弹回】: " + serverProcessedData.getOrderId() + " -> " + serverProcessedData.getRemark());
}

@Override
public void onError(Throwable throwable) {}

@Override
public void onCompleted() {
System.out.println("双向通道圆满关闭。");
}
});

// 2. 客户端欢快地一边干别的事情,一边源源不断地塞数据
System.out.println("客户端开始发送流水...");
requestObserver.onNext(new OrderDTO("ORDER_9001", "USER_1", 12.0));
Thread.sleep(1000);

requestObserver.onNext(new OrderDTO("ORDER_9002", "USER_2", 450.0));
Thread.sleep(1000);

// 3. 彻底结束
requestObserver.onCompleted();
}

因为 Triple 流式调用是完全异步的,你在写测试用例时,如果执行完 requestObserver.onCompleted() 后不加 Thread.sleep(5000) 让主线程等一下,Java 进程就会秒闪退,你就看不到底层 Netty 异步弹回来的精彩数据了。


tripe 协议跨语言调用

在 Dubbo 3 中,Triple 协议(基于 HTTP/2 和 Protobuf)最大的核心卖点正是 跨语言、跨生态的高性能互通。由于采用了 Google 的 Protocol Buffers (Protobuf) 作为通用的接口定义语言(IDL),你只需要编写一份 .proto 文件,就可以利用编译器同时生成 Java、Go、Node.js、Python 等各种语言的桩代码(Stub)。此时的 Dubbo 3 Triple 彻底打破了 Java 阵营的限制,完全演化为了标准、高规格的 gRPC 架构。下面我们就以 Java (服务端) 与 Go 语言 (客户端) 为例,实现一个跨语言的订单调用案例。


编写proto 接口文件

在公共模块中,创建一个名为 order_stream.proto 的文件。不论是 Java 还是 Go,两端都要基于这个文件来生成代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
syntax = "proto3";

// Java 相关的生成配置
option java_multiple_files = true;
option java_package = "com.owlias.janus.service.api";
option java_outer_classname = "OrderStreamProto";

// Go 相关的生成配置
option go_package = "owlias/janus/api;api";

package com.owlias.janus.service;

// 定义跨语言的 Triple 服务
service OrderXLangService {
// 跨语言普通 RPC
rpc GetOrder (OrderRequest) returns (OrderReply);
}

// 请求对象
message OrderRequest {
string user_id = 1;
string order_id = 2;
}

// 响应对象
message OrderReply {
string order_id = 1;
double price = 2;
string status = 3;
}


Java 端的 Provider

编译生成:在 Java 的 pom.xml 中引入 dubbo-compiler 和 protobuf-maven-plugin 插件,执行 mvn compile。 插件会自动根据上面的 .proto 文件生成 Java 类:OrderRequest、OrderReply 以及核心的接口 OrderXLangService。

实现业务逻辑:生成的接口会自带一个内部的 OrderXLangServiceImplBase 抽象类,我们直接继承并实现它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.owlias.janus.service.impl;

import com.owlias.janus.service.api.OrderReply;
import com.owlias.janus.service.api.OrderRequest;
import com.owlias.janus.service.api.DubboOrderXLangServiceTriple;
import org.apache.dubbo.config.annotation.DubboService;

@DubboService // 挂载到 Dubbo 3
public class OrderXLangServiceImpl extends DubboOrderXLangServiceTriple.OrderXLangServiceImplBase {

@Override
public OrderReply getOrder(OrderRequest request) {
System.out.println("【Java 服务端】收到来自异构语言(Go)的请求, 用户: " + request.getUserId());

// 使用 Protobuf 的 Builder 模式构建响应
return OrderReply.newBuilder()
.setOrderId(request.getOrderId())
.setPrice(599.0)
.setStatus("SUCCESS_FROM_JAVA")
.build();
}
}


Go 端的 Consumer

在 Go 语言生态中,我们直接使用 Dubbo-Go 3.2.x 框架。

编译生成 Go 桩代码:在 Go 目录下,使用 protoc 编译器和 dubbo-go-cli 插件生成 Go 的 _triple.pb.go 文件:

1
protoc --go_out=. --go-triple_out=. order_stream.proto

编写 Go 客户端调用代码。

Go 客户端完全不需要感知 Java 的存在,它只需要通过标准的 Triple 协议,朝着 Java 暴露的 50051 端口直接发起 gRPC/Triple 调用即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package main

import (
"context"
"fmt"
"os"

"dubbo.apache.org/dubbo-go/v3/config"
_ "dubbo.apache.org/dubbo-go/v3/imports"
"owlias/janus/api" // 引入刚才通过 protoc 生成的 go 桩代码包
)

// 1. 定义一个客户端结构体,绑定生成的接口引用
type OrderClientImpl struct {
GetOrder func(ctx context.Context, req *api.OrderRequest) (*api.OrderReply, error)
}

func main() {
// 2. 初始化 Dubbo-Go 框架配置(可直接配置连接 Nacos,这里以本地直连演示)
clientConfig := &config.ClientConfig{
References: map[string]*config.ReferenceConfig{
"OrderXLangServiceClient": {
Interface: "com.owlias.janus.service.OrderXLangService", // 必须和 proto 里的 package+service 对齐
Protocol: "tri", // 明确指定走 Triple 协议
URL: "tri://127.0.0.1:50051", // 目标 Java 服务的地址与端口
},
},
}

rootConfig := config.NewRootConfigBuilder().SetClient(clientConfig).Build()
if err := config.LoadWithOptions(config.WithRootConfig(rootConfig)); err != nil {
panic(err)
}

// 3. 实例化我们的客户端
orderClient := &OrderClientImpl{}
rootConfig.GetConsumerConfig().References["OrderXLangServiceClient"].SetConsumerService(orderClient)

// 4. 发起跨语言 RPC 调用
fmt.Println("【Go 客户端】开始向 Java 服务端发起 Triple 协议调用...")

req := &api.OrderRequest{
UserId: "zhangsan",
OrderId: "XLANG_9999",
}

reply, err := orderClient.GetOrder(context.Background(), req)
if err != nil {
fmt.Printf("调用失败: %v\n", err)
os.Exit(1)
}

// 5. 打印从 Java 端跨语言吐回的数据
fmt.Printf("【Go 客户端】收到 Java 返回结果 -> 订单号: %s, 价格: %.2f, 状态: %s\n",
reply.OrderId, reply.Price, reply.Status)
}


跨语言联调验证

  • 启动 Java 端:运行 service-order。Java 进程会在后台监听 50051 端口,等待 HTTP/2 + Protobuf 流量。
  • 运行 Go 端:在 Go 目录下执行 go run main.go。

Go 终端输出:

1
2
【Go 客户端】开始向 Java 服务端发起 Triple 协议调用...
【Go 客户端】收到 Java 返回结果 -> 订单号: XLANG_9999, 价格: 599.00, 状态: SUCCESS_FROM_JAVA

Java 控制台输出:

1
【Java 服务端】收到来自异构语言(Go)的请求, 用户: zhangsan

为什么 Triple 协议跨语言这么轻松?因为在底层,当使用了 Protobuf 之后,Dubbo 3 的 Triple 协议在 Wire 级别(网络传输字节流)已经和普通的 gRPC 协议 100% 兼容了。这意味着,你甚至不需要在 Go 语言端引入 Dubbo-Go 框架,直接用原生最纯粹的 gRPC Go 官方标准库客户端,也能一枪直接打通 Java 端的 Dubbo 3 服务!这就让我们的微服务系统具备了和任何现代技术栈(Python 大模型生态、Node.js 前端网关等)无缝握手的能力。