Dubbo 3 配合 Java 接口使用 Triple 协议

大纲

前言

本文将整合 SpringBoot 与 Dubbo 3,并结合 Java 接口(纯 POJO 接口)使用 Triple 协议,使用的注册中心是 Nacos。值得一提的是,本文实现的 RPC 服务可以使用标准 HTTP 工具(比如 Curl + JSON)直接调用。对于期望平滑升级到 Dubbo 3,并且没有多编程语言业务或者不熟悉 Protobuf 的用户而言,使用 Java 接口直接定义服务的模式是最简单的使用 Triple 协议的方式,此模式下 Triple 协议的使用方式与经典 Dubbo 协议(基于 TCP 长连接)是基本一样的。

特别注意

  • 基于 Java 接口(纯 POJO 接口)+ Triple 协议实现的 RPC 服务,可以通过标准的 HTTP 工具(比如 curl)直接调用 RPC 服务,缺点是不适合开发多编程语言业务。
  • 由于 gRPC 仅支持 Protobuf 模式(使用 .proto 文件定义服务),因此基于 Java 接口 + Triple 协议实现的 RPC 服务无法与谷歌官方原生的 gRPC 客户端(协议)互相调用;若需要实现互相调用,可以改为 配合 Protobuf(IDL)使用 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:抽取出来的公共模块,存放公用的实体类和接口
  • provider:服务提供者,实现 api 模块中的接口
  • customer:服务消费者,调用服务提供者中的接口

案例代码

API 模块

  • 引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
<properties>
<lombok.version>1.18.36</lombok.version>
</properties>

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
  • 定义实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

private Long id;

private String name;

private Integer age;

}
  • 定义 Java 接口(纯 POJO 接口)
1
2
3
4
5
6
7
public interface UserService {

Boolean update(User user);

User getById(Long id);

}

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>
  • 接口实现类,@DubboService 注解主要用于暴露服务,使其能够被 Dubbo 框架识别并注册到服务注册中心
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.apache.dubbo.config.annotation.DubboService;

@DubboService
public class UserServiceImpl implements UserService {

@Override
public Boolean update(User user) {
System.out.println(user);
return true;
}

@Override
public User getById(Long id) {
return new User(id, "Peter", 18);
}

}
  • 主启动类
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
import org.apache.dubbo.config.annotation.DubboReference;

@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("/getUser/{id}")
public User getUser(@PathVariable("id") Long id) {
return userService.getById(id);
}

@PostMapping("/updateUser")
public Boolean updateUser(@RequestBody User user) {
return userService.update(user);
}

}
  • 主启动类
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/getUser/1 接口,若接口可以正常返回 JSON 响应结果,则说明 Dubbo Triple 协议与 Java 接口正常工作

测试案例二

提示

Dubbo 3 基于 Java 接口(纯 POJO 接口)定义服务,并使用 Triple 协议发布 RPC 服务后,可以通过 HTTP + JSON 的方式直接调用 RPC 服务。

  • (1) 启动 Nacos 服务器

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

  • (3) 通过 curl 工具直接调用 Provider 模块提供的 RPC 服务,若可以正常返回响应结果,则说明 Dubbo Triple 协议与 Java 接口正常工作

1
2
3
4
curl \
-H "Content-Type: application/json" \
-d '[{"id":1,"name":"Peter","age":18}]' \
http://127.0.0.1:50052/com.clay.dubbo.service.UserService/update
1
2
3
4
curl \
-H "Content-Type: application/json" \
-d '[1]' \
http://127.0.0.1:50052/com.clay.dubbo.service.UserService/getById

特别注意:为了可以通过 HTTP + JSON 直接调用 RPC 服务,必须注意以下事项

  • Dubbo 使用的协议必须指定为 tri
  • 服务必须是由纯 Java 接口来定义,不能是 .proto 文件生成的接口
  • 参数格式是 JSON 数组,比如 ["Tom", 18],可以传递多个参数(如下述表格所示)
  • URL 格式:http://host:port/接口全限定名/方法名,端口必须是 Triple 协议占用的端口(比如 50052),并不是 Tomcat 占用的端口
参数形式 JSON 数据
单个基本类型参数["abc"]
多个基本类型参数["abc", 123]
基本类型 + 对象["admin", {"name":"Tom","age":18}]
单个对象参数[{"name":"Tom","age":18}]

下载代码

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

Dubbo 实现 REST 服务

HTTP 接入方式

由于经典 Dubbo 协议(基于 TCP 长连接)无法支持 HTTP 流量直接接入,因此需要有一层网关实现前端 HTTP 协议到后端 Dubbo 协议的转换过程(HTTP -> Dubbo)。Dubbo 框架提供了 泛化调用 能力,可以让网关在无服务接口定义的情况下对后端服务发起调用。

目前社区有很多开源网关产品(Higress、Shenyu、APISIX、Tengine 等)支持 HTTP 协议转换为 Dubbo 协议,它们大部分都提供了可视化界面配置参数映射(泛化调用),同时还支持基于 Nacos、Zookeeper 等主流注册中心的自动地址发现,具体请查看 HTTP 网关接入。Dubbo 自 3.3 版本开始 Triple 协议支持基于 Java 接口以 REST 风格发布标准的 HTTP 服务,因此,发布 REST 风格的服务等同于使用 Triple 协议,只不过需要在服务定义(Java 接口)上加入特定的注解(Spring Web 注解或者 JAX-RS 注解)。对于 Dubbo 老版本的 REST 用户配置是 name: rest,用户可以选择将改为 name: tri;即使不修改也没有问题,Dubbo 框架会自动将 REST 转换为 Triple 协议实现。所以说,REST 只是 Triple 协议的一种特殊的发布形式,为了实现 REST 格式发布,需要为服务接口定义增加注解而已。

实现 REST 服务

在上面的案例中,如类似 http://127.0.0.1:50052/user/update 这样的 REST 风格接口更符合前端常用的访问方式,要做到这一点,除了配置网关(如 Higress)的 Rewrite 规则之外,Dubbo 框架还为 Triple 服务暴露 REST 风格的 HTTP 访问路径提供了内置支持,具体使用方式取决于使用的是基于 Protobuf(使用 .proto 文件)的服务定义模式,还是基于 Java 接口的服务定义模式:

  • Java 接口模式:通过直接为 Java 接口添加注解,可以发布 REST 风格服务,目前支持 Spring Web 与 JAX-RS 两套注解标准。
  • Protobuf 模式:通过使用 grpc-gateway 中间件可以发布 REST 风格服务。

Dubbo 3 基于 Java 接口 + Triple 协议 + Spring Web 注解实现 REST 风格接口,只需要在 Java 接口上添加相应注解即可,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public interface UserService {

@PostMapping("/update")
Boolean update(@RequestBody User user);

@GetMapping("/getById/{id}")
User getById(@PathVariable("id") Long id);

}

详细教程

Dubbo 基于 Java 接口 + Triple 协议实现 REST 服务的详细教程可以看 这里

参考教程