【分布式】面试官问 RPC,该怎么答?

RPC(Remote Procedure Call,远程过程调用)可谓是分布式微服务系统里最重要的组件之一,它是各服务间跨进程通信的桥梁,也是微服务、大型分布式系统的基石。

其作用是让客户端(调用方)调用远程服务器(被调用方)上的方法,像调用本地方法一样简单。

1、什么是 RPC,项目中具体做什么用?

RPC 是一种跨进程通信协议,它允许客户端(调用方)像调用本地函数一样,调用远程服务器(被调用方)上的函数或方法,无需手动处理网络通信、数据序列化 / 反序列化等底层细节。

举个生活例子:你(客户端,比如是微服务中的一个订单服务)想喝奶茶,直接打电话给奶茶店(服务端,比如是微服务中的一个支付服务)下单(调用方法),无需知道奶茶店的制作流程(底层逻辑),只需等待奶茶送到(接收返回结果)——RPC 就像 “电话 + 配送” 的组合,让远程调用变得和 “自己做奶茶” 一样简单。

前面文章讲过为什么要构建微服务,而 RPC 就是不同服务之间的桥梁。

  • 如果项目不是微服务架构,那么订单、库存,支付这一系列的功能都是在一个库里面的(相当于在一个屋子里,低头不见抬头见),他们之间的通信其实就是在本地内存中互相调用;
  • 如果项目是微服务项目,那么这些不同的服务可能部署在不同的服务器(相当于在不同的城市),他们之间需要知道其他服务的 IP、端口号才能知道对方在哪,然后通过 HTTP 等协议在网络中通信。而 RPC 就是帮助我们屏蔽掉这些网络通信细节,像调用本地方法一样调用其他服务里的方法。

2、RPC 解决了什么问题?用 RPC 的优势是什么?为什么不直接用HTTP

  1. 解耦服务调用,屏蔽网络细节:开发者无需关注 “如何建立 TCP 连接”,“如何处理数据包丢失”,“如何解析二进制数据”,只需专注业务逻辑。
  2. 标准化通信协议:RPC 框架定义了统一的请求 / 响应格式、序列化规则,避免不同服务间因通信格式不统一导致的兼容问题。
  3. 提升分布式开发效率:将复杂的分布式调用简化为 “本地函数调用”,降低开发门槛,减少重复编码(如手动封装网络请求)。
  4. 支持跨语言通信:主流 RPC 框架(如 gRPC)支持多语言调用(Java 调用 Go、Python 调用 C++),满足异构系统需求。
  5. 性能更好,传输速度快:RPC 可以基于二进制协议(Protobuf/Thrift),体积更小、性能更高。

3、RPC 底层是如何实现的?如何设计一个 RPC ?

介绍 RPC 底层,其实就是把其使用过程中屏蔽掉的网络通信细节描述清楚。

这里只简单讲解下通用的 RPC 通信全过程(面试一般也够用了),具体底层细节感兴趣的同学可以去深入学习,比如可以去看 gRPC,Dubbo 这些主流 RPC 框架的源码。

客户端存根 Stub / 服务端骨架 Skeleton

RPC 中的 Stub / Skeleton 相当于一个代理人,帮助我们完成 构造 Req, Resp,序列化,反序列化 等操作。

  • 客户端存根负责:把用户调用翻译成 RPC 请求(构造请求对象、序列化、设置元数据/header、调用连接发送、等待/处理返回值并反序列化)。
  • 构造 Request -> 将请求的函数名,参数等信息序列化 -> 发起网络请求
  • 接收 Response -> 将结果反序列化 -> 拿到返回值
  • 服务端骨架负责:把到达的网络请求反序列化,映射到具体的服务实现(handler),并把返回值序列化回去。
  • 接收 Request -> 将请求反序列化为函数名,参数等信息 -> 执行函数
  • 将执行结果序列化 -> 发起网络请求将 Response 返回客户端

  • 典型实现:使用 IDL(接口定义语言,如 protobuf / Thrift / IDL)生成 stub 与 skeleton 的代码。

序列化 / 反序列化

一个对象 User(name = "张三", age = 20, gender = "男") 这种形式的数据是无法在网络中传输的,需要给它转换成可跨语言/跨平台二进制流 或者 JSON 等格式才可以在网络中传输。这个过程就是序列化和反序列化。

  • 序列化:把对象的状态信息转换为可存储或传输的形式,如二进制流或文本。
  • 反序列化:对调一下,把可存储或传输的形式转换为对象的状态信息
  • 常见工程做法:二进制协议 (Protobuf) 用于高性能;文本协议 (JSON) 用于调试和弱类型场景

