Nacos + Spring Boot 3 + Dubbo 3 + gRPC/Triple 整合实战

欢迎你来读这篇博客,这篇博客主要是关于 Nacos + Spring Boot 3 + Dubbo 3 + gRPC/Triple 的完整整合实践。

这不是一篇“贴几个依赖就结束”的文章,而是按真实项目的方式,从架构选型、版本选择、工程结构、Proto 契约、Provider、Consumer、Nacos 注册发现、gRPC 兼容访问、参数治理、排错清单一路跑通。

序言

在 Java 微服务体系里,Spring Boot 3 解决的是应用开发与自动装配问题,Dubbo 3 解决的是 RPC 调用、服务治理、负载均衡、集群容错、协议抽象问题,Nacos 解决的是注册发现和配置管理问题,gRPC/Protobuf 解决的是跨语言、强类型、高性能接口契约问题。

把它们放在一起,最推荐的落地方式不是“Spring Boot 起一个原生 gRPC 服务,然后再旁边接一个 Dubbo”,而是使用 Dubbo 3 Triple

Triple 是 Dubbo 3 提出的基于 HTTP/2 的 RPC 协议,它兼容 gRPC 生态。也就是说:

  • Java 内部服务可以用 Dubbo 的 @DubboService@DubboReference、Nacos 注册发现、负载均衡、超时、重试、分组、版本等能力。
  • 跨语言调用可以基于 Protobuf IDL 生成客户端,按 gRPC 风格调用。
  • Spring Boot 3 只负责应用启动、Web 调试入口、配置管理、Actuator 观测等外围能力。

一句话:Dubbo 3 Triple = Dubbo 服务治理能力 + gRPC/HTTP2/Protobuf 互通能力。

当然,名字里有 gRPC,就很容易让人头大:到底是 Dubbo Triple?还是原生 gRPC?还是 Dubbo 的 grpc protocol?别急,本文会拆开讲清楚,不然这几个概念站一起像开了个技术圆桌,谁也不让谁先发言。

正文

一、整体架构

本文实现一个最小但完整的业务案例:

  • rpc-api:定义 .proto 契约,并生成 Protobuf 消息类和 Dubbo Triple Stub。
  • user-provider:用户服务提供方,通过 Dubbo 3 Triple 暴露 RPC 服务,并注册到 Nacos。
  • order-consumer:订单服务消费方,通过 Nacos 发现 Provider,并使用 Dubbo 3 调用远程用户服务。
  • grpcurl:模拟标准 gRPC 客户端,直接调用 Dubbo Triple 暴露的服务。
flowchart LR
    A[order-consumer<br/>Spring Boot 3] -->|DubboReference| B[Nacos Registry]
    C[user-provider<br/>Spring Boot 3] -->|register instance| B
    A -->|Triple / HTTP2 / Protobuf| C
    D[grpcurl / Go / Node / Python gRPC Client] -->|gRPC compatible call| C

    subgraph api[rpc-api]
      P[user.proto]
      G[Generated Java Message + Dubbo Stub]
    end

    api --> A
    api --> C

1.1 为什么不用原生 gRPC Starter 作为主线?

原生 gRPC 在 Java 中当然可以直接用,比如 grpc-javagrpc-spring-boot-starter。但是一旦进入企业微服务,你马上会遇到这些问题:

  • 服务注册发现怎么接?
  • 多环境隔离怎么做?
  • 超时、重试、负载均衡、版本、分组怎么统一治理?
  • Java 内部调用如何保持 Dubbo 生态?
  • 以后要不要接 Dubbo Admin、路由规则、权重、标签路由?

如果你的系统本来就在 Dubbo 体系里,那直接用 Dubbo 3 Triple + Nacos 会顺很多。原生 gRPC 更适合:

  • 多语言服务占主导;
  • 服务治理靠 Kubernetes / Service Mesh / xDS / Envoy;
  • 项目不需要 Dubbo 的生态能力;
  • 对 Dubbo 完全没有历史包袱。

本文主线是:Spring Boot 3 + Dubbo 3 + Triple + Protobuf + Nacos


二、版本选择

本文示例版本如下:

组件 示例版本 说明
JDK 17+ Spring Boot 3 最低要求 Java 17
Spring Boot 3.5.x 本文使用 Spring Boot 3 系列
Dubbo 3.3.x 使用 Dubbo 3 的官方 Spring Boot Starter
Nacos Server 3.2.x 或 2.3.x+ 本地已有 Nacos 3.2.x 可以使用;保守演示可用 2.3.x
Protobuf 3.25.x 用于 IDL 和消息生成
Maven 3.8+ Spring Boot 3 与 Dubbo 3 项目常规构建

建议固定版本,不要所有东西都写 latest。微服务版本管理不是买彩票,中奖概率不高,但踩坑概率挺稳定。

2.1 关于 Nacos 版本的说明

Dubbo 3.3 官方文档给出的 Nacos 推荐客户端版本是 2.3.0,兼容范围是 Nacos 2.x。Nacos 3.x 官方升级文档说明,Nacos 3.x Server 对 2.x Client 和 3.x Client 是兼容的,但不再兼容 0.x/1.x Client。

