gRPC 和传统 RPC 有啥不一样?一篇讲清楚!
现在大家做系统开发,都喜欢搞"微服务架构"——简单说就是把一个大系统拆成很多小服务,这样更灵活也更容易扩展。那这些服务之间怎么沟通呢?就得靠一种技术叫 RPC(远程过程调用)。今天我们就来聊聊它的"进化版":gRPC,看看它和传统的 RPC 到底有啥不一样。
一、先搞懂几个概念
什么是 RPC?
可以把它理解成"跨机器调用函数"的方式。就像你在本地调用一个函数一样,但其实它是在另一台服务器上运行的。传统 RPC 有很多种实现,比如 XML-RPC、JSON-RPC、SOAP 等,数据格式多是 XML 或 JSON。
那 gRPC 是啥?
Google 出品的一个更高效的 RPC 框架,基于 HTTP/2 协议,数据格式用的是 Protocol Buffers(简称 Protobuf)。性能好、效率高,还能自动生成代码,听起来就很香对吧?
二、gRPC 和传统 RPC 的几大区别(白话版)
| 对比点 | 传统 RPC | gRPC |
|---|---|---|
| 传输协议 | 通常用 HTTP/1 或 TCP | HTTP/2,支持多路复用,速度快 |
| 数据格式 | XML/JSON,可读但体积大 | Protobuf,体积小,解析快 |
| 代码生成 | 通常手动写 | 支持自动生成客户端/服务端代码 |
| 流式处理 | 一般不支持 | 支持四种调用模式,支持双向流 |
| 跨语言支持 | 有点费劲 | 官方支持多语言(Go、Python 等) |
| 错误处理 | 用 HTTP 状态码处理 | 用标准错误码机制,支持详细描述 |
三、举个例子更直观
用传统 JSON-RPC 调接口
{
"jsonrpc": "2.0",
"method": "getUserProfile",
"params": {
"userId": 123,
"includeDetails": true
},
"id": 1
}
人类能看懂,但数据量大,解析速度也慢。
用 gRPC + Protobuf
首先定义协议:
syntax = "proto3";
service UserService {
rpc GetUserProfile(UserRequest) returns (UserProfile) {}
}
message UserRequest {
int32 user_id = 1;
bool include_details = 2;
}
message UserProfile {
int32 user_id = 1;
string username = 2;
string email = 3;
}
然后就可以这样调用:
request = user_pb2.UserRequest(user_id=123, include_details=True)
response = stub.GetUserProfile(request)
print(f"用户名: {response.username}")
结构更清晰、体积更小、传输效率更高。
四、请求处理方式对比
传统RPC的调用方式
# XML-RPC示例
import xmlrpc.client
# 创建客户端
server = xmlrpc.client.ServerProxy("http://localhost:8000")
# 每次调用都会建立新连接
result = server.get_user_info(user_id=123)
print(f"用户信息: {result}")
# 又得重新连接
another_result = server.get_product_details(product_id=456)
就像每次打电话都要重新拨号一样,费时间!
gRPC的调用方式
import grpc
import user_service_pb2
import user_service_pb2_grpc
# 创建一个连接通道
with grpc.insecure_channel('localhost:50051') as channel:
# 创建调用对象
stub = user_service_pb2_grpc.UserServiceStub(channel)
# 同一个连接可以调用多个方法
response1 = stub.GetUser(user_service_pb2.GetUserRequest(user_id=123))
response2 = stub.GetProduct(user_service_pb2.GetProductRequest(product_id=456))
# 还能做流式调用,像看视频一样一点点接收数据
for product in stub.ListProducts(user_service_pb2.ListProductsRequest(category="手机")):
print(f"产品: {product.name}, 价格: {product.price}")
就像建立一条专线,通话不断,还能边说边听,太方便了!
五、性能差距有多大?
场景:获取 1000 个用户信息
传统 REST(HTTP/1 + JSON)版本:
import requests
import time
start_time = time.time()
users = []
# 发送1000个独立的HTTP请求,每次都要建连接
for i in range(1000):
response = requests.get(f"http://api.example.com/users/{i}")
users.append(response.json())
duration = time.time() - start_time
print(f"REST API: 获取了{len(users)}个用户,耗时{duration:.2f}秒")
# 输出: REST API: 获取了1000个用户,耗时10.45秒
gRPC 版本:
import grpc
import user_pb2
import user_pb2_grpc
import time
start_time = time.time()
with grpc.insecure_channel('api.example.com:50051') as channel:
stub = user_pb2_grpc.UserServiceStub(channel)
# 一次请求获取所有用户,批量处理
users = list(stub.GetUsers(user_pb2.GetUsersRequest(limit=1000)))
duration = time.time() - start_time
print(f"gRPC: 获取了{len(users)}个用户,耗时{duration:.2f}秒")
# 输出: gRPC: 获取了1000个用户,耗时1.23秒
总结:gRPC 更快,因为它:
支持连接复用(不用每次都重新连)
使用 Protobuf,数据更轻更快
流式处理,批量效率高
六、错误处理方式对比
REST 错误处理:
服务端返回的错误:
{
"error": {
"code": 404,
"message": "User not found",
"details": "The user with ID 12345 does not exist"
}
}
客户端处理:
fetch('/api/users/12345')
.then(response => {
if (!response.ok) {
return response.json().then(err => {
throw new Error(`${err.error.message}: ${err.error.details}`);
});
}
return response.json();
})
.catch(error => console.error('错误:', error));
靠 HTTP 状态码,但格式不统一,需要手动解析。
gRPC 错误处理:
服务端定义错误:
def GetUser(self, request, context):
user = database.find_user(request.user_id)
if not user:
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details(f"找不到用户 {request.user_id}")
return user_pb2.UserProfile() # 返回空对象
return user
客户端处理错误:
try:
response = stub.GetUser(request)
print(f"用户信息: {response}")
except grpc.RpcError as e:
if e.code() == grpc.StatusCode.NOT_FOUND:
print(f"错误: 用户不存在 - {e.details()}")
else:
print(f"RPC错误: {e.code()} - {e.details()}")
标准的错误码 + 描述,客户端可以直接 catch。像处理本地异常一样方便!
七、实际应用场景选择
什么时候用传统REST API?
前端直接调API
// 浏览器调REST API就很方便
fetch('/api/products')
.then(res => res.json())
.then(products => console.log(products));接第三方平台 比如接微信支付、支付宝API,人家都是REST的,你也得跟着来
简单系统 小项目不追求性能,REST开发速度快
什么时候用gRPC?
微服务内部通信 服务多了,内部调用频繁,用gRPC又快又稳
实时数据应用
// 股票价格实时推送
func (s *StockServer) PriceStream(request *pb.StockRequest, stream pb.StockService_PriceStreamServer) error {
for {
price := getLatestPrice(request.Symbol)
stream.Send(&pb.StockPrice{
Symbol: request.Symbol,
Price: price,
Timestamp: time.Now().Unix(),
})
time.Sleep(1 * time.Second)
}
}移动端应用 手机流量金贵,gRPC数据小,省流量
多语言系统 Python服务调Go服务,Java服务调C#服务,都不是问题
八、总结一句话
REST API就像普通话,大家都听得懂;gRPC像高速公路,虽然有门槛,但一旦上了路就飞快!
如果你在做面向普通用户的接口,或者简单系统,REST API足够了。
但如果你在构建微服务、需要高性能、多语言、流式处理能力,那就果断上gRPC!
gRPC 和传统 RPC 有啥不一样?一篇讲清楚!的更多相关文章
- 带入gRPC:对 RPC 方法做自定义认证
带入gRPC:对 RPC 方法做自定义认证 原文地址:带入gRPC:对 RPC 方法做自定义认证项目地址:https://github.com/EDDYCJY/go... 前言 在前面的章节中,我们介 ...
- gRPC创建Java RPC服务
1.说明 本文介绍使用gRPC创建Java版本的RPC服务, 包括通过.proto文件生成Java代码的方法, 以及服务端和客户端代码使用示例. 2.创建生成代码工程 创建Maven工程,grpc-c ...
- 即时通信(RPC)的Rtmp实现--代码实现篇
实现的一般步骤是: step 1: 定义NetConnection对象连接rtmp,并监听NetStatusEvent.NET_STATUS事件 step 2: 在NetStatusEvent.NET ...
- 系统间通信(10)——RPC的基本概念
1.概述 经过了详细的信息格式.网络IO模型的讲解,并且通过JAVA RMI的讲解进行了预热.从这篇文章开始我们将进入这个系列博文的另一个重点知识体系的讲解:RPC.在后续的几篇文章中,我们首先讲解R ...
- RPC介绍
转载http://blog.csdn.net/mindfloating/article/details/39474123/ 近几年的项目中,服务化和微服务化渐渐成为中大型分布式系统架构的主流方式,而 ...
- ScalaPB(2): 在scala中用gRPC实现微服务
gRPC是google开源提供的一个RPC软件框架,它的特点是极大简化了传统RPC的开发流程和代码量,使用户可以免除许多陷阱并聚焦于实际应用逻辑中.作为一种google的最新RPC解决方案,gRPC具 ...
- 系统间通信——RPC架构设计
架构设计:系统间通信(10)——RPC的基本概念 1.概述经过了详细的信息格式.网络IO模型的讲解,并且通过JAVA RMI的讲解进行了预热.从这篇文章开始我们将进入这个系列博文的另一个重点知识体系的 ...
- RPC简介及框架选择
简单介绍RPC协议及常见框架,对比传统restful api和RPC方式的优缺点.常见RPC框架,gRPC及序列化方式Protobuf等 HTTP协议 http协议是基于tcp协议的,tcp协议是流式 ...
- gRPC初探——概念介绍以及如何构建一个简单的gRPC服务
目录 引言 1. gRPC简介 2. 使用Protocol Buffers进行服务定义 2.1 定义消息 2.2 定义服务接口 3.构建简单的gRPC服务 3.1 编写proto文件,定义消息和接口 ...
- Go微服务 grpc/protobuf
了解grpc/protobuf gRPC是一个高性能.通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers ...
随机推荐
- mysql8.0无备份通过idb文件恢复数据过程、idb文件修复和tablespace id不一致处理
周末突然接到一位一年多没联系的妹妹打来电话,"刘哥,快来救救我",我脑海瞬间冒出妙瓦底,电信火苲马扁.....,当时就冒汗了,心想这个妹子怎么被... 问其原由,原来是他们公司服务 ...
- 看图识WAF
看图识WAF 宝塔 腾讯云T-Sec 阿里云盾 创宇盾
- SQL Server统计信息更新会被阻塞或引起会话阻塞吗?
在SQL Server数据库中,统计信息更新(UPDATE STATISTICS)会被其它会话阻塞吗?统计信息更新(UPDATE STATISTICS)会引起其它会话阻塞吗?在回答这两个问题前,我们必 ...
- Hive表误删恢复
一.简介 因hive表删除后,hdfs文件会先放入回收站,定期清理回收站.在回收之前可以进行清理数据 二.恢复步骤 2.1 看表存储是否损坏select type from dw.ods_test1 ...
- mybatis之生命周期及作用域
SqlSessionFactoryBuilder 一旦创建了SqlSessionFactory之后就没有作用了 局部变量 SqlSessionFactory 可以理解为数据库的连接池 SqlSessi ...
- MYSQL数据空洞解析
## 背景引入 MYSQL中数据表A,在删除了一半的数据后,发现表空间的大小并没有减少,这是什么原因导致的呢? 定义 当对一定量数据执行delete操作时,MySQL将数据删除后进而导致页合并或者页删 ...
- 1 使用ollama完成DeepSeek本地部署
1 ollama 1.1 什么是ollama ollama是一个开源的 LLM(大型语言模型)服务工具,用于简化在本地运行大语言模型,降低使用大语言模型的门槛,使得大模型的开发者.研究人员和爱好者能够 ...
- 安川Yaskawa机器人DX100示教器维修的方法
安川Yaskawa机器人DX100示教器维修的优劣势分析 安川Yaskawa机器人示教编程,工业机器人维修,即操作人员经过安川机器人示教器,ABB机器人保养,手动操控机器人的关节运动,以使机器人运动到 ...
- Go实现动态开点线段树
1.线段树介绍 线段树是一种用于高效处理区间查询和区间更新的数据结构,当我们需要解决一个频繁更新区间值的问题的时候,就可以采用线段树的结构进行解决.线段树的核心思想是将区间分为多个子区间进行管理,越往 ...
- docker - [02] 安装部署
一.环境准备 1.需要会一点点Linux基础 2.CentOS 7+ 3.XShell连接服务器进行远程操作 Centos7.x 虚拟机环境 序号 主机名 IP 操作系统 1 ctos79-01 19 ...