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?

  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!

From:https://www.cnblogs.com/xiaoxiblog/p/18832594
萧熙
100+评论
captcha