RPC是什么?HTTP是什么?RPC和HTTP有什么区别?

摘要:从一次"微服务间调用用HTTP还是RPC"的技术选型争论出发,深度剖析RPC和HTTP的本质区别。通过Dubbo、gRPC、Feign的实现原理对比,以及序列化、传输协议、服务治理的差异分析,揭秘为什么公司内部用RPC、对外接口用HTTP、以及为什么gRPC比HTTP/1.1快5倍。配合时序图展示调用流程,给出不同场景下的选型建议。


💥 翻车现场

周四下午,技术评审会议。

技术总监:"微服务间调用,你们准备用什么?"
哈吉米:"HTTP啊,Spring Cloud全家桶,Feign调用很方便。"
架构师:"我建议用RPC,Dubbo性能更好。"
哈吉米:"RPC和HTTP有啥区别?不都是远程调用吗?"
架构师:"差别大了!性能、序列化、服务治理都不同。"

会后,哈吉米一脸懵。

哈吉米(疑问):"RPC到底是什么?和HTTP有什么区别?什么时候用RPC,什么时候用HTTP?"

南北绿豆和阿西噶阿西来了。

南北绿豆:"RPC和HTTP经常被混淆,其实它们不是一个层面的概念。"
阿西噶阿西:"来,我给你讲清楚。"


🤔 RPC是什么?

RPC的定义

RPC(Remote Procedure Call):远程过程调用

核心思想

让远程调用像本地调用一样简单

本地调用:
result = userService.getUserById(123);

远程调用(RPC):
result = userService.getUserById(123);  // 看起来一样

实际:
1. 客户端序列化参数(123)
2. 网络传输到服务器
3. 服务器反序列化参数
4. 服务器执行方法
5. 服务器序列化返回值
6. 网络传输回客户端
7. 客户端反序列化返回值

南北绿豆:"RPC的目标是:屏蔽网络通信的细节,让远程调用像本地调用。"


RPC的核心组件

RPC框架的组成:

1. 客户端Stub(桩)
   - 封装网络调用细节
   - 序列化参数
   - 发送请求
   - 反序列化结果

2. 服务端Skeleton(骨架)
   - 接收请求
   - 反序列化参数
   - 调用本地方法
   - 序列化结果
   - 返回响应

3. 注册中心
   - 服务注册
   - 服务发现

4. 网络传输层
   - TCP/HTTP
   - 序列化协议(Protobuf、Hessian、JSON)

架构图

客户端                                 服务端
  │                                      │
  │  result = userService.getUser(123)  │
  │            ↓                         │
  │      [客户端Stub]                    │
  │            ↓                         │
  │      序列化(123)                     │
  │            ↓                         │
  │    ┌──网络传输──┐                    │
  │    │  TCP/HTTP │                    │
  │    └────────────┘                   │
  │            ↓                         │
  │                              [服务端Skeleton]
  │                                      ↓
  │                              反序列化(123)
  │                                      ↓
  │                              调用本地方法
  │                              getUser(123)
  │                                      ↓
  │                              序列化结果
  │    ┌──网络传输──┐                    │
  │    │  返回结果  │                    │
  │    └────────────┘                   │
  │            ↓                         │
  │      反序列化                         │
  │            ↓                         │
  │      返回result                      │

🤔 HTTP是什么?

HTTP的定义

HTTP(HyperText Transfer Protocol):超文本传输协议

特点

1. 应用层协议(第7层)
2. 基于TCP(可靠传输)
3. 无状态(每次请求独立)
4. 文本协议(可读性好)

请求格式:
GET /api/user/123 HTTP/1.1
Host: www.example.com
Content-Type: application/json

响应格式:
HTTP/1.1 200 OK
Content-Type: application/json

{"id": 123, "name": "alice"}

HTTP调用示例

// Spring Cloud Feign(基于HTTP)
@FeignClient(name = "user-service")
public interface UserClient {
    
    @GetMapping("/api/user/{id}")
    User getUser(@PathVariable Long id);
}

// 实际HTTP请求
GET http://user-service/api/user/123 HTTP/1.1
Host: 192.168.1.100:8080
Accept: application/json

// HTTP响应
HTTP/1.1 200 OK
Content-Type: application/json

{"id": 123, "name": "alice", "age": 25}

🎯 RPC vs HTTP:核心区别

对比表

特性RPCHTTP
概念层次调用方式(应用层概念)传输协议(应用层协议)
传输协议可以基于TCP、HTTPHTTP/1.1、HTTP/2
序列化Protobuf、Hessian、Kryo(二进制)JSON、XML(文本)
性能⭐⭐⭐⭐⭐ 高⭐⭐⭐ 中
可读性差(二进制)好(文本)
服务治理✅ 丰富(Dubbo)⚠️ 需要额外组件
跨语言⚠️ 看框架✅ 天然支持
浏览器支持

本质区别

阿西噶阿西:"很多人搞混RPC和HTTP,其实它们不是一个层面的概念。"

HTTP:
- 是一种协议
- 规定了数据格式(请求行、请求头、请求体)
- 应用层协议(OSI第7层)

RPC:
- 是一种调用方式
- 可以基于HTTP实现(如gRPC用HTTP/2)
- 也可以基于TCP实现(如Dubbo)
- 是应用层的概念,不是协议

关系:
RPC ⊄ HTTP
RPC ⊅ HTTP
RPC可以用HTTP实现(gRPC),也可以用TCP实现(Dubbo)

图解

┌──────────────────────────────────┐
│           RPC(调用方式)          │
│                                  │
│  ┌────────────┐  ┌────────────┐ │
│  │基于HTTP/2  │  │  基于TCP   │ │
│  │  (gRPC)    │  │  (Dubbo)   │ │
│  └────────────┘  └────────────┘ │
└──────────────────────────────────┘

┌──────────────────────────────────┐
│       HTTP(传输协议)            │
│                                  │
│  用于:浏览器、RESTful API、RPC  │
└──────────────────────────────────┘

南北绿豆:"所以问'RPC和HTTP的区别',其实是在问'基于TCP的RPC和基于HTTP的REST API的区别'。"


🎯 Dubbo(基于TCP的RPC) vs Feign(基于HTTP的REST)

Dubbo调用流程

// Dubbo接口定义
public interface UserService {
    User getUser(Long id);
}

// 服务端实现
@DubboService
public class UserServiceImpl implements UserService {
    
    @Override
    public User getUser(Long id) {
        return userMapper.selectById(id);
    }
}

// 客户端调用
@DubboReference
private UserService userService;

public void test() {
    User user = userService.getUser(123L);  // 看起来像本地调用
}

底层流程

sequenceDiagram
    participant Client as 客户端
    participant Stub as Dubbo Stub
    participant TCP as TCP连接
    participant Skeleton as Dubbo Skeleton
    participant Server as 服务端

    Client->>Stub: 1. userService.getUser(123)
    Stub->>Stub: 2. 序列化参数<br/>Hessian(123) → 二进制
    Stub->>TCP: 3. 发送TCP包<br/>协议:Dubbo私有协议
    
    TCP->>Skeleton: 4. 接收TCP包
    Skeleton->>Skeleton: 5. 反序列化参数<br/>二进制 → 123
    Skeleton->>Server: 6. 调用本地方法<br/>getUser(123)
    Server->>Skeleton: 7. 返回User对象
    
    Skeleton->>Skeleton: 8. 序列化结果<br/>User对象 → 二进制
    Skeleton->>TCP: 9. 发送TCP包
    
    TCP->>Stub: 10. 接收TCP包
    Stub->>Stub: 11. 反序列化结果<br/>二进制 → User对象
    Stub->>Client: 12. 返回User对象

特点

  • ✅ 基于TCP(长连接,性能好)
  • ✅ 二进制序列化(体积小)
  • ✅ 服务治理丰富(负载均衡、降级、限流)

Feign调用流程

// Feign接口定义
@FeignClient(name = "user-service")
public interface UserClient {
    
    @GetMapping("/api/user/{id}")
    User getUser(@PathVariable Long id);
}

// 客户端调用
@Autowired
private UserClient userClient;

public void test() {
    User user = userClient.getUser(123L);
}

底层流程

客户端调用:
userClient.getUser(123)
    ↓
Feign生成HTTP请求:
GET http://user-service/api/user/123 HTTP/1.1
Accept: application/json
    ↓
从Nacos获取user-service的实例列表
    ↓
负载均衡选择一个实例(192.168.1.100:8080)
    ↓
发送HTTP请求
    ↓
服务端Controller接收:
@GetMapping("/api/user/{id}")
public User getUser(@PathVariable Long id) {
    return userService.getUserById(id);
}
    ↓
返回JSON:
{"id": 123, "name": "alice"}
    ↓
Feign反序列化JSON → User对象
    ↓
返回给客户端

特点

  • ✅ 基于HTTP(通用性好)
  • ✅ JSON序列化(可读性好)
  • ⚠️ 文本协议(体积大,性能差)

🎯 性能对比

测试场景

调用:
User user = service.getUser(123L);

返回对象:
{
  "id": 123,
  "username": "alice",
  "phone": "13800138000",
  "email": "alice@example.com",
  "age": 25
}

序列化体积对比

序列化方式体积说明
JSON156字节可读性好,体积大
Protobuf32字节二进制,体积小
Hessian45字节二进制

性能测试(10000次调用)

方案协议序列化总耗时QPS
Feign(HTTP/1.1)HTTP/1.1JSON8.2秒1219
gRPC(HTTP/2)HTTP/2Protobuf1.5秒6666
Dubbo(TCP)TCPHessian1.2秒8333

性能差距

  • Dubbo比Feign快6.8倍
  • gRPC比Feign快5.5倍

🎯 为什么RPC性能更好?

原因1:长连接 vs 短连接

HTTP/1.1(Feign)

每次请求:
1. 建立TCP连接(三次握手,1-2ms)
2. 发送HTTP请求
3. 接收HTTP响应
4. 关闭连接(四次挥手,1-2ms)

或者Keep-Alive(长连接):
1. 复用连接(不关闭)
2. 但有超时时间(通常60秒)

Dubbo(TCP长连接)

启动时建立连接:
1. 建立TCP连接(三次握手)
2. 连接保持(心跳保活)

每次请求:
1. 直接发送数据(复用连接)
2. 接收响应

优势:
- 节省握手/挥手时间
- 连接复用率高

原因2:二进制 vs 文本

HTTP(JSON)

// 请求
POST /api/user/create HTTP/1.1
Host: user-service
Content-Type: application/json
Content-Length: 156

{"username":"alice","phone":"13800138000","email":"alice@example.com"}

// 响应
HTTP/1.1 200 OK
Content-Type: application/json

{"code":0,"data":{"id":123,"username":"alice"}}

体积:
请求:约200字节(HTTP头 + JSON)
响应:约150字节

Dubbo(Hessian)

// 请求(二进制,简化表示)
[Header: 16字节][Body: 45字节]

体积:
请求:61字节
响应:约60字节

体积对比:
Dubbo / HTTP = 60 / 200 = 30%

传输对比

传输100万次请求:

HTTP(JSON):
请求大小:200字节
总传输:200字节 × 100万 = 200MB

Dubbo(Hessian):
请求大小:60字节
总传输:60字节 × 100万 = 60MB

网络传输节省:(200 - 60) / 200 = 70%

原因3:服务治理

Dubbo的服务治理

内置功能:
1. 负载均衡(随机、轮询、一致性哈希)
2. 服务降级(失败后降级)
3. 限流(TPS限制)
4. 熔断(失败率过高,熔断)
5. 重试(失败自动重试)
6. 超时控制
7. 版本控制(灰度发布)
8. 分组(环境隔离)

HTTP(Feign)

需要额外组件:
1. 负载均衡:Ribbon
2. 熔断:Hystrix/Resilience4j
3. 限流:Sentinel
4. 服务发现:Eureka/Nacos

问题:
- 组件多,复杂度高
- 需要分别配置

🎯 gRPC:基于HTTP/2的RPC

gRPC的优势

gRPC = RPC + HTTP/2 + Protobuf

特点:
1. 基于HTTP/2(多路复用、二进制帧)
2. Protobuf序列化(体积小)
3. 流式传输(支持双向流)
4. 跨语言(Protocol Buffers跨语言)

gRPC示例

// user.proto
syntax = "proto3";

service UserService {
  rpc GetUser (UserRequest) returns (UserResponse);
}

message UserRequest {
  int64 id = 1;
}

message UserResponse {
  int64 id = 1;
  string username = 2;
  string phone = 3;
}

客户端调用

// gRPC客户端
UserServiceBlockingStub stub = UserServiceGrpc.newBlockingStub(channel);

UserRequest request = UserRequest.newBuilder()
    .setId(123)
    .build();

UserResponse response = stub.getUser(request);

性能

HTTP/1.1 vs HTTP/2(gRPC):

HTTP/1.1:
- 队头阻塞(一个请求一个TCP连接)
- 文本协议(体积大)

HTTP/2(gRPC):
- 多路复用(一个连接多个请求)
- 二进制帧(体积小)
- 头部压缩(HPACK)

性能提升:5-10倍

🎯 何时用RPC,何时用HTTP?

选型建议

南北绿豆:"根据场景选择。"

场景推荐方案原因
微服务内部调用RPC(Dubbo、gRPC)性能好,服务治理强
对外开放APIHTTP(RESTful)通用性好,浏览器支持
移动端调用HTTP(RESTful)通用性好,易调试
跨语言调用gRPC跨语言支持好
实时通信gRPC(流式)支持双向流
简单场景HTTP实现简单

实际案例

阿里巴巴

内部:Dubbo(RPC)
- 订单服务 ↔ 库存服务(Dubbo)
- 支付服务 ↔ 账户服务(Dubbo)

对外:HTTP(RESTful)
- 移动App → 开放API(HTTP)
- 第三方接入 → 开放API(HTTP)

Google

内部:gRPC
- 微服务间调用(gRPC)

对外:HTTP
- Google Maps API(HTTP)
- YouTube API(HTTP)

🎯 完整调用流程对比

Dubbo调用流程

sequenceDiagram
    participant Client as 客户端应用
    participant Registry as Nacos注册中心
    participant Provider as 服务提供者

    Note over Client,Provider: 启动阶段
    Provider->>Registry: 1. 注册服务<br/>userService, ip:port
    Client->>Registry: 2. 订阅服务<br/>userService
    Registry->>Client: 3. 返回提供者列表
    
    Note over Client,Provider: 调用阶段
    Client->>Client: 4. 本地调用<br/>userService.getUser(123)
    Client->>Client: 5. 序列化参数(Hessian)
    Client->>Provider: 6. TCP发送(长连接复用)
    Provider->>Provider: 7. 反序列化参数
    Provider->>Provider: 8. 执行方法
    Provider->>Provider: 9. 序列化结果
    Provider->>Client: 10. TCP返回
    Client->>Client: 11. 反序列化结果
    Client->>Client: 12. 返回User对象
    
    Note over Client,Provider: 全程二进制,无HTTP头

Feign调用流程

sequenceDiagram
    participant Client as 客户端应用
    participant Feign as Feign客户端
    participant Ribbon as Ribbon负载均衡
    participant HTTP as HTTP请求
    participant Controller as 服务端Controller

    Client->>Feign: 1. userClient.getUser(123)
    Feign->>Ribbon: 2. 获取user-service实例列表
    Ribbon->>Ribbon: 3. 负载均衡选择实例
    Ribbon->>Feign: 4. 返回:192.168.1.100:8080
    
    Feign->>Feign: 5. 构造HTTP请求<br/>GET /api/user/123
    Feign->>HTTP: 6. 发送HTTP请求
    
    HTTP->>Controller: 7. HTTP请求到达
    Controller->>Controller: 8. 执行方法
    Controller->>Controller: 9. 序列化JSON
    Controller->>HTTP: 10. HTTP响应
    
    HTTP->>Feign: 11. 接收响应
    Feign->>Feign: 12. 反序列化JSON
    Feign->>Client: 13. 返回User对象
    
    Note over Client,Controller: 文本协议,有HTTP头

🎓 面试标准答案

题目:RPC和HTTP有什么区别?

答案

本质区别

  • RPC:调用方式(远程过程调用)
  • HTTP:传输协议(应用层协议)

RPC可以基于HTTP实现(如gRPC),也可以基于TCP实现(如Dubbo)

通常说的"RPC vs HTTP",是指

  • 基于TCP的RPC(如Dubbo)
  • 基于HTTP的REST API(如Feign)

具体区别

特性RPC(Dubbo)HTTP(Feign)
传输协议TCPHTTP/1.1
连接方式长连接短连接或Keep-Alive
序列化Hessian/Protobuf(二进制)JSON(文本)
体积小(60字节)大(200字节)
性能高(8000 QPS)中(1200 QPS)
服务治理丰富(内置)需要额外组件
跨语言看框架天然支持
调试难(二进制)易(文本可读)

选型建议

  • 内部微服务:RPC(Dubbo、gRPC)
  • 对外API:HTTP(RESTful)
  • 跨语言:gRPC
  • 简单场景:HTTP

🎉 结束语

晚上9点,技术选型会议继续。

哈吉米:"我理解了!RPC和HTTP不是对立的,RPC可以基于HTTP实现。"

南北绿豆:"对,通常说的RPC vs HTTP,其实是Dubbo(基于TCP)vs Feign(基于HTTP)的对比。"

阿西噶阿西:"记住:内部调用用RPC性能好,对外接口用HTTP通用性好。"

哈吉米:"还有gRPC兼顾了两者的优点:基于HTTP/2,但用Protobuf序列化,性能接近Dubbo。"

南北绿豆:"对,没有银弹,根据场景选方案!"


记忆口诀

RPC是调用方式,HTTP是传输协议
RPC可基于TCP,也可基于HTTP
Dubbo基于TCP长连接,Feign基于HTTP短连接
二进制序列化体积小,文本协议可读性好
内部调用用RPC,对外接口用HTTP