Files
interview/questions/07-系统设计/RPC框架.md
yasinshaw 0e46a367c4 refactor: rename files to Chinese and organize by category
Organized 50 interview questions into 12 categories:
- 01-分布式系统 (9 files): 分布式事务, 分布式锁, 一致性哈希, CAP理论, etc.
- 02-数据库 (2 files): MySQL索引优化, MyBatis核心原理
- 03-缓存 (5 files): Redis数据结构, 缓存问题, LRU算法, etc.
- 04-消息队列 (1 file): RocketMQ/Kafka
- 05-并发编程 (4 files): 线程池, 设计模式, 限流策略, etc.
- 06-JVM (1 file): JVM和垃圾回收
- 07-系统设计 (8 files): 秒杀系统, 短链接, IM, Feed流, etc.
- 08-算法与数据结构 (4 files): B+树, 红黑树, 跳表, 时间轮
- 09-网络与安全 (3 files): TCP/IP, 加密安全, 性能优化
- 10-中间件 (4 files): Spring Boot, Nacos, Dubbo, Nginx
- 11-运维 (4 files): Kubernetes, CI/CD, Docker, 可观测性
- 12-面试技巧 (1 file): 面试技巧和职业规划

All files renamed to Chinese for better accessibility and
organized into categorized folders for easier navigation.

Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-03-01 00:10:53 +08:00

21 KiB
Raw Blame History

RPC 框架

问题

背景在分布式系统中服务间通信需要高效、可靠的远程调用机制。RPCRemote Procedure Call框架屏蔽了网络通信的复杂性使远程调用像本地调用一样简单。

问题

  1. 什么是 RPC它和 HTTP REST 有什么区别?
  2. Dubbo 的核心架构和工作原理是什么?
  3. gRPC 的优势是什么?它如何实现高性能?
  4. 请描述 Dubbo 的负载均衡策略
  5. Dubbo 的服务注册与发现机制是怎样的?
  6. RPC 框架如何实现序列化?常见的序列化协议有哪些?
  7. 在实际项目中如何选择 RPC 框架?
  8. RPC 框架如何处理超时、重试和熔断?

标准答案

1. RPC vs HTTP REST

RPC 定义

远程过程调用Remote Procedure Call是一种计算机通信协议允许运行在一台计算机的程序调用另一台计算机的子程序而开发者无需额外编码这种交互。

对比表

特性 RPC (Dubbo/gRPC) HTTP REST
传输协议 TCP (长连接) HTTP/1.1 (短连接) / HTTP/2
序列化 二进制Hessian/Protobuf JSON/XML
性能 高(紧凑、高效) 中(文本解析开销)
易用性 需要接口定义 无需定义,浏览器直接访问
耦合度 强耦合(需要 stub 代码) 松耦合
流量管理 需要网关 天然支持Nginx等
适用场景 内部微服务通信 对外 API、跨语言调用

代码对比

RPC 调用Dubbo

// 服务提供者
public interface UserService {
    User getUserById(Long id);
}

// 服务消费者
// 像调用本地方法一样调用远程服务
@Reference
private UserService userService;

public void process() {
    User user = userService.getUserById(1L);
}

HTTP REST 调用

// 服务提供者
@RestController
@RequestMapping("/api/users")
public class UserController {
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.getById(id);
    }
}

// 服务消费者
RestTemplate restTemplate = new RestTemplate();
public void process() {
    String url = "http://user-service/api/users/1";
    User user = restTemplate.getForObject(url, User.class);
}

2. Dubbo 核心架构

