gRPC微服务通信实战:Protocol Buffers定义与拦截器开发

gRPC是Google开源的高性能RPC框架,基于HTTP/2和Protocol Buffers实现,支持双向流式通信。在微服务架构中,gRPC相比REST API具有更低的序列化开销和更强的类型约束。以下是gRPC在Go语言微服务中的实战配置,覆盖Proto定义、服务实现、拦截器开发和负载均衡的完整流程。

Protocol Buffers消息定义

使用proto3语法定义服务接口和消息结构。在proto/目录下创建user.proto:

syntax = "proto3";

package user.v1;
option go_package = "github.com/example/proto/user/v1;userv1";

// 用户服务定义
service UserService {
  // 创建用户
  rpc CreateUser(CreateUserRequest) returns (User);
  // 获取用户
  rpc GetUser(GetUserRequest) returns (User);
  // 更新用户
  rpc UpdateUser(UpdateUserRequest) returns (User);
  // 删除用户
  rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
  // 批量查询(服务端流式)
  rpc ListUsers(ListUsersRequest) returns (stream User);
  // 批量导入(客户端流式)
  rpc ImportUsers(stream CreateUserRequest) returns (ImportResponse);
  // 实时聊天(双向流式)
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
  UserRole role = 5;
  int64 created_at = 6;
  int64 updated_at = 7;
}

enum UserRole {
  ROLE_UNSPECIFIED = 0;
  ROLE_USER = 1;
  ROLE_ADMIN = 2;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
  int32 age = 3;
  UserRole role = 4;
}

message GetUserRequest {
  string id = 1;
}

message UpdateUserRequest {
  string id = 1;
  optional string name = 2;
  optional string email = 3;
  optional int32 age = 4;
  optional UserRole role = 5;
}

message DeleteUserRequest {
  string id = 1;
}

message DeleteUserResponse {
  bool success = 1;
}

message ListUsersRequest {
  int32 page = 1;
  int32 page_size = 2;
  optional UserRole role_filter = 3;
}

message ImportResponse {
  int32 success_count = 1;
  int32 fail_count = 2;
  repeated string errors = 3;
}

message ChatMessage {
  string user_id = 1;
  string content = 2;
  int64 timestamp = 3;
}

生成Go代码:

protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    proto/user.proto

服务端实现

package server

import (
    "context"
    "crypto/uuid"
    "time"
    
    userv1 "github.com/example/proto/user/v1"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

type UserServer struct {
    userv1.UnimplementedUserServiceServer
    repo UserRepo
}

func NewUserServer(repo UserRepo) *UserServer {
    return &UserServer{repo: repo}
}

func (s *UserServer) CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.User, error) {
    if req.GetName() == "" || req.GetEmail() == "" {
        return nil, status.Error(codes.InvalidArgument, "name and email are required")
    }
    
    // 检查邮箱重复
    existing, _ := s.repo.FindByEmail(ctx, req.GetEmail())
    if existing != nil {
        return nil, status.Error(codes.AlreadyExists, "email already exists")
    }
    
    user := &userv1.User{
        Id:        uuid.New().String(),
        Name:      req.GetName(),
        Email:     req.GetEmail(),
        Age:       req.GetAge(),
        Role:      req.GetRole(),
        CreatedAt: time.Now().Unix(),
        UpdatedAt: time.Now().Unix(),
    }
    
    if err := s.repo.Create(ctx, user); err != nil {
        return nil, status.Error(codes.Internal, "failed to create user")
    }
    
    return user, nil
}

// 服务端流式:批量查询
func (s *UserServer) ListUsers(req *userv1.ListUsersRequest, stream userv1.UserService_ListUsersServer) error {
    offset := int(req.GetPage()-1) * int(req.GetPageSize())
    users, err := s.repo.List(stream.Context(), offset, int(req.GetPageSize()), req.GetRoleFilter())
    if err != nil {
        return status.Error(codes.Internal, "query failed")
    }
    
    for _, user := range users {
        if err := stream.Send(user); err != nil {
            return err
        }
    }
    return nil
}

// 双向流式:实时聊天
func (s *UserServer) Chat(stream userv1.UserService_ChatServer) error {
    for {
        msg, err := stream.Recv()
        if err != nil {
            break
        }
        
        msg.Timestamp = time.Now().Unix()
        
        // 广播给所有连接的客户端
        if err := s.broadcast(msg); err != nil {
            return err
        }
    }
    return nil
}

