gRPC服务定义与Protobuf通信实战:Go语言服务端实现与拦截器配置

Protobuf消息定义与服务接口规范

gRPC基于Protocol Buffers(Protobuf)定义服务接口和消息格式。Protobuf使用.proto文件描述数据结构,通过编译器生成多语言代码。相比REST API的JSON文本序列化,Protobuf采用二进制编码,数据体积小3-10倍,序列化速度快10-100倍。

定义用户服务的.proto文件:

syntax = "proto3";

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

// 用户服务定义
service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
  rpc StreamUsers(StreamUsersRequest) returns (stream User);
}

message User {
  int64 id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
  repeated string roles = 5;
  google.protobuf.Timestamp created_at = 6;
}

message GetUserRequest {
  int64 id = 1;
}

message GetUserResponse {
  User user = 1;
}

message ListUsersRequest {
  int32 page = 1;
  int32 page_size = 2;
  string sort = 3;
}

message ListUsersResponse {
  repeated User users = 1;
  int32 total = 2;
}

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

message CreateUserResponse {
  User user = 1;
}

message StreamUsersRequest {
  string role = 1;
}

proto3语法相比proto2简化了必选字段和默认值处理。所有字段默认可选,标量类型有零值默认。repeated字段对应数组,stream关键字定义服务端流式响应。使用protoc编译生成Go代码:

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

Go语言gRPC服务端实现

基于生成的代码实现UserService服务端逻辑:

package main

import (
    "context"
    "log"
    "net"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
    pb "github.com/example/proto/user/v1"
)

type userServer struct {
    pb.UnimplementedUserServiceServer
    users map[int64]*pb.User
}

func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
    user, ok := s.users[req.Id]
    if !ok {
        return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id)
    }
    return &pb.GetUserResponse{User: user}, nil
}

func (s *userServer) ListUsers(ctx context.Context, req *pb.ListUsersRequest) (*pb.ListUsersResponse, error) {
    var users []*pb.User
    for _, u := range s.users {
        users = append(users, u)
    }
    return &pb.ListUsersResponse{
        Users: users,
        Total: int32(len(users)),
    }, nil
}

func (s *userServer) StreamUsers(req *pb.StreamUsersRequest, stream pb.UserService_StreamUsersServer) error {
    for _, u := range s.users {
        for _, role := range u.Roles {
            if role == req.Role {
                if err := stream.Send(u); err != nil {
                    return err
                }
            }
        }
    }
    return nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    
    s := grpc.NewServer(
        grpc.UnaryInterceptor(loggingInterceptor),
        grpc.StreamInterceptor(streamLoggingInterceptor),
    )
    
    pb.RegisterUserServiceServer(s, &userServer{
        users: makeUserStore(),
    })
    reflection.Register(s)
    
    log.Println("gRPC server listening on :50051")
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

gRPC客户端调用与拦截器配置

服务端和客户端均可配置拦截器(Interceptor),实现日志记录、认证鉴权、链路追踪等横切关注点。以下实现一元拦截器:

// 服务端日志拦截器
func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    start := time.Now()
    resp, err := handler(ctx, req)
    log.Printf("method=%s duration=%s err=%v", info.FullMethod, time.Since(start), err)
    return resp, err
}

// 客户端认证拦截器
func authInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    token := getTokenFromContext(ctx)
    ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token)
    return invoker(ctx, method, req, reply, cc, opts...)
}

// 客户端调用
func callUserService() {
    conn, err := grpc.Dial("localhost:50051",
        grpc.WithTransportCredentials(insecure.NewCredentials()),
        grpc.WithUnaryInterceptor(authInterceptor),
    )
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    
    client := pb.NewUserServiceClient(conn)
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    resp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: 1})
    if err != nil {
        log.Fatalf("could not get user: %v", err)
    }
    log.Printf("User: %s, Email: %s", resp.User.Name, resp.User.Email)
}

拦截器按注册顺序链式执行,每个拦截器可决定是否调用下一个handler。生产环境中通常配置多个拦截器:recovery拦截器捕获panic、logging拦截器记录调用日志、auth拦截器验证JWT令牌、metrics拦截器上报Prometheus指标。

gRPC与REST API对比与选型

gRPC和REST是微服务间通信的两种主流方案。gRPC基于HTTP/2和Protobuf,支持双向流式传输,强类型接口约束,适合内部服务间高性能通信。REST基于HTTP/1.1和JSON,浏览器友好,生态成熟,适合对外API和前后端通信。

gRPC的优势:二进制编码效率高,HTTP/2多路复用减少连接开销,流式通信适合实时数据推送,代码生成保证接口类型安全。劣势:浏览器需gRPC-Web代理层,调试不如REST直观,Protobuf对前端不够友好。

REST的优势:人类可读的JSON格式,浏览器原生支持,工具链丰富(Swagger/Postman),学习成本低。劣势:JSON序列化慢,HTTP/1.1队头阻塞,无强类型约束,接口文档易过期。

选型建议:内部微服务通信优先gRPC,性能和类型安全优势显著。对外API和前后端通信使用REST。混合架构中可通过gRPC-Gateway自动生成REST代理,一套Protobuf定义同时服务gRPC和REST客户端。

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

(0)
小编小编
上一篇 9小时前
下一篇 9小时前

相关推荐