核心概念

RPC 比 HTTP 快,主要体现在传输效率序列化开销连接管理路由寻址四个方面。这些优化使得 RPC 在微服务内部调用场景下性能提升 30%-50% 以上。

需要注意:这里的 HTTP 主要指传统的 HTTP/1.1 + JSON 方案。HTTP/2 + Protobuf 的性能已经接近专有 RPC 协议。

性能优势分析

1. 传输协议开销

HTTP 协议的开销

POST /api/user/getUser HTTP/1.1
Host: user-service.com
Content-Type: application/json
Accept: application/json
User-Agent: Apache-HttpClient/4.5.13
Connection: keep-alive
Content-Length: 25

{"userId": 123456789}

Dubbo 协议的开销

+---------------+---------------+---------------+
| Magic(2字节)  | Flag(1字节)   | Status(1字节) |
+---------------+---------------+---------------+
| Request ID(8字节)                             |
+---------------+---------------+---------------+
| Data Length(4字节)    | Data                  |
+---------------+---------------+---------------+

对比分析

  • HTTP 头部:通常 200-500 字节
  • Dubbo 头部:16 字节
  • 头部节省:95% 以上
// HTTP 请求示例
POST /api/user HTTP/1.1          // 约 23 字节
Host: example.com                // 约 20 字节
Content-Type: application/json   // 约 30 字节
Content-Length: 50               // 约 18 字节
// 其他头部...                    总计: 200+ 字节

// Dubbo 协议示例
0xdabb                           // 魔数: 2 字节
0x42                             // 标志: 1 字节
0x00                             // 状态: 1 字节
0x0000000000000001               // 请求ID: 8 字节
0x00000032                       // 数据长度: 4 字节
// 总计: 16 字节

2. 序列化效率

JSON 序列化(HTTP 常用)

// 传输数据
{
    "userId": 123456789,
    "userName": "张三",
    "age": 25,
    "email": "zhangsan@example.com"
}
// 大小:约 85 字节

Hessian2 序列化(Dubbo 默认)

// 同样的数据,Hessian2 序列化后约 40 字节
// 压缩率:约 50%

性能对比表

序列化方式 数据大小 序列化耗时 反序列化耗时
JSON 100% 100% 100%
Hessian2 50% 30% 40%
Protobuf 40% 20% 25%
Kryo 45% 15% 20%
// Dubbo 序列化配置
@Service(serialization = "hessian2")  // 默认,性能与兼容性平衡
@Service(serialization = "kryo")       // 更快,但需要额外依赖
@Service(serialization = "protobuf")   // Google 方案,跨语言友好
public class UserServiceImpl implements UserService {
    // ...
}

3. 连接管理

HTTP 短连接模式

// 每次请求的流程
1. TCP 三次握手1.5 RTT
2. 发送 HTTP 请求
3. 接收 HTTP 响应
4. TCP 四次挥手2 RTT
// 总开销:3.5 RTT + 数据传输时间

HTTP Keep-Alive 模式

// 连接复用,但有限制
Connection: keep-alive
Keep-Alive: timeout=5, max=100

// 问题:
// 1. 超时后仍需重新建连
// 2. 单个连接串行处理请求
// 3. 服务端需要维护大量连接

Dubbo 长连接 + 多路复用

// Dubbo 连接特点
@Reference(
    connections = 1,     // 单个长连接
    lazy = false         // 启动时建立连接
)
private UserService userService;

// 优势:
// 1. 长连接一直保持,无握手开销
// 2. 一个连接支持并发多个请求(多路复用)
// 3. 异步非阻塞通信

性能对比

// HTTP 短连接:每次调用 3.5 RTT
1000 次调用 = 3500 RTT

// HTTP Keep-Alive:首次 1.5 RTT,后续 0 RTT(直到超时)
1000 次调用 = 1.5 RTT理想情况

// Dubbo 长连接:启动时 1.5 RTT,后续 0 RTT
1000 次调用 = 0 RTT运行时

4. 负载均衡与服务发现