架构图

                    ┌─────────────────┐
                    │   Registry      │
                    │  (注册中心)      │
                    │  Zookeeper/Nacos│
                    └─────────────────┘
                           ▲   ▲
                           │   │
          Register         │   │         Subscribe
          (注册)            │   │          (订阅)
                           │   │
    ┌──────────────────────┴───┴──────────────────────┐
    │                                              │
    │  Provider                        Consumer     │
    │  ┌──────────┐                    ┌──────────┐│
    │  │Protocol  │                    │Protocol  ││
    │  │  (协议层) │                    │  (协议层) ││
    │  └──────────┘                    └──────────┘│
    │  ┌──────────┐                    ┌──────────┐│
    │  │  Cluster │◄──────────────────►│  Cluster ││
    │  │  (集群层) │    Directory      │  (集群层) ││
    │  └──────────┘                    └──────────┘│
    │  ┌──────────┐                    ┌──────────┐│
    │  │   Proxy  │                    │   Proxy  ││
    │  │ (代理层)  │                    │ (代理层) ││
    │  └──────────┘                    └──────────┘│
    │  ┌──────────┐                    ┌──────────┐│
    │  │  Service │                    │  Service ││
    │  │  (服务层) │                    │  (服务层) ││
    │  └──────────┘                    └──────────┘│
    └─────────────────────────────────────────────┘
                │
                │ Invoke
                │ (调用)
                ▼
         ┌──────────┐
         │  Channel │
         │ (网络层)  │
         └──────────┘
         │
         │ Exchange
         │ (数据交换)
         ▼
         ┌──────────┐
         │  Serialize│
         │  (序列化)  │
         └──────────┘

核心角色

1. Container服务容器

  • 负责启动、加载和运行服务提供者
  • 通常是 Spring 容器

2. Provider服务提供者

  • 暴露服务的应用
  • 启动时向注册中心注册服务

3. Consumer服务消费者

  • 调用远程服务的应用
  • 启动时向注册中心订阅服务

4. Registry注册中心

  • 服务注册与发现
  • 常见实现Zookeeper、Nacos、Redis

5. Monitor监控中心

  • 统计服务调用次数和调用时间
  • 常见实现Dubbo Admin、Prometheus

代码示例

服务提供者配置

<!-- provider.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo
       http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- 提供方应用信息 -->
    <dubbo:application name="user-provider"/>

    <!-- 使用 Zookeeper 注册中心 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <!-- 使用 dubbo 协议暴露服务 -->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.example.UserService"
                   ref="userService" version="1.0.0"/>

    <!-- 服务实现 -->
    <bean id="userService" class="com.example.UserServiceImpl"/>
</beans>

服务消费者配置

<!-- consumer.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo
       http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- 消费方应用信息 -->
    <dubbo:application name="user-consumer"/>

    <!-- 使用 Zookeeper 注册中心 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <!-- 生成远程服务代理 -->
    <dubbo:reference id="userService"
                     interface="com.example.UserService"
                     version="1.0.0"
                     timeout="3000"
                     retries="2"/>
</beans>

3. gRPC 高性能原理

核心特性

1. HTTP/2 多路复用

HTTP/1.1:
Request 1 ──► TCP Connection 1 ──► Response 1
Request 2 ──► TCP Connection 2 ──► Response 2
Request 3 ──► TCP Connection 3 ──► Response 3

HTTP/2:
Request 1 ──┐
Request 2 ──┼─► TCP Connection ──► Response 1
Request 3 ──┘                       Response 2
                                     Response 3

2. Protobuf 二进制序列化

// user.proto
syntax = "proto3";

package user;

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
}

message User {
  int64 id = 1;
  string name = 2;
  string email = 3;
}

message GetUserRequest {
  int64 id = 1;
}

message ListUsersRequest {
  int32 page = 1;
  int32 size = 2;
}

message ListUsersResponse {
  repeated User users = 1;
  int32 total = 2;
}

性能对比

JSON: {"id":1,"name":"Alice","email":"alice@example.com"}
     └─ 56 字节

