1. 什么是 RPC?和 HTTP 调用有什么区别?
RPC 是远程调度框架,主要功能是服务注册发现以及远程通信,支持多种通信协议以及不用的序列化机制。主要用于微服务项目中不同服务之间的通信,利用代理机制使得调用者可以忽略连接发起请求等细节,而是像本地调用一样调用另一个服务的接口。
而 HTTP 调用只是单纯的利用 httpClient 或者 okClient 等请求工具发起 HTTP 请求。HTTP 调用可以是 RPC 通信协议的一种实现选择,可以采用 TCP,HTTP/2 等协议。
如果是内部系统不同服务之间的调用,可以采用 TCP + 二进制序列化的形式实现以追求高性能,而在需要对外提供接口 API 的时候,可以采用 HTTP 协议统一规范。
2. 一次完整的 RPC 调用流程是怎样的?
调用方通过类似本地服务调用的方式调用外部服务,而在启动项目时, RPC 框架已经自动将外部服务的接口生成了代理类,所以在调用外部接口时,会执行代理拦截器的业务逻辑,主要是封装类,方法名,参数类型,参数值等到一个请求中,再通过通信协议 HTTP,TCP 等发起请求,如果有序列化机制,还会将请求序列化压缩为二进制形式,被调用端方接收到请求后,反序列化解析请求并通过反射,Beanfactory 等机制调用具体服务实例的方法,最后封装为结果并返回。
3. RPC 框架中,如何实现服务注册与发现?
在 RPC 框架中,服务注册与发现通过一个注册中心实现,服务提供者启动时将自己的服务信息注册进去,服务消费者则在调用前从注册中心获取目标服务地址,从而实现服务解耦和动态发现,常用组件包括 Redis、Zookeeper、Nacos 等。
4. 如何实现客户端的负载均衡?常见策略有哪些?
在自定义 RPC 框架中,客户端负载均衡通常发生在发起远程调用之前,具体流程如下:
1.拉取服务列表
客户端通过服务注册中心(如 Nacos、Zookeeper、Redis 等)根据 serviceName
拉取对应的可用服务实例列表。每个实例包含:
host
、port
:服务地址serviceName
:服务名metadata
:服务版本、环境(如version=1.0
、env=prod
)zone/region
:区域信息,用于区域感知策略- 其他信息:权重、协议类型、健康状态等
2.执行负载均衡策略
拉取服务列表后,客户端使用负载均衡模块在多个实例中选择一个目标节点发起调用。可以直接使用现成的负载均衡库(如 Dubbo 的 LoadBalance 接口),也可以自定义策略实现。
5. RPC 调用中的序列化方式有哪些?有什么区别?
RPC 调用中常用序列化方式包括 JSON、Kryo、Protobuf、Thrift 等,不同方式在性能、跨语言、可读性之间存在权衡。业务调试选 JSON,性能敏感选 Protobuf/Kryo,跨语言通信选 Protobuf/Thrift。
特性 |
Protobuf |
JSON |
Kryo |
性能 |
✅ 高 |
❌ 低 |
✅ 高 |
可读性 |
❌ 差 |
✅ 好 |
❌ 差 |
跨语言 |
✅ 强 |
✅ 强 |
❌ 弱(Java专用) |
类型安全 |
✅ 强 |
❌ 弱 |
✅ 强 |
体积 |
✅ 小 |
❌ 大 |
✅ 小 |
6. RPC 的同步调用和异步调用有什么区别?
- 同步调用(Synchronous):发送请求后阻塞等待结果返回
- 异步调用(Asynchronous):发送请求后立即返回,结果稍后通过回调、Future 等方式获取
我们在系统架构中采用同步 RPC + 异步 MQ 的组合策略:
- 对于链路关键、实时性高的场景(如下单、权限验证等),采用 RPC 实时调用,接口设计简洁明确。
- 对于非关键、可容忍延迟的业务(如日志记录、短信通知、第三方回调等),使用消息队列(Kafka/RabbitMQ)解耦业务、提升吞吐、增强可用性。
- 同时结合失败重试、死信队列、消费者幂等性保障消息消费可靠性。
7. 什么是连接池?为什么 RPC 框架中需要连接池?
连接池(Connection Pool)是一种资源池技术,用于维护一定数量的可复用连接(如数据库连接、HTTP连接等)。当应用需要使用连接时,不用每次新建和关闭连接,而是从连接池中取一个可用连接,用完后归还池中,达到连接复用和减少资源开销的目的。
在 RPC 框架中,客户端和服务端需要频繁发起网络请求,频繁新建和关闭连接开销大且影响响应速度,使用连接池能显著提升调用效率和系统稳定性,是保障高性能、高并发的关键组件。
8. RPC 框架中的超时与重试是如何实现的?
在 RPC 框架中,超时控制用于防止客户端无限等待响应,一般通过 Future 或通信层设置超时时间来实现;而重试机制在调用失败时根据策略自动选择其他节点重试,提高系统可用性,但要注意幂等性、异常类型和重试次数等控制,避免误伤或雪崩。
ExecutorService executor = Executors.newCachedThreadPool();
Future<Response> future = executor.submit(() -> rpcClient.send(request));
try {
// 阻塞等待最多 3 秒
Response response = future.get(3, TimeUnit.SECONDS);
// 正常处理响应
} catch (TimeoutException e) {
// 超时处理逻辑,比如重试或返回失败
future.cancel(true); // 取消任务,尝试中断线程
} catch (ExecutionException e) {
// 业务异常处理
} catch (InterruptedException e) {
// 当前线程被中断处理
}
for (int i = 0; i < retryCount; i++) {
ServiceInstance instance = loadBalancer.select(serviceName, instances);
try {
return invoke(instance, request, timeout);
} catch (Exception e) {
log.warn("调用失败,第 {} 次尝试:{}", i + 1, e.getMessage());
// 继续尝试下一个实例
}
}
throw new RpcRetryException("重试失败");
9. 在分布式调用链中,如何实现链路追踪(Trace)?
就是在每一次的服务调用时都形成一系列标识信息,并跟随请求发送,无论成功与否都会将当前链路信息存到链路追踪系统。
- traceId 表示整个调用链的唯一标识,贯穿请求始终。
- spanId 标识当前这次服务调用的唯一标识。
- parentSpanId 关联上游调用,形成调用链的父子关系。
- metainfo 包含了方法名、服务名、耗时、状态等调用细节。
- 每个服务只要参与调用,无论成功或失败,都会产生日志(span)并发送到追踪系统,帮助构建完整的调用链路视图。
链路追踪系统是分布式架构中非常重要的监控工具,主要功能包括:
- 调用链数据采集:自动收集各服务间的调用关系和调用时长,形成完整的调用链路。
- 调用链数据存储和可视化:将调用链信息存储后,通过可视化界面展示调用拓扑和时间线,方便分析。
- 性能监控和瓶颈定位:统计各个服务和接口的响应时间,快速定位性能瓶颈。
- 异常诊断和故障追踪:捕获异常和错误调用,帮助快速定位故障根因。
- 服务依赖分析:分析服务之间的调用关系,辅助系统架构优化。
- 告警机制:针对异常调用和性能异常触发告警,保障系统稳定。
整体来说,链路追踪帮助我们在复杂的分布式系统中快速定位问题,提高系统的可观测性和运维效率。
10. 如何保障 RPC 服务的高可用性?
- 超时与重试机制
通过设置合理的请求和响应超时时间,避免调用方长时间阻塞;同时结合重试机制,限制最大重试次数,并确保接口具备幂等性,避免重复执行带来的副作用。 - 服务降级(Fallback)机制
在调用失败或超时情况下,自动切换到预设的降级逻辑,保证核心业务不中断,提高系统的容错能力。 - 链路追踪与监控
通过链路追踪系统收集服务调用的全链路信息,监控服务调用的健康状态和性能指标,及时发现异常调用并触发告警,提升运维效率和系统可靠性。 - 负载均衡和服务注册发现
利用注册中心动态管理服务实例,结合负载均衡策略,实现请求的均匀分配和故障节点的自动剔除,保证服务的高可用。 - 限流降级与熔断机制
当系统压力过大时,通过限流、熔断机制保护服务,避免雪崩效应,保障核心服务稳定。