3. gRPC基础

gRPC 是 Google 开源的高性能远程过程调用框架,基于 HTTP/2 协议传输,默认使用 Protocol Buffers(protobuf)作为接口定义语言与序列化工具。在微服务架构中,gRPC 凭借其低延迟、强类型、多语言支持和双向流特性,成为服务间通信的核心选项之一。

3.1 Protocol Buffers 语法基础

Protocol Buffers 是一种语言中立、平台中立的序列化结构数据机制,通过 .proto 文件定义数据结构与服务接口。相比 JSON/XML,protobuf 序列化后体积小 3~10 倍,解析速度快 20~100 倍。

3.1.1 消息类型定义

使用 message 关键字定义结构化数据,每个字段包含类型、名称和唯一编号。

syntax = "proto3";

package ecommerce;

message Product {
  int32 id = 1;
  string name = 2;
  double price = 3;
  repeated string tags = 4;       // 数组类型
  map<string, string> attributes = 5; // 映射类型
  Category category = 6;          // 嵌套消息
}

message Category {
  int32 id = 1;
  string name = 2;
}

3.1.2 标量类型与字段规则

Protobuf 类型 C# 对应类型 Java 对应类型 Go 对应类型 字段规则
double double double float64 默认值 0.0
float float float float32 默认值 0.0
int32 int int int32 可变长编码,负数效率低
int64 long long int64 可变长编码
string string String string UTF-8 编码,默认空串
bool bool boolean bool 默认 false
bytes ByteString ByteString []byte 不进行 UTF-8 校验
repeated List<T> List<T> []T 有序列表,可包含 0 个元素
map<K,V> Dictionary<K,V> Map<K,V> map[K]V 键类型必须是标量
字段编号规则:1~15 编号使用 1 字节编码,16~2047 使用 2 字节编码。频繁出现的字段应分配 1~15 编号以节省空间。预留编号范围 19000~19999 供内部实现使用。

3.1.3 枚举与 oneof

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;  // 必须从 0 开始
  PENDING = 1;
  CONFIRMED = 2;
  SHIPPED = 3;
  DELIVERED = 4;
  CANCELLED = 5;
}

message Order {
  string order_id = 1;
  oneof payment {                 // 互斥字段,只能设置其中一个
    string credit_card = 2;
    string paypal_id = 3;
    string bank_transfer = 4;
  }
  OrderStatus status = 5;
}

3.2 gRPC 服务定义

在 .proto 文件中使用 service 关键字定义 RPC 服务,每个方法指定请求消息类型和响应消息类型。gRPC 支持四种通信模式,通过不同的流式关键字组合实现。

3.2.1 服务定义语法

service OrderService {
  // Unary:客户端发送单个请求,服务端返回单个响应
  rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);

  // Server Streaming:客户端发送单个请求,服务端返回流式响应
  rpc ListOrders(ListOrdersRequest) returns (stream Order);

  // Client Streaming:客户端发送流式请求,服务端返回单个响应
  rpc BatchCreateOrder(stream CreateOrderRequest) returns (BatchCreateOrderResponse);

  // Bidirectional Streaming:双方同时发送流式数据
  rpc ProcessOrderStream(stream OrderEvent) returns (stream OrderResult);
}

3.2.2 生成代码与依赖

通过 protoc 编译器生成目标语言的 gRPC 代码:

# 生成 Go 代码
protoc --go_out=. --go-grpc_out=. ecommerce/order.proto

# 生成 Java 代码
protoc --java_out=. --grpc-java_out=. ecommerce/order.proto

# 生成 Python 代码
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ecommerce/order.proto
关键依赖:protobuf 编译器(protoc) + 对应语言的 gRPC 插件。Go 需要 protoc-gen-go-grpc,Java 需要 protoc-gen-grpc-java,Python 需要 grpcio-tools。

3.3 四种通信模式详解

gRPC 基于 HTTP/2 的流式传输能力,提供了四种通信模式,覆盖从简单请求-响应到复杂双向流的所有场景。

gRPC 四种通信模式架构图 Unary RPC 客户端 → 服务端 单个请求 → 单个响应 Client Server 请求 → 响应 典型:创建订单 延迟最低 Server Streaming 客户端 → 服务端 单个请求 → 流式响应 Client Server 典型:订单列表 大结果集分页 Client Streaming 客户端 → 服务端 流式请求 → 单个响应 Client Server 典型:批量上传 文件分片上传 Bidirectional 客户端 ↔ 服务端 双向流式通信 Client Server 典型:实时聊天 游戏状态同步 通信模式选择指南 • Unary:适用于大多数 CRUD 操作,延迟最低,实现最简单 • Server Streaming:服务端推送大量数据,如日志流、监控指标、大列表 • Client Streaming:客户端批量提交数据,如批量导入、文件上传、事件聚合 • Bidirectional Streaming:实时双向交互,如聊天、协作编辑、实时游戏 性能对比(基准测试参考):Unary 延迟最低,Bidirectional 吞吐最高 流式模式需注意:连接管理、背压控制、错误恢复、资源释放 安全建议:所有模式均需启用 TLS/mTLS,流式连接需设置超时和重试策略

