1. 什麼是 gRPC?

gRPC 是一個高性能、開源的通用 RPC 框架,由 Google 開發。它基於 HTTP/2 協議,使用 Protocol Buffers 作為接口定義語言和序列化工具。

Go語言proto使用入門_Server

為什麼選擇 gRPC?

  • 高性能:基於 HTTP/2,支持多路複用和流式傳輸
  • 跨語言:支持多種編程語言
  • 強類型:通過 proto 文件明確定義接口
  • 代碼生成:自動生成客户端和服務端代碼
  • 雙向流:支持客户端流、服務端流和雙向流

2. gRPC 核心組件

2.1 Protocol Buffers (.proto 文件)

Protocol Buffers(簡稱 Protobuf)是 Google 開發的一種高效、跨語言、跨平台的數據序列化機制.proto 文件就是 Protobuf 的接口定義文件,用於描述數據結構(message)和服務接口(service)。通過編譯 .proto 文件,可以自動生成多種語言的代碼(Go、Java、Python、C++ 等),從而在不同系統之間高效傳輸結構化數據

syntax = "proto3";

package user;

option go_package = ".;user";

//📝 Powered by Moshow 鄭鍇 | 更多技術乾貨:
// 定義服務
service UserService {
  rpc GetUser (UserRequest) returns (UserResponse);
  rpc CreateUser (CreateUserRequest) returns (UserResponse);
  rpc StreamUsers (StreamUsersRequest) returns (stream UserResponse);
}

// 定義消息類型
message UserRequest {
  string user_id = 1;
}

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

message UserResponse {
  string user_id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
  string created_at = 5;
}

message StreamUsersRequest {
  int32 batch_size = 1;
}

2.2 gRPC Server

gRPC Server 就是運行在服務端的 遠程過程調用服務實現。它負責監聽網絡端口,接收客户端通過 gRPC 協議發來的請求,調用本地實現的業務邏輯(Handler),並將結果序列化後返回給客户端。 gRPC Server = 服務端容器 + 接口實現。它負責 接收請求 → 調用業務邏輯 → 返回響應

  • 服務端實現(Handler): 在 .proto 文件中定義了服務接口和方法,服務端需要在代碼中實現這些方法。例如 SayHello 方法在 Go 中會對應一個函數實現。
  • 監聽與調度: gRPC Server 會在指定端口(如 :50051)監聽請求,接收到請求後根據方法名路由到對應的 Handler。
  • 序列化與反序列化: 請求和響應數據會通過 Protocol Buffers(Protobuf)進行高效的二進制序列化和反序列化。
  • 通信協議: gRPC Server 基於 HTTP/2,支持多路複用、頭部壓縮和流式通信(客户端流、服務端流、雙向流)。
package main

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

    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
    user "your-module-path/user" // 根據實際路徑修改
)

//📝 Powered by Moshow 鄭鍇 | 更多技術乾貨:
// 實現服務接口
type userServer struct {
    user.UnimplementedUserServiceServer
    users map[string]*user.UserResponse
}

func (s *userServer) GetUser(ctx context.Context, req *user.UserRequest) (*user.UserResponse, error) {
    log.Printf("GetUser called with ID: %s", req.UserId)
    
    if user, exists := s.users[req.UserId]; exists {
        return user, nil
    }
    return nil, grpc.Errorf(grpc.Code(nil), "user not found")
}

func (s *userServer) CreateUser(ctx context.Context, req *user.CreateUserRequest) (*user.UserResponse, error) {
    log.Printf("CreateUser called: %s, %s", req.Name, req.Email)
    
    newUser := &user.UserResponse{
        UserId:    generateID(),
        Name:      req.Name,
        Email:     req.Email,
        Age:       req.Age,
        CreatedAt: time.Now().Format(time.RFC3339),
    }
    
    s.users[newUser.UserId] = newUser
    return newUser, nil
}

func (s *userServer) StreamUsers(req *user.StreamUsersRequest, stream user.UserService_StreamUsersServer) error {
    log.Printf("StreamUsers called with batch size: %d", req.BatchSize)
    
    for _, user := range s.users {
        if err := stream.Send(user); err != nil nil {
            return err
        }
        time.Sleep(100 * time.Millisecond) // 模擬處理延遲
    }
    return nil
}

