微服务架构中 gRPC 的应用
一、为什么使用gRPC
1. 性能优势
传统的前后端交互方式如下:
# server
from flask import Flask, jsonify, requestapp = Flask(__name__)@app.route("/user/<int:user_id>", methods=["GET"])
def get_user(user_id):return jsonify({"id": user_id,"name": f"User {user_id}"})if __name__ == "__main__":app.run(port=5000)-----------------------------------------------------# client
import requestsresp = requests.get("http://localhost:5000/user/1")
print(resp.json()) # 输出: {'id': 1, 'name': 'User 1'}
但是,在构建大型分布式系统、微服务架构或跨语言通信的系统时,传统的 RESTful 接口和 JSON 传输方式在某些方面逐渐暴露出性能与扩展性的瓶颈。
比如,一个服务需要每秒处理 1 万个请求,每个请求传输一个结构化的订单对象(比如嵌套字段较多)。那么,就会遇到以下问题:
JSON 是文本格式,体积大,解析慢;
每次都需要进行字符串解析和对象映射(反序列化),消耗 CPU;
嵌套结构多时,反序列化容易出错、调试困难。
而 gRPC 使用 Protobuf,二进制格式,解析速度远快于 JSON,CPU 占用更低。
再比如,服务 A 需要同时调用服务 B 和服务 C,然后聚合数据返回前端。那么又会产生如下问题:
REST 基于 HTTP/1.1,请求需要串行排队,无法并发复用 TCP;
每个请求都要建立 HTTP 连接(三次握手),慢;
请求头巨大(冗余),比如每次都发送
User-Agent
、Accept
等;
gRPC 基于 HTTP/2,支持多路复用(multiplexing),一个连接上同时发送多个请求不阻塞,极大减少延迟。
2. 接口协议的统一
传统项目开发中,多个微服务分别由不同团队维护,使用 REST 风格的 API,有的写 Swagger,有的直接靠 README 文档说明。那么就会有以下几个问题:
没有统一的接口标准;
接口定义冗余、重复、错误;
需要手动维护文档,代码和接口易不一致;
新人不清楚应该怎么调某个服务,需要找人问或抓包。
gRPC 使用 .proto
统一定义接口 + 数据结构,并自动生成服务端与客户端代码,接口一致性有保障。
3. 跨语言沟通
如果服务 A 使用 Java,服务 B 使用 Go,服务 C 使用 Python。它们之间通过 REST 接口通信。以下问题必须解决。
需要各自解析 JSON,接口字段不一致可能导致错误;
字段拼写错误无法被编译器发现;
需要写大量重复的 HTTP 请求封装代码;
零散维护,没有统一规范和 SDK。
gRPC 支持多语言,使用同一个 .proto
文件就能自动生成多语言 SDK,省时省力。
因此,在小项目中,REST 是简单直接的解决方案;但在大型项目中,性能、接口规范、多语言支持和通信模式等方面的挑战,往往让 REST 力不从心。gRPC 就是为这些“高级场景”而生的。
二、如何使用gRPC
1. 一个简单的例子
1.1 定义 proto 文件(user.proto)
syntax = "proto3";service UserService {rpc GetUser (UserRequest) returns (UserReply);
}message UserRequest {int32 id = 1;
}message UserReply {int32 id = 1;string name = 2;
}
保存为 user.proto
,然后使用 protoc
生成代码:
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. user.proto
接下来会生成两个 python 源码文件,分别是:
user_pb2.py
包含 Protobuf 消息定义(如
UserRequest
、UserReply
);提供序列化、反序列化等方法;
与
--python_out=.
参数有关。
user_pb2_grpc.py
包含 gRPC 的服务接口定义和 stub 实现;
自动生成服务端基类(如
UserServiceServicer
)和客户端 stub(如UserServiceStub
);与
--grpc_python_out=.
参数有关。
比如,user_pb2_grpc.py生成以后可能长这样,
import grpc
import user_pb2 as user__pb2class UserServiceStub(object):def __init__(self, channel):self.GetUser = channel.unary_unary('/UserService/GetUser',request_serializer=user__pb2.UserRequest.SerializeToString,response_deserializer=user__pb2.UserReply.FromString,)class UserServiceServicer(object):def GetUser(self, request, context):# 用户应实现此方法raise NotImplementedError('Method not implemented!')def add_UserServiceServicer_to_server(servicer, server):rpc_method_handlers = {'GetUser': grpc.unary_unary_rpc_method_handler(servicer.GetUser,request_deserializer=user__pb2.UserRequest.FromString,response_serializer=user__pb2.UserReply.SerializeToString,),}generic_handler = grpc.method_handlers_generic_handler('UserService', rpc_method_handlers)server.add_generic_rpc_handlers((generic_handler,))
1.2 定义gRPC 服务端
import grpc
from concurrent import futures
import user_pb2
import user_pb2_grpcclass UserService(user_pb2_grpc.UserServiceServicer):def GetUser(self, request, context):return user_pb2.UserReply(id=request.id, name=f"User {request.id}")def serve():server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))user_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server)server.add_insecure_port('[::]:50051')server.start()print("gRPC server started on port 50051")server.wait_for_termination()if __name__ == "__main__":serve()
1.3 定义gRPC 客户端
import grpc
import user_pb2
import user_pb2_grpcdef run():channel = grpc.insecure_channel('localhost:50051')stub = user_pb2_grpc.UserServiceStub(channel)response = stub.GetUser(user_pb2.UserRequest(id=1))print(response) # 输出: id: 1 name: "User 1"if __name__ == "__main__":run()
2. 请求流程
首选,浏览器发起请求,后端框架会把这个请求路由到 handler。然后在处理业务逻辑时,会调用 gRPC 客户端(gRPC连接池)。然后,gRPC客户端发起调用:
response = stub.GetUser(UserRequest(id=1))
然后,gRPC框架内部(由proto生成的user_pb2_grpc.py)执行如下逻辑,此时,会进行这样的转化:UserRequest(id=1)
对象 → .SerializeToString()
→ 二进制数据。最后,数据通过 HTTP/2 发送给 gRPC 服务端。
# user_pb2_grpc.py 中 Stub 定义了:
self.GetUser = channel.unary_unary('/UserService/GetUser', # 服务路径request_serializer=UserRequest.SerializeToString, # 序列化函数response_deserializer=UserReply.FromString, # 反序列化函数
)
然后,gRPC 服务端接收到二进制数据后。使用 UserRequest.FromString
自动反序列化 → UserRequest(id=1)
对象。然后调用gRPC服务端所写的代码:
class MyUserService(UserServiceServicer):def GetUser(self, request, context):# request 是反序列化后的 UserRequest 实例return UserReply(id=request.id, name=f"User {request.id}")
最后,使用 UserReply.SerializeToString
→ 序列化成二进制,发回客户端。客户端接收响应后,使用 UserReply.FromString
→ 得到 UserReply
对象,作为函数返回值返回给调用者。
rpc_method_handlers = {'GetUser': grpc.unary_unary_rpc_method_handler(servicer.GetUser,request_deserializer=user__pb2.UserRequest.FromString,response_serializer=user__pb2.UserReply.SerializeToString,),
}
3. 总结
gRPC 客户端请求时会将 Python 中的请求对象通过 request_serializer
(如 .SerializeToString()
)进行序列化,发送给服务端,服务端接收后反序列化调用你实现的 UserServiceServicer.GetUser()
方法并返回响应对象,再通过 response_serializer
序列化后传回客户端,客户端用 response_deserializer
(如 .FromString()
)反序列化恢复为 Python 对象。