HTTP 方案

客户端 -> Nginx -> 服务A
              -> 服务B
              -> 服务C

// 多一跳,增加延迟:
// 延迟 = 客户端->Nginx + Nginx->服务端

Dubbo 方案

客户端 -> 服务A
      -> 服务B  (客户端直连,框架内负载均衡)
      -> 服务C

// 客户端直连,减少一跳:
// 延迟 = 客户端->服务端
// Dubbo 客户端负载均衡
@Reference(
    loadbalance = "random",  // 随机
    retries = 2              // 失败重试
)
private UserService userService;

// 客户端直接选择服务提供者,无需中间代理

性能优化实践

1. Dubbo 性能调优

// 服务提供者优化
@Service(
    protocol = "dubbo",
    serialization = "kryo",       // 使用更快的序列化
    threads = 200,                // 业务线程池大小
    accepts = 1000,               // 最大连接数
    payload = 8 * 1024 * 1024     // 最大传输数据 8MB
)
public class UserServiceImpl implements UserService {
    // ...
}

// 服务消费者优化
@Reference(
    timeout = 3000,               // 调用超时 3 秒
    retries = 1,                  // 失败重试 1 次
    loadbalance = "leastactive",  // 最少活跃数负载均衡
    connections = 10,             // 每个提供者 10 个连接
    actives = 20                  // 每个服务最大并发 20
)
private UserService userService;

2. 协议选择

// Dubbo 协议:最常用,性能最优
<dubbo:protocol name="dubbo" port="20880" />

// Triple 协议:基于 HTTP/2,兼容 gRPC
<dubbo:protocol name="triple" port="50051" />

// Rest 协议:对外提供 HTTP 接口
<dubbo:protocol name="rest" port="8080" />

// 多协议支持
@Service(protocol = {"dubbo", "rest"})
public class UserServiceImpl implements UserService {
    // 同时支持 RPC 和 HTTP 调用
}

3. 异步调用

// 同步调用
User user = userService.getUser(123L);  // 阻塞等待

// 异步调用(性能更好)
CompletableFuture<User> future = 
    RpcContext.getContext().asyncCall(() -> userService.getUser(123L));

future.thenAccept(user -> {
    // 回调处理
    System.out.println(user);
});

实际性能数据

基于典型的微服务调用场景(局域网环境):

维度 HTTP + JSON Dubbo + Hessian2 提升幅度
单次调用延迟 10ms 3ms 70%
QPS(单连接) 1000 3000 200%
网络带宽占用 100KB/s 40KB/s 60%
CPU 占用 20% 12% 40%
// 基准测试代码
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3)
@Measurement(iterations = 5)
public class RpcBenchmark {
    
    @Benchmark
    public void httpCall() {
        // HTTP + JSON 调用
        restTemplate.getForObject(url, User.class);
    }
    
    @Benchmark
    public void dubboCall() {
        // Dubbo + Hessian2 调用
        userService.getUser(123L);
    }
}

// 结果:
// httpCall   thrpt   5   1000.123  ops/s
// dubboCall  thrpt   5   3000.456  ops/s

答题总结

RPC 比 HTTP 快的四大核心原因

  1. 协议开销小
    • HTTP 头部 200+ 字节,Dubbo 仅 16 字节
    • 节省 95% 的头部开销
  2. 序列化高效
    • JSON 可读性好但体积大、速度慢
    • Hessian2/Protobuf 体积小 50%,速度快 3-5 倍
  3. 连接管理优
    • 长连接无握手开销
    • 多路复用提升并发能力
    • 异步非阻塞通信
  4. 路由更直接
    • 客户端直连服务端,减少一跳
    • 框架内置负载均衡,无需额外代理

性能提升:在典型场景下,RPC 比 HTTP 快 2-3 倍,适合微服务内部高频调用。

面试技巧:强调性能优势的同时,也要提到 HTTP 的优势(通用性、可调试性),说明两者是互补而非替代关系。HTTP/2 + Protobuf 已经接近 RPC 性能。