Dubbo 3 配合 Protobuf(IDL)使用 Triple 协议

大纲

前言

本文将整合 SpringBoot 与 Dubbo 3,并结合 IDL + Protobuf 使用 Triple 协议,使用的注册中心是 Nacos。值得一提的是,本文实现的 RPC 服务可以与谷歌官方原生的 gRPC 客户端(协议)互相调用,适合开发多编程语言业务。

特别注意

  • 由于 gRPC 仅支持 Protobuf 模式(使用 .proto 文件定义服务),因此基于 IDL + Protobuf + Triple 协议实现的 RPC 服务可以与谷歌官方原生的 gRPC 客户端(协议)互相调用,适合开发多编程语言业务。
  • 基于 IDL + Protobuf + Triple 协议实现的 RPC 服务,默认不可以通过标准的 HTTP 工具(比如 curl)直接调用;若需要通过标准的 HTTP 工具直接调用 RPC 服务,可以改为 配合 Java 接口使用 Triple 协议

学习资源

版本对应关系

SpringBoot 与 Dubbo 的版本必须互相匹配,否则不同版本之间可能会存在兼容性问题,最终导致服务无法正常运行。两者的版本对应关系如下:

Dubbo 分支最新版本 JDKSpringBoot 详细说明
3.3.x3.3.08, 17, 212.x、3.x 生产可用(推荐,长期维护)! 最新 Triple 协议升级,内置 Metrics、Tracing、GraalVM 支持等
3.2.x3.2.108, 172.x、3.x 生产可用(长期维护)!
3.1.x3.1.118, 172.x、3.x 仅修复安全漏洞!
3.0.x3.0.1582.x 停止维护!
2.7.x2.7.2382.x 停止维护!
2.6.x2.6.206, 7- 停止维护!
2.5.x2.5.106, 7- 停止维护!

如果仍然使用版本低于 2.7.0 的旧版 Dubbo,请使用以下 Spring Boot 启动器:

Dubbo Spring Boot StarterDubboSpring Boot
0.2.1.RELEASE2.6.5+2.x
0.1.2.RELEASE2.6.5+1.x

Triple 协议介绍

提示

从 Dubbo 3.2 开始,Dubbo 官方已经废弃原有的 gRPC 协议,使用 Triple 协议进行替代。Triple 协议完全兼容 gRPC 协议,更详细的协议介绍请看 这里

Triple 协议的核心概念

  • Triple 协议是 Dubbo 3 引入的新一代 RPC 通信协议,旨在替代经典 Dubbo 协议(基于 TCP 长连接),支持更加现代化、开放的服务互通标准。
  • Triple 协议本质上是基于 gRPC/HTTP/2 规范的 RPC 协议,但是它做了兼容性扩展,支持 Dubbo 的接口声明风格,保留了 Dubbo 的生态优势。
  • Triple 协议是一个基于 HTTP 传输层协议的 RPC 协议,它完全兼容 gRPC,可运行在 HTTP/1、HTTP/2 之上,并同时结合了 HTTP/2、Protobuf 和 Dubbo 的能力。

Triple 协议的诞生背景

为什么 Dubbo 3 推出 Triple 协议呢?主要原因有以下几个:

  • 经典 Dubbo 协议是私有的 TCP 协议,无法与多语言、云原生环境很好兼容。
  • 微服务逐步走向云原生 + 多语言 + Service Mesh + 网关接入。
  • gRPC 成为业界事实标准,Dubbo 社区需要更开放的通信协议。

Triple 协议的核心特点

特点说明
基于 HTTP/2 多路复用、长连接、低延迟
多语言互操作性兼容 gRPC,支持跨语言调用
IDL 支持灵活可选 Protobuf 或 Java 接口定义(兼容 Dubbo 2 的 API 方式)
支持流式调用实现了 gRPC 的流式通信能力
安全性更好支持 HTTP/2 的 TLS/SSL
更好的网关兼容性能直接被 Envoy、APISIX 等 API 网关代理
保留 Dubbo 的服务治理如注册中心、路由、限流、熔断等
支持零侵入升级 Dubbo 2 可以平滑过渡到 Triple