所以你有两种选择:

  • 保守稳定版:Dubbo 3.3.x + Nacos Server 2.3.x/2.4.x。
  • 较新版:Dubbo 3.3.x + Nacos Server 3.2.x,先在本地和测试环境验证注册发现、配置中心、鉴权、namespace、group。

本文配置以 nacos://127.0.0.1:8848 为例。


三、启动 Nacos

3.1 Docker 启动 Nacos 3.2.x

本地开发可以直接用 standalone 模式:

1
2
3
4
5
6
7
8
docker run -d \
--name nacos-standalone \
-e MODE=standalone \
-e NACOS_AUTH_ENABLE=false \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
nacos/nacos-server:v3.2.2

访问控制台:

1
http://127.0.0.1:8848/nacos

如果你开启鉴权:

1
2
3
4
5
6
7
8
9
10
11
docker run -d \
--name nacos-standalone \
-e MODE=standalone \
-e NACOS_AUTH_ENABLE=true \
-e NACOS_AUTH_TOKEN='your-base64-secret-token-must-be-long-enough' \
-e NACOS_AUTH_IDENTITY_KEY='serverIdentity' \
-e NACOS_AUTH_IDENTITY_VALUE='security' \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
nacos/nacos-server:v3.2.2

然后 Dubbo 注册地址要带上账号密码:

1
2
3
dubbo:
registry:
address: nacos://127.0.0.1:8848?username=nacos&password=nacos

3.2 Zip 包方式启动

如果你是 zip 包安装:

1
2
cd nacos/bin
sh startup.sh -m standalone

停止:

1
sh shutdown.sh

3.3 端口说明

端口 作用
8848 Nacos 控制台、OpenAPI、客户端访问主端口
9848 Nacos 2.x/3.x 客户端 gRPC 通信端口
9849 Nacos 服务端集群间 gRPC 通信端口

本地 Docker 如果只暴露 8848,有时控制台能打开,但客户端注册发现会出问题。这个坑很常见,像插头只插了一半,看起来通电,实际上 CPU 已经在心里骂人。


四、工程结构

建议使用 Maven 多模块工程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nacos-dubbo3-grpc-demo
├── pom.xml
├── rpc-api
│ ├── pom.xml
│ └── src/main/proto/user.proto
├── user-provider
│ ├── pom.xml
│ └── src/main/java/com/cybermario/demo/user
│ ├── UserProviderApplication.java
│ └── rpc/UserRpcServiceImpl.java
└── order-consumer
├── pom.xml
└── src/main/java/com/cybermario/demo/order
├── OrderConsumerApplication.java
├── controller/OrderController.java
└── service/OrderService.java

为什么要把 proto 放到 rpc-api

因为接口契约应该是独立模块,Provider 和 Consumer 都依赖它。这样能避免 Provider 和 Consumer 互相引用业务模块,后面做多语言接入时,也可以把 proto 文件单独发布给 Go、Node、Python、Rust 等客户端。


五、父工程 pom.xml

根目录 pom.xml

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
57
58
59
60
61
62
63
64
65
66
67
68
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.cybermario.demo</groupId>
<artifactId>nacos-dubbo3-grpc-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>

<modules>
<module>rpc-api</module>
<module>user-provider</module>
<module>order-consumer</module>
</modules>

<properties>
<java.version>17</java.version>
<spring-boot.version>3.5.14</spring-boot.version>
<dubbo.version>3.3.6</dubbo.version>
<protobuf.version>3.25.5</protobuf.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</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-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>${java.version}</release>
</configuration>
</plugin>

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

注意两点:

  1. Spring Boot 3 必须用 JDK 17+。
  2. Dubbo 版本统一交给 dubbo-bom 管理,子模块里不要东一个 3.2.x、西一个 3.3.x。微服务最怕“版本自由发挥”,最后像乐队没有指挥,鼓手开始拉小提琴。

六、rpc-api:定义 Protobuf IDL

6.1 rpc-api/pom.xml

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.cybermario.demo</groupId>
<artifactId>nacos-dubbo3-grpc-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>

<artifactId>rpc-api</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>

<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-maven-plugin</artifactId>
<version>${dubbo.version}</version>
<configuration>
<dubboVersion>${dubbo.version}</dubboVersion>
<dubboGenerateType>tri</dubboGenerateType>
<protoSourceDir>${project.basedir}/src/main/proto</protoSourceDir>
<outputDir>${project.build.directory}/generated-sources/protobuf/java</outputDir>
</configuration>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/protobuf/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

如果你使用某些 Dubbo 版本时发现 dubbo-maven-plugin 没有生成消息类,可以切换到经典的 protobuf-maven-plugin + dubbo-compiler 方式。本文后面会给备用配置。

6.2 user.proto

文件位置:rpc-api/src/main/proto/user.proto

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
syntax = "proto3";

package cybermario.user.v1;

option java_multiple_files = true;
option java_package = "com.cybermario.demo.rpc.user";
option java_outer_classname = "UserProto";