拦截器开发:日志与认证

gRPC拦截器类似Web框架中的中间件,在RPC调用前后注入逻辑。分为一元拦截器(Unary Interceptor)和流拦截器(Stream Interceptor)。

日志拦截器:

package interceptor

import (
    "context"
    "log/slog"
    "time"
    
    "github.com/google/uuid"
    "google.golang.org/grpc"
)

type contextKey string

const RequestIDKey contextKey = "request_id"

func UnaryLoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    requestID := uuid.New().String()
    ctx = context.WithValue(ctx, RequestIDKey, requestID)
    
    start := time.Now()
    
    resp, err := handler(ctx, req)
    
    duration := time.Since(start)
    
    slog.Info("gRPC request",
        "method", info.FullMethod,
        "request_id", requestID,
        "duration", duration.String(),
        "error", err,
    )
    
    return resp, err
}

func StreamLoggingInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
    requestID := uuid.New().String()
    
    start := time.Now()
    err := handler(srv, ss)
    
    slog.Info("gRPC stream",
        "method", info.FullMethod,
        "request_id", requestID,
        "duration", time.Since(start).String(),
        "error", err,
    )
    
    return err
}

认证拦截器:

func UnaryAuthInterceptor(validTokens map[string]string) grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
        // 跳过认证的方法
        if info.FullMethod == "/user.v1.UserService/CreateUser" {
            return handler(ctx, req)
        }
        
        md, ok := metadata.FromIncomingContext(ctx)
        if !ok {
            return nil, status.Error(codes.Unauthenticated, "metadata not found")
        }
        
        tokens := md.Get("authorization")
        if len(tokens) == 0 {
            return nil, status.Error(codes.Unauthenticated, "token not found")
        }
        
        token := strings.TrimPrefix(tokens[0], "Bearer ")
        userID, ok := validTokens[token]
        if !ok {
            return nil, status.Error(codes.Unauthenticated, "invalid token")
        }
        
        ctx = context.WithValue(ctx, UserIDKey, userID)
        
        return handler(ctx, req)
    }
}

链式拦截器组合:

// 使用方式
server := grpc.NewServer(
    grpc.ChainUnaryInterceptor(
        UnaryLoggingInterceptor,
        UnaryAuthInterceptor(authTokens),
        UnaryRecoveryInterceptor,
    ),
    grpc.ChainStreamInterceptor(
        StreamLoggingInterceptor,
        StreamAuthInterceptor(authTokens),
    ),
)

客户端开发与负载均衡

package client

import (
    "context"
    "time"
    
    userv1 "github.com/example/proto/user/v1"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
)

func NewUserClient(target string) (userv1.UserServiceClient, *grpc.ClientConn, error) {
    resolver.SetDefaultScheme("dns")
    
    conn, err := grpc.Dial(
        target,
        grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
        grpc.WithTransportCredentials(insecure.NewCredentials()),
        grpc.WithUnaryInterceptor(clientLogInterceptor),
    )
    if err != nil {
        return nil, nil, err
    }
    
    return userv1.NewUserServiceClient(conn), conn, nil
}

func clientLogInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    start := time.Now()
    err := invoker(ctx, method, req, reply, cc, opts...)
    slog.Info("gRPC client call",
        "method", method,
        "duration", time.Since(start).String(),
        "error", err,
    )
    return err
}

// 使用示例
func main() {
    client, conn, _ := NewUserClient("dns:///user-service:50051")
    defer conn.Close()
    
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    user, err := client.CreateUser(ctx, &userv1.CreateUserRequest{
        Name:  "张三",
        Email: "zhangsan@example.com",
        Age:   28,
        Role:  userv1.UserRole_ROLE_USER,
    })
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("Created user: %s\n", user.Id)
}

gRPC网关与HTTP转码

gRPC服务可通过grpc-gateway同时暴露REST API,方便前端和不支持gRPC的客户端调用:

// 在proto中添加HTTP注解
import "google/api/annotations.proto";

service UserService {
  rpc GetUser(GetUserRequest) returns (User) {
    option (google.api.http) = {
      get: "/api/v1/users/{id}"
    };
  }
  
  rpc CreateUser(CreateUserRequest) returns (User) {
    option (google.api.http) = {
      post: "/api/v1/users"
      body: "*"
    };
  }
}