Triple 协议的应用场景

  • 多语言 RPC 通信(Java ⇄ Go ⇄ Node)
  • 云原生环境(与 Envoy Mesh 兼容)
  • 高并发长连接应用(HTTP/2 多路复用)
  • 数据流实时传输(流式调用)
  • Dubbo 2 平滑升级到 Dubbo 3

Triple 协议发布 REST 服务

Dubbo 3 为 Triple 协议发布 REST 风格的服务提供了内置支持,具体使用方式取决于开发者使用的是基于 Protobuf 的服务定义模式,还是基于 Java 接口的服务定义模式:

  • (1) Java 接口的服务定义模式:通过直接为 Java 接口增加注解,可以发布 REST 风格的服务,目前支持 Spring Web 注解与 JAX-RS 注解。
  • (2) Protobuf 的服务定义模式:通过配合 Protobuf 使用 Triple 协议,使用 IDL 定义服务,并使用 Protobuf 编码业务数据,最后可以选择使用 gRPC-Gateway 发布 REST 风格的服务。

值得一提的是,Protobuf 与 gRPC-Gateway 的简单介绍如下:

  • (1) Protobuf(Protocol Buffers):Google 开发的一种轻量级、高效的数据序列化协议,常用于跨语言、跨平台的数据交换。它类似于 JSON 或 XML,但具有更高的性能和更小的数据体积,适用于网络通信、配置文件、数据存储等场景。
  • (2) gRPC-Gateway:一个基于 Go 语言的开源协议转换工具,它能够将 RESTful 请求(基于 HTTP/JSON)转换为 gRPC 调用(基于 HTTP/2 + Protobuf)。这样,客户端就可以使用普通的 HTTP 请求(如浏览器)来访问 gRPC 服务,而无需专门使用 gRPC 客户端。

提示

若希望 Dubbo 3 使用 Triple 协议 + Java 接口(基于 Spring Web 注解)的服务定义模式来发布 REST 风格的服务,可以看 这里 的教程。

Triple 协议支持的调用模型

RPC 模式说明
Unary RPC 单次请求 - 响应
Server Streaming 服务端流式返回
Client Streaming 客户端流式请求
Bi-directional Streaming 双向流式

Triple 与经典 Dubbo 协议的区别

特性 Triple(Dubbo 3)Dubbo 协议(Dubbo 2)
传输协议 HTTP/2 自定义 TCP 协议(单连接)
序列化协议 Protobuf / Hessian2 / JSONHessian2(默认)
多语言支持是,天然支持 gRPC 互通较差(主要 Java)
网关支持支持 Envoy、APISIX、Kong 不兼容
流式通信支持(Streaming RPC)不支持
扩展性兼容 gRPC 插件生态自定义扩展(Dubbo Filter)
可观测性标准化 Trace、Metrics 自行扩展
连接复用 HTTP/2 多路复用单连接单请求
协议标准化程度标准协议,开放生态私有协议

Triple 协议原理

Triple 协议的序列化机制

  • Triple 协议的本质:

    • 传输层:HTTP/2
    • 编解码:
      • 默认支持 application/grpc+proto,完全兼容 gRPC 标准
      • 兼容 HTTP/JSON 调用(但严格来说,gRPC 标准并没有支持 application/grpc+json,而是 Dubbo 自行扩展了 JSON 兼容功能)
  • Triple 接口类型与序列化逻辑:

    • 如果是 .proto 生成的接口
      • 完全走 Protobuf 序列化
        • 客户端和服务端都用 .proto 定义的结构进行 Protobuf 编解码
        • 兼容标准 gRPC 协议
    • 如果是 Java 接口(非 .proto 生成)
      • HTTP/JSON 调用(如 curl + application/json):
        • 完全走 Hessian2Json 或 Jackson 等 JSON 序列化
        • 传输层是 HTTP/1.1
        • 不会经过 Protobuf 序列化
  • Triple 协议为什么这样设计:

    • Triple 协议本质上是 gRPC 兼容协议
    • gRPC 强制要求底层用 Protobuf 编解码
    • Dubbo 为兼容老用户,支持「Java 接口直发」
    • 如果是 HTTP/JSON 调用,仍保持兼容 JSON(非 Protobuf 序列化)
  • Triple 协议的序列化机制总结