service UserRpc {
rpc GetUser (GetUserRequest) returns (GetUserReply);
rpc CreateUser (CreateUserRequest) returns (CreateUserReply);
}

message GetUserRequest {
string user_id = 1;
}

message GetUserReply {
string user_id = 1;
string username = 2;
string nickname = 3;
string mobile = 4;
int64 created_at = 5;
}

message CreateUserRequest {
string username = 1;
string nickname = 2;
string mobile = 3;
}

message CreateUserReply {
string user_id = 1;
bool success = 2;
string message = 3;
}

6.3 Proto 设计规范

几个非常重要的习惯:

  1. 字段编号一旦发布,不要随便改。
  2. 删除字段时,不要复用老字段编号。
  3. 新增字段要保证默认值安全。
  4. RPC 方法尽量保持“一入参一出参”。
  5. Java 包名和 proto package 分开设计。

示例:

1
2
3
4
5
6
7
8
9
// 不再使用 mobile 字段时,不要把 4 号字段拿去给别的含义。
message GetUserReply {
string user_id = 1;
string username = 2;
string nickname = 3;
reserved 4;
reserved "mobile";
int64 created_at = 5;
}

6.4 编译 rpc-api

在根目录执行:

1
mvn clean install -pl rpc-api

成功后,你会在 target/generated-sources/protobuf/java 看到类似这些生成类:

1
2
3
4
5
com.cybermario.demo.rpc.user.GetUserRequest
com.cybermario.demo.rpc.user.GetUserReply
com.cybermario.demo.rpc.user.CreateUserRequest
com.cybermario.demo.rpc.user.CreateUserReply
com.cybermario.demo.rpc.user.UserRpcDubbo

不同 Dubbo 插件版本生成的服务接口名字可能略有不同。常见两类:

1
2
3
4
5
// 类型一:生成外层类 + 内部接口
UserRpcDubbo.IUserRpc

// 类型二:直接生成接口
UserRpc

本文示例以后者不确定时以前者为主:UserRpcDubbo.IUserRpc。如果你的版本生成的是直接接口,把代码里的 UserRpcDubbo.IUserRpc 替换成 UserRpc 即可。


七、user-provider:服务提供方

7.1 user-provider/pom.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.cybermario.demo</groupId>
<artifactId>nacos-dubbo3-grpc-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>

<artifactId>user-provider</artifactId>

<dependencies>
<dependency>
<groupId>com.cybermario.demo</groupId>
<artifactId>rpc-api</artifactId>
<version>${project.version}</version>
</dependency>

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

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

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

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

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

7.2 application.yml

文件位置:user-provider/src/main/resources/application.yml

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
server:
port: 8081

spring:
application:
name: user-provider

management:
endpoints:
web:
exposure:
include: health,info,metrics

logging:
level:
root: info
org.apache.dubbo: info

# Dubbo 3 配置
dubbo:
application:
name: user-provider
logger: slf4j
# 本地多个 Dubbo 应用同时启动时,QoS 端口容易冲突。
qos-enable: false

protocol:
# Dubbo 3 Triple 协议,基于 HTTP/2,兼容 gRPC。
name: tri
# 固定端口,方便 grpcurl / 外部语言测试。生产也建议固定或由容器端口管理。
port: 50051
# IDL + Protobuf 场景建议显式声明 protobuf。
serialization: protobuf

registry:
# 无鉴权本地 Nacos
address: nacos://127.0.0.1:8848
# Dubbo3 推荐应用级注册。也可以设置为 all 同时保留接口级注册。
register-mode: instance

provider:
timeout: 3000
retries: 0

如果 Nacos 开启鉴权:

1
2
3
dubbo:
registry:
address: nacos://127.0.0.1:8848?username=nacos&password=nacos

如果需要 namespace 隔离:

1
2
3
4
5
dubbo:
registry:
address: nacos://127.0.0.1:8848
parameters:
namespace: dev-namespace-id

如果需要 group 隔离:

1
2
3
4
dubbo:
registry:
address: nacos://127.0.0.1:8848
group: DUBBO_DEV_GROUP

7.3 启动类

文件位置:user-provider/src/main/java/com/cybermario/demo/user/UserProviderApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.cybermario.demo.user;

import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* 用户服务提供方启动类。
*
* <p>通过 @EnableDubbo 启用 Dubbo 注解扫描,
* 使 @DubboService 标注的服务能够被导出并注册到 Nacos。</p>
*/
@EnableDubbo(scanBasePackages = "com.cybermario.demo.user")
@SpringBootApplication
public class UserProviderApplication {

public static void main(String[] args) {
SpringApplication.run(UserProviderApplication.class, args);
}
}

@EnableDubbo 很关键。没加它,@DubboService 就像穿了隐身衣,代码在那儿,Dubbo 看不见。

7.4 实现 UserRpc 服务

文件位置:user-provider/src/main/java/com/cybermario/demo/user/rpc/UserRpcServiceImpl.java

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package com.cybermario.demo.user.rpc;