func generateID() string {
    return fmt.Sprintf("user-%d", time.Now().UnixNano())
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    
    s := grpc.NewServer()
    user.RegisterUserServiceServer(s, &userServer{
        users: make(map[string]*user.UserResponse),
    })
    
    // 啓用反射,便於調試和工具使用
    reflection.Register(s)
    
    log.Printf("gRPC server listening at %v", lis.Addr())
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

2.3 gRPC Client

  • gRPC Client = Stub + 網絡通信層,它把本地調用轉化為遠程請求。
  • 藉助 Protobuf 和 HTTP/2,gRPC Client 能實現 高性能、跨語言、強類型 的遠程調用。
  • 在 Go、Java、Node.js、Postman 等環境中,客户端都能通過相同的 .proto 文件與服務端交互
package main

import (
    "context"
    "log"
    "time"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    user "your-module-path/user" // 根據實際路徑修改
)

func main() {
    //📝 Powered by Moshow 鄭鍇 | 更多技術乾貨:
    conn, err := grpc.Dial("localhost:50051", 
        grpc.WithTransportCredentials(insecure.NewCredentials()),
        grpc.WithBlock())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    
    client := user.NewUserServiceClient(conn)
    
    // 創建用户
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    
    createResp, err := client.CreateUser(ctx, &user.CreateUserRequest{
        Name:  "John Doe",
        Email: "john@example.com",
        Age:   30,
    })
    if err != nil {
        log.Fatalf("could not create user: %v", err)
    }
    log.Printf("Created user: %v", createResp)
    
    // 獲取用户
    getUserResp, err := client.GetUser(ctx, &user.UserRequest{
        UserId: createResp.UserId,
    })
    if err != nil {
        log.Fatalf("could not get user: %v", err)
    }
    log.Printf("Got user: %v", getUserResp)
    
    // 流式用户
    streamCtx, streamCancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer streamCancel()
    
    stream, err := client.StreamUsers(streamCtx, &user.StreamUsersRequest{
        BatchSize: 5,
    })
    if err != nil {
        log.Fatalf("could not stream users: %v", err)
    }
    
    for {
        userResp, err := stream.Recv()
        if err != nil {
            break
        }
        log.Printf("Streamed user: %v", userResp)
    }
}

3. 項目結構和依賴

3.1 項目結構

my-grpc-project/
├── proto/
│   └── user.proto
├── user/
│   ├── user.pb.go
│   └── user_grpc.pb.go
├── server/
│   └── main.go
├── client/
│   └── main.go
└── go.mod

3.2 依賴安裝

# 安裝 Protocol Buffers 編譯器
# macOS
brew install protobuf

# Ubuntu
sudo apt-get 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-grpc_out=. proto/user.proto

3.3 go.mod 依賴

module my-grpc-project

go 1.19

require (
    google.golang.org/grpc v1.50.0
    google.golang.org/protobuf v1.28.1
)

4. 其他語言調用 gRPC

4.1 Java 調用

Maven 依賴
<dependencies>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-netty-shaded</artifactId>
        <version>1.50.0</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>1.50.0</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>1.50.0</version>
    </dependency>
</dependencies>
Java 客户端代碼
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import user.UserServiceGrpc;
import user.User;

public class JavaClient {
    public static void main(String[] args) {
        ManagedChannel channel = ManagedChannelBuilder
            .forAddress("localhost", 50051)
            .usePlaintext()
            .build();
        
        UserServiceGrpc.UserServiceBlockingStub stub = 
            UserServiceGrpc.newBlockingStub(channel);
        
        // 創建用户
        User.CreateUserRequest createRequest = User.CreateUserRequest.newBuilder()
            .setName("Jane Smith")
            .setEmail("jane@example.com")
            .setAge(25)
            .build();
        
        User.UserResponse createResponse = stub.createUser(createRequest);
        System.out.println("Created user: " + createResponse);
        
        // 獲取用户
        User.UserRequest getRequest = User.UserRequest.newBuilder()
            .setUserId(createResponse.getUserId())
            .build();
        
        User.UserResponse getResponse = stub.getUser(getRequest);
        System.out.println("Got user: " + getResponse);
        
        channel.shutdown();
    }
}

4.2 Node.js 調用

安裝依賴
npm install @grpc/grpc-js @grpc/proto-loader
Node.js 客户端代碼
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

const PROTO_PATH = __dirname + '/user.proto';

const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
    keepCase: true,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true
});

const userProto = grpc.loadPackageDefinition(packageDefinition).user;

const client = new userProto.UserService(
    'localhost:50051',
    grpc.credentials.createInsecure()
);

// 創建用户
const createRequest = {
    name: 'Bob Johnson',
    email: 'bob@example.com',
    age: 35
};

client.createUser(createRequest, (err, response) => {
    if (err) {
        console.error('Error:', err);
        return;
    }
    console.log('Created user:', response);
    
    // 獲取用户
    const getRequest = { user_id: response.user_id };
    client.getUser(getRequest, (err, userResponse) => {
        if (err) {
            console.error('Error:', err);
            return;
        }
        console.log('Got user:', userResponse);
    });
});

// 流式調用
const streamRequest = { batch_size: 3 };
const stream = client.streamUsers(streamRequest);

stream.on('data', (user) => {
    console.log('Streamed user:', user);
});

stream.on('end', () => {
    console.log('Stream ended');
});

4.3 Postman 調用 gRPC

Postman 從版本 8.0 開始支持 gRPC 請求:

步驟 1:創建 gRPC 請求
  1. 打開 Postman → New → gRPC Request
  2. 輸入服務器地址:localhost:50051
步驟 2:導入 proto 文件
  1. 點擊 "Import a .proto file"
  2. 選擇你的 user.proto 文件
  3. 選擇要調用的服務和方法
步驟 3:構造請求消息
{
 28
}
步驟 4:發送請求
  1. 點擊 "Invoke" 發送請求
  2. 查看響應結果

5. 高級特性

5.1 攔截器(中間件)

// 服務端攔截器
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: %v, Error: %v", 
        info.FullMethod, time.Since(start), err)
    return resp, err
}

// 使用攔截器
s := grpc.NewServer(
    grpc.UnaryInterceptor(loggingInterceptor),
)

5.2 錯誤處理

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

func (s *userServer) GetUser(ctx context.Context, req *user.UserRequest) (*user.UserResponse, error) {
    if req.UserId == "" {
        return nil, status.Errorf(codes.InvalidArgument, "user_id is required")
    }
    
    user, exists := s.users[req.UserId]
    if !exists {
        return nil, status.Errorf(codes.NotFound, "user not found: %s", req.UserId)
    }
    
    return user, nil
}

6. 總結

gRPC 提供了強大的跨語言 RPC 能力,主要優勢包括:

  • 性能優異:基於 HTTP/2,支持流式傳輸
  • 開發效率:通過 proto 文件自動生成代碼
  • 類型安全:強類型接口定義
  • 跨語言支持:Java、Node.js、Python、Go 等
  • 工具生態:Postman、grpcurl 等調試工具