gRPC服务定义与Protobuf序列化在Go微服务通信中的实践

gRPC基于HTTP/2和Protocol Buffers实现高性能RPC通信,在微服务架构中广泛用于内部服务间调用。后端开发中,gRPC通过Protobuf定义接口契约,自动生成多语言客户端和服务器代码,相比REST+JSON方案在序列化效率和类型安全方面优势明显。Go语言作为gRPC的一等公民语言,grpc-go库提供了完整的实现支持。

Protobuf接口定义与Go代码生成

使用proto3语法定义服务接口和消息类型:

// proto/user_service.proto
syntax = "proto3";

package user.v1;

option go_package = "github.com/example/user-service/api/v1;userv1";

service UserService {
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
  rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
  rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
}

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

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

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

message CreateUserResponse {
  User user = 1;
}

message GetUserRequest {
  int64 id = 1;
}

message GetUserResponse {
  User user = 1;
}

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

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

message UpdateUserRequest {
  int64 id = 1;
  optional string name = 2;
  optional string email = 3;
  optional string phone = 4;
  optional UserStatus status = 5;
}

message UpdateUserResponse {
  User user = 1;
}

message DeleteUserRequest {
  int64 id = 1;
}

message DeleteUserResponse {
  bool success = 1;
}

安装protoc编译器和Go插件,生成代码:

# 安装protoc编译器
apt install -y protobuf-compiler

# 安装Go插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# 生成Go代码
protoc   --go_out=.   --go_opt=paths=source_relative   --go-grpc_out=.   --go-grpc_opt=paths=source_relative   proto/user_service.proto

# 生成文件:
# api/v1/user_service.pb.go
# api/v1/user_service_grpc.pb.go

Go gRPC服务器实现与拦截器中间件

package main

import (
	"context"
	"fmt"
	"net"
	"log"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/grpc/reflection"

	userv1 "github.com/example/user-service/api/v1"
)

type UserServiceServer struct {
	userv1.UnimplementedUserServiceServer
}

func (s *UserServiceServer) CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.CreateUserResponse, error) {
	if req.GetName() == "" {
		return nil, status.Error(codes.InvalidArgument, "用户名不能为空")
	}
	if req.GetEmail() == "" {
		return nil, status.Error(codes.InvalidArgument, "邮箱不能为空")
	}

	user := &userv1.User{
		Id:        time.Now().Unix(),
		Name:      req.GetName(),
		Email:     req.GetEmail(),
		Phone:     req.GetPhone(),
		Status:    userv1.UserStatus_USER_STATUS_ACTIVE,
		CreatedAt: time.Now().Unix(),
		UpdatedAt: time.Now().Unix(),
	}

	return &userv1.CreateUserResponse{User: user}, nil
}

func (s *UserServiceServer) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
	if req.GetId() <= 0 {
		return nil, status.Error(codes.InvalidArgument, "无效的用户ID")
	}

	user := &userv1.User{
		Id:    req.GetId(),
		Name:  "测试用户",
		Email: "test@example.com",
		Status: userv1.UserStatus_USER_STATUS_ACTIVE,
	}

	return &userv1.GetUserResponse{User: user}, nil
}

func (s *UserServiceServer) ListUsers(ctx context.Context, req *userv1.ListUsersRequest) (*userv1.ListUsersResponse, error) {
	pageSize := int(req.GetPageSize())
	if pageSize <= 0 {
		pageSize = 10
	}
	if pageSize > 100 {
		pageSize = 100
	}

	users := make([]*userv1.User, 0, pageSize)
	for i := 0; i < pageSize; i++ {
		users = append(users, &userv1.User{
			Id:    int64(i + 1),
			Name:  fmt.Sprintf("用户%d", i+1),
			Email: fmt.Sprintf("user%d@example.com", i+1),
			Status: userv1.UserStatus_USER_STATUS_ACTIVE,
		})
	}

	return &userv1.ListUsersResponse{
		Users: users,
		Total: 1000,
	}, nil
}

// 一元拦截器:日志记录
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("gRPC %s | 耗时: %v | 错误: %v", info.FullMethod, duration, err)

	return resp, err
}

// 一元拦截器:请求超时控制
func timeoutInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	done := make(chan struct{})
	var resp interface{}
	var err error

	go func() {
		resp, err = handler(ctx, req)
		close(done)
	}()

	select {
	case <-done:
		return resp, err
	case <-ctx.Done():
		return nil, status.Error(codes.DeadlineExceeded, "请求超时")
	}
}

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

	server := grpc.NewServer(
		grpc.ChainUnaryInterceptor(
			loggingInterceptor,
			timeoutInterceptor,
		),
	)

	userv1.RegisterUserServiceServer(server, &UserServiceServer{})

	reflection.Register(server)

	log.Println("gRPC服务启动,监听 :50051")
	if err := server.Serve(lis); err != nil {
		log.Fatalf("服务启动失败: %v", err)
	}
}

gRPC客户端调用与连接池管理

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	userv1 "github.com/example/user-service/api/v1"
)

func main() {
	conn, err := grpc.Dial(
		"localhost:50051",
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithDefaultCallOptions(
			grpc.MaxCallRecvMsgSize(16 * 1024 * 1024),
		),
	)
	if err != nil {
		log.Fatalf("连接失败: %v", err)
	}
	defer conn.Close()

	client := userv1.NewUserServiceClient(conn)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	createResp, err := client.CreateUser(ctx, &userv1.CreateUserRequest{
		Name:  "张三",
		Email: "zhangsan@example.com",
		Phone: "13800138000",
	})
	if err != nil {
		log.Fatalf("创建用户失败: %v", err)
	}
	fmt.Printf("创建用户成功: ID=%d, Name=%s
", createResp.User.Id, createResp.User.Name)

	getResp, err := client.GetUser(ctx, &userv1.GetUserRequest{Id: createResp.User.Id})
	if err != nil {
		log.Fatalf("查询用户失败: %v", err)
	}
	fmt.Printf("查询用户: %+v
", getResp.User)

	listResp, err := client.ListUsers(ctx, &userv1.ListUsersRequest{
		PageSize: 5,
	})
	if err != nil {
		log.Fatalf("查询列表失败: %v", err)
	}
	for i, user := range listResp.Users {
		fmt.Printf("  [%d] ID=%d, Name=%s
", i+1, user.Id, user.Name)
	}
	fmt.Printf("总计: %d
", listResp.Total)
}

gRPC与REST对比及HTTP网关集成

实际项目中gRPC和REST通常共存:内部服务间通信用gRPC获取性能优势,对外API用REST保持兼容性。通过grpc-gateway可以自动生成REST代理:

# 在proto文件中添加google.api.http注解
import "google/api/annotations.proto";

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

# 生成网关代码
protoc   --grpc-gateway_out=.   --grpc-gateway_opt=paths=source_relative   proto/user_service.proto

# 网关服务器
func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    mux := runtime.NewServeMux()
    err := userv1.RegisterUserServiceHandlerFromEndpoint(
        ctx, mux, "localhost:50051",
        []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())},
    )
    if err != nil {
        log.Fatal(err)
    }

    log.Println("HTTP网关启动,监听 :8080")
    http.ListenAndServe(":8080", mux)
}

Protobuf序列化速度比JSON快3-10倍,二进制体积减少约30-50%。gRPC基于HTTP/2多路复用特性,在长连接场景下比REST的HTTP/1.1+连接池方案吞吐量更高。微服务内部高频调用场景,gRPC的延迟和带宽优势显著。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/grpc-fu-wu-ding-yi-yu-protobuf-xu-lie-hua-zai-go-wei-fu-wu/

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

相关推荐