Protobuf: [0x08 0x01 0x12 0x05 0x41 0x6C 0x69 0x63 0x65 ...]
        └─ ~20 字节(压缩 60%+

3. 流式传输

# 服务端流式 RPC
async def ListUsers(request, context):
    for user in database.iter_users():
        yield user  # 持续发送,无需等待全部数据

# 客户端流式 RPC
async def UploadUsers(request_iterator, context):
    for user_request in request_iterator:
        database.save(user_request.user)
    return UploadStatus(success=True)

# 双向流式 RPC
async def Chat(request_iterator, context):
    async for msg in request_iterator:
        response = process_message(msg)
        yield response

代码示例Python

服务端

import grpc
from concurrent import futures
import user_pb2
import user_pb2_grpc

class UserServiceImpl(user_pb2_grpc.UserServiceServicer):
    def GetUser(self, request, context):
        # 查询数据库
        user = db.query(User).filter_by(id=request.id).first()
        return user_pb2.User(
            id=user.id,
            name=user.name,
            email=user.email
        )

    def ListUsers(self, request, context):
        # 服务端流式响应
        users = db.query(User).limit(request.size).offset(request.page * request.size)
        for user in users:
            yield user_pb2.User(id=user.id, name=user.name, email=user.email)

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    user_pb2_grpc.add_UserServiceServicer_to_server(UserServiceImpl(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()

if __name__ == '__main__':
    serve()

客户端

import grpc
import user_pb2
import user_pb2_grpc

def run():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = user_pb2_grpc.UserServiceStub(channel)

        # 简单 RPC
        response = stub.GetUser(user_pb2.GetUserRequest(id=1))
        print(f"User: {response.name}")

        # 服务端流式 RPC
        for user in stub.ListUsers(user_pb2.ListUsersRequest(page=0, size=10)):
            print(f"User: {user.name}")

if __name__ == '__main__':
    run()

4. Dubbo 负载均衡策略

策略对比

策略 说明 适用场景
Random随机 随机选择 provider 性能相近的实例
RoundRobin轮询 按权重轮询 性能有差异的实例
LeastActive最少活跃 优先调用活跃数少的 性能差异大
ConsistentHash一致性哈希 相同参数路由到同一 provider 有状态服务
ShortestResponse最短响应 优先选择响应时间短的 对延迟敏感

代码示例

配置负载均衡

<dubbo:reference id="userService"
                 interface="com.example.UserService"
                 loadbalance="roundRobin"  <!-- 轮询 -->
                 timeout="3000"/>

自定义负载均衡

public class CustomLoadBalance extends AbstractLoadBalance {
    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        // 自定义负载均衡逻辑
        // 例如:基于地理位置的负载均衡
        String location = getUserLocation();
        return invokers.stream()
            .filter(invoker -> invoker.getUrl().getParameter("location").equals(location))
            .findFirst()
            .orElse(invokers.get(0));
    }
}

// 注册自定义负载均衡
SPI.register(CustomLoadBalance.class);

LeastActive 原理

Provider A: Active = 5 (正在处理 5 个请求)
Provider B: Active = 2 (正在处理 2 个请求)
Provider C: Active = 8 (正在处理 8 个请求)

选择顺序B > A > C
原因B 的负载最轻,应该优先分配

5. 服务注册与发现

Zookeeper 实现

目录结构

/dubbo
  └─ com.example.UserService
      ├─ providers
      │   ├─ dubbo://192.168.1.10:20880/...?version=1.0.0
      │   ├─ dubbo://192.168.1.11:20880/...?version=1.0.0
      │   └─ dubbo://192.168.1.12:20880/...?version=1.0.0
      └─ consumers
          └─ consumer://192.168.1.20/...?version=1.0.0

工作流程

1. Provider 启动
   ↓
2. 创建临时节点 /dubbo/.../providers/dubbo://ip:port/...
   ↓
3. Consumer 启动
   ↓
4. 订阅 /dubbo/.../providers/ 节点
   ↓
5. 获取 provider 列表
   ↓
6. 监听 provider 变化(新增/下线)
   ↓
7. 动态更新本地缓存

代码示例Zookeeper

// 注册中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://127.0.0.1:2181");
registry.setTimeout(5000);

// 或者使用 Nacos
RegistryConfig registry = new RegistryConfig();
registry.setAddress("nacos://127.0.0.1:8848");

服务健康检查

// Dubbo 心跳机制
public class HeartbeatTask implements Runnable {
    @Override
    public void run() {
        // 每隔 5 秒发送心跳
        channel.send heartbeat();
    }
}

// Zookeeper 临时节点特性
// - Provider 断开连接后,临时节点自动删除
// - Consumer 立即感知到下线,剔除该 provider

6. 序列化协议对比

常见序列化协议

协议 优点 缺点 适用场景
Hessian 简单、高效 不支持跨语言 Dubbo 默认
Protobuf 高性能、跨语言 需要定义 .proto gRPC
JSON 易读、跨语言 冗长、解析慢 HTTP REST
Kryo 高性能 不支持跨语言 Dubbo
Avro 动态 schema、跨语言 性能略低 Hadoop 生态
FST 高性能、兼容 JDK 不支持跨语言 Dubbo

性能对比

序列化性能排名(从快到慢):
Kryo > FST > Protobuf > Hessian > Avro > JSON

序列化后大小排名(从小到大):
Protobuf ≈ Kryo < Hessian < Avro < JSON

代码示例Protobuf

// user.proto
syntax = "proto3";

message User {
    int64 id = 1;
    string name = 2;
    string email = 3;
    repeated string tags = 4;
}
# 编译 Protobuf
protoc --python_out=. user.proto
# Python 序列化
import user_pb2

user = user_pb2.User()
user.id = 1
user.name = "Alice"
user.email = "alice@example.com"
user.tags.extend(["vip", "active"])

# 序列化
serialized = user.SerializeToString()  # 二进制数据

# 反序列化
user2 = user_pb2.User()
user2.ParseFromString(serialized)

7. RPC 框架选型

选型决策树

是否需要跨语言调用?
├─ 是 → gRPCProtobuf 跨语言支持最好)
└─ 否 → 继续判断

是否需要高性能?
├─ 是 → DubboTCP 长连接、Hessian 序列化)
└─ 否 → 继续判断