3.4 四种通信模式实现示例

3.4.1 Unary RPC(一元模式)

最基础的请求-响应模式,客户端发送一个请求,服务端返回一个响应。适用于查询、创建、更新等原子操作。

// Go 服务端实现
func (s *orderServer) CreateOrder(ctx context.Context, req *pb.CreateOrderRequest) (*pb.CreateOrderResponse, error) {
    // 业务逻辑:验证请求、写入数据库
    order := &Order{
        ID:     generateOrderID(),
        UserID: req.UserId,
        Items:  req.Items,
        Status: OrderStatusPending,
    }
    if err := db.Save(order); err != nil {
        return nil, status.Errorf(codes.Internal, "failed to create order: %v", err)
    }
    return &pb.CreateOrderResponse{
        OrderId: order.ID,
        Status:  "created",
    }, nil
}

// Go 客户端调用
resp, err := client.CreateOrder(ctx, &pb.CreateOrderRequest{
    UserId: "user_123",
    Items:  []*pb.OrderItem{{ProductId: "prod_456", Quantity: 2}},
})
if err != nil {
    log.Fatalf("create order failed: %v", err)
}
fmt.Printf("Order created: %s\n", resp.OrderId)

3.4.2 Server Streaming RPC(服务端流模式)

客户端发送单个请求,服务端返回一个消息流。适用于需要推送大量数据的场景,如日志流、监控指标、大列表分页。

// Go 服务端实现
func (s *orderServer) ListOrders(req *pb.ListOrdersRequest, stream pb.OrderService_ListOrdersServer) error {
    orders, err := db.GetOrdersByUser(req.UserId)
    if err != nil {
        return status.Errorf(codes.Internal, "failed to list orders: %v", err)
    }
    for _, order := range orders {
        if err := stream.Send(order.ToProto()); err != nil {
            return err
        }
        // 可添加限速逻辑
        time.Sleep(10 * time.Millisecond)
    }
    return nil
}

// Go 客户端调用
stream, err := client.ListOrders(ctx, &pb.ListOrdersRequest{UserId: "user_123"})
for {
    order, err := stream.Recv()
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Fatalf("receive order failed: %v", err)
    }
    fmt.Printf("Order: %s, Status: %s\n", order.OrderId, order.Status)
}

3.4.3 Client Streaming RPC(客户端流模式)

客户端发送一个消息流,服务端返回单个响应。适用于批量提交、文件上传、事件聚合等场景。

// Go 服务端实现
func (s *orderServer) BatchCreateOrder(stream pb.OrderService_BatchCreateOrderServer) error {
    var orders []*pb.CreateOrderRequest
    for {
        req, err := stream.Recv()
        if err == io.EOF {
            // 所有请求接收完毕,批量处理
            results := processBatch(orders)
            return stream.SendAndClose(&pb.BatchCreateOrderResponse{
                TotalCreated: int32(len(results)),
                OrderIds:     results,
            })
        }
        if err != nil {
            return status.Errorf(codes.Internal, "receive failed: %v", err)
        }
        orders = append(orders, req)
        // 可设置最大批量限制
        if len(orders) >= 1000 {
            results := processBatch(orders)
            return stream.SendAndClose(&pb.BatchCreateOrderResponse{
                TotalCreated: int32(len(results)),
                OrderIds:     results,
            })
        }
    }
}

// Go 客户端调用
stream, err := client.BatchCreateOrder(ctx)
for _, req := range batchRequests {
    if err := stream.Send(req); err != nil {
        log.Fatalf("send failed: %v", err)
    }
}
resp, err := stream.CloseAndRecv()
if err != nil {
    log.Fatalf("close and recv failed: %v", err)
}
fmt.Printf("Created %d orders\n", resp.TotalCreated)

3.4.4 Bidirectional Streaming RPC(双向流模式)

客户端和服务端可以同时发送和接收消息流,双方独立读写。适用于实时聊天、协作编辑、游戏状态同步等场景。

// Go 服务端实现
func (s *orderServer) ProcessOrderStream(stream pb.OrderService_ProcessOrderStreamServer) error {
    // 使用 goroutine 处理并发读写
    errChan := make(chan error, 1)

    // 接收客户端消息
    go func() {
        for {
            event, err := stream.Recv()
            if err == io.EOF {
                errChan <- nil
                return
            }
            if err != nil {
                errChan <- err
                return
            }
            // 处理事件并发送结果
            result := processEvent(event)
            if err := stream.Send(result); err != nil {
                errChan <- err
                return
            }
        }
    }()

    return <-errChan
}

