Go微服务:gRPC入门

gRPC是Google开源的高性能RPC框架,基于HTTP/2和Protocol Buffers。在微服务架构中,gRPC在性能和类型安全上完胜REST。本文用Go实现一个完整的gRPC服务。

1. Protocol Buffers定义

先定义服务接口。创建proto/user.proto

syntax = "proto3";

package user;

option go_package = "github.com/example/grpc-demo/proto/user";

// 用户服务
service UserService {
    // 一元RPC
    rpc GetUser(GetUserRequest) returns (GetUserResponse);
    rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
    // 服务端流式RPC
    rpc ListUsers(ListUsersRequest) returns (stream UserInfo);
}

message GetUserRequest {
    int64 id = 1;
}

message GetUserResponse {
    UserInfo user = 1;
}

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

message CreateUserResponse {
    int64 id = 1;
    string message = 2;
}

message ListUsersRequest {
    int32 page_size = 1;
}

message UserInfo {
    int64 id = 1;
    string name = 2;
    string email = 3;
    int32 age = 4;
}

Proto3语法要点:

  • service定义服务接口,rpc定义方法
  • stream关键字标记流式响应
  • 字段编号(= 1)是序列化标识,一旦确定不能更改

2. protoc代码生成

安装protoc编译器和Go插件:

# 安装protoc
# macOS: brew install protobuf
# Linux: apt install 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

生成代码:

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

会生成两个文件:

  • user.pb.go:消息类型的序列化/反序列化代码
  • user_grpc.pb.go:gRPC服务端和客户端的接口代码

3. 实现gRPC服务端

package main

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

    pb "github.com/example/grpc-demo/proto/user"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

type userServer struct {
    pb.UnimplementedUserServiceServer
    mu    sync.Mutex
    users map[int64]*pb.UserInfo
    nextID int64
}

func newUserServer() *userServer {
    return &userServer{
        users:  make(map[int64]*pb.UserInfo),
        nextID: 1,
    }
}

func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
    s.mu.Lock()
    defer s.mu.Unlock()

    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) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
    s.mu.Lock()
    defer s.mu.Unlock()

    id := s.nextID
    s.nextID++

    s.users[id] = &pb.UserInfo{
        Id:    id,
        Name:  req.Name,
        Email: req.Email,
        Age:   req.Age,
    }

    return &pb.CreateUserResponse{
        Id:      id,
        Message: fmt.Sprintf("user %s created", req.Name),
    }, nil
}

func (s *userServer) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
    s.mu.Lock()
    defer s.mu.Unlock()

    for _, user := range s.users {
        if err := stream.Send(user); err != nil {
            return err
        }
        time.Sleep(100 * time.Millisecond) // 模拟延迟
    }
    return nil
}

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

    grpcServer := grpc.NewServer()
    pb.RegisterUserServiceServer(grpcServer, newUserServer())

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

关键点:

  • 嵌入UnimplementedUserServiceServer实现向前兼容
  • 使用status.Errorf返回gRPC标准错误码
  • 流式RPC通过stream.Send()逐个发送消息

4. 实现gRPC客户端

package main

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

    pb "github.com/example/grpc-demo/proto/user"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
)

func main() {
    conn, err := grpc.Dial("localhost:50051",
        grpc.WithTransportCredentials(insecure.NewCredentials()),
    )
    if err != nil {
        log.Fatalf("failed to connect: %v", err)
    }
    defer conn.Close()

    client := pb.NewUserServiceClient(conn)
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    // 创建用户
    createResp, err := client.CreateUser(ctx, &pb.CreateUserRequest{
        Name:  "Alice",
        Email: "alice@example.com",
        Age:   28,
    })
    if err != nil {
        log.Fatalf("CreateUser failed: %v", err)
    }
    fmt.Printf("Created: id=%d, msg=%s\n", createResp.Id, createResp.Message)

    // 查询用户
    getResp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: createResp.Id})
    if err != nil {
        log.Fatalf("GetUser failed: %v", err)
    }
    fmt.Printf("Got: %+v\n", getResp.User)

    // 流式列表
    stream, err := client.ListUsers(ctx, &pb.ListUsersRequest{PageSize: 10})
    if err != nil {
        log.Fatalf("ListUsers failed: %v", err)
    }
    for {
        user, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatalf("stream recv error: %v", err)
        }
        fmt.Printf("Stream: %+v\n", user)
    }
}

5. 流式RPC简介

gRPC支持四种通信模式:

模式 定义 场景
一元 rpc Method(Req) returns (Resp) 普通请求-响应
服务端流 rpc Method(Req) returns (stream Resp) 分页数据、实时推送
客户端流 rpc Method(stream Req) returns (Resp) 文件上传、批量写入
双向流 rpc Method(stream Req) returns (stream Resp) 聊天、实时协作
// 双向流示例
service ChatService {
    rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}

流式RPC基于HTTP/2的多路复用实现,一个TCP连接可以承载多个并发的RPC调用。

小结

gRPC的开发流程是:定义Proto -> 生成代码 -> 实现服务 -> 编写客户端。相比REST,gRPC的优势在于强类型约束、高效的二进制序列化和原生的流式支持。在Go微服务架构中,服务间通信用gRPC基本是标配。