是否需要简单易用?
├─ 是 → Spring Cloud OpenFeign基于 HTTP REST
└─ 否 → Dubbo

已有技术栈?
├─ Spring Cloud → OpenFeign/Dubbo
├─ Kubernetes → gRPC服务网格友好
└─ Dubbo → 继续使用 Dubbo

实际项目经验

场景 1电商内部服务

选择Dubbo
原因:
- 内部服务,都是 Java 技术栈
- 对性能要求高(高并发下单)
- 需要负载均衡、熔断降级

配置:
- 使用 Hessian 序列化
- Zookeeper 注册中心
- LeastActive 负载均衡

场景 2跨语言微服务

选择gRPC
原因:
- 后端 Java数据分析 PythonAI 服务 Go
- 需要统一的服务间通信协议
- Protobuf 高性能且跨语言

配置:
- Protobuf 定义接口
- HTTP/2 传输
- 多语言代码生成

8. 超时、重试和熔断

超时配置

<!-- Dubbo 超时 -->
<dubbo:reference id="userService"
                 interface="com.example.UserService"
                 timeout="3000"/>  <!-- 3 秒超时 -->

<!-- 方法级超时 -->
<dubbo:reference id="userService"
                 interface="com.example.UserService">
    <dubbo:method name="getUserById" timeout="1000"/>
    <dubbo:method name="listUsers" timeout="5000"/>
</dubbo:reference>

重试机制

<dubbo:reference id="userService"
                 interface="com.example.UserService"
                 retries="2"/>  <!-- 失败后重试 2 次 -->

<!-- 工作流程 -->
第一次调用 → 失败
    ↓
第二次调用 → 失败
    ↓
第三次调用 → 成功/失败

