现在大家做系统开发,都喜欢搞"微服务架构"——简单说就是把一个大系统拆成很多小服务,这样更灵活也更容易扩展。那这些服务之间怎么沟通呢?就得靠一种技术叫 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. 060_面向过程和面向对象区别 061_对象是什么_对象和数据管理 062_对象和类的关系_属性_成员变量_方法 063_一个典型类的写法和调用_类的UML图入门 064_内存分析详解_栈_堆_方法区_栈帧_程序执行的内存变化过程

    060_面向过程和面向对象区别 061_对象是什么_对象和数据管理 062_对象和类的关系_属性_成员变量_方法 public class SxtStu {//定义了一个类,包含的成员变量,属性,方法 ...

  2. 在没有网关的IP地址上编写路由,实现另一个网段可以访问到该主机

    应用场景:该主机两个网卡分别对应两个IP地址 192.168.1.2网段为医保网,并且主机使用了改网段的的网关.192.168.100.99网段地址为互联网地址,没有使用该网段的网关. 我们开发小组通 ...

  3. 使用Vant做移动端对图片预览ImagePreview和List的理解

    使用Vant3做移动端的感受 最近在使用Vant3做移动端. 感觉还可以,使用起来也简单,但是也遇见一些坑. 图片预览ImagePreview的使用 在使用图片预览的时候, 我们在main.js中进行 ...

  4. shell 变量的运算、保存硬件信息脚本

    变量的数学运算 方法一:expr ##加减乘除 [root@localhost ~]# num1=10[root@localhost ~]# num2=16[root@localhost ~]# ex ...

  5. Flink - [06] 状态管理

    题记部分 一.Flink中的状态 由一个任务维护,并且用来计算某个结果的所有数据,都属于这个任务的状态. 可以认为状态就是一个本地变量,可以被任务的业务逻辑访问. Flink会进行状态管理,包括状态一 ...

  6. go 限流器 rate

    前言 Golang 官方提供的扩展库里就自带了限流算法的实现,即 golang.org/x/time/rate.该限流器也是基于 Token Bucket(令牌桶) 实现的. 限流器的内部结构 tim ...

  7. wps时间戳转换成日期

    第一步 打开WPS表格,选择空表格 第二步 右击选择"设置单元格格式" 第三步 选择"日期",然后选择需要的日期类型 第四步 然后在表格里,输入公式 =(D2/ ...

  8. composer 指定php版本

    需要指定php和composer的位置,然后再去执行composer命令 ## 指定PHP版本 指定composer 指定载入包 /usr/local/php7/bin/php composer /u ...

  9. Django实战项目-学习任务系统-用户登录

    第一步:先创建一个Django应用程序框架代码 1,先创建一个Django项目 django-admin startproject mysite 将创建一个目录,其布局如下: mysite/ mana ...

  10. 如何在 Github 上获得 1000 star?

    作为程序员,Github 是第一个绕不开的网站.我们每天都在上面享受着开源带来的便利,我相信很多同学也想自己做一个开源项目,从而获得大家的关注.然而,理想很丰满,现实却是开发了很久的项目仍然无人问津. ...