import com.cybermario.demo.rpc.user.CreateUserReply;
import com.cybermario.demo.rpc.user.CreateUserRequest;
import com.cybermario.demo.rpc.user.GetUserReply;
import com.cybermario.demo.rpc.user.GetUserRequest;
import com.cybermario.demo.rpc.user.UserRpcDubbo;
import java.time.Instant;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.rpc.RpcContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* 用户 RPC 服务实现。
*
* <p>这里实现的是由 user.proto 生成的 Dubbo Triple Stub 接口。</p>
*/
@DubboService(
version = "1.0.0",
group = "dev",
timeout = 3000
)
public class UserRpcServiceImpl implements UserRpcDubbo.IUserRpc {

private static final Logger log = LoggerFactory.getLogger(UserRpcServiceImpl.class);

private final Map<String, UserRecord> userStore = new ConcurrentHashMap<>();

public UserRpcServiceImpl() {
userStore.put("1001", new UserRecord("1001", "mario", "SuperMario", "13800000001", 1717776000000L));
userStore.put("1002", new UserRecord("1002", "luigi", "Luigi", "13800000002", 1717776001000L));
}

/**
* 查询用户。
*/
@Override
public GetUserReply getUser(GetUserRequest request) {
String traceId = RpcContext.getServerAttachment().getAttachment("x-trace-id");
log.info("receive getUser request, userId={}, traceId={}", request.getUserId(), traceId);

UserRecord record = userStore.get(request.getUserId());
if (record == null) {
return GetUserReply.newBuilder()
.setUserId(request.getUserId())
.setUsername("unknown")
.setNickname("用户不存在")
.setMobile("")
.setCreatedAt(0L)
.build();
}

return GetUserReply.newBuilder()
.setUserId(record.userId())
.setUsername(record.username())
.setNickname(record.nickname())
.setMobile(record.mobile())
.setCreatedAt(record.createdAt())
.build();
}

/**
* Dubbo 生成的异步方法。
*/
@Override
public CompletableFuture<GetUserReply> getUserAsync(GetUserRequest request) {
return CompletableFuture.completedFuture(getUser(request));
}

/**
* 创建用户。
*/
@Override
public CreateUserReply createUser(CreateUserRequest request) {
String userId = UUID.randomUUID().toString().replace("-", "");
long now = Instant.now().toEpochMilli();

userStore.put(userId, new UserRecord(
userId,
request.getUsername(),
request.getNickname(),
request.getMobile(),
now
));

return CreateUserReply.newBuilder()
.setUserId(userId)
.setSuccess(true)
.setMessage("create user success")
.build();
}

/**
* Dubbo 生成的异步方法。
*/
@Override
public CompletableFuture<CreateUserReply> createUserAsync(CreateUserRequest request) {
return CompletableFuture.completedFuture(createUser(request));
}

private record UserRecord(
String userId,
String username,
String nickname,
String mobile,
long createdAt
) {
}
}

如果你的 Dubbo 插件生成的是直接接口 UserRpc,那实现类改成:

1
2
3
public class UserRpcServiceImpl implements UserRpc {
// 方法体不变
}

八、order-consumer:服务消费方

8.1 order-consumer/pom.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.cybermario.demo</groupId>
<artifactId>nacos-dubbo3-grpc-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>

<artifactId>order-consumer</artifactId>

<dependencies>
<dependency>
<groupId>com.cybermario.demo</groupId>
<artifactId>rpc-api</artifactId>
<version>${project.version}</version>
</dependency>

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

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

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

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

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

8.2 application.yml

文件位置:order-consumer/src/main/resources/application.yml

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
server:
port: 8082

spring:
application:
name: order-consumer

management:
endpoints:
web:
exposure:
include: health,info,metrics

logging:
level:
root: info
org.apache.dubbo: info

dubbo:
application:
name: order-consumer
logger: slf4j
qos-enable: false

protocol:
name: tri
# Consumer 端一般不需要暴露 RPC 端口,-1 表示随机端口。
port: -1
serialization: protobuf

registry:
address: nacos://127.0.0.1:8848
register-mode: instance
# 如果希望在 Nacos 控制台也看到 consumer 信息,可以打开。
parameters:
register-consumer-url: true

consumer:
check: false
timeout: 3000
retries: 0

consumer.check=false 的意思是:启动时即使 Provider 暂时不存在,Consumer 也先启动成功。开发环境非常实用。生产环境则要根据系统要求决定,有些核心服务应该启动强校验,避免上线后才发现依赖没了。

8.3 启动类

文件位置:order-consumer/src/main/java/com/cybermario/demo/order/OrderConsumerApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.cybermario.demo.order;

import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* 订单服务消费方启动类。
*/
@EnableDubbo(scanBasePackages = "com.cybermario.demo.order")
@SpringBootApplication
public class OrderConsumerApplication {

public static void main(String[] args) {
SpringApplication.run(OrderConsumerApplication.class, args);
}
}

8.4 OrderService

文件位置:order-consumer/src/main/java/com/cybermario/demo/order/service/OrderService.java

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
package com.cybermario.demo.order.service;

