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