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

    Linux下处理^M字符,很多时候windows环境中编辑过的文件,在Linux下经常会出现^M字符,可以通过以下方式处理该字符. 方法一:vim命令打开文件,然后在vim命令模式下输入以下内容: 1 ...

  2. 开源的 DeepSeek-R1「GitHub 热点速览」

    春节假期回来,一睁眼全是王炸级的开源模型 DeepSeek-R1! GitHub 地址→github.com/deepseek-ai/DeepSeek-R1 DeepSeek-R1 开源还不到一个月, ...

  3. 天线的OTA测试

    有源测试 (即OTA测试) 把使用综测仪的测试叫做有源测试(Active).使用有源测试的测试速度比较无源相对要慢,但是因为手机是一个复杂材料体,往往无源测试对发射性能的模拟是可信的,但是对于接收性能 ...

  4. 面试题64. 求1+2+…+n

    地址:https://leetcode-cn.com/problems/qiu-12n-lcof/ <?php /** 求 1+2+...+n ,要求不能使用乘除法.for.while.if.e ...

  5. .NET周刊【2月第4期 2025-02-23】

    国内文章 [翻译] 为什么 Tracebit 用 C# 开发 https://www.cnblogs.com/liuliu-66/p/-/why-tracebit-is-written-in-c-sh ...

  6. Appflowy cloud 部署测试避坑指南

    在进行 Appflowy cloud 部署测试时,我可谓是踩坑无数.下面,我想从几个关键方面来分享一下我的经验. 先给大家讲讲我的基础情况.Appflowy cloud 的部署是在 docker 环境 ...

  7. 关于jsp的MySQL数据库连接问题

    <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding= ...

  8. go 密码 hash 加密

    目录 bcrypt加密算法原理和应用 简单使用 一起实现一个demo 获取用户输入的密码 Hash & Salt 用户的密码 目前我们做了什么 验证密码 更新 Main 函数 全部代码 bcr ...

  9. 使用SVM在数字验证码识别中的应用研究课程报告

    第1章 概要设计 1.1 设计目的 支持向量机作为一类强大的监督学习模型,以其出色的泛化能力,在手写数字识别.面部检测.图像分类等多个领域展现出了其优越性.其在处理小样本.非线性及高维模式识别任务中表 ...

  10. 编写你的第一个 Django 应用程序,第3部分

    本教程从教程 2 停止的地方开始.我们是 继续网络投票应用程序,并将专注于创建公众界面 – "视图". 在我们的投票应用程序中,我们将有以下四个视图: 问题"索引&quo ...