传输协议,连接管理

  • 连接复用与多路复用(比如 gRPC 的 HTTP/2):避免每次请求都重建 TCP/TLS 连接,降低延迟。
  • Keepalive/heartbeat:检测死亡连接并尽快回收资源。
  • TLS / mTLS:传输加密和双向证书验证(微服务间常用 mTLS)。
  • 连接池:客户端维护到后端实例的长连接池以复用连接。

服务发现

前文 分布式架构演进 我们讨论过,各服务之间通过 Consul, Nacos, Eureka 等中间件来实现服务注册与发现,每个服务将自己的IP 端口号 等信息注册到这些中间件,这样各个服务之间就可以通过统一的注册中心找到彼此了。

服务发现有两种模式(后面会出文档详细讲服务发现):

(1) 客户端发现(Client-side discovery)

  • 客户端在调用服务时,直接向 服务注册中心 查询目标服务实例列表。
  • 客户端自己选择一个实例(通常有负载均衡策略,如轮询、最少连接、哈希一致性等)。
  • 代表实现:Eureka(Netflix)、Consul、Zookeeper。

流程:

客户端 -> 注册中心:获取服务实例列表 客户端 -> 服务实例:发起 RPC 调用

(2) 服务端发现(Server-side discovery)

  • 客户端只知道一个 统一网关或负载均衡器 的地址。
  • 网关/负载均衡器在接收到请求后,再去查注册中心,挑选一个真实实例。
  • 代表实现:Kubernetes Service + kube-proxy / Envoy / Istio。

流程:客户端 -> 负载均衡器(LB) -> 注册中心查找 -> 服务实例

可靠性策略

  • 超时(Timeout):调用层设定 RPC 超时,防止无限等待。
  • 重试(Retry):当调用失败时重试,但必须考虑幂等性,避免副作用。
  • 指数退避(Backoff):避免对短暂故障的洪泛重试。
  • 熔断器(Circuit Breaker):对连续失败的后端短路调用,保护本地资源。
  • 限流(Rate limiting):保护后端不被突发流量压垮。

4、RPC 如何实现跨语言调用?Java 调用 Golang 函数

一句话总结:通过 统一的接口描述(IDL) + 语言无关的序列化格式 + 各语言的代码生成器 / 运行时库,把「方法签名 + 数据结构」固定成一种“契约(contract)”,客户端与服务端各自用本地语言的 stub/实现来与这个契约交互,网络上交换的是与语言无关的二进制/文本消息。

IDL(接口描述语言)

定义服务方法、消息类型与字段编号/名称(对齐不同语言的类型系统)。例如:Protobuf,Thrift

给个例子:通过这个 接口描述语言,RPC 会生成不同语言的 Stub, Skeleton,它们会帮助我们屏蔽掉网络通信细节,这样我们就可以专注于业务逻辑,不用担心语言的差异。

syntax = "proto3";
package order;

message OrderItem {
  int64 sku = 1;
  int32 qty = 2;
}

message GetOrderRequest {
  int64 order_id = 1;
}

message GetOrderResponse {
  int64 order_id = 1;
  string status = 2;
  repeated OrderItem items = 3;
}

service OrderService {
  rpc GetOrder(GetOrderRequest) returns (GetOrderResponse);
}

代码生成 (codegen)

用上面的 IDL 生成客户端存根(stub)和服务端桩(skeleton/handler scaffold),生成的代码是目标语言的“胶水”,负责序列化/反序列化、发送/接收网络请求

序列化后传输

在网络上传输的是与语言无关的字节流(Protobuf)或 JSON 文本

传输到两端后,各语言的 Stub,Skeleton 会将这些字节流或JSON文本,统一转换成本语言的格式

#微服务架构##Java##后端##面试题#
全部评论
mark收藏了
点赞 回复 分享
发布于 今天 09:04 四川

相关推荐

昨天 12:15
门头沟学院 Java
猫头夜鹰:请问收到意向要点接受拒绝吗,还是开奖之后再接受拒绝
点赞 评论 收藏
分享
09-18 20:26
已编辑
门头沟学院 Java
点赞 评论 收藏
分享
评论
2
3
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务