Dubbo 如何实现像本地方法一样调用远程方法的?
一则或许对你有用的小广告
欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书
《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于
Spring AI + Spring Boot 3.x + JDK 21...,查看介绍《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,查看介绍;演示链接:http://116.62.199.48:7070/《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/
新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍
截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观
面试考察点
- RPC 基础理解:这题不是问你 “Dubbo 能不能远程调用”,而是看你能不能讲清楚 RPC 怎么把一次网络请求伪装成本地方法调用。
- Dubbo 调用链路:消费者代理、服务发现、负载均衡、协议编码、网络传输、服务端反射调用,这几段要能顺下来。
- 源码关键模型:能顺手提到
Proxy、Invoker、Protocol、ExchangeClient、Filter这些抽象,面试官会觉得你不只是停留在使用层。
核心答案
Dubbo 之所以能让远程调用看起来像本地方法调用,主要靠 动态代理 + 服务发现 + 网络通信 + 序列化/反序列化 + 服务端反射调用 这一套机制。
消费者拿到的不是 provider 那边真正的实现类,而是 Dubbo 在本地生成的代理对象。你调用 userService.getUser(1) 时,代理对象会拦住这次调用,把接口名、方法名、参数类型、参数值等信息封装成一次 RPC 请求。然后它通过注册中心找到 provider 地址,再经过负载均衡选出一台机器,最后用 Netty 这类网络框架把请求发到服务端。
服务端收到请求后,会先反序列化参数,再根据接口名和方法名找到真正的服务实现类,通过反射执行本地方法。方法执行完以后,服务端把返回值序列化发回消费者,消费者侧代理对象再把这个结果返回给业务代码。
所以你写代码时看到的是一次普通方法调用,底层跑的其实是一整套远程 RPC 流程。
深度解析
一、先从消费者侧代理说起
平时我们在业务代码里可能会这么写:
@DubboReference
private UserService userService;
public UserDTO queryUser(Long userId) {
return userService.getUser(userId);
}
这里的 userService 看起来像一个普通 Java 对象,但它通常不是 provider 的真实实现类,而是 Dubbo 在消费者侧生成的代理对象。
这个代理对象干的事情很直接:拦截方法调用,把 Java 方法调用翻译成 RPC 请求。
比如你调用 getUser(1L),代理对象会拿到这些信息:
| 信息 | 示例 |
|---|---|
| 接口名 | com.demo.UserService |
| 方法名 | getUser |
| 参数类型 | Long |
| 参数值 | 1L |
| 版本/分组 | version、group |
| 附加信息 | 超时时间、隐式参数、链路信息等 |
这就是 “像本地方法一样调用” 的第一层原因:业务代码不用自己处理网络、连接、编解码这些细节,代理对象在背后接过去了。
二、一次 Dubbo 调用大概怎么走?
这条链路可以按几步理解:
- 消费者调用代理对象:业务代码以为自己在调用本地接口。
- 代理对象封装请求:Dubbo 把方法调用转换成
Invocation,里面放着方法名、参数、附件等信息。 - 服务发现和负载均衡:从注册中心或本地缓存目录里拿 provider 列表,再挑出一台机器。
- 网络发送请求:Dubbo 协议把请求编码成二进制数据,通过 Netty 发出去。
- 服务端执行真实方法:provider 根据请求信息找到服务实现类,然后执行目标方法。
- 结果返回消费者:返回值经过序列化、网络传输、反序列化,再由代理对象交还给业务代码。
说白了,Dubbo 把 “方法调用” 包成了 “网络请求”,又把 “网络响应” 拆回了 “方法返回值”。
三、Dubbo 里面几个关键抽象
这块不用把源码背成绕口令,但几个核心概念最好能说出来。
| 抽象 | 作用 |
|---|---|
Proxy |
在消费者侧生成接口代理,让远程调用看起来像本地调用 |
Invocation |
描述一次方法调用,包括方法名、参数类型、参数值、附件等 |
Invoker |
Dubbo 对 “可执行调用对象” 的统一抽象,消费者和服务端都会用到 |
Protocol |
负责服务暴露和引用,比如 dubbo 协议、tri 协议 |
Directory |
管理服务提供者列表,通常和注册中心数据有关 |
LoadBalance |
从多个 provider 中选一个 |
Filter |
调用链增强,比如日志、监控、限流、鉴权等 |
Invoker 是 Dubbo 里特别核心的一个模型。消费者侧调用代理,最后会转成对 Invoker.invoke() 的调用;服务端暴露服务时,也会把真实服务实现包装成 Invoker。这个设计挺妙的:不管本地实现还是远程服务,最后都被抽成一次 invoke。
四、服务端是怎么执行真实方法的?
服务提供者启动时,会把服务实现类暴露出去,比如 UserServiceImpl。Dubbo 会把这个实现类包装成一个可调用对象,注册到本地服务暴露表里,同时把服务地址注册到注册中心。
当服务端收到消费者发来的请求后,通常会做这些事:
- 解析请求里的接口名、方法名、参数类型和参数值。
- 找到对应的服务实现对象,比如
UserServiceImpl。 - 通过反射或方法句柄调用目标方法。
- 拿到返回值或异常。
- 把结果序列化后写回给消费者。
这里有个点很容易被说漏:消费者和 provider 之间传的不是 Java 方法本身,而是执行这次方法调用所需的元数据和参数。真正的方法执行,发生在服务端。
五、它和普通 HTTP 调用有什么区别?
| 对比项 | Dubbo RPC | 普通 HTTP API |
|---|---|---|
| 调用方式 | 像调用接口方法一样调用 | 手动拼 URL、参数、请求体 |
| 契约形式 | Java 接口更常见 | HTTP 路径、请求方法、JSON 结构 |
| 通信协议 | Dubbo 协议、Triple 等 | HTTP/1.1、HTTP/2 |
| 性能特点 | 二进制协议通常更紧凑 | JSON/文本协议可读性更好 |
| 治理能力 | 注册发现、负载均衡、路由、容错比较完整 | 通常要配合网关、注册中心等组件 |
现在 Dubbo 也支持 Triple 这类更现代的协议,所以它和 HTTP 体系的边界没早年那么硬了。面试里抓住一个重点就行:Dubbo 更强调面向接口的服务调用,不希望业务代码到处手写网络请求。
面试高频追问
-
Dubbo 用的是 JDK 动态代理还是 Javassist?
Dubbo 历史上支持多种代理生成方式,比如
JDK动态代理、Javassist等。具体用哪种和版本、配置有关。面试时可以这么说:消费者侧一定会生成接口代理,但代理实现方式是可以替换的。 -
为什么要有
Invoker这个抽象?因为 Dubbo 需要把不同来源的调用统一起来。消费者侧远程调用、服务端本地实现、集群容错包装之后的调用,都可以抽象成
Invoker.invoke()。这样过滤器、集群、协议扩展这些能力才能接在同一条调用链上。 -
Dubbo 调用一定是同步的吗?
不一定。业务代码里最常见的是同步调用,但 Dubbo 也支持异步调用、泛化调用、回调等能力。同步调用只是把远程响应包装成了普通方法返回值。
-
如果远程调用失败怎么办?
Dubbo 会按集群容错策略处理,比如失败重试、快速失败、失败安全等。生产里一定要想清楚接口能不能重试,像写接口、扣库存这种非幂等操作,如果乱重试,很容易把问题放大。
常见面试变体
- “Dubbo 的一次 RPC 调用流程是什么?”
- “Dubbo 为什么能做到透明远程调用?”
- “Dubbo 消费者引用服务的过程是什么?”
- “Dubbo 里的
Invoker是干什么的?”
记忆口诀
代理拦截,封装请求;发现服务,负载选择;网络发送,服务端执行;结果返回,看似本地。
总结
Dubbo 能像本地方法一样调用远程方法,关键在消费者侧的代理对象。代理对象把接口调用转成 RPC 请求,服务端根据请求调用真实实现类,再把结果返回给消费者。业务代码看到的是 userService.getUser(),底层其实经过了服务发现、负载均衡、协议编解码和网络通信。