// 网关服务启动
func main() {
    ctx := context.Background()
    mux := runtime.NewServeMux()
    opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
    
    err := userv1.RegisterUserServiceHandlerFromEndpoint(ctx, mux, "localhost:50051", opts)
    if err != nil {
        log.Fatal(err)
    }
    
    log.Println("Gateway listening on :8080")
    log.Fatal(http.ListenAndServe(":8080", mux))
}

健康检查与优雅关闭

import (
    healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

// 注册健康检查服务
healthSvc := health.NewServer()
healthSvc.SetServingStatus("user.v1.UserService", healthpb.HealthCheckResponse_SERVING)
healthpb.RegisterHealthServer(server, healthSvc)

// 优雅关闭
func gracefulShutdown(server *grpc.Server) {
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
    
    <-sigCh
    slog.Info("shutting down gRPC server...")
    
    // 设置非SERVING状态,负载均衡器摘除流量
    healthSvc.SetServingStatus("user.v1.UserService", healthpb.HealthCheckResponse_NOT_SERVING)
    
    // 停止接收新请求,等待已有请求完成
    server.GracefulStop()
    
    slog.Info("server stopped gracefully")
}

gRPC的健康检查协议被Kubernetes原生支持,可直接配置livenessProbe和readinessProbe使用grpc健康检查,无需额外暴露HTTP健康检查端口。优雅关闭中先将健康状态设为NOT_SERVING,使负载均衡器将流量摘除后再停止服务,避免请求打到正在关闭的实例上。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/grpc-wei-fu-wu-tong-xin-shi-zhan-protocolbuffers-ding-yi-yu/

赞 (0)
小编小编
上一篇 2026年9月14日
下一篇 2026年9月14日

相关推荐

gRPC微服务通信实战:Protocol Buffers定义与Go服务端实现

gRPC基于HTTP/2和Protocol Buffers,提供高性能、强类型的微服务间通信方案。相比REST API的JSON序列化,gRPC使用二进制编码,传输体积更小,延迟更低,内置流式传输和双向通信支持,适合高并发微服务架构。

Protocol Buffers消息定义与服务接口

定义.proto文件描述服务接口和消息结构:

syntax = "proto3";

package order.v1;
option go_package = "github.com/example/order-service/proto;orderv1";

// 订单服务定义
service OrderService {
  // 创建订单
  rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
  // 查询订单详情
  rpc GetOrder(GetOrderRequest) returns (Order);
  // 流式查询订单列表(服务端流)
  rpc ListOrders(ListOrdersRequest) returns (stream Order);
  // 双向流式订单状态追踪
  rpc TrackOrders(stream TrackRequest) returns (stream OrderStatus);
}

message CreateOrderRequest {
  int64 user_id = 1;
  repeated OrderItem items = 2;
  string address = 3;
  PaymentMethod payment_method = 4;
}

message OrderItem {
  int64 product_id = 1;
  int32 quantity = 2;
  double price = 3;
}

message CreateOrderResponse {
  int64 order_id = 1;
  string order_no = 2;
  double total_amount = 3;
  OrderStatus status = 4;
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_PAID = 2;
  ORDER_STATUS_SHIPPED = 3;
  ORDER_STATUS_COMPLETED = 4;
  ORDER_STATUS_CANCELLED = 5;
}

enum PaymentMethod {
  PAYMENT_METHOD_UNSPECIFIED = 0;
  PAYMENT_METHOD_ALIPAY = 1;
  PAYMENT_METHOD_WECHAT = 2;
  PAYMENT_METHOD_BANK_CARD = 3;
}

proto3与proto2的关键区别:字段默认值不再支持自定义(string默认空字符串,int默认0),required关键字移除,所有字段默认optional。枚举第一个值必须是_UNSPECIFIED = 0,用于表示未设置状态。

使用protoc生成Go代码:

protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    proto/order.proto

Go服务端实现与gRPC中间件

实现OrderService接口:

package service

import (
    "context"
    "time"
    pb "github.com/example/order-service/proto"
)

type OrderServer struct {
    pb.UnimplementedOrderServiceServer
    repo OrderRepository
}

func (s *OrderServer) CreateOrder(ctx context.Context, req *pb.CreateOrderRequest) (*pb.CreateOrderResponse, error) {
    // 参数校验
    if req.UserId <= 0 {
        return nil, status.Error(codes.InvalidArgument, "invalid user_id")
    }
    if len(req.Items) == 0 {
        return nil, status.Error(codes.InvalidArgument, "items cannot be empty")
    }

    // 计算总金额
    var totalAmount float64
    for _, item := range req.Items {
        totalAmount += item.Price * float64(item.Quantity)
    }

    // 创建订单
    order := &Order{
        UserID:      req.UserId,
        Items:       req.Items,
        Address:     req.Address,
        TotalAmount: totalAmount,
        Status:      pb.OrderStatus_ORDER_STATUS_PENDING,
        CreatedAt:   time.Now(),
    }
    if err := s.repo.Create(ctx, order); err != nil {
        return nil, status.Errorf(codes.Internal, "create order failed: %v", err)
    }

    return &pb.CreateOrderResponse{
        OrderId:     order.ID,
        OrderNo:      order.OrderNo,
        TotalAmount: totalAmount,
        Status:      pb.OrderStatus_ORDER_STATUS_PENDING,
    }, nil
}

// 服务端流式响应
func (s *OrderServer) ListOrders(req *pb.ListOrdersRequest, stream pb.OrderService_ListOrdersServer) error {
    orders, err := s.repo.ListByUserID(stream.Context(), req.UserId, req.PageSize)
    if err != nil {
        return status.Errorf(codes.Internal, "query failed: %v", err)
    }
    for _, order := range orders {
        if err := stream.Send(order.ToProto()); err != nil {
            return err
        }
    }
    return nil
}

启动gRPC服务:

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

    // 带中间件的gRPC Server
    s := grpc.NewServer(
        grpc.MaxRecvMsgSize(10*1024*1024),
        grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
            LoggingInterceptor,
            RecoveryInterceptor,
            AuthInterceptor,
        )),
    )
    pb.RegisterOrderServiceServer(s, &OrderServer{repo: repo})

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