import com.cybermario.demo.rpc.user.GetUserReply;
import com.cybermario.demo.rpc.user.GetUserRequest;
import com.cybermario.demo.rpc.user.UserRpcDubbo;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.rpc.RpcContext;
import org.springframework.stereotype.Service;

/**
* 订单业务服务。
*
* <p>模拟订单服务查询订单时,需要远程调用用户服务补充用户信息。</p>
*/
@Service
public class OrderService {

@DubboReference(
version = "1.0.0",
group = "dev",
check = false,
timeout = 3000,
retries = 0
)
private UserRpcDubbo.IUserRpc userRpc;

public Map<String, Object> getOrderDetail(String userId) {
String traceId = UUID.randomUUID().toString().replace("-", "");
RpcContext.getClientAttachment().setAttachment("x-trace-id", traceId);

GetUserRequest request = GetUserRequest.newBuilder()
.setUserId(userId)
.build();

GetUserReply user = userRpc.getUser(request);

Map<String, Object> result = new LinkedHashMap<>();
result.put("orderId", "ORDER-" + System.currentTimeMillis());
result.put("traceId", traceId);
result.put("userId", user.getUserId());
result.put("username", user.getUsername());
result.put("nickname", user.getNickname());
result.put("mobile", user.getMobile());
result.put("createdAt", user.getCreatedAt());
return result;
}
}

8.5 OrderController

文件位置:order-consumer/src/main/java/com/cybermario/demo/order/controller/OrderController.java

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
package com.cybermario.demo.order.controller;

import com.cybermario.demo.order.service.OrderService;
import java.util.Map;
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;

/**
* 订单调试接口。
*/
@RestController
@RequestMapping("/orders")
public class OrderController {

private final OrderService orderService;

public OrderController(OrderService orderService) {
this.orderService = orderService;
}

@GetMapping("/{userId}")
public Map<String, Object> getOrder(@PathVariable String userId) {
return orderService.getOrderDetail(userId);
}
}

九、启动与验证

9.1 编译整个项目

在根目录执行:

1
mvn clean install

9.2 启动 Provider

1
mvn -pl user-provider spring-boot:run

看到类似日志:

1
2
3
DubboBootstrap has started.
Export dubbo service com.cybermario.demo.rpc.user.UserRpc...
Register instance user-provider to nacos...

到 Nacos 控制台查看服务列表,应该能看到:

1
user-provider

如果 register-mode=all,可能还能看到接口级服务名。

9.3 启动 Consumer

1
mvn -pl order-consumer spring-boot:run

9.4 通过 HTTP 调用 Consumer

1
curl http://127.0.0.1:8082/orders/1001

返回示例:

1
2
3
4
5
6
7
8
9
{
"orderId": "ORDER-1718888888888",
"traceId": "0db82df650104f4ca93c5f4df8b1914a",
"userId": "1001",
"username": "mario",
"nickname": "SuperMario",
"mobile": "13800000001",
"createdAt": 1717776000000
}

此时调用链路是:

1
浏览器/curl -> order-consumer HTTP Controller -> DubboReference -> Nacos 发现 user-provider -> Triple RPC -> user-provider

十、使用 grpcurl 直接调用 Dubbo Triple 服务

安装 grpcurl:

1
brew install grpcurl

因为我们没有开启 gRPC Server Reflection,所以要指定 proto 文件:

1
2
3
4
5
6
7
grpcurl \
-plaintext \
-import-path rpc-api/src/main/proto \
-proto user.proto \
-d '{"user_id":"1001"}' \
127.0.0.1:50051 \
cybermario.user.v1.UserRpc/GetUser

返回类似:

1
2
3
4
5
6
7
{
"userId": "1001",
"username": "mario",
"nickname": "SuperMario",
"mobile": "13800000001",
"createdAt": "1717776000000"
}

创建用户:

1
2
3
4
5
6
7
grpcurl \
-plaintext \
-import-path rpc-api/src/main/proto \
-proto user.proto \
-d '{"username":"peach","nickname":"Princess Peach","mobile":"13800000003"}' \
127.0.0.1:50051 \
cybermario.user.v1.UserRpc/CreateUser

这一步非常关键,它证明 Provider 虽然是 Dubbo 3 服务,但可以通过 gRPC 生态工具访问。


十一、经典 protobuf-maven-plugin 备用配置

如果你的 Dubbo 版本中 dubbo-maven-plugin 行为不符合预期,可以在 rpc-api 使用经典配置:

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
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>

<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>
com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
</protocArtifact>

<protocPlugins>
<protocPlugin>
<id>dubbo</id>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-compiler</artifactId>
<version>${dubbo.version}</version>
<mainClass>org.apache.dubbo.gen.tri.Dubbo3TripleGenerator</mainClass>
</protocPlugin>
</protocPlugins>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

这个方式会:

  • compile:生成 Protobuf Java 消息类。
  • compile-custom:调用 Dubbo Triple Generator 生成 Dubbo Stub。

如果 IDE 找不到生成类,记得把生成目录标记为 Generated Sources Root,或者执行:

1
mvn clean compile

十二、Java Interface + Triple 的简化方案

