3. Go gRPC 服务端实现:Unary RPC 与 Server Streaming RPC

本章聚焦于将 .proto 文件定义的服务契约,转化为可运行的 Go 服务端代码。我们将基于 protobuf 编译器生成的桩代码(Stub),分别实现 Unary RPC(一元 RPC)与 Server Streaming RPC(服务端流式 RPC)两种通信模式。这是构建 gRPC 压测闭环的基础服务层,后续所有测试工具都将直接调用这些接口。

3.1 从 proto 到桩代码:生成流程

在编写业务逻辑之前,必须先通过 protoc 编译器生成 Go 语言的服务端接口与消息类型。假设我们已定义好 order.proto 文件,其中包含 OrderService 服务。

核心生成命令:
protoc --go_out=. --go-grpc_out=. \
  --proto_path=protos \
  protos/order.proto

该命令会生成两个关键文件:order.pb.go(消息类型)与 order_grpc.pb.go(服务端/客户端接口)。

生成的 order_grpc.pb.go 中会包含以下核心接口定义:

生成产物 说明 服务端用途
OrderServiceServer 接口 包含所有 RPC 方法的抽象定义 服务端必须实现该接口的所有方法
RegisterOrderServiceServer 将实现类注册到 gRPC 服务器 在 main.go 中调用,绑定服务
请求/响应消息结构体 CreateOrderRequest、OrderStatusResponse 等 作为方法入参和返回值

3.2 Unary RPC 实现:创建订单

Unary RPC 是最简单的请求-响应模式:客户端发送一个请求,服务端返回一个响应。我们以 CreateOrder 方法为例,展示完整的实现流程。

3.2.1 定义 proto 服务

service OrderService {
  // Unary RPC: 创建订单
  rpc CreateOrder(CreateOrderRequest) returns (OrderResponse);
  
  // Server Streaming RPC: 获取订单状态更新
  rpc SubscribeOrderStatus(OrderStatusRequest) returns (stream OrderStatusResponse);
}

message CreateOrderRequest {
  string user_id = 1;
  repeated string item_ids = 2;
  double total_amount = 3;
}

message OrderResponse {
  string order_id = 1;
  string status = 2;
  int64 created_at = 3;
}

3.2.2 服务端实现代码

// order_server.go
type orderServiceServer struct {
  pb.UnimplementedOrderServiceServer // 必须嵌入,保证向前兼容
  mu     sync.Mutex
  orders map[string]*pb.OrderResponse
}

// CreateOrder 实现 Unary RPC
func (s *orderServiceServer) CreateOrder(
  ctx context.Context,
  req *pb.CreateOrderRequest,
) (*pb.OrderResponse, error) {
  // 1. 参数校验
  if req.UserId == "" || len(req.ItemIds) == 0 {
    return nil, status.Errorf(codes.InvalidArgument,
      "user_id and item_ids are required")
  }

  // 2. 业务逻辑:生成订单ID,模拟持久化
  orderID := fmt.Sprintf("ORD-%s-%d", req.UserId, time.Now().UnixNano())
  resp := &pb.OrderResponse{
    OrderId:   orderID,
    Status:    "CREATED",
    CreatedAt: time.Now().Unix(),
  }

  // 3. 模拟存储(压测时可替换为真实DB)
  s.mu.Lock()
  s.orders[orderID] = resp
  s.mu.Unlock()

  log.Printf("[Unary] Order created: %s, user: %s, amount: %.2f",
    orderID, req.UserId, req.TotalAmount)
  return resp, nil
}
压测关注点:Unary RPC 的延迟主要受序列化/反序列化、业务逻辑耗时和网络往返影响。在压测时,CreateOrder 的响应时间应作为核心指标之一。

3.3 Server Streaming RPC 实现:订单状态订阅

Server Streaming RPC 允许服务端在单个请求后,持续向客户端推送多条消息。我们实现 SubscribeOrderStatus 方法,模拟订单状态的实时变更推送。

3.3.1 实现要点

  • 方法签名:返回值为 pb.OrderService_SubscribeOrderStatusServer 流对象。
  • 发送消息:通过 stream.Send() 方法逐条推送。
  • 流控制:监听 stream.Context().Done() 处理客户端取消。
  • 错误处理:可在任意时刻通过 return err 终止流。

3.3.2 服务端实现代码

