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

    nmap扫描 ┌──(root㉿kali)-[~] └─# nmap -p- -A 192.168.167.234 Starting Nmap 7.94SVN ( https://nmap.org ) ...

  2. VMware的快照原理

    本文分享自天翼云开发者社区<VMware的快照原理>,作者:m****n VMware的快照是基于数据块的快照.快照也是以一个文件方式存在的,缺省位置和虚拟机在同一目录下,它是一个Delt ...

  3. 一个登录功能也能玩出这么多花样?sa-token带你轻松搞定多地登录、单地登录、同端互斥登录

    需求场景 说起登录,你可能会不屑一顾,还有比这更简单的功能吗? 获取一下用户提交参数 username + password 和数据库中一比对,有记录返回[登录成功],无记录返回[用户名或密码错误] ...

  4. 探索sqlmap在WebSocket安全测试中的应用

    探索sqlmap在WebSocket安全测试中的应用 WebSocket与HTTP的区别 WebSocket,对于初次接触的人来说,往往会引发一个疑问:既然我们已经有了广泛使用的HTTP协议,为何还需 ...

  5. [记录点滴] OpenResty中Redis操作总结

    [记录点滴] OpenResty中Redis操作总结 0x00 摘要 本文总结了在OpenResty中的操作,与大家分享,涉及知识点为Openresty, Lua, Redis. 0x01 操作记录 ...

  6. C#下.NET配置文件使用(二)

    app.config 与 Settings.settings 用VC#创建一个GUI程序后,就会有一个 Settings.settings 文件. 一旦我们通过VC#给它添加值后,在工程目录下会生成一 ...

  7. C# List LinQ Lambda 表达式

    ------------恢复内容开始------------ # 参考链接 : https://blog.csdn.net/wori/article/details/113144580 首先 => ...

  8. 借Processing语言及IDE做DOS批处理的事务( 批量修改文件夹或文件的名字 )

    一直想用Processing语言做一些批处理的事务,因为其自带的IDE功能紧凑易用,极度轻量,又加上Java语言的生态极具友好,处理一些windows相关操作完全可行,简单快捷. 这次就是用它做[批量 ...

  9. Docker应用部署(Mysql、tomcat、Redis、redis)

    Docker应用部署mysql5.7 1.拉取镜像 docker pull mysql:5.7 2.查看镜像 docker images 3.创建容器 docker run -id \ -p 3307 ...

  10. win7系统清理C盘空间方法实测

    问题描述:win7电脑C盘容易满,采用如下方法清理 方法一:win+r,输入%temp%查看临时文件,手动删除不需要的文件 方法二:减小休眠文件:如果你很少使用休眠模式,可以通过win+r输入cmd命 ...