如果你只是 Java 内部服务互调,不需要跨语言,不需要标准 gRPC 客户端访问,也可以不用 Protobuf,直接使用 Java 接口。

12.1 api 定义

1
2
3
4
5
package com.cybermario.demo.api;

public interface HelloService {
String sayHello(String name);
}

12.2 Provider

1
2
3
4
5
6
7
@DubboService(version = "1.0.0")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello " + name;
}
}

12.3 Consumer

1
2
@DubboReference(version = "1.0.0")
private HelloService helloService;

12.4 配置 Triple

1
2
3
4
dubbo:
protocol:
name: tri
port: 50051

这个方案非常适合 Dubbo2 老系统迁移到 Dubbo3 Triple:改协议配置即可开始往 Triple 迁移。但它不适合拿给 Go、Node、Python 原生 gRPC 客户端直接调用,因为没有 .proto 契约。

选型建议:

场景 推荐方案
纯 Java 内部服务 Java Interface + Triple
有跨语言需求 Protobuf IDL + Triple
要用 grpcurl / Go / Node 调用 Protobuf IDL + Triple
老 Dubbo2 平滑迁移 Java Interface + Triple 起步,再逐步 IDL 化

十三、Dubbo 配置详解

13.1 application.name

1
2
3
dubbo:
application:
name: user-provider

Dubbo3 应用级服务发现中,应用名就是注册到 Nacos 的服务名。这个名字要稳定,不要每次构建都带随机后缀。

13.2 protocol.name = tri

1
2
3
dubbo:
protocol:
name: tri

tri 表示 Dubbo Triple 协议。它基于 HTTP/2,兼容 gRPC。

13.3 protocol.port

1
2
3
dubbo:
protocol:
port: 50051

Provider 端建议固定端口,方便排查、压测、网关暴露、Kubernetes Service 映射。Consumer 端一般写 -1,随机端口即可。

13.4 registry.address

1
2
3
dubbo:
registry:
address: nacos://127.0.0.1:8848

Dubbo 通过 Nacos 做注册发现。Provider 注册自己,Consumer 订阅 Provider 地址。

13.5 register-mode

1
2
3
dubbo:
registry:
register-mode: instance

常见取值:

说明
instance 应用级注册,Dubbo3 推荐方向
interface 接口级注册,兼容 Dubbo2 老模型
all 应用级 + 接口级都注册

老系统迁移期间可以用 all,新系统建议优先 instance

13.6 group 和 version

Provider:

1
@DubboService(group = "dev", version = "1.0.0")

Consumer:

1
@DubboReference(group = "dev", version = "1.0.0")

group 通常用于环境、租户、业务域隔离;version 用于接口版本隔离。两边必须匹配,不然 Consumer 会找不到 Provider。

13.7 timeout 和 retries

1
2
3
4
5
6
7
dubbo:
provider:
timeout: 3000
retries: 0
consumer:
timeout: 3000
retries: 0

对写操作,默认建议 retries=0,否则可能出现重复下单、重复扣款、重复创建数据。读操作可以根据幂等性设置重试。


十四、链路上下文传递

Consumer 设置附件:

1
2
String traceId = UUID.randomUUID().toString().replace("-", "");
RpcContext.getClientAttachment().setAttachment("x-trace-id", traceId);

Provider 读取附件:

1
String traceId = RpcContext.getServerAttachment().getAttachment("x-trace-id");

这个方式适合传递:

  • traceId
  • requestId
  • tenantId
  • operatorId
  • grayTag
  • language
  • clientVersion

但不要把大对象塞进 attachment。RPC 上下文不是移动硬盘,别把它当仓库用。


十五、异常处理建议

Dubbo 调用常见异常可以按层分:

异常类型 常见原因 处理建议
No provider available Provider 未启动、注册失败、group/version 不匹配 看 Nacos 服务列表和 Dubbo 配置
TimeoutException Provider 慢、网络慢、线程池耗尽 加日志、看耗时、调 timeout、优化业务
SerializationException Protobuf 类不一致、字段类型变更不兼容 保持 api 包版本一致,不复用字段编号
NacosException Nacos 地址错误、鉴权错误、9848 未开放 检查 address、username/password、端口
RpcException 业务异常或网络异常包装 统一转换错误码,不要直接暴露堆栈

建议在业务层封装远程调用:

1
2
3
4
5
6
7
8
9
10
11
12
public GetUserReply safeGetUser(String userId) {
try {
return userRpc.getUser(GetUserRequest.newBuilder().setUserId(userId).build());
} catch (Exception ex) {
// 真实项目里建议打业务日志、记录 traceId、做降级。
return GetUserReply.newBuilder()
.setUserId(userId)
.setUsername("fallback")
.setNickname("用户服务暂不可用")
.build();
}
}

注意:不是所有远程调用都应该降级。比如支付、库存扣减、合同签署这类关键链路,乱降级比失败更危险。


十六、生产配置建议

16.1 Provider 端建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dubbo:
provider:
timeout: 3000
retries: 0
executes: 200

protocol:
name: tri
port: 50051
serialization: protobuf