// SubscribeOrderStatus 实现 Server Streaming RPC
func (s *orderServiceServer) SubscribeOrderStatus(
  req *pb.OrderStatusRequest,
  stream pb.OrderService_SubscribeOrderStatusServer,
) error {
  log.Printf("[Streaming] Client subscribed for order: %s", req.OrderId)

  // 模拟订单状态流转:CREATED -> PAID -> SHIPPED -> DELIVERED
  statuses := []string{"CREATED", "PAID", "SHIPPED", "DELIVERED"}
  
  for _, status := range statuses {
    // 检查客户端是否已取消
    select {
    case <-stream.Context().Done():
      log.Printf("[Streaming] Client cancelled: %v", stream.Context().Err())
      return stream.Context().Err()
    default:
    }

    // 构造状态更新消息
    update := &pb.OrderStatusResponse{
      OrderId:    req.OrderId,
      Status:     status,
      UpdateTime: time.Now().Unix(),
    }

    // 发送消息到流
    if err := stream.Send(update); err != nil {
      log.Printf("[Streaming] Send error: %v", err)
      return err
    }

    // 模拟状态变更间隔(压测时可缩短或移除)
    time.Sleep(500 * time.Millisecond)
  }

  return nil // 流正常结束
}
压测陷阱:Server Streaming 的压测需要关注服务端内存占用(未消费的消息堆积)和客户端接收速率。如果客户端消费慢于服务端生产,可能导致背压问题。压测时应监控 grpc_server_stream_messages_sent 和 grpc_server_stream_messages_dropped 指标。

3.4 服务注册与启动

完成业务逻辑实现后,需要将服务注册到 gRPC 服务器并启动监听。

// main.go
func main() {
  lis, err := net.Listen("tcp", ":50051")
  if err != nil {
    log.Fatalf("failed to listen: %v", err)
  }

  s := grpc.NewServer(
    grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
      loggingUnaryInterceptor,   // 自定义日志拦截器
      metricsUnaryInterceptor,   // 压测指标采集
    )),
    grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
      loggingStreamInterceptor,
      metricsStreamInterceptor,
    )),
  )

  // 注册服务实现
  pb.RegisterOrderServiceServer(s, &orderServiceServer{
    orders: make(map[string]*pb.OrderResponse),
  })

  log.Println("gRPC server listening on :50051")
  if err := s.Serve(lis); err != nil {
    log.Fatalf("failed to serve: %v", err)
  }
}

3.5 两种 RPC 模式对比

对比维度 Unary RPC Server Streaming RPC
消息交换次数 1 请求 + 1 响应 1 请求 + N 响应
适用场景 CRUD 操作、查询、认证 日志推送、状态监控、实时通知
服务端资源 短连接,请求结束后释放 长连接,需维护流状态
压测核心指标 QPS、P99 延迟 吞吐量(msg/s)、背压表现
错误处理 返回 error 即可 需处理流中断、客户端取消

3.6 服务端架构总览

下图展示了本章实现的 gRPC 服务端完整架构,包含两种 RPC 模式的处理路径:

gRPC 服务端架构:Unary + Server Streaming gRPC 客户端 Unary 调用 CreateOrder Streaming 订阅 SubscribeOrderStatus gRPC Server :50051 拦截器链 日志 | 指标 | 认证 服务注册 RegisterOrderServiceServer OrderService 实现 CreateOrder() 参数校验 → 业务逻辑 → 存储 → 返回响应 SubscribeOrderStatus() 流初始化 → 循环 Send() → 监听 Done() → 结束 内存存储 (orders map) 压测可替换为 Redis/MySQL 压测关注点 • Unary: QPS, P99 延迟 • Streaming: msg/s, 背压

3.7 实现检查清单

在进入压测环节前,请确认服务端实现满足以下条件:

  1. 接口完整性:所有 proto 定义的 RPC 方法均已实现,未遗漏。
  2. 错误语义:使用 status.Errorf 返回标准 gRPC 错误码(如 InvalidArgument、NotFound)。
  3. 流安全:Server Streaming 方法中正确处理 Context.Done(),避免 goroutine 泄漏。
  4. 并发安全:共享状态(如 orders map)使用 sync.Mutex 或 sync.RWMutex 保护。
  5. 可观测性:关键路径添加日志输出,便于压测时定位问题。
下一步准备:服务端实现完成后,即可进入第 4 章——使用 ghz 或自定义 Go 客户端对本章实现的 Unary 和 Streaming 接口进行基准压测,验证服务端性能基线。