现在大家做系统开发,都喜欢搞"微服务架构"——简单说就是把一个大系统拆成很多小服务,这样更灵活也更容易扩展。那这些服务之间怎么沟通呢?就得靠一种技术叫 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?

  1. 前端直接调API

    // 浏览器调REST API就很方便
    fetch('/api/products')
    .then(res => res.json())
    .then(products => console.log(products));
  2. 接第三方平台 比如接微信支付、支付宝API,人家都是REST的,你也得跟着来

  3. 简单系统 小项目不追求性能,REST开发速度快

什么时候用gRPC?

  1. 微服务内部通信 服务多了,内部调用频繁,用gRPC又快又稳

  2. 实时数据应用

    // 股票价格实时推送
    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)
    }
    }
  3. 移动端应用 手机流量金贵,gRPC数据小,省流量

  4. 多语言系统 Python服务调Go服务,Java服务调C#服务,都不是问题

八、总结一句话

REST API就像普通话,大家都听得懂;gRPC像高速公路,虽然有门槛,但一旦上了路就飞快!

如果你在做面向普通用户的接口,或者简单系统,REST API足够了。

但如果你在构建微服务、需要高性能、多语言、流式处理能力,那就果断上gRPC!

gRPC 和传统 RPC 有啥不一样?一篇讲清楚!的更多相关文章

  1. 带入gRPC:对 RPC 方法做自定义认证

    带入gRPC:对 RPC 方法做自定义认证 原文地址:带入gRPC:对 RPC 方法做自定义认证项目地址:https://github.com/EDDYCJY/go... 前言 在前面的章节中,我们介 ...

  2. gRPC创建Java RPC服务

    1.说明 本文介绍使用gRPC创建Java版本的RPC服务, 包括通过.proto文件生成Java代码的方法, 以及服务端和客户端代码使用示例. 2.创建生成代码工程 创建Maven工程,grpc-c ...

  3. 即时通信(RPC)的Rtmp实现--代码实现篇

    实现的一般步骤是: step 1: 定义NetConnection对象连接rtmp,并监听NetStatusEvent.NET_STATUS事件 step 2: 在NetStatusEvent.NET ...

  4. 系统间通信(10)——RPC的基本概念

    1.概述 经过了详细的信息格式.网络IO模型的讲解,并且通过JAVA RMI的讲解进行了预热.从这篇文章开始我们将进入这个系列博文的另一个重点知识体系的讲解:RPC.在后续的几篇文章中,我们首先讲解R ...

  5. RPC介绍

    转载http://blog.csdn.net/mindfloating/article/details/39474123/ 近几年的项目中,服务化和微服务化渐渐成为中大型分布式系统架构的主流方式,而 ...

  6. ScalaPB(2): 在scala中用gRPC实现微服务

    gRPC是google开源提供的一个RPC软件框架,它的特点是极大简化了传统RPC的开发流程和代码量,使用户可以免除许多陷阱并聚焦于实际应用逻辑中.作为一种google的最新RPC解决方案,gRPC具 ...

  7. 系统间通信——RPC架构设计

    架构设计:系统间通信(10)——RPC的基本概念 1.概述经过了详细的信息格式.网络IO模型的讲解,并且通过JAVA RMI的讲解进行了预热.从这篇文章开始我们将进入这个系列博文的另一个重点知识体系的 ...

  8. RPC简介及框架选择

    简单介绍RPC协议及常见框架,对比传统restful api和RPC方式的优缺点.常见RPC框架,gRPC及序列化方式Protobuf等 HTTP协议 http协议是基于tcp协议的,tcp协议是流式 ...

  9. gRPC初探——概念介绍以及如何构建一个简单的gRPC服务

    目录 引言 1. gRPC简介 2. 使用Protocol Buffers进行服务定义 2.1 定义消息 2.2 定义服务接口 3.构建简单的gRPC服务 3.1 编写proto文件,定义消息和接口 ...

  10. Go微服务 grpc/protobuf

    了解grpc/protobuf gRPC是一个高性能.通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers ...

随机推荐

  1. Algernon pg walkthrough Window

    第一次打window 从简单的开始打起吧 nmap └─# nmap -p- -A 192.168.150.65 Starting Nmap 7.94SVN ( https://nmap.org ) ...

  2. Linux mint安装百度云

    deb包下载 BCloud 作者官网https://github.com/XuShaohua/bcloud-packages 链接: https://pan.baidu.com/s/1hskY04G ...

  3. Transformer-Squared:停止微调 LLMs

    Transformer-Squared:停止微调 LLMs 自适应大语言模型背后的架构.Transformer-Squared 的数学与代码,以及奇异值分解 DALL-E 生成的图片 我们已经进入了这 ...

  4. 安全稳定地远程访问飞牛NAS

    春节前从一个网友那里了解到一个新的NAS--飞牛. 起因是我们一个用户用我们的SD-WAN来远程访问飞牛NAS,市面上做NAS的很多,之所以单独体验这个产品主要是: 不需要购买硬件,这个是非常重要的, ...

  5. [BZOJ3569] DZY Loves Chinese II 题解

    考虑不联通的情况.图不好做,就造一棵生成树出来,由于是无向图,所以只有树边和返祖边. 发现在一条树边断开后,生成树会分成两个连通块,由覆盖这条树边的返祖边链接,只有这些返祖边也全部断开,原图才会不联通 ...

  6. 移动硬盘插入win10检测到却不显示盘符解决方法

    1.开始菜单中的设置-----设备. 2.选择"蓝牙和其他设备" 3.在其他设备栏中就能看到检测到的移动硬盘,点击删除设备后重新插入移动硬盘即可在此电脑上显示盘符.

  7. Typecho 如何开启 HTTPS

    一般来说,我们直接开启 HTTPS 就行,开启后进去网站后台修改网站的 URL 即可. 但是我昨天发现,我的工具箱迁移服务器之后,前台看着是很正常的,但是后台的登陆页面引入的依然的 http 标头,所 ...

  8. 奥特曼autMan对接BBK-JD扫码登录+微信扫码登录

    1.BBK京东扫码容器配置 首先进入root目录 cd /root 拉取镜像创建容器 docker run -dit \ -v $PWD/conf:/data/conf \ -v $PWD/logs: ...

  9. 通过fetch_mcp,让Cline能够获取网页内容。

    fetch_mcp介绍 GitHub地址:https://github.com/zcaceres/fetch-mcp 此MCP服务器提供了以多种格式(包括HTML.JSON.纯文本和Markdown) ...

  10. C# USB 摄像头 OpenCV 视频picBox呈现,抓拍图像保存呈现。

    1.winform 应用程序,两个picturebox空间,一个用于视频呈现,一个用于抓拍呈现. 2.引用包OpenCvSharp4.OpenCvSharp4.Extensions.OpenCvSha ...