システム構築にgRPCを導入した話

golang.tokyo #8

28 August 2017

dice_zu(daisuzu)

自己紹介

Twitter: @dice_zu
GitHub: https://github.com/daisuzu
Blog: http://daisuzu.hatenablog.com

gRPCとは?

Google製のRPC

構築したシステム

以下のような契約管理システム

1. クライアントからオーダーを受け付けて、
2. 外部のシステムに送信すると、
3. 非同期で処理結果が返ってくるので、
4. それをクライアントに返す。

                      '                                               ::    ___________
                      '                                +--------+     ::   (           )
                      '                                | sender | ----::-- (           )
                      '                                +--------+     ::   (           )
                      '                               /               ::   (           )
        +--------+    '    +--------+   +------------+                ::   (  external )
        | client | ---'--- | server | - | relay node |                ::   (  service  )
        +--------+    '    +--------+   +------------+                ::   (           )
                      '                               \               ::   (           )
                      '                                +----------+   ::   (           )
                      '                                | receiver | --::-- (           )
                      '                                +----------+   ::   (___________)

なぜgRPCを採用したのか

RESTのエンドポイント辛い問題

コード生成で開発の効率化

service Contract {
  rpc Create (CreateRequest) returns (CreateResponse) {}
  rpc Update (UpdateRequest) returns (UpdateResponse) {}
  rpc Activate (ActivateRequest) returns (ActivateResponse) {}

  rpc XXX (XXXRequest) returns (XXXResponse) {}
  rpc YYY (YYYRequest) returns (YYYResponse) {}
  rpc ZZZ (ZZZRequest) returns (ZZZResponse) {}
}

JSON SchemaやSwaggerと比べると、

Streaming RPC

他システムの刷新を促進

想定クライアントが

だったので全体的にgRPC対応のモダンなシステムにしたかった

しかし...

ほとんどのクライアントがRESTを要求

OK

NG

結局 grpc-gateway を使ってRESTインターフェースも提供した

開発を終えての感想

良かったところ

メッセージのstructとサービスのinterfaceのみ
余計なものが含まれない

type service struct{}

func (s *service) Create(ctx context.Context, in *pb.CreateRequest) (*pb.CreateResponse, error) {
    return &pb.CreateResponse{}, nil
}

func main() {
    s := grpc.NewServer()
    pb.RegisterContractServer(s, &service{})

    l, _ := net.Listen("tcp", ADDR)
    s.Serve(l)
}

困ったところ

生成されたコード (pbパッケージ) からクライアントを生成するツールを作った

(今は改善されているが) 何も対策をしてないとサーバが落ちる
レスポンスの生成に気をつけつつ、interceptorでハンドリング

gRPCは常時接続だが、無通信の間に接続先コンテナが替わってしまうことがあった
この時、次のリクエストを送信するまでエラーにならないので発見が遅れた
クライアント側の grpc.WithDialer でTCPのkeepaliveを設定して回避

まとめ

興味がある人は是非導入を!
そして知見の共有をお願いしますm(_ _)m

Thank you

Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)