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

gRPC勉強会

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Dai Tsuruga Dai Tsuruga
August 28, 2023
64

 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 }