调用方式序列化服务定义模式
gRPC 标准调用(HTTP/2 + application/grpc+proto直接走 Protobuf 序列化基于 .proto 文件定义服务
HTTP/JSON 调用(HTTP/1 + application/json直接走 Hessian2Json 或 Jackson 序列化(不经过 Protobuf 序列化)基于 Java 接口(纯 POJO 接口)定义服务

Triple 协议的两种调用方式

服务定义方式是否支持 HTTP + JSON 调用是否支持 gRPC 客户端(协议)调用是否支持跨编程语言调用
基于 .proto 文件的服务定义❌ 不支持 HTTP + JSON 调用✅ 支持 gRPC 客户端(协议)调用,由于必须用 Protobuf 二进制,通常用 gRPC 调用✅ 支持跨编程语言调用
基于 Java 接口的服务定义(纯 POJO 接口)✅ 支持 HTTP + JSON 调用,内部直接走 Hessian2Json 或 Jackson 序列化(不经过 Protobuf 序列化)❌ 不支持 gRPC 客户端(协议)调用,由于 gRPC 仅支持 Protobuf 模式(使用 .proto 文件定义服务),因此 Java 接口 + Triple 协议的模式无法与谷歌官方原生的 gRPC 客户端(协议)互相调用❌ 不支持跨编程语言调用

Dubbo 使用案例

本节将整合 SpringBoot 与 Dubbo 3,并配合 IDL + Protobuf 使用 Triple 协议,使用的注册中心是 Nacos,完整的项目目录结构如图所示。值得一提的是,本教程的内容也适用于 Spring Cloud 项目。

版本说明

组件版本说明
SpringBoot3.4.2
Dubbo Spring Boot Starter3.3.2依赖 Dubbo 3.3.2
Nacos Server2.5.0Nacos 服务器,作为服务注册中心
JDK17支持 JDK 17 及以上版本

模块说明

  • api:抽取出来的公共模块,存放 .proto 文件、公用的实体类和接口
  • provider:服务提供者,实现 api 模块中的接口
  • customer:服务消费者,调用服务提供者中的接口

案例代码

API 模块

提示

  • 这里不需要在本地操作系统手动安装 Protobuf,因为只要使用 protobuf-maven-plugin 插件,并正确配置了 protocArtifact,Maven 就会自动下载 protoc 编译器,不再需要手动安装。
  • 通常只有以下场景才需要在本地安装 Protobuf,比如手工使用 protoc 命令编译 .proto 文件、跨语言开发(如生成 C++、Python、Go 代码)、Maven 插件支持不足等。
Proto 协议文件

这里使用 IDL 定义跨语言服务,而不是直接使用 Java 接口,其中 .proto 文件(比如 UserService.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
// 指定 proto 文件的语法版本
syntax = "proto3";

// 是否将每个 message / service / enum 生成到单独的 Java 文件中
option java_multiple_files = true;

// 指定生成的 Java 包名
option java_package = "com.clay.dubbo.api";

// 如果没有开启 java_multiple_files,所有代码会生成在 UserServiceProto.java 文件中
option java_outer_classname = "UserServiceProto";

// iOS Objective-C 的类名前缀(可选,只有 iOS 需要,防止类名冲突)
option objc_class_prefix = "USERSRV";

// Proto 的包名(逻辑命名空间,不影响 Java 包)
package userservice;

// 请求消息体
message HelloRequest {
string name = 1;
}

// 响应消息体
message HelloReply {
string message = 1;
}

// 定义 RPC 服务接口
service UserService {
// 定义一个 RPC 方法 SayHello
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

特别注意

为了共用 protoc 根据 .proto 文件自动生成的代码,这里将 .proto 文件存放在 api 模块中(比如存放在 /src/main/proto/ 目录),然后其他业务模块可以直接依赖 api 模块,以此实现重用代码的目的。

Maven 配置文件
配置文件内容
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
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<dubbo.version>3.3.2</dubbo.version>
<protoc.version>3.25.3</protoc.version>
</properties>

<dependencies>
<!-- Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!-- Protobuf -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<!-- 建议和 protoc 版本保持一致 -->
<version>${protoc.version}</version>
</dependency>
</dependencies>

<build>
<extensions>
<!-- 跨平台支持 -->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.0</version>
</extension>
</extensions>

<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<!-- 是否在代码生成前清空输出目录(可选) -->
<clearOutputDirectory>false</clearOutputDirectory>
<!-- 自定义 .proto 文件所在的路径(可选)-->
<protoSourceRoot>${basedir}/src/main/proto</protoSourceRoot>
<!-- 自定义 protoc 生成 Java 文件的存放目录(可选) -->
<outputDirectory>${basedir}/build/generated/source/proto/main/java</outputDirectory>
<!-- Maven 会自动下载对应平台的 protoc 可执行文件,用于根据 .proto 文件生成 Protobuf 的 Java 类(DTO、Message)代码 -->
<protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
<!-- 用来给 protoc 添加自定义代码生成插件(比如 Dubbo Triple 的 Stub 代码生成器)-->
<protocPlugins>
<protocPlugin>
<id>dubbo</id>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-compiler</artifactId>
<!-- dubbo-compiler 的版本必须与使用的 dubbo 版本一致 -->
<version>${dubbo.version}</version>
<mainClass>org.apache.dubbo.gen.tri.Dubbo3TripleGenerator</mainClass>
</protocPlugin>
</protocPlugins>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<!-- 添加 protoc 生成 Java 文件的存放目录 -->
<source>${basedir}/build/generated/source/proto/main/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
配置文件说明
1
<protoSourceRoot>${basedir}/src/main/proto</protoSourceRoot>
  • 自定义 .proto 文件所在的路径,默认可以不指定,因为 Protobuf 的 Maven 插件会自动扫描项目中的 .proto 文件。
1
<outputDirectory>${basedir}/build/generated/source/proto/main/java</outputDirectory>
  • 自定义 protoc 生成 Java 文件的存放目录,默认可以不指定,因为默认会存放在当前模块的 /target/generated-sources/protobuf/ 目录中。
1
<protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
  • 这是用来根据 .proto 文件生成 Protobuf 的 Java 类(DTO、Message)代码的可执行文件,其中 exe 只是 Maven 的打包类型(classifier),并不代表文件扩展名是 .exe,即不代表是 Windows 平台的可执行文件。
1
2
3
4
5
6
7
8
<protocPlugin>
<id>dubbo</id>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-compiler</artifactId>
<!-- dubbo-compiler 的版本必须与使用的 dubbo 版本一致 -->
<version>${dubbo.version}</version>
<mainClass>org.apache.dubbo.gen.tri.Dubbo3TripleGenerator</mainClass>
</protocPlugin>
  • 这是用来给 protoc 添加自定义代码生成插件(比如 Dubbo Triple 的 Stub 代码生成器)。也就是说,除了生成 Protobuf 的 Java 类(DTO、Message)、还会生成 Dubbo Triple 协议专用的服务接口(Stub)代码。
编译生成代码

代码生成的位置

  • 根据 .proto 文件,通过 Protobuf 的 Maven 插件自动生成代码后,相关的代码源文件默认会存放在当前模块的 /target/generated-sources/protobuf/ 目录中。
  • 一般不需要指定 protoc 生成 Java 文件的输出路径,因为 IDEA 会自动将 /target/generated-sources/protobuf/ 加入到编译路径(之后可以正常引用生成的代码),Maven 会自动编译这些自动生成的代码,但不会污染 src/main/java
  • 如果将 protoc 自动生成的代码指定输出到 src/main/java 目录中,那么生成代码会持久化保存,即使 .proto 文件删除了,旧代码还会残留;而且 mvn clean 不会自动删除 src/main/java 里自动生成的文件,容易积累垃圾代码。
  • 如果需要指定 protoc 生成 Java 文件的输出路径,那么建议指定为 ${basedir}/build/generated/source/proto/main/java,然后再配置 Maven 插件 build-helper-maven-plugin 来添加指定的源码目录路径。
  • 执行以下 Maven 命令,可以根据 .proto 文件编译生成 Protobuf 的 Java 类(DTO、Message)、Dubbo Triple 协议专用的服务接口(Stub)代码
1
mvn clean compile
  • 或者执行以下 Maven 命令
1
mvn clean protobuf:compile
  • 比如,执行 mvn clean compile 命令后,根据 .proto 文件自动生成的代码源文件列表如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
build
└── generated
└── source
└── proto
└── main
└── java
└── com
└── clay
└── dubbo
└── api
├── DubboUserServiceTriple.java
├── HelloRequest.java
├── HelloRequestOrBuilder.java
├── HelloResponse.java
├── HelloResponseOrBuilder.java
├── UserService.java
└── UserServiceProto.java

提示

除了可以使用 Maven 命令根据 .proto 文件自动生成代码,还可以通过 IDEA 的可视化工具来生成代码(如图所示),运行效果类似于执行 mvn clean compile 命令,前提是已配置好 Protobuf 的 Maven 插件。

使用新的插件
  • Dubbo 3.3.0 之前的版本可以使用上面的 Maven 插件 protobuf-maven-plugin(实测 Dubbo 3.3.0 之后的版本也可以正常使用),但 Dubbo 3.3.0 之后的版本提供了新的 dubbo-maven-plugin 插件,使用起来更方便,如下所示:
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
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<dubbo.version>3.3.2</dubbo.version>
<protoc.version>3.25.3</protoc.version>
</properties>

<dependencies>
<!-- Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!-- Protobuf -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<!-- 建议和 protoc 版本保持一致 -->
<version>${protoc.version}</version>
</dependency>
</dependencies>

<build>
<extensions>
<!-- 跨平台支持 -->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.0</version>
</extension>
</extensions>

<plugins>
<plugin>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-maven-plugin</artifactId>
<!-- 要求 dubbo 必须是 3.3.0 及以上版本 -->
<version>${dubbo.version}</version>
<configuration>
<!-- 自定义 protoc 编译器的版本 -->
<protocVersion>${protoc.version}</protocVersion>
<!-- 自定义 .proto 文件所在的路径(可选)-->
<protoSourceDir>${basedir}/src/main/proto</protoSourceDir>
<!-- 自定义 protoc 生成 Java 文件的存放目录(可选) -->
<outputDir>${basedir}/build/generated/source/proto/main/java</outputDir>
<!-- Maven 会自动下载对应平台的 protoc 可执行文件,用于根据 .proto 文件生成 Protobuf 的 Java 类(DTO、Message)代码 -->
<protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
<!-- 指定代码生成的类型 -->
<dubboGenerateType>tri</dubboGenerateType>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
<phase>generate-sources</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<!-- 添加 protoc 生成 Java 文件的存放目录 -->
<source>${basedir}/build/generated/source/proto/main/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
  • 其中 configuration 可配置参数如下表所示:
参数必填参数默认值说明备注
outputDir${project.build.directory}/generated-sources/protobuf/java生成的 Java 文件存放目录
protoSourceDir${basedir}/src/main/proto.proto 文所在的目录
protocArtifactcom.google.protobuf:protoc:3.25.0:exe:操作系统名:操作系统架构protoc 编译器组件
protocVersion3.25.0protoc 编译器的版本
dubboGenerateTypetri代码生成类型可填 tri 或者 tri_reactor
  • 执行以下 Maven 命令,可以根据 .proto 文件编译生成 Protobuf 的 Java 类(DTO、Message)、Dubbo Triple 协议专用的服务接口(Stub)代码
1
mvn clean compile
  • 比如,执行 mvn clean compile 命令后,根据 .proto 文件自动生成的代码源文件列表如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
build
└── generated
└── source
└── proto
└── main
└── java
└── com
└── clay
└── dubbo
└── api
├── DubboUserServiceTriple.java
├── HelloRequest.java
├── HelloRequestOrBuilder.java
├── HelloResponse.java
├── HelloResponseOrBuilder.java
├── UserService.java
└── UserServiceProto.java
  • 除了可以使用 Maven 命令根据 .proto 文件自动生成代码,还可以通过 IDEA 的可视化工具来生成代码(如图所示),前提是已配置好 Dubbo 的 Maven 插件。

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
33
34
35
36
37
38
39
40
41
42
43
44
<properties>
<spring-boot.version>3.4.2</spring-boot.version>
<dubbo.version>3.3.2</dubbo.version>
</properties>

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

<dependencies>
<dependency>
<groupId>com.clay.dubbo</groupId>
<artifactId>dubbo-lesson-01-api</artifactId>
<version>1.0-SNAPSHOT</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>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
  • 接口实现类(继承了由 protoc 根据 .proto 文件自动生成的 DubboUserServiceTriple.UserServiceImplBase 类),@DubboService 注解主要用于暴露服务,使其能够被 Dubbo 框架识别并注册到服务注册中心
1
2
3
4
5
6
7
8
9
10
11
12
13
import org.apache.dubbo.config.annotation.DubboService;

@Slf4j
@DubboService
public class UserServiceImpl extends DubboUserServiceTriple.UserServiceImplBase {

@Override
public HelloResponse sayHello(HelloRequest request) {
log.info("Hello, " + request.getName());
return HelloResponse.newBuilder().setMessage("Hello, " + request.getName() + "!").build();
}

}

特别注意

  • 这里的 UserServiceImpl 继承了由 .proto 文件自动生成的 DubboUserServiceTriple.UserServiceImplBase 类,而不是实现由 .proto 文件自动生成的 UserService 接口。
  • 如果 UserServiceImpl 直接实现由 .proto 文件自动生成的 UserService 接口,效果相当于使用普通 Java 接口来定义服务,其他 gRPC 客户端(协议)不能直接调用 RPC 服务,即无法实现跨语言调用。
  • 如果 UserServiceImpl 直接实现由 .proto 文件自动生成的 UserService 接口,其运行效果相当于 配合 Java 接口使用 Triple 协议,可以通过标准的 HTTP 工具(比如 curl + application/json)调用 RPC 服务,命令如下所示:
    1
    curl -H "Content-Type: application/json" -d '{"name":"dubbo"}' http://127.0.0.1:50052/com.clay.dubbo.api.UserService/sayHello
  • 主启动类
1
2
3
4
5
6
7
8
@SpringBootApplication
public class ProviderApplication {

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

}
  • 配置文件(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
server:
port: 9090

spring:
application:
name: dubbo-provider-application

dubbo:
# 服务信息
application:
name: ${spring.application.name}
qos-enable: false
qos-port: 22222
qos-accept-foreign-ip: false
# 注册中心地址
registry:
address: nacos://192.168.2.235:8848
# 服务提供者的协议
protocol:
name: tri
port: 50052
preferred-protocol: tri # 优先使用 Triple 协议,以避免输出 WARN 信息
# 扫描 Dubbo 相关的注解
scan:
base-packages: com.clay.dubbo.provider

提示

若不希望在 YML 配置文件中指定 dubbo.scan.base-packages 参数,那么可以在主启动类上标注 @EnableDubbo(scanBasePackages = "xxx") 注解或者 @DubboComponentScan(basePackages = "xxx") 注解来替代。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<properties>
<spring-boot.version>3.4.2</spring-boot.version>
<dubbo.version>3.3.2</dubbo.version>
</properties>

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

<dependencies>
<dependency>
<groupId>com.clay.dubbo</groupId>
<artifactId>dubbo-lesson-01-api</artifactId>
<version>1.0-SNAPSHOT</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>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
  • 业务测试类,@DubboReference 注解主要用于在服务消费者端引用远程服务提供者的服务
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
import org.apache.dubbo.config.annotation.DubboReference;

@Slf4j
@RestController
@RequestMapping("/system")
public class SystemController {

/**
* 引用 Dubbo 服务
* <p> 可以指定版本、负载均衡算法、重试次数、超时毫秒等。
* <p> 负载均衡默认为 random,还可以配置为:roundrobin 、leastactive 、consistenthash
* <p> 超时毫秒数 timeout 可以统一在 application.yml 进行配置,也可以在具体服务上做个性化配置
* <p> 重试次数 retries 可以统一在 application.yml 进行配置,也可以在具体服务上做个性化配置,但是对于新增(插入)等非幂等操作,建议不要重试
*/
@DubboReference
private UserService userService;

@GetMapping("/sayHello/{name}")
public String getUser(@PathVariable("name") String name) {
// RPC 请求的参数
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
// 发起 RPC 请求
HelloResponse response = userService.sayHello(request);
// 获取 RPC 请求的结果
log.info("result: {}", response.getMessage());
return response.getMessage();
}

}
  • 主启动类
1
2
3
4
5
6
7
8
@SpringBootApplication
public class ConsumerApplication {

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

}
  • 配置文件(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
server:
port: 9095

spring:
application:
name: dubbo-consumer-application

dubbo:
# 服务信息
application:
name: ${spring.application.name}
qos-enable: false
qos-port: 22222
qos-accept-foreign-ip: false
# 注册中心地址
registry:
address: nacos://192.168.2.235:8848
# 扫描 Dubbo 相关的注解
scan:
base-packages: com.clay.dubbo.consumer
# 消费行为配置
consumer:
# 关闭了启动检查,这样消费者启动时,不会到注册中心里面检查服务提供者是否存在
check: false
# 建议统一配置为不重试请求,对于查询等幂等操作来说可以在代码中单独配置重试次数
retries: 0
# 默认情况下限制请求必须在 1000 毫秒内完成,对于具体服务可以在代码中单独配置
timeout: 1000

提示

若不希望在 YML 配置文件中指定 dubbo.scan.base-packages 参数,那么可以在主启动类上标注 @EnableDubbo(scanBasePackages = "xxx") 注解或者 @DubboComponentScan(basePackages = "xxx") 注解来替代。

测试代码

测试案例一

  • (1) 启动 Nacos 服务器

  • (2) 在 IDEA 开发工具内,分别启动 Provider 和 Consumer 模块

  • (3) 使用 Postman 等工具访问 Consumer 模块的 http://127.0.0.1:9095/system/sayHello/dubbo 接口,若接口可以返回 Hello dubbo 结果,则说明 Dubbo Triple 协议与 Protobuf(IDL) 正常工作

测试案例二

提示

  • 基于 IDL + Protobuf + Triple 协议实现的 RPC 服务,可以通过 gRPC 客户端、Dubbo 的客户端 Stub、grpcurl(命令行 gRPC 测试工具)等直接调用。
  • 下面将演示如何使用 grpcurl(命令行 gRPC 测试工具)直接调用 IDL + Protobuf + Triple 协议实现的 RPC 服务,grpcurl 工具可以从 GitHub 下载得到。
  • (1) 启动 Nacos 服务器

  • (2) 在 IDEA 开发工具内,单独启动 Provider 模块

  • (3) 切换到 .proto 文件所在的目录,执行以下 grpcurl 命令直接调用 Provider 模块提供的 RPC 服务,若可以正常返回 JSON 结果 {"message":"Hello, Dubbo!"},则说明 Dubbo Triple 协议与 Protobuf(IDL)正常工作

1
2
3
4
grpcurl -plaintext \
-proto ./UserService.proto \
-d '{"name":"Dubbo"}' \
127.0.0.1:50052 userservice.UserService/SayHello

特别注意:为了可以通过 gRPC 客户端(比如 grpcurl 工具)直接调用 RPC 服务,必须注意以下事项

  • Dubbo 使用的协议必须指定为 tri
  • 服务必须是由 .proto 文件生成的接口来定义,而不是纯 Java POJO 接口
  • 参数格式是 JSON 对象,比如 {"name":"Dubbo"}
  • 指定 .proto 文件的路径:比如 -proto ./UserService.proto
  • 指定 gRPC 服务的地址和端口:host:port,端口必须是 Triple 协议占用的端口(比如 50052),而不是 Tomcat 占用的端口
  • 指定要调用的 gRPC 方法,格式是:<proto包名>.<proto服务名>/<proto方法名>,大小写敏感,比如 userservice.UserService/SayHello

下载代码

  • 完整的案例代码可以直接从 GitHub 下载对应章节 dubbo-lesson-05

尝试支持 HTTP/JSON 调用服务

  • Dubbo 3 基于 IDL + Protobuf + Triple 协议实现的 RPC 服务,默认只能通过 gRPC 客户端、Dubbo 的客户端 Stub 等直接调用。如果希望通过标准的 HTTP 工具调用(比如 curl),可以在前面的 API 模块中额外引入 protobuf-java-util 依赖来实现:
1
2
3
4
5
6
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<!-- 建议和 protoc 版本保持一致 -->
<version>${protoc.version}</version>
</dependency>
  • 之后可以尝试通过 curl 命令直接调用 RPC 接口,如下所示:
1
2
3
4
curl \
--header "Content-Type: application/json" \
--data '{"name":"Dubbo"}' \
http://127.0.0.1:50052/com.clay.dubbo.api.UserService/SayHello

实测无效

实测在前面的 API 模块中引入 protobuf-java-util 依赖,同时 UserServiceImpl 类继承了由 .proto 文件自动生成的 DubboUserServiceTriple.UserServiceImplBase 类,发现无法通过 curl + application/json 的方式来调用 RPC 服务,返回错误信息 {"message":"Invoker not found","status":"404"}。如果 UserServiceImpl 类实现了由 .proto 文件自动生成的 UserService 接口,相当于基于 Java 接口 + Triple 协议提供 RPC 服务,虽然可以通过 curl + application/json 的方式来调用 RPC 服务,但无法通过 gRPC 客户端(协议)调用 RPC 服务,即失去了跨编程语言调用的功能。

使用插件生成 gRPC 的 Stub 代码

  • 使用 Maven 插件 protobuf-maven-plugin 时,若希望生成 gRPC 的服务接口(Stub)代码,可以参考以下 Maven 配置内容
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
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<dubbo.version>3.3.2</dubbo.version>
<protoc.version>3.25.3</protoc.version>
<grpc.version>1.73.0</grpc.version>
</properties>

<dependencies>
<!-- Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!-- Protobuf -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<!-- 建议和 protoc 版本保持一致 -->
<version>${protoc.version}</version>
</dependency>
<!-- gRPC Stub 相关 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<!-- 兼容 JDK 8 -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>

<build>
<extensions>
<!-- 跨平台支持 -->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.0</version>
</extension>
</extensions>

<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<!-- 是否在代码生成前清空输出目录(可选) -->
<clearOutputDirectory>false</clearOutputDirectory>
<!-- 自定义 .proto 文件所在的路径(可选)-->
<protoSourceRoot>${basedir}/src/main/proto</protoSourceRoot>
<!-- Maven 会自动下载对应平台的 protoc 可执行文件,用于根据 .proto 文件生成 Protobuf 的 Java 类(DTO、Message)代码 -->
<protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}
</protocArtifact>
<!-- Maven 会自动下载对应平台的 protoc-gen-grpc-java 可执行文件,用于根据 .proto 文件生成 gRPC 的 Stub 代码 -->
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<!-- 用来给 protoc 添加自定义代码生成插件(比如 Dubbo Triple 的 Stub 代码生成器)-->
<protocPlugins>
<protocPlugin>
<id>dubbo</id>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-compiler</artifactId>
<!-- dubbo-compiler 的版本必须与使用的 dubbo 版本一致 -->
<version>${dubbo.version}</version>
<mainClass>org.apache.dubbo.gen.tri.Dubbo3TripleGenerator</mainClass>
</protocPlugin>
</protocPlugins>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
<goal>compile-custom</goal>
<goal>test-compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
  • 对比前面案例的 Maven 配置文件,可以发现增加了以下依赖组件
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
<properties>
<grpc.version>1.73.0</grpc.version>
</properties>

<dependencies>
<!-- gRPC Stub 相关 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<!-- 兼容 JDK 8 -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
  • 对比前面案例的 Maven 配置文件,可以发现还增加了以下 configuration 配置,并去除了 outputDirectory 配置,不再指定 protoc 生成 Java 文件的存放目录
1
2
3
<!-- Maven 会自动下载对应平台的 protoc-gen-grpc-java 可执行文件,用于根据 .proto 文件生成 gRPC 的 Stub 代码 -->
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
  • 最终执行 mvn clean compile 命令后,根据 .proto 文件自动生成的代码源文件列表如下(默认存放在当前模块的 /target/generated-sources/protobuf 目录中):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
target/generated-sources
├── annotations
└── protobuf
├── grpc-java
│   └── com
│   └── clay
│   └── dubbo
│   └── api
│   └── UserServiceGrpc.java
└── java
└── com
└── clay
└── dubbo
└── api
├── DubboUserServiceTriple.java
├── HelloRequest.java
├── HelloRequestOrBuilder.java
├── HelloResponse.java
├── HelloResponseOrBuilder.java
├── UserService.java
└── UserServiceProto.java
  • gRPC 的 Stub & Skeleton 代码存放在新生成的 UserServiceGrpc.java 文件中,代码完全不依赖 Dubbo。这个文件是 gRPC 的 Java 桥接文件,它主要起到 gRPC 客户端调用代码(Stub)和服务端注册实现(Skeleton)的作用,其主要包含以下内容:
内容作用
服务定义 (Service Descriptor) 描述 gRPC 服务,供框架底层反射、注册用
Stub 类(异步 / 阻塞 / 未来模式)客户端调用 RPC 接口用
抽象基类(UserServiceImplBase服务端继承,实现业务逻辑

参考教程