当前位置: 首页 > java >正文

RPC 和 HTTP 的区别

在这里插入图片描述

目录

      • 核心思想:一个简单的比喻
      • 一、 概念与定位
        • 1. HTTP (HyperText Transfer Protocol)
        • 2. RPC (Remote Procedure Call)
      • 二、 核心区别对比
      • 三、 Java代码示例
        • 场景:根据用户ID获取用户信息。
        • 1. HTTP/REST 示例 (使用Spring Web)
        • 2. RPC 示例 (使用 gRPC)
      • 四、 实践与选型建议 (何时使用哪一个?)
        • 选择 HTTP/REST 的场景:
        • 选择 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 依赖 (核心)

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

服务端 UserController.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/setterpublic 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)

// 使用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)

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 类,我们继承它并实现我们的业务逻辑。

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 用于向客户端发送响应的流观察者*/@Overridepublic 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 文件生成的代码。

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

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

  • API Gateway (入口): 使用HTTP/REST暴露API给外部客户端(Web, App)。
  • 内部服务之间: 使用gRPC进行高效、低延迟的通信。
http://www.xdnf.cn/news/20475.html

相关文章:

  • 网络中的PAT:小端口映射的大能量
  • 4.存储虚拟化
  • Linux系统检测硬盘失败解救方法
  • 计算机组成原理:计算机硬件的基本组成
  • 零基础学习数据采集与监视控制系统SCADA
  • 【C++】vector 深度剖析及模拟实现
  • Jmeter性能测试
  • 【读文献】Buffer和level shift的偏置电压设计
  • Day21 保护操作系统
  • 【01背包问题变体】P1282 多米诺骨牌
  • MySQL集群高可用架构之组复制 (MGR)
  • 校园洒水车cad+三维图+设计说书
  • 金属也有“记忆力”?—聊聊二合一玛哈特矫平机如何“消除”金属的记忆
  • 修复存在坏块或05、C4、C5 S.M.A.R.T错误的硬盘
  • Spring Cloud Alibaba快速入门02-Nacos
  • FRCNet
  • Fab资源快速导入UE
  • Shell 脚本实现系统监控与告警
  • Spring Boot中MyBatis的定义与使用
  • IOC为什么交由spring容器管理?
  • 操作系统研发工作心得体会 - 于复杂性中构建秩序
  • 每日一题(2)
  • MySQL学习记录-索引
  • 携程社招前端面经
  • pthread_detach函数
  • 2025最新超详细FreeRTOS入门教程:第二章 FreeRTOS任务创建
  • 设计一个 AB 测试平台
  • 实例和对象的区别
  • 【目录-单选】鸿蒙HarmonyOS开发者基础
  • 自适应滤波器:Ch4 最小均方(LMS)算法