拦截器与超时重试配置

日志拦截器记录请求耗时和错误:

func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    start := time.Now()
    resp, err := handler(ctx, req)
    duration := time.Since(start)

    log.Printf("method=%s duration=%v error=%v", info.FullMethod, duration, err)
    return resp, err
}

// Recovery拦截器捕获panic
func RecoveryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = status.Errorf(codes.Internal, "panic: %v", r)
            log.Printf("panic recovered: %v, stack: %s", r, debug.Stack())
        }
    }()
    return handler(ctx, req)
}

// 认证拦截器
func AuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, status.Error(codes.Unauthenticated, "no metadata")
    }
    token := md.Get("authorization")
    if len(token) == 0 || !validateToken(token[0]) {
        return nil, status.Error(codes.Unauthenticated, "invalid token")
    }
    return handler(ctx, req)
}

客户端配置超时与重试:

conn, err := grpc.Dial("localhost:50051",
    grpc.WithTransportCredentials(insecure.NewCredentials()),
    grpc.WithDefaultServiceConfig(`{
        "loadBalancingPolicy": "round_robin",
        "methodConfig": [{
            "name": [{"service": "order.v1.OrderService"}],
            "retryPolicy": {
                "maxAttempts": 3,
                "initialBackoff": "0.1s",
                "maxBackoff": "1s",
                "backoffMultiplier": 2,
                "retryableStatusCodes": ["UNAVAILABLE", "DEADLINE_EXCEEDED"]
            },
            "timeout": "5s"
        }]
    }`),
)

gRPC内置重试机制基于HTTP/2的GOAWAY帧和Retry-After头,客户端在服务端返回可重试状态码时自动重试。重试策略通过service config配置,不修改代码即可调整。生产环境建议设置maxAttempts为3,initialBackoff为100ms,避免重试风暴。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/grpc-wei-fu-wu-tong-xin-shi-zhan-protocolbuffers-ding-yi-yu/

赞 (0)
小编小编
上一篇 2026年9月7日
下一篇 2026年9月7日

相关推荐

gRPC微服务通信实战:Protocol Buffers定义与拦截器中间件设计

gRPC基于HTTP/2和Protocol Buffers构建,提供高性能、强类型的微服务通信方案。相比REST的JSON文本序列化,gRPC使用二进制Protobuf编码,消息体积更小、解析速度更快。HTTP/2的多路复用和头部压缩机制使gRPC在内部服务间通信场景下性能优势明显,适合高吞吐、低延迟的微服务架构。

