RPC 不写网址,它到底怎么找到对方的
2026-03-20
你真正困惑的点,其实非常关键:
如果 RPC 不是像 HTTP 那样,直接写一个完整地址,那它到底怎么知道该调用谁?
答案是:
RPC 不是不需要地址,而是“你平时不用自己手写地址”,地址这件事被框架和基础设施接管了。
你可以先记住这句话。下面我用特别小白的话,一层一层讲开。
一、先说结论:RPC 不是不要地址,而是把地址藏起来了
很多人第一次看 RPC,会以为它很神奇,像这样:
$userService->getUserInfo(1001);
感觉根本没写:
-
IP
-
端口
-
URL
-
路由
那它怎么找到对方?
其实答案很简单:
底层仍然一定要知道“对方在哪”,否则网络请求根本发不出去。
只是这个“对方在哪”,不是你业务代码手写,而是交给别的东西来处理了。
所以你要把它理解成:
HTTP 是你自己手填导航地址,RPC 是你告诉司机“去国贸三期”,司机自己导航过去。
你没看见路线,不代表没有路线。
二、为什么 HTTP 里你更容易看到“地址”
比如你调用一个普通接口,经常会写成这样:
http://user-service:8080/api/user/detail?id=1001
这里你能很直观看到:
1. 主机
user-service
或者 192.168.1.10
2. 端口
8080
3. 路径
/api/user/detail
所以你会感觉:
哦,我就是靠这个地址找到它的。
这个很直白。
三、RPC 为什么表面上看不到这些
因为 RPC 框架做了一层“代理”或者“中间翻译”。
你业务里写的是:
$userService->getUserInfo(1001);
但实际上背后不是直接执行本地方法,而是:
1. 先识别你要调哪个服务
比如 UserService
2. 再识别你要调哪个方法
比如 getUserInfo
3. 再去某个地方查
“UserService 现在部署在哪几台机器上?”
4. 选一台可用机器
比如:
-
10.2.1.15:9001
-
10.2.1.16:9001
-
10.2.1.17:9001
5. 把请求发过去
发给某一个实例
所以:
你虽然没写地址,但 RPC 框架在背后替你查地址、选地址、连地址。
四、你可以把 RPC 理解成“按名字找人”,不是“按门牌找人”
这个比喻非常适合你记忆。
HTTP 更像什么
HTTP 更像你自己拿着详细地址去找人:
“北京市朝阳区 xx 路 xx 号 3 楼 302”
你自己知道详细门牌号。
RPC 更像什么
RPC 更像你跟前台说:
“我找财务部的小王。”
然后前台帮你查:
-
小王今天在哪层
-
在哪个工位
-
现在在不在
-
如果小王不在,财务部还有谁能处理
然后再帮你转过去。
所以 RPC 更像:
按“服务名”找人,不是按“固定门牌号”找人。
五、那 RPC 到底靠什么识别对方
核心靠两样东西:
1. 服务名
比如:
-
UserService
-
OrderService
-
VerifyService
-
TokenService
这相当于“我要找谁”。
2. 服务注册信息
也就是一张“服务通讯录”
这张通讯录里会写:
-
UserService 在哪几台机器上
-
每台机器的 IP 是什么
-
端口是什么
-
哪些实例还活着
-
哪些已经挂了
这就相当于“那个人现在具体坐在哪”。
六、最关键的角色:注册中心
这个东西你一定要理解,因为它就是你这个疑问的核心答案。
注册中心,你可以把它理解成:
一本动态通讯录 + 实时在线名单。
比如系统里有三个用户服务实例:
-
10.0.0.11:9001
-
10.0.0.12:9001
-
10.0.0.13:9001
它们启动时,会主动去注册中心报到:
“我是 UserService,我的地址是 10.0.0.11:9001,我现在活着。”
“我是 UserService,我的地址是 10.0.0.12:9001,我现在活着。”
“我是 UserService,我的地址是 10.0.0.13:9001,我现在活着。”
于是注册中心就记住了:
UserService ├── 10.0.0.11:9001 ├── 10.0.0.12:9001 └── 10.0.0.13:9001
当你这边调用:
$userService->getUserInfo(1001);
RPC 框架就会先去问注册中心:
“请问 UserService 现在哪些机器可用?”
注册中心返回:
-
10.0.0.11:9001
-
10.0.0.12:9001
-
10.0.0.13:9001
然后框架自己挑一台发请求。
这就完成寻址了。
七、所以不是没有 IP 和端口,而是你不亲自写
这个地方你一定要彻底想通。
很多人误会 RPC 是:
完全不用 IP、端口。
其实不可能。
因为只要是网络通信,最终一定得落到某个具体地址:
-
IP
-
域名
-
端口
否则包都不知道往哪发。
真正的区别是:
HTTP 常常是你业务代码里直接写
比如:
http://10.0.0.11:8080/api/user/detail
RPC 常常是框架运行时帮你查出来
比如:
“UserService” → 查到 10.0.0.11:9001
所以可以说:
RPC 只是把“硬编码地址”换成了“服务名 + 动态解析地址”。
八、那“方法名”和“服务名”又是怎么识别的
这个要分两层看。
第一层:先找到哪台机器
靠服务名,比如:
-
UserService
-
VerifyService
这是“找到哪一家”。
第二层:到了那台机器后,调哪个方法
靠方法名,比如:
-
getUserInfo
-
verifyUser
-
createToken
这是“找到这家里的哪个窗口”。
你可以理解成:
服务名
像公司名字
比如“天健兴业用户中心”
方法名
像公司里的具体业务窗口
比如“一号窗口查用户”“二号窗口校验身份”
所以一次 RPC 调用,背后通常包含:
-
服务名
-
方法名
-
参数
类似这样:
{
"service": "UserService",
"method": "getUserInfo",
"params": [1001]
}
然后先靠 service 找机器,再靠 method 找机器里的处理逻辑。
九、为什么 RPC 不愿意让你手写固定 IP
因为固定 IP 很笨,不灵活。
比如你手写:
10.0.0.11:9001
如果这台机器挂了怎么办?
那你代码就废了。
再比如,用户服务为了抗并发,扩容成 5 台机器:
-
10.0.0.11:9001
-
10.0.0.12:9001
-
10.0.0.13:9001
-
10.0.0.14:9001
-
10.0.0.15:9001
这时候如果你还手写某一台 IP,就会有几个问题:
1. 单点问题
只打一台,很容易打挂
2. 扩容无感知
新机器加了,你也用不上
3. 容灾差
某台挂了你就报错
所以 RPC 更喜欢:
只认服务名,不认死地址。
这样底层可以动态切换。
十、RPC 底层常见的完整寻址流程
我给你用最白的话走一遍。
假设你写:
$userService->getUserInfo(1001);
背后大概是这样。
第一步:本地代理对象接管调用
你以为你在调 userService,其实这个 userService 往往不是一个真正的本地业务类,而是一个“代理对象”。
这个代理对象专门负责:
-
拦截你的方法调用
-
组装请求
-
发给远程服务
也就是说,它像个秘书。
你一说“帮我查用户”,秘书就开始办事了。
第二步:确定服务名
代理对象知道自己代表的是 UserService
第三步:去注册中心查地址
查到当前可用实例:
-
10.0.0.11:9001
-
10.0.0.12:9001
第四步:负载均衡挑一台
比如轮询选中:
-
10.0.0.12:9001
第五步:把方法和参数打包
打包成请求消息:
{
"service": "UserService",
"method": "getUserInfo",
"params": [1001]
}
第六步:通过网络发到 10.0.0.12:9001
这个时候真正还是发给了一个 IP + 端口
第七步:服务端收到后解析
发现你要调:
-
服务:UserService
-
方法:getUserInfo
-
参数:[1001]
第八步:执行并返回结果
所以你看,整个链路里:
IP 和端口一直都存在,只是它们在幕后。
十一、那如果没有注册中心呢
也可以做 RPC,但会比较笨。
比如你在配置文件里写死:
UserService: 10.0.0.11:9001 OrderService: 10.0.0.21:9001
那框架照样能调。
这种本质上也是 RPC,只不过寻址方式不是“动态查注册中心”,而是“读固定配置”。
所以你可以这样理解:
有注册中心
像通讯录实时更新,谁在线、谁下线都知道
没注册中心
像你自己拿着一张纸条,纸条上写死了电话号
能用,但不灵活。
十二、再说“地址路由”这个词,你可能混了两层东西
你说“服务那边的地址路由”,这里其实容易把两件事混到一起。
第一层:找到哪台机器
这是网络层寻址
比如:
-
10.0.0.12:9001
这是“去哪个楼”。
第二层:找到哪个方法
这是服务内部路由
比如:
-
UserService.getUserInfo
-
UserService.verifyUser
这是“进楼以后去哪个办公室”。
HTTP 里这两层通常写在一个 URL 里,所以你更容易看到:
http://10.0.0.12:9001/api/user/detail
里面:
-
10.0.0.12:9001是机器地址 -
/api/user/detail是业务路由
但 RPC 往往拆开了:
-
机器地址:运行时动态解析
-
业务方法:在请求包里标记
所以你会感觉“没有路由”,其实不是没有,而是换了表达方式。
十三、一个特别形象的比喻:打总机
RPC 特别像以前公司打总机。
你不是每次都直接拨某个人的分机号。
你可能只说:
“帮我转财务部,找小王。”
总机做了这些事:
1. 确认你找财务部
这就是服务名
2. 查财务部现在有哪些人在岗
这就是注册中心的可用实例列表
3. 选一个可接电话的人
这就是负载均衡
4. 接通后告诉对方你要办什么业务
这就是方法名 + 参数
所以 RPC 不是不寻址,而是:
从“自己手拨号码”变成了“总机代为转接”。
十四、为什么这对微服务特别重要
因为微服务里,服务实例经常变化。
今天用户服务可能在:
-
A 机器
-
B 机器
明天可能扩容到:
-
C 机器
-
D 机器
后天 A 机器挂了,E 机器又补上来。
如果你业务代码里全写死 IP,那维护会疯掉。
RPC 采用服务发现机制之后,你只管写:
$userService->getUserInfo(1001);
底层会自己感知:
-
哪些实例还活着
-
哪些挂了
-
该打给谁
这就是它的价值。
十五、你可以这样建立最终认知
你现在最应该建立的,不是“RPC 很神秘”,而是下面这个认知:
第一层
RPC 调用表面上像本地方法调用
第二层
但底层一定还是网络通信
第三层
既然是网络通信,就一定还是要知道对方的实际地址
第四层
只是这个地址不是你手写,而是由服务发现、注册中心、代理对象、负载均衡这些东西帮你处理了
这个逻辑一旦通了,你后面看 RPC 就不会迷糊了。
十六、最适合你记忆的一句话
RPC 不是没有地址,而是“按服务名找地址”,不是“你自己手写死地址”。
十七、我再给你一个超简版
你看到的
$userService->getUserInfo(1001);
你没看到的
1. 识别服务名:UserService 2. 去注册中心查:UserService 在哪几台机器上 3. 选一台:10.0.0.12:9001 4. 组装请求:method=getUserInfo, params=[1001] 5. 发过去 6. 对方执行后返回
十八、最后用你的语言收一下
所以你问的这个点,答案其实就是:
他不是“不用 ip、端口、路由”,而是这些东西被藏到框架底层了。
你业务层只写“我要调用哪个服务、哪个方法”。
至于:
-
去哪台机器
-
用哪个端口
-
当前哪台活着
-
该选哪个实例
都由 RPC 框架自动完成。
这就是为什么你表面看不到地址,但它仍然能精准找到对方。
如果你愿意,我下一条可以继续给你画一张:
“HTTP 接口寻址 vs RPC 寻址”的对照图
我会直接用你熟悉的:
controller → service → 对方系统
这个链路给你讲,一下就彻底通了。
发表评论: