RPC 和 HTTP 的区别

目录

      • 核心思想:一个简单的比喻
      • [一、 概念与定位](#一、 概念与定位)
        • [1. HTTP (HyperText Transfer Protocol)](#1. HTTP (HyperText Transfer Protocol))
        • [2. RPC (Remote Procedure Call)](#2. RPC (Remote Procedure Call))
      • [二、 核心区别对比](#二、 核心区别对比)
      • [三、 Java代码示例](#三、 Java代码示例)
      • [四、 实践与选型建议 (何时使用哪一个?)](#四、 实践与选型建议 (何时使用哪一个?))
        • [选择 HTTP/REST 的场景:](#选择 HTTP/REST 的场景:)
        • [选择 RPC (特别是 gRPC) 的场景:](#选择 RPC (特别是 gRPC) 的场景:)
      • 总结

核心思想:一个简单的比喻

在我们深入技术细节之前,先用一个比喻来帮助理解:

  • HTTP 就像去餐厅点餐

    • 你(客户端)看着一份标准菜单(HTTP方法:GET, POST, PUT, DELETE)。
    • 你告诉服务员(网络):"我想要一份宫保鸡丁"(URL: /dishes/kung-pao-chicken),这是"获取"(GET)操作。
    • 服务员给你端上菜(响应)。
    • 整个过程是标准化的,你关心的是"资源"(菜品),并通过标准动作(点餐、加菜)来操作它。你不需要知道后厨是怎么做的。
  • RPC 就像直接给厨师打电话

    • 你(客户端)有一本厨师的"技能手册"(服务接口定义,如 .proto 文件)。
    • 你直接在电话里对厨师(服务端)说:"帮我做一份 (方法名: makeDish特辣的 (参数: spiceLevel="extra_hot"宫保鸡丁 (参数: dishName="kung-pao-chicken")"。
    • 你调用的是一个具体的动作(方法),而不是操作一个资源。这个调用过程就像在调用你自己本地代码里的一个方法一样,网络细节被框架隐藏了。

一、 概念与定位

1. HTTP (HyperText Transfer Protocol)

HTTP是一个应用层协议,是互联网上数据通信的基础。它最初被设计用来传输HTML页面,但现在已成为交换任何类型数据的通用协议(如 JSON、XML、图片等)。

当我们谈论在应用中使用HTTP时,通常指的是构建 RESTful API 。REST (Representational State Transfer) 是一种基于HTTP协议的架构风格,它强调:

  • 资源(Resource): 系统中的一切皆为资源,每个资源都有一个唯一的标识符(URI)。
  • 表现层(Representation): 资源的表现形式,如JSON或XML。
  • 状态转移(State Transfer): 通过HTTP动词(GET, POST, PUT, DELETE, PATCH)对资源进行操作,从而实现服务端资源状态的变更。

核心定位 :HTTP/REST关注的是对资源的增删改查(CRUD),它是一种面向资源的设计思想。

2. RPC (Remote Procedure Call)

RPC是一种编程模型通信范式。它的核心目标是让开发人员在调用远程服务上的方法时,感觉就像调用本地方法一样,而无需关心底层复杂的网络通信细节。

开发者只需要定义一个服务接口(Service Interface),RPC框架会自动生成客户端的"桩"(Stub)和服务端的"骨架"(Skeleton)。

  • 客户端桩(Stub): 看起来像一个本地对象,但其内部实现是将方法调用和参数序列化,并通过网络发送到服务端。
  • 服务端骨架(Skeleton): 接收网络请求,反序列化数据,然后调用实际的服务端业务逻辑。

核心定位 :RPC关注的是执行一个远程动作(Action),它是一种面向过程/服务的设计思想。


二、 核心区别对比

特性维度 HTTP (通常指RESTful API) RPC (以gRPC为例)
抽象层次 资源(Resource)。客户端通过HTTP动词操作URI定义的资源。关注"是什么"。 方法/函数(Method/Function)。客户端调用服务端定义好的方法。关注"做什么"。
通信协议 通常基于 HTTP/1.1HTTP/2。协议本身是通用的。 可以基于多种协议。现代RPC框架(如gRPC)建立在HTTP/2之上,以获得高性能。老式RPC可能使用自定义TCP协议。
数据格式 灵活,可读性好。通常是JSON或XML,基于文本。 高效,结构化。通常是二进制格式,如Protobuf (Protocol Buffers), Thrift, Avro。需要预先定义Schema。
耦合度 松耦合。客户端和服务端只需要对资源和数据格式(如JSON结构)达成共识。 紧耦合 。客户端和服务端强依赖于预先定义的接口文件(IDL - Interface Definition Language,如.proto文件)。服务端接口变更通常需要客户端重新生成代码。
性能 相对较低。文本协议(JSON)解析开销大,HTTP/1.1头部冗余,连接复用效率低。 相对较高。二进制协议(Protobuf)体积小,序列化/反序列化速度快,HTTP/2的多路复用和头部压缩等特性使其非常高效。
服务定义 隐式定义。通常通过文档(如Swagger/OpenAPI)来描述API。 显式、强制定义 。通过IDL文件(如gRPC的.proto)来严格定义服务、方法和消息体,具有强类型约束。
可读性与调试 极佳 。请求和响应都是人类可读的,可以使用浏览器、curl、Postman等工具轻松调试。 较差 。二进制流对人类不友好,需要专门的工具(如grpcurl)或UI(如BloomRPC)来进行调试。
跨语言支持 极好。任何支持HTTP协议栈的语言都可以轻松实现。 。主流RPC框架(Dubbo,gRPC, Thrift)通过代码生成工具支持多种语言,但需要依赖特定的框架库。
浏览器支持 原生支持。所有浏览器都内置了HTTP客户端。 不直接支持。需要通过网关(如gRPC-Web + Envoy proxy)转换协议才能被浏览器调用。

三、 Java代码示例

让我们通过Spring Boot 3来实现这两个模式,直观地感受差异。

场景:根据用户ID获取用户信息。
1. HTTP/REST 示例 (使用Spring Web)

这种方式非常直观,关注的是 /users/{id} 这个资源。

pom.xml 依赖 (核心)

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

服务端 UserController.java

java 复制代码
package com.example.httpdemo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@RestController
@RequestMapping("/users")
public class UserController {

    // 模拟数据库
    private static final Map<Long, User> userDatabase = new ConcurrentHashMap<>();

    static {
        userDatabase.put(1L, new User(1L, "Alice", "alice@example.com"));
        userDatabase.put(2L, new User(2L, "Bob", "bob@example.com"));
    }

    /**
     * 通过GET请求获取用户信息资源
     * @param id 用户ID
     * @return 用户对象,Spring Boot会自动序列化为JSON
     */
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        User user = userDatabase.get(id);
        if (user == null) {
            // 在实际项目中,这里应该返回404 Not Found并有统一的异常处理
            throw new UserNotFoundException("User not found with id: " + id);
        }
        return user;
    }

    // DTO (Data Transfer Object)
    public static class User {
        private Long id;
        private String name;
        private String email;

        // 省略构造函数、getter/setter
        public User(Long id, String name, String email) {
            this.id = id;
            this.name = name;
            this.email = email;
        }

        public Long getId() { return id; }
        public String getName() { return name; }
        public String getEmail() { return email; }
    }
  
    // 自定义异常
    static class UserNotFoundException extends RuntimeException {
        public UserNotFoundException(String message) {
            super(message);
        }
    }
}

客户端调用 (使用RestTemplateWebClient)

java 复制代码
// 使用curl命令模拟客户端
// curl http://localhost:8080/users/1
// 响应: {"id":1,"name":"Alice","email":"alice@example.com"}

// 使用Spring的RestTemplate
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/users/1";
User user = restTemplate.getForObject(url, User.class);
System.out.println("Fetched User: " + user.getName());
2. RPC 示例 (使用 gRPC)

这种方式更复杂,需要先定义接口,然后实现。关注的是 getUser 这个方法调用。

步骤1: 定义服务接口 (src/main/proto/user.proto)

protobuf 复制代码
syntax = "proto3";

// 生成的Java类所在的包
option java_package = "com.example.grpcdemo.grpc";
// 是否将proto文件中的message生成为单独的Java文件
option java_multiple_files = true;

// 定义服务
service UserService {
  // 定义一个名为GetUser的方法
  rpc GetUser (UserRequest) returns (UserResponse);
}

// 定义请求消息体
message UserRequest {
  int64 user_id = 1;
}

// 定义响应消息体
message UserResponse {
  int64 id = 1;
  string name = 2;
  string email = 3;
}

步骤2: pom.xml 添加gRPC和Protobuf插件及依赖

这部分比较繁琐,需要添加 grpc-spring-boot-starter, protobuf-maven-plugin 等。插件会在编译时根据 .proto 文件自动生成Java代码。

步骤3: 服务端实现 (UserServiceImpl.java)

生成的代码会包含一个 UserServiceGrpc.UserServiceImplBase 类,我们继承它并实现我们的业务逻辑。

java 复制代码
package com.example.grpcdemo.service;

import com.example.grpcdemo.grpc.*;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@GrpcService // 这个注解来自 grpc-spring-boot-starter,自动将该服务注册到gRPC服务器
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {

    // 模拟数据库
    private static final Map<Long, UserResponse> userDatabase = new ConcurrentHashMap<>();

    static {
        userDatabase.put(1L, UserResponse.newBuilder().setId(1L).setName("Alice").setEmail("alice@example.com").build());
        userDatabase.put(2L, UserResponse.newBuilder().setId(2L).setName("Bob").setEmail("bob@example.com").build());
    }

    /**
     * 实现proto文件中定义的GetUser方法
     * @param request 封装了请求参数的UserRequest对象
     * @param responseObserver 用于向客户端发送响应的流观察者
     */
    @Override
    public void getUser(UserRequest request, StreamObserver<UserResponse> responseObserver) {
        long userId = request.getUserId();
        UserResponse user = userDatabase.get(userId);

        if (user == null) {
            // 通过onError向客户端传递错误
            responseObserver.onError(io.grpc.Status.NOT_FOUND
                    .withDescription("User not found with id: " + userId)
                    .asRuntimeException());
            return;
        }

        // 通过onNext发送响应数据
        responseObserver.onNext(user);
        // 通过onCompleted表示调用结束
        responseObserver.onCompleted();
    }
}

步骤4: 客户端调用

客户端也需要依赖 .proto 文件生成的代码。

java 复制代码
package com.example.grpcdemo.client;

import com.example.grpcdemo.grpc.UserRequest;
import com.example.grpcdemo.grpc.UserResponse;
import com.example.grpcdemo.grpc.UserServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

public class GrpcClient {
    public static void main(String[] args) {
        // 1. 创建通信的Channel
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 9090) // gRPC服务默认端口
                .usePlaintext() // 使用非加密连接(仅用于测试)
                .build();

        // 2. 从Channel创建客户端Stub (阻塞式)
        UserServiceGrpc.UserServiceBlockingStub blockingStub = UserServiceGrpc.newBlockingStub(channel);

        // 3. 准备请求对象
        UserRequest request = UserRequest.newBuilder()
                .setUserId(1L)
                .build();
      
        try {
            // 4. 像调用本地方法一样调用远程方法
            UserResponse response = blockingStub.getUser(request);
            System.out.println("Fetched User: " + response.getName());
        } catch (Exception e) {
            System.err.println("Request failed: " + e.getMessage());
        } finally {
            // 5. 关闭Channel
            channel.shutdown();
        }
    }
}

可以看到,blockingStub.getUser(request) 这行代码对开发者隐藏了所有网络细节,这就是RPC的核心魅力。


四、 实践与选型建议 (何时使用哪一个?)

作为架构师或资深开发者,技术选型是我们的日常。以下是我的建议:

选择 HTTP/REST 的场景:
  1. 对外开放的API (Public APIs):当你的API需要被第三方开发者、合作伙伴或前端Web/移动应用调用时,HTTP/REST是事实上的标准。它的通用性、易于理解和调试的特点是无与伦比的。
  2. 简单的、面向资源的服务:如果你的服务主要是提供数据的增删改查,那么RESTful风格是极其自然和合适的。
  3. 追求松耦合和快速迭代的前端协作:前端和后端可以通过一份简单的JSON约定或Swagger文档并行开发,耦合度低。
  4. 需要利用广泛的生态系统:HTTP有大量的代理、缓存、负载均衡、监控工具支持,生态非常成熟。
选择 RPC (特别是 gRPC) 的场景:
  1. 内部微服务间通信 (East-West Traffic):在复杂的微服务架构中,服务间的调用频率极高,对性能和延迟非常敏感。gRPC的二进制协议和HTTP/2带来的性能优势非常显著。
  2. 性能是关键指标的系统:对于实时游戏、金融交易、物联网数据采集等对低延迟、高吞吐量有苛刻要求的场景。
  3. 需要强类型契约:当团队希望通过接口定义文件(IDL)来强制统一前后端的数据模型和接口规范时,RPC是绝佳选择。这可以避免很多因数据类型不匹配导致的运行时错误。
  4. 需要复杂通信模式:gRPC原生支持四种通信模式:简单请求-响应、服务端流、客户端流、双向流。这对于需要流式处理数据的场景(如实时推送、文件上传)非常有用,而HTTP/1.1实现起来非常复杂。

总结

HTTP/REST RPC/gRPC
思想 面向资源 面向动作/服务
强项 通用性、兼容性、可读性 性能、效率、强类型约束
最佳应用 对外API、Web服务、前端交互 内部微服务间的高性能通信
开发体验 简单直观,调试方便 略显复杂(需IDL和代码生成),但契约先行

最后的忠告 :在现代分布式系统中,RPC和HTTP并非"二选一"的对立关系,而是"各司其职"的协作关系。一个典型的微服务系统架构可能是这样的:

  • API Gateway (入口): 使用HTTP/REST暴露API给外部客户端(Web, App)。
  • 内部服务之间: 使用gRPC进行高效、低延迟的通信。
相关推荐
赖龙5 小时前
记录SSL部署,链路不完整问题
网络·网络协议·ssl
吐个泡泡v6 小时前
网络编程基础:一文搞懂 Socket、HTTP、HTTPS、TCP/IP、SSL 的关系
网络·网络协议·http·https·socket·ssl·tcp
Blurpath6 小时前
如何利用静态代理IP优化爬虫策略?从基础到实战的完整指南
爬虫·网络协议·ip代理·住宅代理
liulilittle7 小时前
HTTP简易客户端实现
开发语言·网络·c++·网络协议·http·编程语言
板鸭〈小号〉9 小时前
UDP-Server(2)词典功能
网络·网络协议·udp
摸着石头过河的石头11 小时前
HTTP内容类型:从基础到实战的全方位解析
前端·http·浏览器
Coding_Doggy11 小时前
苍穹外卖Day10 | 订单状态定时处理、来单提醒、客户催单、SpringTask、WebSocket、cron表达式
网络·websocket·网络协议
小马哥编程11 小时前
计算机网络:以太网中的数据传输
网络·网络协议·计算机网络
堕落年代12 小时前
Spring Boot HTTP状态码详解
spring boot·后端·http