gRPC-Javaの仕組みが気になったのでコードリーディングしてみた

# はじめに

[gRPCのJavaチュートリアル](https://grpc.io/docs/tutorials/basic/java.html)をやってみて、どういう仕組みで動いてるか気になったので色々調べた。

- gRPC-Javaの利用方法などはチュートリアルに記載してあるので細かい説明はしません
- どうやって通信しているのか、細かい内部の実装がどうなっているかについて、自分の中で理解した内容の整理です

## 確認バージョン
releases: [gRPC-Java v1.16.1](https://github.com/grpc/grpc-java/tree/v1.16.1)

- チュートリアルで使ったのは[routeguide](https://github.com/grpc/grpc-java/tree/v1.16.1/examples/src/main/java/io/grpc/examples/routeguide)パッケージのソース
- gradleの設定はこちら

```gradle
compile 'io.grpc:grpc-netty-shaded:1.16.1'
compile 'io.grpc:grpc-protobuf:1.16.1'
compile 'io.grpc:grpc-stub:1.16.1'
```

## gRPCについて
そもそも[gRPC](https://grpc.io/)について[公式ドキュメント](https://grpc.io/)からポイントを抜粋。

- [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/overview)を利用し、送受信するデータのシリアライズ化とRPCのインターフェース定義を行う
  - Protocol Buffersの定義ファイルにはデータのシリアライズ定義のみだが、gRPCではそこにRPCのインターフェースも定義するように拡張している
  - シリアライズするデータやインターフェースの大部分のコードは自動生成されるため、ビジネスロジックに集中して開発できる
- 通信は[HTTP/2](https://http2.github.io/)を利用する
  - 通信の確立、ヘッダやデータの送受信はHTTP/2の形式
  - 1つのTCPコネクションで双方向のストリーミング通信が可能であり、以下の4タイプのrpcメソッドを定義できる

|type|概要|
|----|---|
|A simple RPC|1リクエストに対して1レスポンスを返す|
|A server-side streaming RPC|1リクエストに対して複数レスポンスを返す|
|A client-side streaming RPC|複数リクエストに対して1レスポンスを返す|
|A bidirectional streaming RPC|複数リクエストと複数レスポンスを双方向にやりとりする|

## gRPC-Javaについて
gRPC-Javaの実装のポイント。

- サーバ側では通信制御部分は[Netty](https://netty.io/)を利用している
- クライアント側はAndroidの場合は[OkHttp](https://square.github.io/okHTTP/)、それ以外は[Netty](https://netty.io/)を利用している
  - 今回はクライアント側の処理はあまり調べられていない

- Androidかどうかは[ServiceProviders#loadAll](https://github.com/grpc/grpc-java/blob/v1.16.1/core/src/main/java/io/grpc/ServiceProviders.java#L61)内で[`ClassLoader`を利用して判別](https://github.com/grpc/grpc-java/blob/v1.16.1/core/src/main/java/io/grpc/ServiceProviders.java#L90)している。
- Androidではない場合、[ServiceProviders#getCandidatesViaServiceLoader](https://github.com/grpc/grpc-java/blob/v1.16.1/core/src/main/java/io/grpc/ServiceProviders.java#L103)の中で[java.util.ServiceLoader#load](https://docs.oracle.com/javase/jp/8/docs/api/java/util/ServiceLoader.html)を利用して[META-INF/services/io.grpc.ServerProvider](https://github.com/grpc/grpc-java/blob/v1.16.1/netty/src/main/resources/META-INF/services/io.grpc.ServerProvider)に書かれているクラスを取得している
  - [java.util.ServiceLoader#load](https://docs.oracle.com/javase/jp/8/docs/api/java/util/ServiceLoader.html)って初めて知った。。

## Nettyについて
gRPC-Javaで利用されているNettyとは、Javaでnon-blocking I/O(以下、NIO)のアプリケーションを作成できるフレームワークである。（Java サーブレットは使わない）

gRPCでの登場するスレッドやNIOのイベント名、Nettyの処理はこんな感じ。

- bossスレッドで`OP_ACCEPT`を検知し、`OP_READ`イベントを登録
- `OP_READ`や`OP_WRITE`の処理はworkerスレッドが実行
- workerスレッドはクライアントのリクエスト内容をreadして、gRPCのメソッド呼び出しが必要になったタイミングでexecutorスレッドを生成して処理を実行させる

|登場人物 |役割 |
|----|----|
|bossスレッド<br>(ServerBootstrapのparentGroup)|Nettyが利用するネットワークのI/Oを検知するスレッド。起動時にデフォルトでは1つ生成される。|
|workerスレッド<br>(ServerBootstrapのchildGroup)|Nettyが利用するI/Oイベントごとの処理をするスレッド。デフォルトは[プロセッサの数x2](https://github.com/grpc/grpc-java/blob/v1.16.1/netty/src/main/java/io/grpc/netty/Utils.java#L197)。|
|executorスレッド|gRPCで定義したメソッドを実行するスレッド。メソッド呼び出しごとに生成される。|

|I/Oイベント名 |概要 |
|----|----|
|OP_ACCEPT|クライアントからの接続|
|OP_READ|ネットワークI/Oの読み取り|
|OP_WRITE|ネットワークI/Oの書き出し|

### 補足
Nettyについては「[JJUG CCC 2018 Spring - I-7 (俺が)はじめての Netty](https://www.slideshare.net/mikeneck/jjug-ccc-2018-spring-i7-netty)」がすごく参考になった。

## HTTP/2について
googleの[Introduction to HTTP/2](https://developers.google.com/web/fundamentals/performance/http2/?hl=ja)より、gRPCを理解するためのポイントを抜粋。

HTTP/1.xでは改行で区切られたプレーンテキストが1つのリクエスト(またはレスポンス)とされているが、HTTP/2ではこれを1つのメッセージと表し、さらにメッセージは以下のフレーム単位に分割される

  - HEADERSフレーム(HTTP/1.xのヘッダ)
  - DATAフレーム(HTTP/1.xのボディ)

またフレームは1つのTCP接続で並列、双方向にやりとりができる。
つまりリクエスト毎にTCPコネクションを確立しなくて済み、一つのリクエストに対して複数のレスポンスや、サーバプッシュなどが可能となる。


# gRPC-Javaの処理概要
これより先は「コードのどの部分でどんな処理を〜」みたいな話になるので、その前に一連の処理について自分の中で解釈した内容を要約。

1. gRPCサーバは起動時にNettyが利用するbossスレッドを生成する
2. gRPCクライアントがgRPCサーバへリクエストする
3. bossスレッドはネットワークI/Oイベントの`OP_ACCEPT`を検知するとNettyのworkerスレッドを生成(もしくはプールから取得)して`OP_READ`/`OP_WRITE`の処理を委譲
4. workerスレッドはTCPコネクション確立(3ウェイハンドシェイクなど)や、HTTP/2のHEADERSフレーム、DATAフレームなどI/Oイベントが発生するたびに呼び出される
5. クライアントからgRPC呼び出しに必要なHTTP/2のメッセージ(複数のフレーム)の受け取りを完了したら、workerスレッドはgRPCメソッド実行用のexecutorスレッドを生成する
  1. Nettyとしてはbossスレッドとworkerスレッドのみで動作するイベントループの仕組みであるが、そこからgRPCメソッド呼び出しごとにスレッドが生成される
  2. そのためgRPCメソッド内でブロッキングな処理を実行してもNettyのスレッドはブロックされないようである
6. executorスレッドはgRPCメソッドを実行し、レスポンスの`OP_WRITE`を登録
7. workerスレッドが`OP_WRITE`を検知して、クライアントへレスポンスを返す
8. クライアントがまだ接続を続ける場合は再度TCPコネクションを確立する必要がないので、`5. ~ 7.`の処理を何度も並列で実行できる
9. クライアントが接続を切る場合はTCPの切断を行う


# Server側のコードリーディング
TODO 自分のメモ用に書き殴ってるので、あとで追加とか整理する。（いろんなスレッドを生成したり、コールバックでさらに新たなスレッド生成したりとかで追うのが難しかったので間違っていたら教えてもらえると嬉しいです。。）

## 起動
- RouteGuideServer#mainを実行
- ServerBuilder#forPortでServerBuilderのインスタンスを取得する
  - ServiceProviders#loadAllの中でClassLoaderを見てAndroidアプリかそうでないかを判別してる
  - Androidでない場合は、java.utilパッケージのServiceLoader#loadの仕組みを使って[META-INF/services/io.grpc.ServerProvider](https://github.com/grpc/grpc-java/blob/v1.16.1/netty/src/main/resources/META-INF/services/io.grpc.ServerProvider)から[NettyServerProvider.java](https://github.com/grpc/grpc-java/blob/v1.16.1/netty/src/main/java/io/grpc/netty/NettyServerProvider.java)のインスタンスを取得している
- NettyServerBuilder#addServiceで、自動生成されたxxxGrpc.xxxImplBaseを継承したクラスを渡す
  - ここでProtocol Buffersで定義したメソッドがbindされる
  - [AbstractServerImplBuilder.java#L146](https://github.com/grpc/grpc-java/blob/v1.16.1/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java#L146)で自動生成されたコードのbindServiceが呼ばれるため
- NettyServerBuilder#build
  - ServerImplインスタンスを作成して返す
  - この時AbstractServerImplBuilder#buildTransportServerでNettyServerのインスタンスを生成して引数に渡し、ServerImplの持っているInternalServer transportServerというフィールドへ
- Builderから取得したServerImpl#start内で、NettyServer#startを実行
  - NettyServer#allocateSharedGroupsでNettyに必要なparent(boss)とchild(worker)のNioEventLoopGroupインスタンスを作成
  - ServerBootstrapをnewしてNettyサーバを起動させる
  - ChannelInitializerを実装し、ServerBootstrap#childHandlerに渡す
  - io.netty.bootstrap.AbstractBootstrap#bind
    - initAndRegisterでbossスレッドを起動
      - io.netty.channel.nio.NioEventLoop.runで`OP_ACCEPT`イベントの待ち受け開始
- executorに`grpc-default-executor-%d`の名前のスレッドを生成するThreadFactory(ThreadFactoryBuilder$ThreadFactory)を持つThreadPoolExecutorを渡す
  - gRPCのメソッドは呼ばれるたびにここからスレッドを生成して実行する
- NettyServerを起動するとmainスレッドは、Runtime.getRuntime().addShutdownHook()を使って、終了時の処理を定義している
- さらにmainスレッドはRouteGuideServer.blockUntilShutdownを呼び出してWAIT状態へ

## リクエスト受信
- workerスレッドがgRPCメソッドの呼び出しリクエストを受け付けると、[NettyServerHandlerがServerImpl$ServerTransportListenerImpl#streamCreated](https://github.com/grpc/grpc-java/blob/v1.16.1/netty/src/main/java/io/grpc/netty/NettyServerHandler.java#L437)を実行する
- ServerImpl$ServerTransportListenerImpl#streamCreatedは[ContextRunnableを継承したStreamCreatedのインスタンスを、ThreadPoolExecutorをwrapしたSerializingExecutorに渡す](https://github.com/grpc/grpc-java/blob/v1.16.1/core/src/main/java/io/grpc/internal/ServerImpl.java#L495)
- このexecuteはThreadFactoryBuilder$ThreadFactoryで`grpc-default-executor-%d`というスレッド名で以下のRunnableタスクを実行する
  - StreamCreated#run->StreamCreated#runInContextが実行される
  - そしてServerImpl#JumpToApplicationThreadServerStreamListener$MessagesAvailable#runInContextが呼ばれる
  - さらにServerImpl\$JumpToApplicationThreadServerStreamListener$1HalfClosed#runInContextHalfClosedgRPCのメソッドが実行される
- gRPCで定義した`getFeature`が呼ばれる時はこんな感じ

```java
getFeature:130, RouteGuideServer$RouteGuideService (grpc.routeguide)
invoke:462, RouteGuideGrpc$MethodHandlers (grpc.routeguide)
onHalfClose:171, ServerCalls$UnaryServerCallHandler$UnaryServerCallListener (io.grpc.stub)
halfClosed:283, ServerCallImpl$ServerStreamListenerImpl (io.grpc.internal)
runInContext:710, ServerImpl$JumpToApplicationThreadServerStreamListener$1HalfClosed (io.grpc.internal)
run:37, ContextRunnable (io.grpc.internal)
run:123, SerializingExecutor (io.grpc.internal)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
```