注意:幂等性操作才能重试(如查询),非幂等操作(如下单)不能重试

<!-- 非幂等操作禁用重试 -->
<dubbo:method name="createOrder" retries="0"/>

熔断降级Dubbo

// 使用 Sentinel 实现熔断
@SentinelResource(value = "getUserById",
    blockHandler = "handleBlock",
    fallback = "handleFallback")
public User getUserById(Long id) {
    return userService.getUserById(id);
}

// 熔断处理
public User handleBlock(Long id, BlockException ex) {
    // 熔断时返回默认值
    return new User(-1L, "Default", "default@example.com");
}

// 降级处理
public User handleFallback(Long id, Throwable ex) {
    // 异常时返回降级数据
    return new User(-1L, "Fallback", "fallback@example.com");
}

熔断规则配置

// Sentinel 熔断规则
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource("getUserById");
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);  // 平均响应时间
rule.setCount(100);  // 100ms
rule.setTimeWindow(10);  // 10 秒熔断时间
rules.add(rule);
DegradeRuleManager.loadRules(rules);

9. 实际项目经验

场景 1订单系统性能优化

问题订单创建接口延迟高2 秒)
排查:
1. 调用链追踪发现库存服务耗时最长
2. 库存服务使用 HTTP RESTJSON 序列化慢
3. 每次调用都建立新连接

解决:
1. 将库存服务从 HTTP REST 迁移到 Dubbo
2. 使用 Hessian 序列化
3. 启用长连接复用
4. 配置 LeastActive 负载均衡

结果:延迟降低到 300ms提升 85%

场景 2服务注册中心故障

问题Zookeeper 集群故障,服务调用失败
排查:
Consumer 每次调用都查询注册中心,导致无法发现服务

解决:
1. Dubbo 默认会缓存 provider 列表到本地
2. 配置缓存策略
   <dubbo:registry address="zookeeper://127.0.0.1:2181"
                  file="${user.home}/output/dubbo.cache"/>
3. 注册中心故障时,使用本地缓存

结果:注册中心故障不影响已有服务调用

场景 3序列化兼容性问题

问题:升级服务版本后,旧客户端调用失败
原因:
- 新增字段使用了不可序列化的类型
- 客户端版本不兼容

解决:
1. Protobuf 默认兼容(新增字段不影响)
2. Hessian 需要保证序列化 ID 一致
3. 使用版本号区分服务
   <dubbo:service interface="..." version="1.0.0"/>
   <dubbo:service interface="..." version="2.0.0"/>
4. 灰度升级,逐步切换流量

结果:平滑升级,零停机

10. 阿里 P7 加分项

架构设计能力

  • 设计过大规模 RPC 框架的集群架构(百万级 QPS
  • 有自定义 RPC 框架开发经验
  • 实现过服务网格与传统 RPC 框架的融合

深度理解

  • 熟悉 Dubbo 源码SPI 机制、代理设计、集群容错)
  • 理解 gRPC 的 HTTP/2 和 Protobuf 底层原理
  • 有序列化协议的选型和优化经验

性能调优

  • 优化过 TCP 参数连接池、KeepAlive、缓冲区大小
  • 调整过 JVM 参数减少 GC减少对象创建、使用堆外内存
  • 优化过网络参数MTU、TCP_NODELAY

生产实践

  • 解决过 TCP 粘包/拆包问题
  • 处理过序列化安全漏洞(如 Hessian 反序列化 RCE
  • 实现过服务优雅上下线(注册预热、优雅停机)

可观测性

  • 集成过分布式追踪SkyWalking、Jaeger
  • 实现过 RPC 调用链路监控
  • 设计过服务性能指标大盘QPS、延迟、成功率

跨语言调用

  • 有 gRPC 多语言实现经验Java、Go、Python
  • 解决过 Protobuf 跨语言兼容性问题
  • 实现过动态代理生成(如 Python 调用 Java 服务)