registry:
address: nacos://nacos.prod:8848?username=${NACOS_USERNAME}&password=${NACOS_PASSWORD}
register-mode: instance
parameters:
namespace: ${NACOS_NAMESPACE}

16.2 Consumer 端建议

1
2
3
4
5
6
7
8
9
10
11
dubbo:
consumer:
timeout: 3000
retries: 0
check: true

registry:
address: nacos://nacos.prod:8848?username=${NACOS_USERNAME}&password=${NACOS_PASSWORD}
register-mode: instance
parameters:
namespace: ${NACOS_NAMESPACE}

16.3 环境变量注入

1
2
3
export NACOS_USERNAME=nacos
export NACOS_PASSWORD=nacos
export NACOS_NAMESPACE=prod-namespace-id

application.yml

1
2
3
4
5
dubbo:
registry:
address: nacos://nacos.prod:8848?username=${NACOS_USERNAME}&password=${NACOS_PASSWORD}
parameters:
namespace: ${NACOS_NAMESPACE}

不要把生产密码写死在 Git 仓库里。否则以后安全审计来了,你的 commit history 会像犯罪现场一样诚实。


十七、Nacos 控制台怎么看

启动 Provider 后,在 Nacos 控制台进入:

1
服务管理 -> 服务列表

你可能看到:

1
user-provider

如果配置了 register-mode=all,还可能看到接口级服务。

重点看:

  • 服务是否存在;
  • 实例 IP 是否正确;
  • 端口是否是 Dubbo Triple 端口,而不是 Spring Boot Web 端口;
  • 分组是否正确;
  • 命名空间是否正确;
  • 实例是否健康。

如果本机有多网卡、Docker、VPN,Provider 注册 IP 可能不对。可以指定:

1
2
3
dubbo:
protocol:
host: 127.0.0.1

生产不要随便写死 127.0.0.1,否则 Consumer 会发现一个“只会指向自己”的 Provider,像问路问到了镜子。


十八、多 Provider 负载均衡

启动两个 Provider:

1
2
3
mvn -pl user-provider spring-boot:run -Dspring-boot.run.arguments="--server.port=8081 --dubbo.protocol.port=50051"

mvn -pl user-provider spring-boot:run -Dspring-boot.run.arguments="--server.port=8083 --dubbo.protocol.port=50052"

Consumer 配置负载均衡:

1
2
3
4
5
6
7
8
@DubboReference(
version = "1.0.0",
group = "dev",
loadbalance = "roundrobin",
timeout = 3000,
retries = 0
)
private UserRpcDubbo.IUserRpc userRpc;

常见负载均衡策略:

策略 说明
random 随机,默认常用
roundrobin 轮询
leastactive 最少活跃调用数
consistenthash 一致性哈希,适合有状态或缓存亲和场景

十九、灰度与版本治理

19.1 通过 version 做接口版本隔离

Provider V1:

1
2
3
@DubboService(version = "1.0.0", group = "dev")
public class UserRpcV1ServiceImpl implements UserRpcDubbo.IUserRpc {
}

Provider V2:

1
2
3
@DubboService(version = "2.0.0", group = "dev")
public class UserRpcV2ServiceImpl implements UserRpcDubbo.IUserRpc {
}

Consumer 指定版本:

1
2
@DubboReference(version = "2.0.0", group = "dev")
private UserRpcDubbo.IUserRpc userRpc;

19.2 通过 group 做环境隔离

开发环境:

1
@DubboService(group = "dev")

测试环境:

1
@DubboService(group = "test")

生产环境:

1
@DubboService(group = "prod")

一般来说,环境隔离更推荐使用 Nacos namespace;业务分组或特殊灰度可以使用 Dubbo group。不要把所有隔离能力都揉成一坨,不然后面排查问题时就像拆耳机线,越急越乱。


二十、Dubbo Triple 与原生 gRPC 的边界

20.1 Dubbo Triple 能做什么

  • 使用 HTTP/2 传输。
  • 使用 Protobuf IDL。
  • 兼容 gRPC 客户端访问。
  • 接入 Dubbo 注册发现、负载均衡、超时、重试、路由、分组、版本。
  • 适合 Java Dubbo 体系与多语言体系并存的团队。

20.2 原生 gRPC 能做什么

  • 使用 Google gRPC 标准栈。
  • 生态中有非常多语言 SDK。
  • Kubernetes、Envoy、xDS、Service Mesh 体系更常见。
  • 不依赖 Dubbo。

20.3 不建议混用的方式

不要在同一个服务里随意同时开启:

  • 一个 Dubbo Triple 端口;
  • 一个原生 gRPC 端口;
  • 一个 Dubbo 协议端口;
  • 一个 Spring Web 端口;
  • 然后所有端口都想注册到一个 Nacos 服务里。

不是不能做,而是治理复杂度会暴涨。除非你非常明确每个端口的职责,否则先收敛:内部 Dubbo 调用 + 外部 gRPC 兼容访问,都走 Triple。


二十一、常见问题排查

21.1 Nacos 控制台看不到服务

检查:

