Upgrade to Pro — share decks privately, control downloads, hide ads and more …

gRPC勉強会

Avatar for Dai Tsuruga Dai Tsuruga
August 28, 2023
51

 gRPC勉強会

Avatar for Dai Tsuruga

Dai Tsuruga

August 28, 2023
Tweet

Transcript

  1. RPC とは • Remote Procedure Callの略 • プログラム中の手続きや関数、メソッドを 呼び出すのと同じような手順で異なる サーバ、プロセスの関数などを呼び出す

    仕組み クライアント Method() サーバ Method() クライアント側のMethod()を実行 リクエストを送信 リクエストを元にサーバ側の Method()が呼び出される レスポンス送信 Method()の実行結果を受け取る
  2. 2. Protocol Bufferを使う • Protocol Bufferに基づいてコードを自動生成するツールが提供されて いるため、通信用のデータの構造やメソッドの定義を手作業で書く必要 がなくなり、実装の手間が省ける • Protocol

    Bufferはバイナリフォーマットであるため、シリアライズとでシリ アライズが高速で行われるため、パフォーマンスが向上する • 多くのプログラミング言語でサポートされているので、異なる言語間での データ共有が容易になる
  3. 2. Protocol Bufferを使う ・Protocol Bufferの記述例 syntax = "proto3"; option go_package

    = "github.com/Daaaai0809/grpc-interceptor-sample/proto"; package proto; service AuthSampleService { rpc LoginMethod (LoginRequest) returns (LoginResponse) {} rpc AdminMethod (AdminRequest) returns (AdminResponse) {} rpc RequiredAuthMethod (stream RequiredAuthRequest) returns (stream RequiredAuthResponse) {} rpc NotRequiredAuthMethod (stream NotRequiredAuthRequest) returns (NotRequiredAuthResponse) {} } message LoginRequest { string userName = 1; string password = 2; } message LoginResponse { string token = 1; }
  4. type AuthSampleServiceServer interface { LoginMethod(context.Context, *LoginRequest) (*LoginResponse, error) AdminMethod(context.Context, *AdminRequest)

    (*AdminResponse, error) RequiredAuthMethod(context.Context, *RequiredAuthRequest) (*RequiredAuthResponse, error) NotRequiredAuthMethod(context.Context, *NotRequiredAuthRequest) (*NotRequiredAuthResponse, error) mustEmbedUnimplementedAuthSampleServiceServer() } // UnimplementedAuthSampleServiceServer must be embedded to have forward compatible implementations. type UnimplementedAuthSampleServiceServer struct { } func (UnimplementedAuthSampleServiceServer) LoginMethod(context.Context, *LoginRequest) (*LoginResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method LoginMethod not implemented") } 2. Protocol Bufferを使う ・自動生成されたコードの一部(Go)
  5. func (s *Server) LoginMethod(ctx context.Context, req *proto.LoginRequest) (*proto.LoginResponse, error) {

    userName := req.GetUserName() if userName == "" { return nil, fmt.Errorf("user name is empty") } password := req.GetPassword() if password == "" { return nil, fmt.Errorf("password is empty") } role, err := auth.CheckPassword(userName, password) if err != nil { return nil, err } token, err := auth.GenerateToken(userName, role) if err != nil { return nil, err } return &proto.LoginResponse{ Token: token, }, nil } 2. Protocol Bufferを使う ・自動生成されたコードの利用(Go)
  6. これからやること • Protocol Bufferの記述 • Unary RPC, Server Stream, Client

    Stream, Bidirectional Streamを利用した 簡単な処理の実装(サーバ・クライアント両方) • Interceptorの実装(Interceptor: gRPCにおけるMiddleware的なもの) * *印 : 時間があれば
  7. . ├── client │ └── main.go ├── go.mod ├── go.sum

    ├── interceptor │ ├── client │ │ ├── stream_interceptor.go │ │ └── unary_interceptor.go │ └── server │ ├── stream_interceptor.go │ └── unary_interceptor.go ├── proto │ ├── prac.pb.go │ ├── prac.proto │ └── prac_grpc.pb.go ├── protoc.sh └── server └── main.go ディレクトリ構成
  8. syntax = "proto3"; option go_package = "github.com/saitamau-maximum/grpc-sample-answer/"; package proto; •

    syntax = “proto3”でProtocol Bufferのバージョン指定 • option go_packageでgoのパッケージ指定 • package protoでパッケージ宣言(Goのパッケージと概念は一緒)
  9. service SampleService { rpc unarySample(UnaryRequest) returns (UnaryResponse); } message UnaryRequest

    { string name = 1; } message UnaryResponse { string message = 1; } • service XXX {} でサービス定義 • Service内でrpc {MethodName} ({Request}) returns ({Response})でメソッド定 義 • message XXX {} でデータ構造を定義 • messageの中ではstring, intなどの基本的な型以外にも外部からインポート することでdate型が使えたり、自分で定義したデータ構造が使える • まずはUnary RPCから
  10. type UnaryRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields

    Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } proto/prac.pb.go func (x *UnaryRequest) GetName() string { if x != nil { return x.Name } return "" } • GetName()で送信されたRequestのNameを参照できる。これも自動生成。
  11. type SampleServiceServer struct{ proto.UnimplementedSampleServiceServer } server/main.go func (s *SampleServiceServer) UnarySample(ctx

    context.Context, req *proto.UnaryRequest) (*proto.UnaryResponse, error) { name := req.GetName() if name == "" { return nil, fmt.Errorf("name is required") } msg := fmt.Sprintf("Hello %s", name) return &proto.UnaryResponse{ Message: msg, }, nil }
  12. func main() { port := "18080" lis, err := net.Listen("tcp",

    fmt.Sprintf(":%s", port)) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() proto.RegisterSampleServiceServer(s, NewSampleServiceServer()) reflection.Register(s) go func () { log.Printf("start server port: %s", port) s.Serve(lis) }() quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt) <-quit log.Println("stopping server...") s.GracefulStop() } server/main.go(続き)
  13. var ( scanner *bufio.Scanner client proto.SampleServiceClient address = "localhost:18080" )

    client/main.go • scanner : 標準入力を受け取るための構造体 • client : SampleServiceのクライアント側のインタフェース • address : gRPCサーバのアドレス
  14. func UnarySample() { fmt.Println("Unary RPC") fmt.Print("Name: ") scanner.Scan() name :=

    scanner.Text() req := &proto.UnaryRequest{ Name: name, } res, err := client.UnarySample(context.Background(), req) if err != nil { log.Fatalf("failed to call UnarySample: %v", err) return } fmt.Println(res.GetMessage()) } client/main.go
  15. conn, err := grpc.Dial( address, grpc.WithInsecure(), ) if err !=

    nil { log.Fatalf("failed to connect: %v", err) return } defer conn.Close() client = proto.NewSampleServiceClient(conn) client/main.go : main()内
  16. service SampleService { rpc unarySample(UnaryRequest) returns (UnaryResponse); rpc serverStreamSample(ServerStreamRequest) returns

    (stream ServerStreamResponse); } prac.proto message ServerStreamRequest { string name = 1; } message ServerStreamResponse { string message = 1; }
  17. func (s *SampleServiceServer) ServerStreamSample(req *proto.ServerStreamRequest, stream proto.SampleService_ServerStreamSampleServer) error { name

    := req.GetName() if name == "" { return fmt.Errorf("name is required") } for i := 0; i < 10; i++ { var msg string if i < 9 { msg = fmt.Sprintf("[%d] Hello %s!!", i, name) } else { msg = fmt.Sprintf("[%d] This is the last message. Hello %s!!", i, name) } err := stream.Send(&proto.ServerStreamResponse{ Message: msg, }) if err != nil { return err } time.Sleep(1 * time.Second) } return nil } server/main.go
  18. func ServerStreamSample() { fmt.Println("ServerStream RPC") fmt.Print("Name: ") scanner.Scan() name :=

    scanner.Text() req := &proto.ServerStreamRequest{ Name: name, } stream, err := client.ServerStreamSample(context.Background(), req) if err != nil { log.Fatalf("failed to call ServerStreamSample: %v", err) return } for { res, err := stream.Recv() if errors.Is(err, io.EOF) { return } if err != nil { log.Fatalf("failed to receive: %v", err) return } fmt.Println(res.GetMessage()) } }
  19. service SampleService { rpc unarySample(UnaryRequest) returns (UnaryResponse); rpc serverStreamSample(ServerStreamRequest) returns

    (stream ServerStreamResponse); rpc clientStreamSample(stream ClientStreamRequest) returns (ClientStreamResponse); } message ClientStreamRequest { string name = 1; } message ClientStreamResponse { string message = 1; }
  20. func (s *SampleServiceServer) ClientStreamSample(stream proto.SampleService_ClientStreamSampleServer) error { nameList := make([]string,

    0) for { req, err := stream.Recv() if errors.Is(err, io.EOF) { msg := fmt.Sprintf("Hello %s !!", strings.Join(nameList, ", ")) return stream.SendAndClose(&proto.ClientStreamResponse{ Message: msg, }) } if err != nil { return err } nameList = append(nameList, req.GetName()) } }
  21. func ClientStreamSample() { fmt.Println("ClientStream RPC") fmt.Println("Input names. If you want

    to stop, input 'exit'") stream, err := client.ClientStreamSample(context.Background()) if err != nil { log.Fatalf("failed to call ClientStreamSample: %v", err) return } sendCount := 0 for (sendCount < 5) { sendCount++ fmt.Print("> ") scanner.Scan() name := scanner.Text() if name == "exit" { break } err := stream.Send(&proto.ClientStreamRequest{ Name: name, }) if err != nil { log.Fatalf("failed to send: %v", err) return } } res, err := stream.CloseAndRecv() if err != nil { log.Fatalf("failed to close: %v", err) return } fmt.Println(res.GetMessage()) }
  22. service SampleService { rpc unarySample(UnaryRequest) returns (UnaryResponse); rpc serverStreamSample(ServerStreamRequest) returns

    (stream ServerStreamResponse); rpc clientStreamSample(stream ClientStreamRequest) returns (ClientStreamResponse); rpc bidirectionalStreamSample(stream BidirectionalStreamRequest) returns (stream BidirectionalStreamResponse); } message BidirectionalStreamRequest { string name = 1; } message BidirectionalStreamResponse { string message = 1; }
  23. func (s *SampleServiceServer) BidirectionalStreamSample(stream proto.SampleService_BidirectionalStreamSampleServer) error { for { req,

    err := stream.Recv() if errors.Is(err, io.EOF) { return } if err != nil { fmt.Errorf("failed to receive: %v", err) } name := req.GetName() msg := fmt.Sprintf("Hello %s !!", name) err = stream.Send(&proto.BidirectionalStreamResponse{ Message: msg, }) if err != nil { return err } } }
  24. package interceptor import ( "log" "context" "google.golang.org/grpc" ) func UnaryServerInterceptor(ctx

    context.Context, req interface{}, info *grpc.UnaryServerInfo, hadler grpc.UnaryHandler) (interface{}, error) { log.Println("[pre] UnaryServerInterceptor: ", info.FullMethod) res, err := hadler(ctx, req) log.Println("[post] UnaryServerInterceptor: ", info.FullMethod) return res, err }