Protocol Buffers服务定义与代码生成

使用.proto文件定义服务接口和消息格式。一个典型的用户服务定义:

syntax = "proto3";

package user.v1;

option go_package = "github.com/example/proto/user/v1;userv1";

service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc ListUsers(ListUsersRequest) returns (stream User);
  rpc CreateUserBatch(stream CreateUserRequest) returns (CreateUserBatchResponse);
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}

message GetUserRequest {
  int64 id = 1;
}

message GetUserResponse {
  User user = 1;
}

message User {
  int64 id = 1;
  string name = 2;
  string email = 3;
  UserStatus status = 4;
  int64 created_at = 5;
}

enum UserStatus {
  USER_STATUS_UNSPECIFIED = 0;
  USER_STATUS_ACTIVE = 1;
  USER_STATUS_INACTIVE = 2;
  USER_STATUS_BANNED = 3;
}

message ListUsersRequest {
  int32 page_size = 1;
  string page_token = 2;
  UserStatus status_filter = 3;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
}

message CreateUserBatchResponse {
  repeated int64 ids = 1;
}

message ChatMessage {
  string content = 1;
  int64 timestamp = 2;
}

gRPC支持四种调用模式:一元调用(Unary)、服务端流(Server Streaming)、客户端流(Client Streaming)和双向流(Bidirectional Streaming)。使用protoc工具生成各语言客户端和服务端代码:

protoc --go_out=. --go-grpc_out=.   --proto_path=proto   proto/user/v1/user.proto

Go服务端实现与注册

生成代码后实现服务接口,以Go为例:

package server

import (
    "context"
    "database/sql"
    userv1 "github.com/example/proto/user/v1"
)

type UserServer struct {
    userv1.UnimplementedUserServiceServer
    db *sql.DB
}

func (s *UserServer) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
    var user userv1.User
    err := s.db.QueryRowContext(ctx,
        "SELECT id, name, email, status, created_at FROM users WHERE id = ?",
        req.Id,
    ).Scan(&user.Id, &user.Name, &user.Email, &user.Status, &user.CreatedAt)
    if err != nil {
        return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id)
    }
    return &userv1.GetUserResponse{User: &user}, nil
}

func (s *UserServer) ListUsers(req *userv1.ListUsersRequest, stream userv1.UserService_ListUsersServer) error {
    rows, err := s.db.Query(
        "SELECT id, name, email, status FROM users WHERE status = ? LIMIT ?",
        req.StatusFilter, req.PageSize,
    )
    if err != nil {
        return status.Errorf(codes.Internal, "query failed: %v", err)
    }
    defer rows.Close()

    for rows.Next() {
        var user userv1.User
        if err := rows.Scan(&user.Id, &user.Name, &user.Email, &user.Status); err != nil {
            return status.Errorf(codes.Internal, "scan failed: %v", err)
        }
        if err := stream.Send(&user); err != nil {
            return err
        }
    }
    return nil
}

启动gRPC服务器并注册服务:

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

    grpcServer := grpc.NewServer(
        grpc.UnaryInterceptor(unaryInterceptor),
        grpc.StreamInterceptor(streamInterceptor),
    )

    userv1.RegisterUserServiceServer(grpcServer, &UserServer{db: db})

    log.Println("gRPC server listening on :50051")
    grpcServer.Serve(lis)
}

拦截器中间件与请求链设计

gRPC拦截器类似HTTP中间件,在请求处理前后插入横切逻辑。一元拦截器用于日志、认证、限流等:

func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    start := time.Now()

    // 认证检查
    if err := checkAuth(ctx); err != nil {
        return nil, err
    }

    // 请求日志
    log.Printf("gRPC call: %s, request: %+v", info.FullMethod, req)

    // 执行handler
    resp, err := handler(ctx, req)

    // 响应日志与耗时
    duration := time.Since(start)
    if err != nil {
        log.Printf("gRPC error: %s, duration: %v, error: %v", info.FullMethod, duration, err)
        metrics.GRPCErrors.WithLabelValues(info.FullMethod, status.Code(err).String()).Inc()
    } else {
        log.Printf("gRPC done: %s, duration: %v", info.FullMethod, duration)
        metrics.GRPCLatency.WithLabelValues(info.FullMethod).Observe(duration.Seconds())
    }

    return resp, err
}

