Open2

grpc

engineer rebornengineer reborn

todo

protoファイルが設計図
=> コマンドで生成されるのは interfaceでclient と serverは自分で実装する必要あり

・port
50051がデフォルト
50052~からが複数サービス
443はロードバランサー越しで通信

・どんな時に使う
内部通信、パフォーマンス

・サンプル

  • 外部とのやりとり => go api
  • 内部通信
  • 作業
    • grpc とrest aoiは別々に起動
engineer rebornengineer reborn

grpc

  • マイクロサービスアーキテクチャーで利用されるケースが多い
  • 双方向通信
  • 処理が早いなど

概念

  • サービス
    • Procedure(関数)をメソッド、そしてそのメソッドをいくつかまとめて一括りにしたものをサービス
  • メソッド

コンポーネント

.protoファイル

  • 設計図

xx.pb.go

  • リクエストとレスポンスを定義したコード
// 生成されたGoの構造体
type HelloRequest struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}

type HelloResponse struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}

xx_grpc.pb.go

  • サービス部分のコード
    • 複数のメソッド(プロシージャー: hello関数のような)をまとめたもの
    • RegisterGreetingServiceServer などが作成
// 生成されたGoのコード
type GreetingServiceServer interface {
	// サービスが持つメソッドの定義
	Hello(context.Context, *HelloRequest) (*HelloResponse, error)
	mustEmbedUnimplementedGreetingServiceServer()
}

// クライアント
func NewGreetingServiceClient(cc grpc.ClientConnInterface) GreetingServiceClient {
	return &greetingServiceClient{cc}
}

// server

func RegisterGreetingServiceServer(s grpc.ServiceRegistrar, srv GreetingServiceServer) {
	// If the following call pancis, it indicates UnimplementedGreetingServiceServer was
	// embedded by pointer and is nil.  This will cause panics if an
	// unimplemented method is ever invoked, so we test this at initialization
	// time to prevent it from happening at runtime later due to I/O.
	if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
		t.testEmbeddedByValue()
	}
	s.RegisterService(&GreetingService_ServiceDesc, srv)
}

severを作成する

  • cmd/server/main.go
    • _grpc.pb.goで生成された RegisterGreetingServiceServer などを利用
    • サーバー起動
	port := 8080
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		panic(err)
	}

	// 2. gRPCサーバーを作成 これはパッケージ
	s := grpc.NewServer()

	// 3 GreetingService を sを通じて登録
	hellopb.RegisterGreetingServiceServer(s, NewMyServer())

	reflection.Register(s)

client

  • _groc.pb.goで生成された、NewGreetingServiceClientで接続
	fmt.Println("start gRPC Client.")

	// 1. 標準入力から文字列を受け取るスキャナを用意
	scanner = bufio.NewScanner(os.Stdin)

	// 2. gRPCサーバーとのコネクションを確立 (バックエンドとコネクション作るんだ)
	address := "localhost:8080"
	conn, err := grpc.Dial(
		address,

		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithBlock(),
	)
	if err != nil {
		log.Fatal("Connection failed.")
		return
	}
	defer conn.Close()

	// 3. gRPCクライアントを生成
	client = hellopb.NewGreetingServiceClient(conn)

  • _groc.pb.goで生成された、メソッドを利用できる
type GreetingServiceClient interface {
	// サービスが持つメソッドの定義
	Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error)
}

修正方法

  • .protoのInterfacewを変更する
// protoのバージョンの宣言
syntax = "proto3";

// protoファイルから自動生成させるGoのコードの置き先
// (詳細は4章にて)
option go_package = "pkg/grpc";

// packageの宣言
package myapp;

// サービスの定義
service GreetingService {
	// サービスが持つメソッドの定義
	rpc Hello (HelloRequest) returns (HelloResponse); 
}

// 型の定義
message HelloRequest {
	string name = 1;
}

message HelloResponse {
	string message = 1;
}
  • protocコマンドで,変更を反映する
protoc --go_out=../pkg/grpc --go_opt=paths=source_relative \
	--go-grpc_out=../pkg/grpc --go-grpc_opt=paths=source_relative \
	hello.proto

動作確認 gRPCurl

$ brew install grpcurl
$ which grpcurl
[パスが表示されればインストール成功]

// サービス一覧
 grpcurl -plaintext localhost:8080 list

// あるサービスのメソッド一覧
grpcurl -plaintext localhost:8080 list myapp.GreetingService

// メソッド呼び出し

$ grpcurl -plaintext -d '{"name": "hsaki"}' localhost:8080 myapp.GreetingService.Hello

ステータスコード

https://zenn.dev/hsaki/books/golang-grpc-starting/viewer/errorcode#grpcエラーコード一覧

17種類

ミドルウェア(インターセプタ)

https://zenn.dev/hsaki/books/golang-grpc-starting/viewer/serverinterceptor

  • クライント・バックエンド両方

参考

https://zenn.dev/hsaki/books/golang-grpc-starting/viewer/codegenerate

作業

  • ツール入れる
brew install protobuf
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
  • composeでサーバーをセット
FROM golang:1.24-alpine AS go

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY
. .

RUN go build -o grpc-server ./grpcserver/main.go

EXPOSE 50051

CMD ["/app/grpc-server"]
  grpc_server:
    build:
      context: .
      dockerfile: ./grpcserver/Dockerfile
    env_file:
    - .env
  • ディレクトリ作成

    • grpcserver
      • エンドポイント
        • server
    • gateway(内部通信を表現したい場合)
      • pb.go
      • client.go
  • コード生成

protoc \
  --go_out=paths=source_relative:gateway/coin \
  --go-grpc_out=paths=source_relative:gateway/coin \
  proto/coin.proto