// Go 客户端调用
stream, err := client.ProcessOrderStream(ctx)
// 启动接收 goroutine
go func() {
    for {
        result, err := stream.Recv()
        if err == io.EOF {
            return
        }
        if err != nil {
            log.Fatalf("receive failed: %v", err)
        }
        fmt.Printf("Result: %s\n", result.Status)
    }
}()
// 发送事件
for _, event := range events {
    if err := stream.Send(event); err != nil {
        log.Fatalf("send failed: %v", err)
    }
}
stream.CloseSend()

3.5 通信模式对比与选型

特性 Unary Server Streaming Client Streaming Bidirectional
请求数量 1 1 N(流) N(流)
响应数量 1 N(流) 1 N(流)
延迟 最低 中等 中等 较高(首包)
吞吐量 中等 高 高 最高
实现复杂度 低 中 中 高
连接复用 HTTP/2 多路复用 单流 单流 单流
典型场景 CRUD、查询 日志、监控、推送 批量导入、上传 聊天、实时协作
错误处理 简单 流中断处理 流中断处理 复杂,需协调
背压控制 不适用 服务端控制发送速率 客户端控制发送速率 双方需协商
流式通信注意事项:
  • 流式连接会长期占用 HTTP/2 流,需合理设置连接池大小
  • 服务端流和双向流必须实现背压控制,防止内存溢出
  • 客户端流需在发送完毕后调用 CloseAndRecv() 或 CloseSend()
  • 所有流式模式都需处理 io.EOF 作为正常结束信号
  • 生产环境建议为流式连接设置超时和重试策略
  • 双向流中,客户端和服务端的读写 goroutine 需独立处理,避免死锁

3.6 gRPC 安全基础

gRPC 原生支持 TLS/mTLS 加密通信,确保服务间数据传输的机密性和完整性。安全配置在服务端和客户端分别进行。

3.6.1 服务端 TLS 配置

// Go 服务端启用 TLS
func main() {
    // 加载证书和密钥
    certFile := "server.crt"
    keyFile := "server.key"
    creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
    if err != nil {
        log.Fatalf("failed to load credentials: %v", err)
    }

    // 创建 gRPC 服务器并配置 TLS
    s := grpc.NewServer(
        grpc.Creds(creds),
        grpc.KeepaliveParams(keepalive.ServerParameters{
            MaxConnectionIdle:     5 * time.Minute,
            MaxConnectionAge:      30 * time.Minute,
            MaxConnectionAgeGrace: 5 * time.Minute,
            Time:                  1 * time.Hour,
            Timeout:               20 * time.Second,
        }),
    )

    pb.RegisterOrderServiceServer(s, &orderServer{})
    lis, _ := net.Listen("tcp", ":50051")
    s.Serve(lis)
}

3.6.2 客户端 TLS 配置

// Go 客户端启用 TLS
func main() {
    // 加载 CA 证书用于验证服务端
    caFile := "ca.crt"
    creds, err := credentials.NewClientTLSFromFile(caFile, "server.example.com")
    if err != nil {
        log.Fatalf("failed to load CA: %v", err)
    }

    conn, err := grpc.Dial(
        "localhost:50051",
        grpc.WithTransportCredentials(creds),
        grpc.WithKeepaliveParams(keepalive.ClientParameters{
            Time:                10 * time.Second,
            Timeout:             20 * time.Second,
            PermitWithoutStream: true,
        }),
    )
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()

    client := pb.NewOrderServiceClient(conn)
    // 后续调用...
}
安全最佳实践:
  • 生产环境必须启用 TLS,禁止使用 grpc.WithInsecure()
  • 使用 mTLS 实现双向认证,服务端和客户端都验证对方证书
  • 证书使用内部 CA 签发,定期轮换(建议 90 天)
  • 结合 Token 认证(JWT/OAuth2)实现应用层鉴权
  • 流式连接需设置 keepalive 参数,防止连接泄漏
  • 使用 grpc.MaxRecvMsgSize 和 grpc.MaxSendMsgSize 限制消息大小

3.7 性能优化与最佳实践

gRPC 性能优化关键路径 协议层优化 • 使用 Protobuf 而非 JSON • 字段编号 1~15 优先 • 避免 repeated 嵌套过深 传输层优化 • HTTP/2 多路复用连接池 • 合理设置 keepalive 参数 • 启用 gzip 压缩(大消息) 应用层优化 • 使用流式模式减少 RPC 次数 • 批量操作合并请求 • 异步非阻塞调用 性能优化清单 1. 连接池:每个目标服务维护 2~10 个长连接,避免频繁建连 2. 消息大小:单消息建议 < 4MB,超过时使用流式分片 3. 超时设置:Unary 设置 5~30s,流式设置 60s