流式拦截器处理流请求:

func streamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
    ctx := ss.Context()

    if err := checkAuth(ctx); err != nil {
        return err
    }

    wrappedStream := &serverStreamWrapper{
        ServerStream: ss,
        method:       info.FullMethod,
    }

    return handler(srv, wrappedStream)
}

type serverStreamWrapper struct {
    grpc.ServerStream
    method string
}

func (w *serverStreamWrapper) SendMsg(m interface{}) error {
    log.Printf("stream send: %s, msg: %+v", w.method, m)
    return w.ServerStream.SendMsg(m)
}

func (w *serverStreamWrapper) RecvMsg(m interface{}) error {
    err := w.ServerStream.RecvMsg(m)
    if err == nil {
        log.Printf("stream recv: %s, msg: %+v", w.method, m)
    }
    return err
}

多拦截器链式组合

实际项目需要多个拦截器协同工作,如认证-日志-限流-恢复。Go gRPC支持传入多个拦截器但需要手动链式包装,或使用go-grpc-middleware库:

import grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware"

grpcServer := grpc.NewServer(
    grpcMiddleware.WithUnaryServerChain(
        recovery.UnaryServerInterceptor(),   // panic恢复
        logging.UnaryServerInterceptor(),     // 请求日志
        auth.UnaryServerInterceptor(),        // JWT认证
        ratelimit.UnaryServerInterceptor(),   // 限流
    ),
    grpcMiddleware.WithStreamServerChain(
        recovery.StreamServerInterceptor(),
        logging.StreamServerInterceptor(),
        auth.StreamServerInterceptor(),
    ),
)

拦截器按声明顺序执行,每个拦截器调用handler前是请求预处理阶段,调用后是响应后处理阶段。执行顺序类似洋葱模型:外层拦截器先执行预处理,内层先执行后处理。

客户端调用与连接池管理

gRPC客户端使用grpc.Dial建立连接,底层复用HTTP/2连接,支持多路复用:

conn, err := grpc.Dial(
    "localhost:50051",
    grpc.WithTransportCredentials(insecure.NewCredentials()),
    grpc.WithDefaultServiceConfig(`{
        "loadBalancingPolicy": "round_robin",
        "methodConfig": [{
            "name": [{"service": "user.v1.UserService"}],
            "retryPolicy": {
                "maxAttempts": 3,
                "initialBackoff": "0.1s",
                "maxBackoff": "1s",
                "retryableStatusCodes": ["UNAVAILABLE", "DEADLINE_EXCEEDED"]
            }
        }]
    }`),
    grpc.WithUnaryInterceptor(clientLogInterceptor),
)
if err != nil {
    log.Fatalf("dial failed: %v", err)
}
defer conn.Close()

client := userv1.NewUserServiceClient(conn)
resp, err := client.GetUser(ctx, &userv1.GetUserRequest{Id: 1})

Service Config配置了负载均衡策略和重试策略。DNS解析多个地址时round_robin策略自动轮询。重试策略仅对幂等操作安全,需谨慎配置retryableStatusCodes。

连接池在gRPC中由底层HTTP/2连接自动管理。单个TCP连接支持100个并发流(HTTP/2默认),可通过grpc.WithMaxConcurrentStreams调整。高并发场景下建议保持连接复用,避免频繁建连。

错误处理与状态码规范

gRPC定义了标准状态码,服务端通过status.Errorf返回带状态码的错误:

switch {
case err == sql.ErrNoRows:
    return nil, status.Error(codes.NotFound, "user not found")
case strings.Contains(err.Error(), "duplicate"):
    return nil, status.Error(codes.AlreadyExists, "email already registered")
case strings.Contains(err.Error(), "connection refused"):
    return nil, status.Error(codes.Unavailable, "database unavailable")
default:
    return nil, status.Error(codes.Internal, err.Error())
}

客户端根据状态码执行重试或向上传递错误:status.Code(err)获取状态码,status.Convert(err).Message()获取错误消息。建议在API网关层将gRPC状态码映射为HTTP状态码,如NotFound对应404、AlreadyExists对应409、PermissionDenied对应403。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/grpc-wei-fu-wu-tong-xin-shi-zhan-protocolbuffers-ding-yi-yu/

赞 (0)
小编小编
上一篇 2026年8月24日
下一篇 2026年8月24日

相关推荐