Dubbo 3:服务通信
1. Dubbo3 是什么:微服务之间“像调用本地方法一样调用远程服务”
Dubbo3 是 Java 微服务里的 RPC 远程调用框架,核心作用是解决 服务之间如何通信、如何发现、如何负载均衡、如何治理 的问题。普通 Spring Boot 单体项目里,Controller 调 Service 是本地方法调用,因为它们都在同一个 JVM 进程里;但是微服务拆开以后,order-service、user-service、file-service 是多个独立启动的应用,甚至部署在不同机器上,这时订单服务不能直接 new UserService(),也不能直接 @Autowired 注入用户服务里的实现类。Dubbo 做的事情就是:让 Provider 暴露一个 Java 接口,Consumer 注入这个接口的代理对象,然后像调用本地 Service 一样调用远程服务。
一句话理解:Dubbo 把“本地接口方法调用”转换成“远程网络调用”,再把远程执行结果返回给当前服务。 你写的代码可能只是 userRpcService.getById(id),但底层其实经历了代理对象、注册中心、负载均衡、序列化、网络传输、Provider 执行、反序列化返回等过程。
Dubbo 的核心不是帮你写业务,而是帮你管理微服务之间的远程调用基础设施。它解决的问题包括:服务地址不能写死,Provider 可能扩容、下线、重启;一个服务可能有多个实例,需要负载均衡;远程调用可能失败,需要超时、重试、熔断、降级;服务升级时可能需要版本、分组、灰度;线上运行时还需要监控、链路追踪、限流等服务治理能力。
可以这样和 Spring Boot / Nacos 的关系放在一起记:
Spring Boot:负责把一个服务应用跑起来。
Nacos / Zookeeper:负责服务注册和发现,解决“服务在哪里”。
Dubbo:负责服务之间的 RPC 调用,解决“怎么调用”。
重点看
1. api 模块:有哪些 RpcService 接口?
2. @DubboService:当前服务提供了什么远程能力?
3. @DubboReference:当前服务依赖了哪些远程能力?
4. application.yml:注册中心、协议、端口、超时、重试怎么配?
1.1 Dubbo3 和 Spring Cloud Alibaba
Dubbo3 是微服务内部常用的 RPC 远程调用框架。它解决的核心问题是:在单体项目里,一个类可以直接通过 @Autowired 注入另一个 Service,然后像普通 Java 方法一样调用;但拆成微服务后,用户服务、订单服务、文件服务已经变成不同 JVM、不同进程,甚至部署在不同机器上,不能再直接 new 对象,也不能直接注入对方的实现类。Dubbo 的作用就是把“远程服务调用”伪装成本地方法调用:消费者只依赖一个公共接口,通过 @DubboReference 注入远程代理对象,代码看起来是在调用 Java 方法,实际底层会完成服务发现、网络通信、序列化、负载均衡、超时控制、失败重试等 RPC 调用流程。
如果你学过 Spring Cloud Alibaba,可以把它理解成一套微服务基础设施,而 Dubbo3 是其中“服务间调用”这一层的另一种选择。Spring Cloud Alibaba 常见组件包括 Nacos、OpenFeign、Sentinel、Gateway、Seata 等:Nacos 负责服务注册发现和配置管理,OpenFeign 负责基于 HTTP 的声明式远程调用,Sentinel 负责限流熔断,Gateway 负责统一入口和路由转发,Seata 负责分布式事务。Dubbo3 并不是替代整个 Spring Cloud Alibaba,而是主要替代或增强其中的远程调用部分。它也可以和 Nacos、Sentinel 等组件一起使用,例如 Dubbo 服务注册到 Nacos,消费者再从 Nacos 发现服务提供者。
在调用方式上,OpenFeign 更像是“调用别人的 Controller 接口”。Feign 通常基于 HTTP/REST 工作,接口方法会映射成一次 HTTP 请求,例如 @FeignClient 配合 @GetMapping("/users/{id}"),本质上是在访问另一个服务暴露出来的 HTTP API。Dubbo3 更像是“调用别人的 Service 接口”。服务提供方把 Java 接口实现类通过 @DubboService 暴露成远程服务,服务消费方通过 @DubboReference 注入这个接口的远程代理对象,然后像调用本地 Service 一样调用远程方法。简单记就是:Feign 面向 HTTP 接口,Dubbo 面向 Java 服务接口;Feign 更贴近 Controller 层,Dubbo 更贴近 Service 层。
Controller、普通 Service 和 Dubbo Service 的职责也要区分清楚。Controller 主要给前端、浏览器、Postman、App、外部系统调用,通信方式是 HTTP,典型注解是 @RestController。普通 Service 是本服务内部业务逻辑,只在当前 JVM 内部调用,典型注解是 @Service。Dubbo Service 是把某些业务能力开放给其他微服务调用,本质上仍然是业务服务,但它不是暴露 HTTP 接口,而是暴露 RPC 接口,典型注解是 @DubboService。所以一个方法到底放在 Controller 还是 Dubbo Service,取决于它是给“外部 HTTP 客户端”调用,还是给“内部其他微服务”调用。
Dubbo 项目一般会单独抽出一个 api 模块,用来存放远程调用的公共契约,例如 UserRpcService、FileRpcService、DTO、请求对象、响应对象、枚举等。服务提供者依赖这个 api 模块并实现接口,服务消费者也依赖这个 api 模块并调用接口。这样做的好处是 Provider 和 Consumer 不直接依赖彼此的业务实现,只共同依赖一份稳定的接口契约。不要把数据库 Entity、Mapper、Controller 放到 api 模块里,因为这些属于某个服务的内部实现细节,不应该暴露给其他微服务。api 模块应该只放“远程调用时必须让双方都知道的东西”。
一个 Dubbo 调用的大致流程是:服务提供方启动时,扫描到 @DubboService,把这个接口实现注册到注册中心,例如 Nacos;注册中心记录这个服务叫什么、有哪些提供者实例、地址和端口是什么。服务消费方启动时,扫描到 @DubboReference,Dubbo 会根据接口名、版本、分组等信息去 Nacos 找可用的服务提供者,并在本地生成一个代理对象。业务代码调用这个代理对象的方法时,Dubbo 会把方法名、参数、接口信息序列化成网络请求,发送给远程 Provider;Provider 收到请求后反序列化参数,找到真正的实现类方法执行,再把返回结果序列化传回 Consumer。对开发者来说,这一切看起来就是一次普通 Java 方法调用。
所以看 Dubbo3 项目时,重点不是先看 Controller,而是先看三个地方:api 模块里定义了哪些 RpcService 接口,代表系统对外暴露了哪些远程能力;服务提供方有哪些 @DubboService,代表当前服务真正实现并注册了哪些 RPC 能力;服务消费方有哪些 @DubboReference,代表当前服务依赖了哪些其他微服务能力。最后再看 application.yml,确认注册中心地址、Dubbo 协议、服务端口、超时时间、重试次数、版本号、分组等配置。这样顺着“接口定义 → 服务暴露 → 服务引用 → 配置注册中心”的顺序看,Dubbo 项目的结构就会清晰很多。
1.2一次 Dubbo 调用到底发生了什么
表面代码通常非常简单:
UserDTO user = userRpcService.getById(userId);
但这行代码在 Consumer 里调用的不是 Provider 里的真实实现类对象,而是 Dubbo 在 Consumer 本地生成的代理对象。完整流程可以理解成:
Consumer 业务代码
↓
调用 @DubboReference 注入的接口代理对象
↓
代理对象接住方法调用,拿到接口名、方法名、参数、返回类型、版本、分组等信息
↓
Dubbo 从注册中心获取可用 Provider 地址列表
↓
根据负载均衡策略选择一个 Provider 实例
↓
把请求参数序列化,通过 tri / dubbo 协议发出网络请求
↓
Provider 接收请求,反序列化参数
↓
Dubbo 在 Provider 本地找到真正的 @DubboService 实现类
↓
执行真实方法,例如 UserRpcServiceImpl.getById(userId)
↓
Provider 把返回结果序列化后通过网络返回
↓
Consumer 反序列化拿到 UserDTO,继续执行本地业务
所以 Dubbo 的调用过程不是:
Provider 把 UserRpcServiceImpl 对象传给 Consumer,
Consumer 直接调用这个真实对象。
而是:
Consumer 本地调用代理对象;
代理对象通过网络请求 Provider;
Provider 在自己本地执行真实实现类;
Provider 把方法返回值传回 Consumer。
所以你看到的 userService.getById(id) 表面上像本地调用,实际执行的是 代理对象 + 注册中心 + 网络通信 + 序列化 + 服务端方法执行。这和 MyBatis Mapper 有点像:你写的是接口,实际注入进来的不是普通实现类,而是框架生成的代理对象。区别是 MyBatis 代理对象负责执行 SQL,Dubbo 代理对象负责发起远程 RPC 调用。
这点和 MyBatis Mapper 很像:你写的是接口,注入进来的不是你手写的实现类,而是框架生成的代理对象。区别是 MyBatis 代理负责执行 SQL,Dubbo 代理负责发起远程 RPC 调用。
可以用伪代码理解 Dubbo 代理对象:
public class UserRpcServiceProxy implements UserRpcService {
@Override
public UserDTO getById(Long id) {
// 代理对象不查数据库,也不处理真实业务
// 它只负责把这次接口调用包装成 RPC 请求
return dubboClient.invoke(
"com.xxx.api.UserRpcService",
"getById",
new Object[]{id}
);
}
}
这不是真实 Dubbo 源码,只是帮助理解。代理对象的核心作用是:实现接口,但不真正处理业务;接收方法调用,然后转发给远程 Provider 执行。
1.3 微服务项目分层:Controller、Facade、Service、DubboService、Mapper 分别干什么
真实项目里不要只看 Dubbo 注解,还要先看清分层。一个比较清晰的微服务后端通常会有这些层:
Controller
↓
Facade / Application Service
↓
普通业务 Service
↓
Mapper / Repository / Redis / MQ / OSS
如果需要调用其他微服务:
Facade / Service
↓
@DubboReference 远程代理
↓
其他服务的 @DubboService
↓
其他服务内部的 Facade / Service / Mapper
Controller 是 HTTP 入口,负责接收前端、浏览器、Postman、网关或者第三方系统发来的 HTTP 请求。它一般做参数接收、登录态获取、基础校验、返回响应,不应该堆大量业务逻辑。
普通 Service 是本服务内部的业务层,比如 UserService、FileService、ShareService。它负责当前领域的核心业务逻辑,例如查用户、保存文件记录、更新分享状态、校验用户空间等。
Mapper 或 Repository 是数据访问层,负责操作数据库。它应该留在当前服务内部,不应该暴露给其他微服务。其他服务如果需要用户信息,不能直接依赖用户服务的 Mapper,更不能直接查用户服务数据库,而应该通过用户服务暴露的 RPC 接口获取。
@DubboService 是 RPC 暴露层,表示当前服务把某个 Java 接口发布成远程服务,给其他微服务通过 Dubbo 调用。它不是 HTTP Controller,没有浏览器 URL,不能像访问 /users/1 一样访问它。
@DubboReference 是 RPC 引用点,表示当前服务需要调用其他微服务暴露出来的远程接口。它注入的是本地代理对象,不是远程真实实现类。
1.4 Facade 门面层:把复杂业务流程封装成一个稳定入口
Dubbo3 可以没有 Facade 层。Facade 不是 Dubbo 的强制概念,而是项目架构设计。Dubbo 只要求提供方用 @DubboService 暴露接口、调用方用 @DubboReference 引用接口。当前项目额外设计 Facade 层,是为了把“微服务对外 RPC 接口”和“模块内部业务 Service”隔离,并配合 @Facade + FacadeAspect 做统一日志、校验和异常包装。
Facade 是分层设计思想,不是 Dubbo 独有;所有对外提供调用的框架(HTTP、Dubbo、gRPC)都有对应的门面层,只是命名、注解不一样;小型项目可省略门面层,中大型微服务为了治理、解耦,必须单独拆分门面。Facade 层 = 对外统一出入口,屏蔽内部 Service、DAO、多表、复杂编排逻辑,外部调用方只跟 Facade 打交道。
Dubbo 一般都会有“远程调用入口层”,但这个入口层不一定叫 Facade。中大型微服务强制拆分 Facade 门面层。把所有对外 RPC 接口统一收拢到 Facade,本地业务逻辑放 Service,彻底隔离两套场景。
Dubbo 负责微服务之间的远程调用。实际项目中通常会专门定义一层“RPC 对外入口”,用于暴露给其他服务调用。卡码网盘项目把这一层命名为 Facade,并在 Facade 方法上加 @Facade 注解配合 FacadeAspect 做统一日志、参数校验、异常包装和响应补全。但 Facade 不是 Dubbo 强制名称,其他项目可能叫 RemoteService、RpcService、Api 等。Dubbo 不强制使用 @Facade,也不强制配 AOP。@Facade + FacadeAspect 是当前项目自己的设计,用自定义注解标记 RPC 门面方法,再用 AOP 统一处理公共逻辑。注解名字、拦截规则、处理内容都可以由项目自己定义。
@DubboService 用在服务提供方的实现类上,表示把这个接口注册成 Dubbo 服务;@DubboReference 用在服务调用方的字段上,表示注入一个远程服务代理对象;@Facade 是项目自定义注解,用在 Dubbo 门面方法上,配合 FacadeAspect 做统一日志、校验、异常包装。
Facade 翻译成“门面”或“外观”,本质是给外部调用方提供一个更简单、更稳定的业务入口。真实项目里,一个业务动作通常不是调用一个 Service 就结束,而是要组合多个内部逻辑:查用户、查文件、校验权限、查分享状态、组装返回对象、调用其他微服务、写日志、发消息。如果 Controller 或 DubboServiceImpl 直接把这些逻辑全写进去,代码会越来越乱,接口层和内部实现也会强耦合。
所以很多项目会加一层 Facade:
Controller / @DubboService
↓
Facade 门面层
↓
多个普通 Service / @DubboReference / Mapper / Redis / MQ
Facade 的重点是 业务编排,普通 Service 的重点是 领域内部业务。例如网盘项目里,查询文件详情可能需要文件信息、用户信息、权限信息、分享信息。FileFacadeService 可以把这些调用组织起来,对 Controller 暴露一个简单方法:
@Service
public class FileFacadeService {
private final FileService fileService;
private final UserFacadeService userFacadeService;
private final ShareService shareService;
public FileFacadeService(FileService fileService,
UserFacadeService userFacadeService,
ShareService shareService) {
this.fileService = fileService;
this.userFacadeService = userFacadeService;
this.shareService = shareService;
}
public FileDetailResponse getFileDetail(Long fileId, Long userId) {
FileInfo file = fileService.getById(fileId);
UserInfo user = userFacadeService.getUserInfo(userId);
boolean canAccess = shareService.checkAccess(fileId, userId);
return FileDetailResponse.of(file, user, canAccess);
}
}
这里 FileFacadeService 不一定亲自查数据库,也不一定保存状态,它更像一个“业务总入口”:把多个服务调用串起来,屏蔽内部复杂性。Controller 只需要调用 Facade,不需要知道底层到底查了几个表、调了几个远程服务。
Facade 和普通 Service 的区别可以这样记:
普通 Service:负责一个领域内部的核心业务,例如 userService、fileService、shareService。
Facade:负责把多个 Service / RPC 调用组合成一个完整业务流程。
Facade 和 DubboServiceImpl 也不完全一样:
@DubboService:负责把某个能力暴露成 RPC 接口,给其他微服务调用。
Facade:负责封装当前服务内部复杂流程,可以被 Controller 调,也可以被 DubboServiceImpl 调。
实际项目里常见结构是:
前端请求
↓
Controller
↓
Facade
↓
普通 Service
↓
Mapper / Redis / OSS
其他微服务请求
↓
@DubboService
↓
Facade
↓
普通 Service
↓
Mapper / Redis / OSS
这样做的好处是:Controller 和 DubboServiceImpl 都很薄,只负责接收请求、参数转换和返回结果;Facade 负责流程编排;普通 Service 负责具体业务;Mapper 负责数据库。项目大了以后,这种分层会比所有逻辑都堆在 Controller 或 ServiceImpl 里清楚很多。
一句话记:Facade 是业务门面层,用来把复杂的内部服务调用封装成一个简单入口;Controller 面向 HTTP,DubboService 面向 RPC,Facade 面向业务流程编排。
2. 核心概念:Provider、Consumer、Registry、Protocol、Proxy
Dubbo3 可以先理解成一套让 Java 微服务之间“像调用本地方法一样调用远程方法”的 RPC 框架。普通单体项目里,OrderService 想调用 UserService,直接 @Autowired 注入就行,因为两个类在同一个 JVM 进程里;但微服务拆开后,order-service 和 user-service 是两个独立启动的应用,甚至可能部署在不同机器上,这时候订单服务不能直接拿到用户服务里的真实对象。Dubbo3 要解决的就是这个问题:用户服务把自己的能力发布出去,订单服务通过接口引用这个能力,Dubbo 在中间负责服务发现、代理生成、网络通信、序列化、负载均衡、超时、重试等工作。
4.1 Provider:服务提供者
Provider 是服务提供者,也就是“真正实现远程能力的一方”。例如 user-service 对外提供用户查询服务,那么它要先在公共 API 模块里定义接口,再在用户服务自己的模块里实现这个接口,最后在实现类上加 @DubboService。这里要注意,@DubboService 的重点不是“定义接口”,而是把这个 Spring Bean 同时发布成 Dubbo 远程服务;也就是说,Spring 能管理这个对象,Dubbo 也能把这个对象暴露给其他微服务调用。官方 Spring Boot 示例也是先定义 DemoService 接口,再用 DemoServiceImpl implements DemoService,并通过 @DubboService 发布服务
public interface UserRpcService {
UserDTO getById(Long id);
}
@DubboService
public class UserRpcServiceImpl implements UserRpcService {
@Override
public UserDTO getById(Long id) {
return new UserDTO(id, "张三");
}
}
这段代码里,UserRpcService 是远程调用契约,意思是“用户服务对外承诺提供 getById 这个能力”;UserRpcServiceImpl 是真正干活的实现类,里面可以查数据库、调缓存、做业务判断;@DubboService 则告诉 Dubbo:这个实现类可以被远程调用。以后订单服务并不关心 UserRpcServiceImpl 里面怎么实现,它只知道自己可以调用 UserRpcService.getById(Long id)。
4.2 Consumer:服务消费者
Consumer 是服务消费者,也就是“调用远程服务的一方”。例如 order-service 下单、查订单详情时需要用户信息,它不应该依赖用户服务的实现类,也不应该直接访问用户服务数据库,而是依赖公共 API 模块里的 UserRpcService 接口,然后通过 @DubboReference 注入一个远程引用。这个引用看起来是 UserRpcService 类型,但它并不是用户服务里的真实实现对象,而是 Dubbo 在消费者本地生成的代理对象。你调用 userRpcService.getById(1L) 时,代码表面上像普通 Java 方法调用,底层其实会被代理拦截,然后封装成一次 RPC 网络请求发到 Provider。官方示例中,消费者也是通过 @DubboReference 获取服务引用,然后像本地方法一样调用 demoService.sayHello("world")
@Service
public class OrderService {
@DubboReference
private UserRpcService userRpcService;
public OrderDetailDTO detail(Long orderId) {
UserDTO user = userRpcService.getById(1L);
return new OrderDetailDTO(orderId, user);
}
}
@DubboReference 注入的不是 Provider 里的真实实现类对象,而是 Dubbo 在 Consumer 本地生成的代理对象。Consumer 调用这个代理对象时,代理对象不会在本地执行真正的业务逻辑,而是把“接口名、方法名、参数”封装成 RPC 请求,通过网络发给 Provider。Provider 收到请求后,在自己的进程里调用真正的 UserRpcServiceImpl 方法,执行完后把返回值序列化并通过网络返回给 Consumer。Consumer 最后拿到返回值,就像本地方法调用一样。
Consumer 调用自己本地的“代理对象” 代理对象通过网络发请求给 Provider Provider 本地真正的实现类执行方法 Provider 把执行结果返回给 Consumer
Consumer 本地调用代理对象; Provider 远程执行真实对象; Dubbo 负责把本地接口调用转换成网络请求,再把远程执行结果返回回来。
4.3 Registry:注册中心
Registry 是注册中心,作用是保存服务地址和服务发现信息。没有注册中心的话,Consumer 就必须写死 Provider 的 IP 和端口,例如 192.168.1.10:50051,这样一旦 Provider 扩容、下线、换机器、重启,Consumer 配置就要改,非常麻烦。引入 Nacos、Zookeeper、Consul 这类注册中心后,Provider 启动时会把自己的应用名、地址、端口、协议、元数据等信息注册上去,Consumer 启动时会向注册中心订阅自己需要的服务,注册中心再把可用 Provider 地址推给 Consumer。之后 Provider 增加实例、实例宕机、服务下线,Consumer 可以感知地址变化,不需要在代码里写死服务地址。Dubbo 官方也把注册中心称为服务治理的核心组件,因为动态扩缩容、负载均衡、流量管理都依赖自动服务发现。
dubbo:
registry:
address: nacos://127.0.0.1:8848
这里的 address 表示 Dubbo 使用哪个注册中心。nacos://127.0.0.1:8848 的意思是使用本机 8848 端口上的 Nacos 作为注册中心;如果写 zookeeper://127.0.0.1:2181,就是使用 Zookeeper。对初学者来说,注册中心可以先理解成“服务通讯录”:Provider 启动后把自己登记进去,Consumer 调用前先查通讯录,拿到可用地址后再发起远程调用。
Dubbo3 和 Dubbo2 在服务发现上有一个重要变化:Dubbo2 主要是接口级服务发现,也就是围绕“某个接口有哪些 Provider 地址”来注册和订阅;Dubbo3 引入并推荐应用级服务发现,也就是围绕“某个应用有哪些实例地址”来注册和订阅,再结合元数据知道这个应用暴露了哪些接口。这样做的好处是注册中心里的数据量更小,更适合大规模微服务集群。官方文档说明,Dubbo3 兼容 Dubbo2 的接口级服务发现,同时定义了新的应用级服务发现模型;如果是新用户,官方建议显式配置 register-mode: instance 来启用应用级服务发现。
dubbo:
registry:
address: nacos://127.0.0.1:8848
register-mode: instance
在 Dubbo3 里,服务发现更推荐从“接口级”转向“应用级”,也就是注册中心更多关注应用实例,接口、方法、参数、协议等详细信息则属于元数据范畴。初学阶段不用把源码细节背下来,只要记住:注册中心解决“Provider 在哪里”的问题,元数据解决“Provider 提供什么接口、方法和调用信息”的问题。
4.4 Protocol:通信协议
Protocol 是通信协议,决定 Consumer 和 Provider 之间具体怎么传输请求。你可以把接口理解成“方法长什么样”,把注册中心理解成“服务在哪里”,把协议理解成“请求怎么发过去”。Dubbo 传统上有自己的 dubbo 协议,而 Dubbo3 更推荐关注 Triple 协议,配置名通常写 tri。Triple 是 Dubbo3 推出的基于 HTTP/2 的 RPC 协议,目标是增强标准化、多语言、网关、云原生和流式通信能力。官方文档也强调,Triple 协议是 Dubbo3 面向云原生和跨语言互通的重要协议能力。
dubbo:
protocol:
name: tri
port: 50051
这里 protocol.name: tri 表示使用 Triple 协议,protocol.port: 50051 表示 Provider 暴露 Dubbo 服务时监听的端口。注意这个端口不是前端访问 Controller 的 HTTP 端口,例如 server.port: 8080;它是 Dubbo RPC 通信端口。也就是说,一个 Spring Boot 应用可能同时有两个端口:server.port 给浏览器、前端、HTTP Controller 用;dubbo.protocol.port 给其他微服务通过 Dubbo 调用用。比如用户服务可能 HTTP 端口是 8081,Dubbo 协议端口是 50051,它既可以提供普通 REST 接口,也可以提供 Dubbo RPC 接口。
可以,这一节你现在已经理解到位了,主要问题是有些内容重复、顺序有点绕。我帮你改成一版更适合做笔记的:先解释 Proxy 是什么,再说明为什么需要它,然后按一次调用流程讲清楚。
4.5 Proxy:代理对象
Proxy 是 Dubbo 在 Consumer 本地生成的代理对象。Consumer 里通过 @DubboReference 注入的 UserRpcService,并不是 Provider 里的真实实现类 UserRpcServiceImpl,而是一个由 Dubbo 创建出来的代理对象。这个代理对象实现了 UserRpcService 接口,所以从代码类型上看,它和普通 UserRpcService 对象一样,可以调用 getById、createUser 这类接口方法。
@DubboReference
private UserRpcService userRpcService;
这里最容易误解的是:Consumer 并没有拿到 Provider 里的 UserRpcServiceImpl 对象。因为 Provider 和 Consumer 是两个独立运行的服务,分别在不同的 JVM 进程里,甚至可能部署在不同机器上。Java 对象本身不能直接跨进程传给另一个服务使用,所以 Provider 不会把实现类对象“传给”Consumer。Consumer 本地拿到的是一个代理对象,这个代理对象只是长得像 UserRpcService,真正作用不是执行业务逻辑,而是帮 Consumer 把方法调用转成远程请求。
从作用上看,代理对象可以理解成 Consumer 本地的接口替身。它实现了同一个接口,所以你可以像调用本地方法一样写:
UserDTO user = userRpcService.getById(1L);
但这行代码实际不是在订单服务本地查询用户,也不是执行 UserRpcServiceImpl.getById(1L)。真正发生的是:Consumer 调用了本地代理对象的 getById 方法,代理对象接住这次调用,拿到接口名、方法名、参数、返回值类型、版本、分组等信息,然后交给 Dubbo 的远程调用组件。Dubbo 再根据注册中心获取可用 Provider 地址,通过指定协议把请求发给 Provider。Provider 收到请求后,在自己的服务进程里找到真正的 UserRpcServiceImpl,执行 getById(1L),最后把执行结果返回给 Consumer。
可以把代理对象想象成这样:
public class UserRpcServiceProxy implements UserRpcService {
@Override
public UserDTO getById(Long id) {
// 代理对象不查数据库、不写业务逻辑
// 它只负责把这次接口调用转成一次远程 RPC 请求
return dubboClient.invoke(
"com.xxx.api.UserRpcService",
"getById",
new Object[]{id}
);
}
}
这段代码不是真实 Dubbo 源码,只是帮助理解。它说明了代理对象的核心作用:实现接口,但不真正处理业务;接收方法调用,然后转发给远程 Provider 执行。
一次完整调用流程可以理解成:
Consumer
调用 userRpcService.getById(1L)
↓
Consumer 本地代理对象 Proxy 接住调用
↓
Proxy 把接口名、方法名、参数等信息交给 Dubbo
↓
Dubbo 从注册中心获得可用 Provider 地址
↓
Dubbo 根据负载均衡策略选择一个 Provider 实例
↓
Dubbo 通过指定协议发送网络请求
↓
Provider 收到请求
↓
Provider 调用本地真实实现类 UserRpcServiceImpl.getById(1L)
↓
Provider 得到 UserDTO,并序列化返回
↓
Consumer 反序列化拿到 UserDTO
所以 Dubbo 的调用过程不是:
Provider 把 UserRpcServiceImpl 对象传给 Consumer,
Consumer 直接调用这个真实对象。
而是:
Consumer 本地调用代理对象;
代理对象通过网络请求 Provider;
Provider 在自己本地执行真实实现类;
Provider 把方法返回值传回 Consumer。
这个设计的好处是,Consumer 写业务代码时不需要关心网络通信细节。它只需要面向接口编程:
userRpcService.getById(1L);
至于 Provider 地址在哪里、走什么协议、请求怎么序列化、多个 Provider 怎么选择、调用失败是否重试、超时如何处理,这些都由 Dubbo 在底层完成。也就是说,Proxy 的作用就是把“远程服务调用”伪装成“本地接口方法调用”,降低微服务之间调用的复杂度。
因此,Dubbo 的核心开发习惯是:
接口放公共 API 模块;
Provider 依赖 API 并实现接口;
Consumer 依赖 API 并引用接口;
Dubbo 在 Consumer 本地生成接口代理;
代理对象负责把接口调用转成远程调用。
最后一句话记忆:
Proxy 是 Consumer 本地的远程调用代理。它看起来像接口对象,调用起来像本地方法,但真正作用是把方法调用转成网络请求,让 Provider 执行真实方法,再把结果返回给 Consumer。
3. 推荐项目结构:接口模块单独抽出来
Dubbo 项目通常会把公共接口单独拆成一个 API 模块。这个 API 不是前端接口地址,也不是 Controller 里的 /api/user/getById,而是“服务之间远程调用时共同遵守的 Java 契约包”。很多初学者会把 Web API 和 Dubbo API 混在一起,其实它们不是一回事。Web API 是给浏览器、前端、小程序、第三方系统通过 HTTP 调用的;Dubbo API 是给后端微服务之间通过 RPC 调用的。
Dubbo 的接口一般定义在公共 api 模块中,例如 mall-api。Provider 和 Consumer 都依赖这个 api 模块:Provider 通过 implements 实现接口,并用 @DubboService 暴露远程服务;Consumer 通过 @DubboReference 引用接口,Dubbo 根据这个接口类型在 Consumer 本地生成代理对象。Consumer 并不是从注册中心下载接口代码,接口代码是在编译阶段通过 Maven/Gradle 依赖获得的;注册中心只负责提供服务地址和元数据,告诉 Consumer 哪些 Provider 可以调用。简单说,api 模块解决“调用什么”,注册中心解决“去哪调用”,Provider 解决“怎么执行”。
mall-api
└── UserRpcService.java
service-user-provider
└── pom.xml 依赖 mall-api
└── 实现 UserRpcService
service-order-consumer
└── pom.xml 依赖 mall-api
└── 使用 @DubboReference 注入 UserRpcService
Consumer 能写这行代码:
@DubboReferenceprivate UserRpcService userRpcService;
前提就是:service-order-consumer 的 classpath 里已经有 UserRpcService.class。这个 .class 来自 mall-api 依赖。
- 开发/编译阶段: Consumer 通过依赖 mall-api,已经拿到了 UserRpcService 接口。
- 启动阶段: Dubbo 根据 @DubboReference 标注的 UserRpcService 类型, 去注册中心找“谁提供了这个接口对应的远程服务”。
- 运行阶段: Consumer 调用 UserRpcService 代理对象, Dubbo 把调用发给 Provider 执行。
mall-api
└── UserRpcService.java
└── UserDTO.java
└── CreateOrderRequest.java
└── OrderStatusEnum.java
service-user-provider
└── UserRpcServiceImpl.java
└── UserService.java
└── UserMapper.java
└── application.yml
service-order-consumer
└── OrderService.java
└── OrderController.java
└── application.yml
mall-api 是公共契约模块,它本身通常不是一个能独立启动的服务,没有启动类,没有 Controller,也不直接连数据库。它只放远程调用双方都需要知道的东西,例如 RPC 接口、DTO、请求对象、响应对象、枚举、常量。比如 UserRpcService 只规定“用户服务对外提供什么能力”,UserDTO 只规定“用户信息返回给别人时长什么样”。至于用户信息具体从 MySQL 查、从 Redis 查、还是从第三方接口查,这些都属于 Provider 内部实现,不应该放进 API 模块。
service-user-provider 是服务提供者模块,它依赖 mall-api,然后实现里面的接口。比如 UserRpcServiceImpl implements UserRpcService,在方法内部调用自己的 UserService、UserMapper、Redis、数据库等。这里可以有 Entity、Mapper、Service、Controller,因为这些是用户服务自己的内部代码。但是这些内部代码不应该暴露给其他微服务,否则服务之间就会耦合得很严重。
service-order-consumer 是服务消费者模块,它也依赖 mall-api,但它不依赖 service-user-provider。订单服务只知道 UserRpcService 这个接口,不知道 UserRpcServiceImpl,也不应该知道用户服务的 Mapper 和数据库表。订单服务需要用户信息时,通过 @DubboReference 注入 UserRpcService 远程代理,然后调用 getById。这样订单服务和用户服务之间只通过 API 契约通信,而不是互相依赖实现细节。
这里最重要的一句话是:API 模块定义合同,Provider 实现合同,Consumer 按合同调用。 如果把 Entity、Mapper、Controller、ServiceImpl 都塞进 API 包,短期看好像省事,长期会导致服务边界混乱。比如用户服务数据库字段改了,订单服务也可能被迫改;用户服务内部 Service 改名了,消费者也可能编译失败。正确做法是:数据库实体类是服务内部模型,DTO 才是服务对外模型;Mapper 是数据访问层,只能留在 Provider 内部;Controller 是 HTTP 入口,也不是 Dubbo 远程契约;API 包只保留稳定、必要、面向远程调用的接口和数据对象。
完整调用流程可以这样理解:用户服务启动后,Spring 创建 UserRpcServiceImpl 对象,Dubbo 发现它上面有 @DubboService,于是把它发布成远程服务,并把应用实例地址、协议端口、服务元数据等信息注册到 Nacos 或 Zookeeper。订单服务启动后,Dubbo 发现 @DubboReference UserRpcService,于是去注册中心查找可用的用户服务 Provider,并在订单服务本地生成一个 UserRpcService 代理对象。当订单服务执行 userRpcService.getById(userId) 时,代理对象会把接口名、方法名、参数、版本、分组等信息封装成 RPC 请求,根据负载均衡策略选择一个 Provider 实例,通过 Triple 或 Dubbo 协议发送过去。用户服务收到请求后,Dubbo 找到真正的 UserRpcServiceImpl.getById 方法执行,得到 UserDTO 后再返回给订单服务。订单服务拿到返回值后,继续执行自己的业务逻辑,比如组装订单详情或创建订单。
所以 Dubbo 项目的依赖关系应该是这样的:
service-user-provider → mall-apiservice-order-consumer → mall-api不要这样:service-order-consumer → service-user-provider
前一种是面向契约编程,服务之间边界清楚;后一种是直接依赖实现模块,会让微服务重新变成“分布式单体”,看起来拆开了,实际上还是互相绑死。
真实流程是:
1. 用户服务启动
2. UserRpcServiceImpl 被 @DubboService 暴露成远程服务
3. 用户服务把自己的服务信息注册到注册中心,例如 Nacos / Zookeeper
4. 订单服务启动
5. @DubboReference 根据接口名 UserRpcService 去注册中心找服务提供者
6. Dubbo 给订单服务生成一个代理对象
7. 订单服务调用 userRpcService.getUserById(userId)
8. 代理对象把方法名、参数、接口名打包成网络请求
9. 请求发送到用户服务
10. 用户服务执行 UserRpcServiceImpl.getUserById
11. 用户服务返回 UserDTO
12. 订单服务拿到结果,继续创建订单
你可以理解成:
mall-api:定义合同
provider:实现合同
consumer:按照合同调用
registry:告诉 consumer provider 在哪里
Dubbo:负责代理、网络通信、序列化、负载均衡、超时、重试
4. Spring Boot 快速使用
4.1 引入依赖
Provider 和 Consumer 都需要 Dubbo Spring Boot Starter。如果用 Nacos 作为注册中心,还需要 Nacos Starter。官方 Spring Boot 配置文档说明,dubbo-spring-boot-starter 会管理核心 Dubbo 依赖、识别 dubbo. 开头的配置项,并扫描 @DubboService 等注解;使用 Nacos 时可以引入 dubbo-nacos-spring-boot-starter。(Apache Dubbo)
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
这里用的是官方文档示例版本。实际项目里最好用父工程统一管理版本,不要每个模块乱写不同版本。官方 Nacos 示例里也给出了 Spring Boot 应用使用 dubbo-spring-boot-starter 和 dubbo-nacos-spring-boot-starter 的方式。(Apache Dubbo)
4.2 启动类开启 Dubbo
@SpringBootApplication
@EnableDubbo
public class UserProviderApplication {
public static void main(String[] args) {
SpringApplication.run(UserProviderApplication.class, args);
}
}
@EnableDubbo 的作用是启用 Dubbo 相关组件,扫描 Dubbo 注解,加载 Dubbo 配置,让 @DubboService 和 @DubboReference 生效。官方示例入口类也使用 @EnableDubbo 来加载和启动 Dubbo 组件。(Apache Dubbo)
4.3 API 模块写接口
public interface UserRpcService {
UserDTO getById(Long id);
}
public class UserDTO implements Serializable {
private Long id;
private String username;
public UserDTO() {
}
public UserDTO(Long id, String username) {
this.id = id;
this.username = username;
}
// getter / setter
}
远程传输对象建议实现 Serializable,字段尽量用简单类型、包装类型、String、集合、普通 DTO,不要传复杂对象、连接对象、Request/Response、MultipartFile 这类和 Web 容器强绑定的东西。
4.4 Provider 实现并发布服务
@DubboService
public class UserRpcServiceImpl implements UserRpcService {
@Override
public UserDTO getById(Long id) {
return new UserDTO(id, "张三");
}
}
@DubboService 不是普通 Controller 接口,也不是 HTTP 接口,它表示把这个 Java 服务暴露成 Dubbo RPC 服务。别人不是通过浏览器 URL 调它,而是通过 Dubbo Consumer 的代理对象调它。
4.5 Provider 配置
server:
port: 8081
spring:
application:
name: service-user
dubbo:
application:
name: service-user
protocol:
name: tri
port: 50051
registry:
address: nacos://127.0.0.1:8848
server.port 是 Spring Boot 的 HTTP 端口,主要给 Controller、Actuator、普通 HTTP 接口用;dubbo.protocol.port 是 Dubbo RPC 端口,给其他微服务远程调用用。两者不是一个东西。一个服务可以同时有 HTTP 端口和 Dubbo 端口。
4.6 Consumer 引用远程服务
@Service
public class OrderService {
@DubboReference
private UserRpcService userRpcService;
public String createOrder(Long userId) {
UserDTO user = userRpcService.getById(userId);
return "为用户 " + user.getUsername() + " 创建订单";
}
}
Consumer 只依赖 mall-api,不依赖 service-user-provider。@DubboReference 会根据接口去注册中心找 Provider,然后生成远程代理。
4.7 Consumer 配置
server:
port: 8082
spring:
application:
name: service-order
dubbo:
application:
name: service-order
registry:
address: nacos://127.0.0.1:8848
consumer:
timeout: 3000
retries: 0
Consumer 一般不一定要配置 dubbo.protocol.port,因为它主要是调用别人,不一定要暴露自己的 Dubbo 服务。如果这个服务既是消费者又是提供者,那也可以配置协议端口并发布自己的服务。
4.7 常用配置速查
dubbo:
application:
name: service-order
protocol:
name: tri
port: 50051
registry:
address: nacos://127.0.0.1:8848
provider:
timeout: 3000
retries: 0
consumer:
timeout: 3000
retries: 0
check: false
dubbo.application.name 是当前 Dubbo 应用名,服务注册、治理、监控都会用到。dubbo.protocol.name 是协议,Dubbo3 常见写 tri;dubbo.protocol.port 是 Provider 暴露 RPC 服务的端口。dubbo.registry.address 是注册中心地址,Nacos 写 nacos://host:8848,Zookeeper 写 zookeeper://host:2181。官方配置文档中也展示了 dubbo.application、dubbo.protocol、dubbo.registry 这些核心配置项。(Apache Dubbo)
timeout 是远程调用超时时间。远程调用不是本地调用,必须设置超时,否则对方服务卡住可能拖死当前服务。retries 是失败重试次数,查询类接口可以适当重试,写操作例如创建订单、扣库存、支付扣款一般不要随便重试,否则可能造成重复执行。check 表示启动时是否检查依赖服务可用;开发环境可以 check: false,避免某个服务没启动导致当前服务启动失败,生产环境要根据依赖强弱决定。
| 配置 | 作用 | 怎么记 |
|---|---|---|
dubbo.application.name |
当前 Dubbo 应用名 | 注册、治理、监控都会用 |
dubbo.registry.address |
注册中心地址 | Nacos 写 nacos://host:8848 |
dubbo.protocol.name |
Dubbo 协议 | Dubbo3 常见 tri |
dubbo.protocol.port |
RPC 暴露端口 | Provider 给别人调用的端口 |
dubbo.consumer.timeout |
消费者调用超时 | 网络调用必须有超时 |
dubbo.consumer.retries |
调用失败重试次数 | 写操作一般设 0 |
dubbo.consumer.check |
启动时是否检查 Provider | 开发可 false,生产谨慎 |
version |
服务版本 | Provider / Consumer 要匹配 |
group |
服务分组 | Provider / Consumer 要匹配 |
5. Dubbo3 的几个重要特征
1. 接口式 RPC:强类型、开发体验接近本地方法
Dubbo 典型写法是 Java Interface + DTO。优点是类型清楚、IDE 友好、调用方便;缺点是服务契约需要维护好,接口变更会影响消费者。接口一旦发布,就不要随便改方法名、参数类型、返回值类型。新增字段一般比删除字段安全,新增方法一般比修改旧方法安全。
2.注册中心服务发现:不用写死 IP
Provider 注册服务,Consumer 订阅服务。这样服务扩容时,Consumer 自动拿到多个实例;服务下线时,Consumer 自动移除不可用实例。Dubbo 的服务发现是基于注册中心协调的 client-based 模型,常见注册中心包括 Nacos、Zookeeper 等。(Apache Dubbo)
3.Triple 协议:Dubbo3 的重点协议
Dubbo3 的 Triple 协议基于 HTTP/2,更适合开放互通、多语言、网关、流式通信等场景。以前 Dubbo2 的私有协议性能强,但互通性弱;Triple 的目标是让 Dubbo 不只适合 Java 内部 RPC,也能更好接入 gRPC 生态和网关体系。(Apache Dubbo)
4.服务治理:负载均衡、超时、重试、分组、版本
Dubbo 不只是“发请求”,还包含服务治理能力。比如多个 Provider 实例时如何选择一个,这叫负载均衡;调用失败怎么办,这涉及重试和容错;同一个接口有新旧实现,可能用 version 区分;同一个接口有不同业务分组,可能用 group 区分。
示例:
@DubboService(version = "1.0.0", group = "user")
public class UserRpcServiceImpl implements UserRpcService {
}
@DubboReference(version = "1.0.0", group = "user", timeout = 3000, retries = 0)
private UserRpcService userRpcService;
Provider 和 Consumer 的 version、group 要匹配,否则可能找不到服务。
5.和 Spring Cloud / OpenFeign 的区别
OpenFeign 更像 HTTP 接口调用,通常基于 REST URL;Dubbo 更像接口级 RPC,直接面向 Java Interface。Feign 写法接近 Controller HTTP 调用,Dubbo 写法接近本地 Service 调用。Feign 的优势是 HTTP 生态通用、对外接口友好;Dubbo 的优势是 RPC 治理能力强、接口调用体验好、性能和服务治理能力更偏内部微服务。实际公司里两者可以共存:对外 HTTP,用 Spring MVC / Gateway;内部高频服务调用,用 Dubbo RPC。
6.Dubbo 和 Nacos 的关系
Dubbo 是 RPC 框架,Nacos 是注册中心/配置中心。Dubbo 负责“怎么调用”,Nacos 负责“服务在哪里”。没有 Nacos 这类注册中心时,Consumer 不知道 Provider 地址;有了 Nacos,Provider 启动后注册自己,Consumer 自动发现它。
最小流程:
user-service 启动
↓
Dubbo 暴露 UserRpcService
↓
注册到 Nacos:service-user / IP / Dubbo端口 / 元数据
↓
order-service 启动
↓
@DubboReference 订阅 UserRpcService
↓
从 Nacos 拿到 user-service 地址
↓
发起 RPC 调用
官方 Nacos 文档中,Spring Boot 使用 Nacos 只需要添加 Nacos Starter 并配置 dubbo.registry.address: nacos://localhost:8848。(Apache Dubbo)
下面是在你原笔记基础上改好的版本。核心是帮你区分:OpenFeign 更像“帮你写 HTTP 请求的工具”,Dubbo3 更像“让你直接调用远程 Service 的 RPC 框架”。
6. Dubbo3 和 OpenFeign 的关系
Dubbo3 和 OpenFeign 都是微服务之间做远程调用的工具。它们解决的是同一个问题:一个服务拆出去以后,另一个服务怎么调用它。比如 order-service 需要查用户信息,而用户逻辑在 user-service 里,这时候订单服务不能直接调用用户服务里的 Java 对象,因为两个服务是独立进程。于是就需要一种远程调用方式:OpenFeign 可以做,Dubbo3 也可以做。
OpenFeign 是 Spring Cloud 体系里常见的声明式 HTTP 客户端。所谓“声明式”,就是你不用自己手写 RestTemplate 或 HttpClient 去拼 URL、发请求、解析响应,而是定义一个 Java 接口,在接口上写 @FeignClient、@GetMapping、@PostMapping 这些注解,Feign 会根据这些注解帮你生成 HTTP 调用逻辑。它本质上还是在访问对方服务的 HTTP 接口,也就是对方的 Controller。
例如用户服务有一个普通 Controller:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public UserDTO getById(@PathVariable Long id) {
return userService.getById(id);
}
}
订单服务里用 Feign 调用它:
//`@FeignClient(name = "user-service")` 标记当前接口是访问名为 user-service 微服务的远程调用客户端,
//运行时自动生成代理,自动完成服务发现、负载均衡、HTTP 收发、JSON 序列化,实现跨微服务像本地方法一样调用 REST 接口
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/users/{id}")
UserDTO getById(@PathVariable Long id);
}
然后订单服务里注入:
@Service
public class OrderService {
@Autowired
private UserFeignClient userFeignClient;
public OrderDetailDTO detail(Long orderId) {
UserDTO user = userFeignClient.getById(1L);
return new OrderDetailDTO(orderId, user);
}
}
这时你写的是 Java 方法调用:
userFeignClient.getById(1L);
但实际发生的是:
order-service
↓ HTTP 请求
GET http://service-user/users/1
↓
user-service 的 UserController
↓
返回 JSON
所以 Feign 的重点是:把 Java 接口方法转换成 HTTP 请求。它调用的是对方暴露出来的 Web 接口,也就是 Controller 层。Nacos 在这里通常负责服务发现:service-user 这个服务名对应哪些 IP 和端口,Feign 通过服务名找到具体实例,再发 HTTP 请求。
Dubbo3 不一样。Dubbo3 是 RPC 框架,它通常不通过 Controller,而是通过专门的 RPC 接口调用对方服务。Provider 这边用 @DubboService 暴露服务接口,Consumer 这边用 @DubboReference 注入远程代理对象。它看起来更像调用远程的 Service 方法。
比如公共 API 模块里定义接口:
public interface UserRpcService {
UserDTO getById(Long id);
}
用户服务作为 Provider 实现接口:
@DubboService
public class UserRpcServiceImpl implements UserRpcService {
@Override
public UserDTO getById(Long id) {
return userService.getById(id);
}
}
订单服务作为 Consumer 调用接口:
@Service
public class OrderService {
@DubboReference
private UserRpcService userRpcService;
public OrderDetailDTO detail(Long orderId) {
UserDTO user = userRpcService.getById(1L);
return new OrderDetailDTO(orderId, user);
}
}
这时你写的也是:
userRpcService.getById(1L);
但实际发生的是:
order-service
↓ Dubbo 代理对象
RPC / Triple 请求
↓
user-service 的 @DubboService
↓
执行 UserRpcServiceImpl.getById
↓
返回 UserDTO
所以 Dubbo3 的重点是:把 Java 接口方法转换成 RPC 请求。它调用的不是 Controller,而是 Provider 暴露出来的 Dubbo 服务接口。Nacos 在这里同样可以负责服务发现:告诉 Consumer 哪些 Provider 实例可以调用。只不过 Feign 拿到地址后发 HTTP 请求,Dubbo 拿到地址后发 RPC 请求。
两者最大的区别可以这样理解:OpenFeign 更贴近“前后端接口那套思路”,因为它走的是 HTTP + Controller + URL;Dubbo3 更贴近“后端 Service 之间直接调用”,因为它走的是 RPC + 接口 + Provider 实现类。
OpenFeign:
Consumer 调 Feign 接口
↓
转成 HTTP 请求
↓
访问 Provider 的 Controller
↓
Controller 调 Service
↓
返回 JSON
Dubbo3:
Consumer 调 Dubbo 接口代理
↓
转成 RPC 请求
↓
访问 Provider 暴露的 @DubboService
↓
直接执行服务实现类
↓
返回结果
在 Spring Cloud Alibaba 项目里,Nacos 经常同时服务于这两种方式。它不负责真正调用,只负责服务注册和发现。你可以把 Nacos 理解成“服务通讯录”:user-service 启动后把自己的地址注册进去,order-service 调用时先从 Nacos 找到 user-service 的可用实例。至于找到以后怎么调用,是走 Feign 的 HTTP,还是走 Dubbo 的 RPC,这是调用框架决定的。
Nacos:服务在哪里
OpenFeign:找到服务后,按 HTTP/Controller 方式调用
Dubbo3:找到服务后,按 RPC/Service 接口方式调用
一般学习和项目选择上,可以这样记:如果你的项目是典型 Spring Cloud / Spring Cloud Alibaba 教程项目,大概率先用 OpenFeign,因为它和 Controller、REST、HTTP、网关这些概念连得更紧,理解成本低,通用性也强。比如服务之间只是普通增删改查、调用频率不特别高、接口本来就以 HTTP 形式暴露,那么 Feign 很顺手。
Dubbo3 更适合内部服务之间高频调用、接口治理要求更强、希望更像 Java Service 调用、需要更完整 RPC 能力的场景。比如订单、库存、支付、用户这些后端服务之间频繁互调,希望有更细的超时、重试、负载均衡、协议、序列化、服务治理能力,就可以使用 Dubbo。Dubbo3 的 Triple 协议还增强了 HTTP/2、流式调用、多语言和云原生方向的能力。
但不要理解成“Dubbo3 一定比 Feign 高级,所以必须替代 Feign”。它们是两种远程调用方案,项目里可以只用 Feign,也可以只用 Dubbo,也可以两者共存。常见理解是:对外 HTTP 接口走 Controller / REST / Feign,对内高频服务调用可以走 Dubbo RPC。具体用哪个,要看项目技术栈、团队习惯、性能要求、治理需求和接口边界。
最后把你这节笔记精炼成一段:
Dubbo3 和 OpenFeign 都用于微服务之间远程调用,但调用方式不同。OpenFeign 是 Spring Cloud 常用的声明式 HTTP 客户端,它把 Java 接口方法转换成 HTTP 请求,通常访问对方服务的 Controller 接口,例如 @FeignClient + @GetMapping;Dubbo3 是 RPC 框架,它把 Java 接口方法转换成 RPC 请求,通常访问对方通过 @DubboService 暴露的服务接口,消费者通过 @DubboReference 注入代理对象调用。两者都可以配合 Nacos 使用,Nacos 负责“服务在哪里”,Feign 或 Dubbo 负责“怎么调用”。简单记:Feign 调 Controller,走 HTTP/REST,适合 Spring Cloud 常规服务调用;Dubbo 调 Service 接口,走 RPC/Triple,更偏内部高频调用和服务治理。项目中不是必须二选一,可以根据场景选择,甚至共存。