0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go言語で書くgRPCテクニック:中級編[Protocol Buffers最適化など]

Posted at

はじめに

gRPCは、高速・軽量なRPCフレームワークで、Go言語との相性が良く、マイクロサービス間の通信やAPIサーバーの実装に最適です。
前回の内容より、もう少し深い内容になります。

対象読者

  • gRPCの基本を理解し、さらに高度な機能を使いたい方
  • ストリーミングRPCやインターセプターを使った効率的な通信を実現したい方

目次

  1. ストリーミングRPCの実装
    • Server Streaming
    • Client Streaming
    • Bidirectional Streaming
  2. Protocol Buffersの最適化
    • 定義の工夫
    • フィールド番号の最適化
    • オプションの使用
  3. gRPCインターセプターによるミドルウェアの実装
    • ロギング
    • 認証

1. ストリーミング RPC の実装

1.1 Server Streaming
Server Streaming は、クライアントから 1 回のリクエストを受け取り、複数回のレスポンスを返す 通信方式です。

1.1.1 .proto ファイルの定義

syntax = "proto3";
package example;

service StreamService {
    rpc ListMessages (MessageRequest) returns (stream MessageResponse) {}
}

message MessageRequest {
    string user = 1;
}

message MessageResponse {
    string text = 1;
}

1.1.2 Server の実装
server.go

package main

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

    "google.golang.org/grpc"
    pb "example"
)

type server struct {
    pb.UnimplementedStreamServiceServer
}

func (s *server) ListMessages(req *pb.MessageRequest, stream pb.StreamService_ListMessagesServer) error {
    messages := []string{"Hello", "How are you?", "Goodbye"}
    for _, msg := range messages {
        res := &pb.MessageResponse{Text: msg}
        if err := stream.Send(res); err != nil {
            return err
        }
        time.Sleep(time.Second)  // 擬似的な遅延
    }
    return nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterStreamServiceServer(s, &server{})
    fmt.Println("Server is running on port 50051")
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
  • stream.Send() を使って、複数回のレスポンスをクライアントに送信
  • サーバーは1度のリクエストに対して複数のメッセージを返す

1.1.3 Client の実装
client.go

package main

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

    "google.golang.org/grpc"
    pb "example"
)

func main() {
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()

    c := pb.NewStreamServiceClient(conn)
    req := &pb.MessageRequest{User: "Gopher"}
    stream, err := c.ListMessages(context.Background(), req)
    if err != nil {
        log.Fatalf("could not list messages: %v", err)
    }

    for {
        res, err := stream.Recv()
        if err != nil {
            break
        }
        fmt.Println("Received: ", res.Text)
    }
}
  • stream.Recv() を使って、複数のレスポンスを受け取る
  • サーバーがレスポンスを送信し続ける限り、クライアントはループして受信

2. Protocol Buffers の最適化

2.1 フィールド番号の最適化

  • Protocol Buffersでは、フィールド番号が小さいほどエンコードサイズが小さくなります。
  • 頻繁に使用するフィールドには1 から順に小さい番号を割り当てる。
message OptimizedMessage {
    string content = 1;  // 頻繁に使うフィールド
    int32 id = 2;
    string optional_field = 3;  // 使用頻度が低いフィールド
}

2.2 オプションの使用

  • optional キーワードを使うことで、フィールドが存在しない場合のデータ量を削減。
message UserProfile {
    string name = 1;
    optional string nickname = 2;  // あってもなくても良い
}

3. gRPCインターセプターによるミドルウェアの実装

3.1 ロギングインターセプター

  • 全てのRPCリクエストに対してログを記録する。
func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    log.Printf("Request - Method:%s; %v", info.FullMethod, req)
    res, err := handler(ctx, req)
    if err != nil {
        log.Printf("Error - Method:%s; %v", info.FullMethod, err)
    }
    return res, err
}

func main() {
    s := grpc.NewServer(grpc.UnaryInterceptor(loggingInterceptor))
}

3.2 認証インターセプター

  • メタデータを確認して、認証を行う。
func authInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok || len(md["authorization"]) == 0 {
        return nil, status.Errorf(codes.Unauthenticated, "missing authorization token")
    }
    return handler(ctx, req)
}

まとめ

項目 説明
ストリーミング RPC 複数回のレスポンスを返す効率的な通信方式
Protocol Buffers の最適化 フィールド番号の最適化やオプションの使用でデータサイズ削減
gRPC インターセプター ロギング、認証などの共通処理をミドルウェア化

gRPCのストリーミングRPCやProtocol Buffersの最適化、インターセプターの実装など、より高度な機能を紹介しました。
次は上級編として、gRPC Gatewayロードバランシング認証と認可 についてまとめようと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?