gRPC
欢迎来到 gRPC 知识库!
⚡ gRPC 简介
gRPC 是 Google 开发的高性能、开源的 RPC (Remote Procedure Call) 框架。它使用 Protocol Buffers 作为接口定义语言(IDL)和底层消息交换格式,基于 HTTP/2 传输协议。
🎯 gRPC vs REST vs GraphQL
| 特性 | gRPC | REST | GraphQL |
|---|---|---|---|
| 协议 | HTTP/2 | HTTP/1.1 | HTTP/1.1 |
| 数据格式 | Protobuf (二进制) | JSON (文本) | JSON (文本) |
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| 浏览器支持 | 有限(需 gRPC-Web) | ✅ | ✅ |
| 流式传输 | ✅ 双向流 | ❌ | 部分(Subscription) |
| 类型安全 | ✅ 强类型 | ❌ | ✅ |
| 代码生成 | ✅ 自动 | ❌ | ✅ |
| 学习曲线 | 🔴 较陡 | 🟢 平缓 | 🟡 中等 |
✅ gRPC 优势
1. 高性能
- HTTP/2 - 多路复用、头部压缩
- Protobuf - 二进制序列化,体积小
- 流式传输 - 服务端流、客户端流、双向流
2. 强类型
- 使用 .proto 文件定义接口
- 自动生成类型安全的客户端和服务端代码
3. 多语言支持
- 官方支持 10+ 语言
- 相同的 .proto 文件生成不同语言代码
4. 流式传输
- 服务端流
- 客户端流
- 双向流
🎯 适用场景
✅ 特别适合
- 微服务通信 - 服务间高性能调用
- 实时通信 - 流式数据传输
- 移动端 - 网络带宽有限
- 多语言环境 - 跨语言 RPC
- 内部 API - 不面向浏览器
⚠️ 不太适合
- 浏览器直接调用(需要 gRPC-Web)
- 需要人类可读的 API(JSON 更友好)
- 简单的 CRUD 场景(REST 更简单)
📖 Protocol Buffers
.proto 文件定义
protobuf
syntax = "proto3";
package user;
// 消息定义
message User {
int64 id = 1;
string username = 2;
string email = 3;
int32 age = 4;
}
message GetUserRequest {
int64 id = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 page_size = 2;
}
message ListUsersResponse {
repeated User users = 1;
int32 total = 2;
}
// 服务定义
service UserService {
// 一元 RPC
rpc GetUser (GetUserRequest) returns (User);
// 服务端流式 RPC
rpc ListUsers (ListUsersRequest) returns (stream User);
// 客户端流式 RPC
rpc CreateUsers (stream User) returns (CreateUsersResponse);
// 双向流式 RPC
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}数据类型
| Proto 类型 | Go | Java | Python | Node |
|---|---|---|---|---|
| double | float64 | double | float | number |
| float | float32 | float | float | number |
| int32 | int32 | int | int | number |
| int64 | int64 | long | int | number/string |
| string | string | String | str | string |
| bool | bool | boolean | bool | boolean |
| bytes | []byte | ByteString | bytes | Buffer |
🚀 快速开始
1. 安装工具
bash
# 安装 protoc 编译器
# macOS
brew install protobuf
# 安装 Go 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest2. 定义服务
protobuf
// user.proto
syntax = "proto3";
package user;
option go_package = "example.com/user";
message User {
int64 id = 1;
string username = 2;
string email = 3;
}
message GetUserRequest {
int64 id = 1;
}
service UserService {
rpc GetUser (GetUserRequest) returns (User);
}3. 生成代码
bash
protoc --go_out=. --go-grpc_out=. user.proto4. 实现服务端 (Go)
go
package main
import (
"context"
"log"
"net"
pb "example.com/user"
"google.golang.org/grpc"
)
// 实现 UserService
type server struct {
pb.UnimplementedUserServiceServer
}
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
// 模拟数据库查询
return &pb.User{
Id: req.Id,
Username: "zhangsan",
Email: "zhang@example.com",
}, nil
}
func main() {
// 监听端口
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 创建 gRPC 服务器
s := grpc.NewServer()
// 注册服务
pb.RegisterUserServiceServer(s, &server{})
log.Println("gRPC server listening on :50051")
// 启动服务
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}5. 实现客户端 (Go)
go
package main
import (
"context"
"log"
"time"
pb "example.com/user"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
// 连接服务器
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// 创建客户端
client := pb.NewUserServiceClient(conn)
// 调用方法
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: 1})
if err != nil {
log.Fatalf("could not get user: %v", err)
}
log.Printf("User: %v", user)
}🌊 流式 RPC
1. 服务端流式
protobuf
service UserService {
rpc ListUsers (ListUsersRequest) returns (stream User);
}go
// 服务端
func (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
users := []pb.User{
{Id: 1, Username: "user1"},
{Id: 2, Username: "user2"},
{Id: 3, Username: "user3"},
}
for _, user := range users {
if err := stream.Send(&user); err != nil {
return err
}
}
return nil
}
// 客户端
stream, err := client.ListUsers(ctx, &pb.ListUsersRequest{})
for {
user, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
log.Printf("User: %v", user)
}2. 客户端流式
protobuf
service UserService {
rpc CreateUsers (stream User) returns (CreateUsersResponse);
}go
// 客户端
stream, err := client.CreateUsers(ctx)
users := []pb.User{
{Username: "user1"},
{Username: "user2"},
}
for _, user := range users {
if err := stream.Send(&user); err != nil {
log.Fatal(err)
}
}
reply, err := stream.CloseAndRecv()3. 双向流式
protobuf
service ChatService {
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}go
// 客户端
stream, err := client.Chat(ctx)
// 发送
go func() {
for {
if err := stream.Send(&pb.ChatMessage{...}); err != nil {
log.Fatal(err)
}
time.Sleep(time.Second)
}
}()
// 接收
for {
msg, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
log.Printf("Received: %v", msg)
}🔒 拦截器(中间件)
一元拦截器
go
// 服务端拦截器
func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
log.Printf("Method: %s", info.FullMethod)
resp, err := handler(ctx, req)
log.Printf("Duration: %v", time.Since(start))
return resp, err
}
// 使用拦截器
s := grpc.NewServer(
grpc.UnaryInterceptor(loggingInterceptor),
)流式拦截器
go
func streamLoggingInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
log.Printf("Stream Method: %s", info.FullMethod)
return handler(srv, ss)
}
s := grpc.NewServer(
grpc.StreamInterceptor(streamLoggingInterceptor),
)🔐 认证
1. 使用 Metadata
go
// 客户端
md := metadata.New(map[string]string{
"authorization": "Bearer token",
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
// 服务端
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "missing metadata")
}
token := md.Get("authorization")
// 验证 token
return &pb.User{...}, nil
}2. 使用 TLS
go
// 服务端
creds, err := credentials.NewServerTLSFromFile("server.crt", "server.key")
s := grpc.NewServer(grpc.Creds(creds))
// 客户端
creds, err := credentials.NewClientTLSFromFile("server.crt", "")
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(creds))🔍 错误处理
go
import "google.golang.org/grpc/status"
import "google.golang.org/grpc/codes"
// 服务端
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
if req.Id <= 0 {
return nil, status.Error(codes.InvalidArgument, "invalid user id")
}
user := findUser(req.Id)
if user == nil {
return nil, status.Error(codes.NotFound, "user not found")
}
return user, nil
}
// 客户端
user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: 1})
if err != nil {
st, ok := status.FromError(err)
if ok {
log.Printf("Code: %v, Message: %s", st.Code(), st.Message())
}
}常用状态码:
codes.OK- 成功codes.InvalidArgument- 参数错误codes.NotFound- 未找到codes.Unauthenticated- 未认证codes.PermissionDenied- 无权限codes.Internal- 服务器内部错误
💡 最佳实践
合理设计 API
- 遵循命名规范
- 使用有意义的消息名称
- 合理使用流式 RPC
错误处理
- 使用标准错误码
- 提供有意义的错误信息
超时设置
- 客户端设置合理的超时时间
- 使用 context 控制生命周期
连接复用
- 复用 gRPC 连接
- 避免频繁创建连接
负载均衡
- 使用 gRPC 负载均衡
- 结合服务发现
监控
- 使用拦截器记录日志
- 集成 Prometheus 监控
📖 学习资源
官方资源
推荐教程
工具推荐
- grpcurl - gRPC 命令行工具
- BloomRPC - gRPC GUI 客户端
- grpcui - gRPC Web UI
准备好了吗?开始你的 gRPC 学习之旅!