1
2
3
dubbo:
registry:
address: nacos://127.0.0.1:8848

检查 Provider 是否有:

1
2
@EnableDubbo
@DubboService

检查启动日志是否有:

1
Register instance user-provider to nacos

21.2 Consumer 报 No provider available

重点检查:

  • Provider 是否启动;
  • Nacos 服务是否健康;
  • Consumer 和 Provider 的 namespace 是否一致;
  • group 是否一致;
  • version 是否一致;
  • 接口包是否是同一个 rpc-api 版本;
  • register-mode 是否导致订阅模型不匹配。

21.3 grpcurl 调不通

检查 Provider 的 Dubbo 协议端口:

1
2
3
dubbo:
protocol:
port: 50051

检查命令是否带 proto:

1
2
3
4
5
6
grpcurl -plaintext \
-import-path rpc-api/src/main/proto \
-proto user.proto \
-d '{"user_id":"1001"}' \
127.0.0.1:50051 \
cybermario.user.v1.UserRpc/GetUser

如果你直接执行:

1
grpcurl -plaintext 127.0.0.1:50051 list

可能失败,因为服务端没有开启 reflection。没有 reflection 时,grpcurl 不知道服务有哪些方法,必须给它 .proto 文件。

21.4 generated-sources 下面有代码,但 IDE 报红

执行:

1
mvn clean compile

然后在 IDEA 中:

1
右键 target/generated-sources/protobuf/java -> Mark Directory as -> Generated Sources Root

21.5 Protobuf 字段没有值

检查 JSON 字段名和 proto 字段名:

1
string user_id = 1;

grpcurl JSON 入参应该写:

1
{"user_id":"1001"}

而 Java 代码里是:

1
request.getUserId()

21.6 Nacos 连接失败

Docker 下检查端口:

1
docker ps

需要看到:

1
2
0.0.0.0:8848->8848/tcp
0.0.0.0:9848->9848/tcp

再检查日志:

1
docker logs -f nacos-standalone

二十二、生产落地清单

上线前建议确认:

  • JDK 版本是 17+。
  • Spring Boot 3、Dubbo 3、Nacos Client 版本已固定。
  • Provider Triple 端口固定并被容器/防火墙放行。
  • Nacos namespace、group、鉴权配置正确。
  • @EnableDubbo 扫描路径覆盖 Provider/Consumer 包。
  • Provider 和 Consumer 的 group/version 匹配。
  • 写操作 retries=0
  • 超时时间按接口 SLA 配置。
  • Proto 字段编号不复用。
  • rpc-api 使用独立版本发布。
  • grpcurl 或跨语言客户端已用 proto 文件验证。
  • 日志中打印 traceId/requestId。
  • Nacos 控制台能看到健康实例。
  • 多实例负载均衡测试通过。
  • 服务下线和重启不会造成大面积失败。

二十三、完整启动顺序

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
# 1. 启动 Nacos
sh nacos/bin/startup.sh -m standalone

# 2. 编译 api
mvn clean install -pl rpc-api

# 3. 编译全部
mvn clean install

# 4. 启动 provider
mvn -pl user-provider spring-boot:run

# 5. 启动 consumer
mvn -pl order-consumer spring-boot:run

# 6. HTTP 验证
curl http://127.0.0.1:8082/orders/1001

# 7. gRPC 兼容验证
grpcurl \
-plaintext \
-import-path rpc-api/src/main/proto \
-proto user.proto \
-d '{"user_id":"1001"}' \
127.0.0.1:50051 \
cybermario.user.v1.UserRpc/GetUser

二十四、总结

本文完成了一个完整的 Nacos + Spring Boot 3 + Dubbo 3 + gRPC/Triple 整合实践:

  • 用 Spring Boot 3 管理应用启动与 Web 调试入口;
  • 用 Dubbo 3 负责 RPC、注册发现、服务治理;
  • 用 Nacos 作为注册中心;
  • 用 Triple 协议承载 HTTP/2 + Protobuf;
  • 用 Protobuf IDL 定义跨语言契约;
  • 用 grpcurl 验证 gRPC 兼容访问。

我的建议很直接:

如果你已经在 Dubbo 体系里,又想要 gRPC/Protobuf 的跨语言能力,优先选择 Dubbo 3 Triple + Protobuf IDL + Nacos。它不会让你丢掉 Dubbo 的治理能力,也能向 gRPC 生态靠拢。

如果你的系统完全是 Kubernetes + Envoy + 多语言原生 gRPC,那可以直接走原生 gRPC,不必强行套 Dubbo。

技术选型不怕复杂,怕的是边界不清。边界一清楚,复杂度就从“玄学现场”变成“工程问题”了。

参考资料

启示录

RPC 的本质不是远程调用,而是边界治理。

协议只是路,契约才是秩序。


Nacos + Spring Boot 3 + Dubbo 3 + gRPC/Triple 整合实战
https://allendericdalexander.github.io/2026/06/09/archtect/nacos_springboot3_dubbo3_grpc_blog/
作者
AtLuoFu
发布于
2026年6月9日
许可协议