Slide 1

Slide 1 text

新卒エンジニアが 0 から Non-Blocking な gRPC サーバーを作った話 株式会社出前館 佐藤 幸樹

Slide 2

Slide 2 text

⾃⼰紹介 2023年4⽉ LINE株式会社 (現LINEヤフー株式会社) 新卒⼊社 2023年7⽉ 株式会社出前館へ出向 商品管理・決済周りのバックエンド開発 学⽣の頃はRailsなどで開発していたが、LINEに⼊ってJVM系の⾔語で 開発するようになりました。 2

Slide 3

Slide 3 text

主な対象 • REST API は書いたことがあるが gRPC でサービスを作ったことがない⽅ • Non-Blockingな処理をするサーバーを作ったことがない⽅ • (Spring Bootでアプリケーションを作ったことがある⽅) gRPC や Non-Blocking に興味を持ってもらい、今後開発する際の技術選定の 参考にしてもらいたい。 3 https://github.com/sato9818/jjug-sample

Slide 4

Slide 4 text

出前館について システム刷新 gRPC と Non-Blocking 技術選定 苦労した点 01 02 03 04 05 4 ⽬次

Slide 5

Slide 5 text

出前館について システム刷新 gRPC と Non-Blocking 技術選定 苦労した点 01 02 03 04 05 5 ⽬次

Slide 6

Slide 6 text

出前館 6 Web サイトやアプリから料理、⾷品、⽇⽤品を注⽂できる 国内最⼤級のデリバリーサービス 2000年 10⽉ 「出前館」サービス開始 2023年 • アクティブユーザー数約650万⼈ • 年間7730万件注⽂

Slide 7

Slide 7 text

出前館 7 バックエンドは • Spring Boot • Java • Kotlin をメインに構築されています 決済サービスとカートサービスを担当

Slide 8

Slide 8 text

出前館について システム刷新 gRPC と Non-Blocking 技術選定 苦労した点 01 02 03 04 05 8 ⽬次

Slide 9

Slide 9 text

システム刷新 問題 • システムのレガシー化 • オンプレ環境を管理する⼯数がかかる • フレームワークや⾔語が古く、サポートが終了している • 古い技術に対応できる⼈が少ない Þ2020年にマイクロサービス化を進めていく過程で、ある程度解消 ✅ オンプレからAWSに移⾏ ✅ コードのリライト 9

Slide 10

Slide 10 text

システム刷新 しかしまだ問題が… • 技術的負債 • 1つの Oracle DB のテーブルに複数のサーバーが参照している(密結合) • 仕様的負債 • 加盟店(レストラン等)によって少しずつ違う仕様があることで、開発コスト増 10

Slide 11

Slide 11 text

システム刷新 再度システム刷新が必要︕ • 仕様のシンプル化: 可能な限りお店独⾃仕様の撤廃 • DB の分離: マイクロサービスごとにDBを持つ (Oracle → MySQL、MongoDB) • コスト削減: AWS → プライベートクラウド • etc… 11

Slide 12

Slide 12 text

After Before 通信⽅式 処理⽅式 REST API Blocking gRPC Non-Blocking 12 システム刷新 DB Oracle MySQL

Slide 13

Slide 13 text

出前館について システム刷新 gRPC と Non-Blocking 技術選定 苦労した点 01 02 03 04 05 13 ⽬次

Slide 14

Slide 14 text

gRPC Google によって開発された、クライアントとサーバ間の通信⼿段の1つ PRC: Remote Procedure Call (遠隔⼿続き呼び出し) `g`については諸説あり (Google? General?) 特徴 • HTTP/2を使った⾼速な通信が可能 • ローカルにある関数を呼び出すかのように、サーバーにリクエストできる • さまざまな⾔語に対応している (Java, Kotlin, Go...) 14

Slide 15

Slide 15 text

gRPC 4つの通信⽅式 • Unary RPC: 1リクエスト 1レスポンス • 例: ユーザー情報の取得 • Server streaming RPC: 1リクエスト nレスポンス • 例: ビデオストリーミング、株価のリアルタイム更新 • Client streaming RPC: nリクエスト 1レスポンス • 例: ファイルアップロード、センサーデータの収集 • Bidirectional streaming RPC: nリクエスト nレスポンス • 例: リアルタイムチャットアプリケーション 15

Slide 16

Slide 16 text

Protocol Buffers (protobuf) Protocol Buffers という IDL (Interface Definition Language*) を⽤いて、 APIのインターフェースを定義する ソースコードを⽣成するには…? • Protobuf のコンパイラである protoc を使う 厳密にはソースコードを⽣成する際には protoc プラグインが必要 (Javaコードを⽣成したい場合は、それ⽤のプラグインが必要) 16 *Interface Definition Language: プログラミング⾔語とは異なり、インターフェースを記述することに重きを置いた⾔語

Slide 17

Slide 17 text

Protocol Buffers (protobuf) 17 syntax = “proto3”; package sample; // ユーザーの情報を取得 service UserService { // IDに基づいてユーザーの情報を取得 rpc GetUser(GetUserRequest) returns (GetUserResponse); } // ユーザー情報を取得するリクエスト message GetUserRequest { int32 id = 1; // 取得したいユーザーのID } // 取得したユーザーの情報 message GetUserResponse { int32 id = 1; // ユーザーのID string name = 2; // ユーザーの名前 string email = 3; // ユーザーのメールアドレス }

Slide 18

Slide 18 text

Protocol Buffers (protobuf) 18 syntax = “proto3”; package sample; // ユーザーの情報を取得 service UserService { // IDに基づいてユーザーの情報を取得 rpc GetUser(GetUserRequest) returns (GetUserResponse); } // ユーザー情報を取得するリクエスト message GetUserRequest { int32 id = 1; // 取得したいユーザーのID } // 取得したユーザーの情報 message GetUserResponse { int32 id = 1; // ユーザーのID string name = 2; // ユーザーの名前 string email = 3; // ユーザーのメールアドレス } package sample; public final class UserServiceOuterClass { private UserServiceOuterClass() {} public static void registerAllExtensions( com.google.protobuf.ExtensionRegistryLite registry) { } public static void registerAllExtensions( com.google.protobuf.ExtensionRegistry registry) { registerAllExtensions( (com.google.protobuf.ExtensionRegistryLite) registry); } public interface GetUserRequestOrBuilder extends // @@protoc_insertion_point(interface_extends:sample.GetUserReq uest) com.google.protobuf.MessageOrBuilder { … $ protoc --java_out=. example.proto protobuf Java クラス

Slide 19

Slide 19 text

REST API 共通 gRPC プロトコル HTTP/1.1が⼀般的 HTTP/2 データフォーマット JSONやXMLなどテキスト ベース Protocol Buffers 開発の容易さ シンプルで直感的 Protocol Buffersを 覚えなきゃいけない分⼤変 Client Sever間の規約 ゆるい 厳しい 互換性 異なる⾔語間で通信可能 19 gRPC vs REST API

Slide 20

Slide 20 text

gRPC 個⼈的に便利だと思った点 • Protocol Buffers で型が定義できるので、リクエストする際に型を間違えて エラーにならない • クライアントとサーバーで同じスキーマを共有するので、タイポしてエラーにならない • Streaming など拡張性が⾼い 20

Slide 21

Slide 21 text

Non-Blocking 処理 スレッドや関数を実⾏した際に、その実⾏したスレッドをブロックしないで結果を得る処理 óBlocking 処理 スレッドや関数を実⾏した際に、その実⾏したスレッドをブロックして結果を得る処理 21

Slide 22

Slide 22 text

⽐較 22 Client Server1 Server2 リクエスト1 リクエスト2 レスポンス1 レスポンス2 待ち 待ち Blocking Client Server1 Server2 リクエスト1 リクエスト2 レスポンス1 レスポンス2 待ちがない Non-Blocking スレッドをブロッキングしている スレッドをブロッキングしない ※リクエスト1とリクエスト2には依存関係がない

Slide 23

Slide 23 text

Blocking 23 public void getUserAndItem(String userId, String itemId) { final User user = getUser(userId); final Item item = getItem(itemId); System.out.println("user: " + user.toString() + ", item: " + item.toString()); } public User getUser(String id) { // サーバーAにUser情報を取りに⾏く。 } public Item getItem(String id) { // サーバーBにItem情報を取りに⾏く } 1秒 2秒 1秒

Slide 24

Slide 24 text

Non-Blocking 24 public void getUserAndItem(String userId, String itemId) { CompletableFuture userFuture = getUser(userId); CompletableFuture itemFuture = getItem(itemId); userFuture.thenCombine(itemFuture, (user, item) -> { System.out.println("user: " + user.toString() + ", item: " + item.toString()); return null; }).join(); } public CompletableFuture getUser(String id) { // サーバーAにUser情報を取りに⾏く。 } public CompletableFuture getItem(String id) { // サーバーBにItem情報を取りに⾏く } 1秒 1秒 1秒ちょい

Slide 25

Slide 25 text

Non-Blocking ⼿法 Javaの場合 • CompletableFuture • Reative Streams Kotlinの場合 • Kotlin Coroutine 25

Slide 26

Slide 26 text

Non-Blocking IO アプリケーションの内部の処理の改善だけでもでも処理は効率化する さらなる効率化をするには・・・ アプリケーションがリクエストを受けて、レスポンスを返すまで全て Non-Blocking にする ネットワーク越しでデータのやり取りをするようなライブラリを全て Non-Blocking (Non-Blocking IO) に 対応させる必要がある 例えば・・・ • DBからのデータの取得 • HTTP Clientを使⽤した他サーバーからのデータの取得 • アプリケーションサーバー 26

Slide 27

Slide 27 text

1つのリクエストにつき、1つのスレッドを割り当てる。 課題 もし⼤量のリクエストが来たら︖ → ⽣成できるスレッドの数は限られている。 → スレッドを増やしすぎるとコンテキストスイッチのコストも上がる。 Blocking IO 27 Client Blockingサーバー Client Client Thread Thread Thread Multi-Thread Blocking IO

Slide 28

Slide 28 text

Blocking IO 28 Client Blockingサーバー Client Client Thread Thread Thread Multi-Thread Blocking IO 課題 別サーバーからのレスポンスが遅延したら︖ → そのスレッドはブロックされて 他のリクエストを処理するのに使えなくなる。 → 内部の処理をNon-blockingにしても スレッド⾃体は占有されてしまう。 別サーバー 遅延 Client New! 占有

Slide 29

Slide 29 text

Non-Blocking IO 29 Non-Blocking IO イベントループという仕組みを使うことでスレッドを ブロックしなくなり、少数のスレッドで複数のリクエストを 処理できるようになる。 暇なスレッドがリクエストを処理に⾏けるイメージ。 Client Non-Blockingサーバー Client Client イベントループ

Slide 30

Slide 30 text

Non-Blocking IO 30 Multi-Thread Blocking IO 別サーバーなどからのレスポンスが 遅延したとしてもその待ち時間にスレッドは 他のリクエストの処理をすることができる。 Client Non-Blockingサーバー Client Client イベントループ 別サーバー 遅延 Client New!

Slide 31

Slide 31 text

Non-Blocking IO • Javaではイベントループを実装しているNettyというネットワークライブラリが 使われることが多い。 参考*: 「JJUG CCC 2018 Spring - I-7 (俺が)はじめての Netty」 • Non-Blocking IO ではアプリケーションの内部でスレッドをブロックしてしまうと、 致命的にパフォーマンスが落ちる。 • 少数のスレッドだけで、スレッドがブロッキングされないこと前提で動いているので、処理 できるスレッドが減ってしまう。 31 * https://www.slideshare.net/slideshow/jjug-ccc-2018-spring-i7-netty/99033170

Slide 32

Slide 32 text

Non-Blocking IO 32 Multi-Thread Blocking IO 別サーバーへのリクエストはNon-Blocking にする必要がある。 Non-Blockingに対応しているライブラリを使おう。 Client Non-Blockingサーバー Client Client イベントループ 別サーバー 遅延 Client New! ブロックしちゃダメ!!

Slide 33

Slide 33 text

Non-Blocking IO対応ライブラリ 33 Blocking IO Non-Blocking IO DB JDBC R2DBC Redis Jedis Lettuce Web framework Spring MVC Spring WebFlux Armeria HTTP Client Spring RestClient Spring Webclient

Slide 34

Slide 34 text

Non-Blocking IO WEBフレームワーク • Spring WebFlux • Armeria • Vert.x • Quarkus • Micronaut • Helidon 34

Slide 35

Slide 35 text

Blocking Non-Blocking 多くのリクエストを捌くのに 必要なリソース 多くのリソース 少数のリソース スケーラビリティ 低い ⾼い 実装の複雑性 (コードの追いやすさ) シンプル 複雑 学習コスト 低い ⾼い 35 Non-Blocking vs Blocking

Slide 36

Slide 36 text

出前館について システム刷新 gRPC と Non-Blocking 技術選定 苦労した点 01 02 03 04 05 36 ⽬次 https://github.com/sato9818/jjug-sample

Slide 37

Slide 37 text

システム概要 37

Slide 38

Slide 38 text

システム概要 38

Slide 39

Slide 39 text

Buf Protobuf のコンパイラ。Protoc のプラグインを使える。 特徴 • Protoc の2倍の速さのコンパイル • Protobuf の Linter を備えている • Protobuf が破壊的変更をしたかどうか検知できる • gRPC の Request の validation をできる • Buf Schema Registry という Protobuf を⼀元管理してくれるプラットフォームを 提供している 39

Slide 40

Slide 40 text

Buf Gradleプラグインも提供されている。 (https://github.com/bufbuild/buf-gradle-plugin) Gralde タスクを通して • ソースコードの⽣成 • Lint • 破壊的変更の検知 などができる。 40

Slide 41

Slide 41 text

Buf 実際にgradleで⽣成する⽅法をみてみましょう。 ブランチ: 1-buf-build 41

Slide 42

Slide 42 text

技術選定 42

Slide 43

Slide 43 text

gRPC-java Java で gRPC サーバー、クライアントを作るライブラリ群。 • サーバー側は Netty でできているのでこのライブラリでサーバーを作成すると Non-Blocking なサーバーになる。 • しかし、アプリケーションが実⾏されるスレッドはブロッキングされても良いような 実装になっているのでブロッキングする実装でもOK。 • ⼀般的に java で gRPC サーバーやクライアントを作りたい場合はこのライブラリ を使うことになる。 参考: https://github.com/grpc/grpc-java 43

Slide 44

Slide 44 text

gRPC-java ⼀旦 gRPC-java だけを使ってサーバーとクライアントがどのように動くかみてみましょう。 ブランチ: 2-grpc-java 44

Slide 45

Slide 45 text

技術選定 45

Slide 46

Slide 46 text

Spring Boot ⾔わずと知れた Java の Web フレームワーク 特徴 • Dependency Injection • Transaction Manager • Data Binding 選定理由 出前館のマイクロサービスのほとんどは Spring Boot を使っているので、 共通フレームワークの1つとして認識されている。 46

Slide 47

Slide 47 text

Spring Boot with gRPC gRPC-java と Spring Boot は親和性がない。 Spring Boot の機能を使って gRPC を実装したい︕ • LogNet/grpc-spring-boot-starter: 2023年9⽉に開発が⽌まってる。 • grpc-ecosystem/grpc-spring • line/armeria 47

Slide 48

Slide 48 text

Spring Boot with gRPC grpc-ecosystem/grpc-spring • Spring Security も対応している • アノテーションベースで Spring Like な開発ができる line/armeria • LINEヤフー社で開発しているので、サポートしてもらいやすい • LINEヤフー社内事例が多く存在し、困った時に社内のコードを検索すると 参考になるコードがたくさんある • 便利機能がたくさんある 参考: https://github.com/grpc/grpc-java 48

Slide 49

Slide 49 text

Armeria Netty の創始者である Trustin が作った Non-Blocking な RPC フレームワーク 特徴 • 旧LINEで開発が始まったOSS • Netty をベースとした Non-Blocking HTTP/2 サーバー • gRPC-java を内包しているので、Non-Blocking な gRPC サーバーを作成できる 49

Slide 50

Slide 50 text

Armeria いいところ • LINEヤフー社内事例が多く存在し、困った時に社内のコードを検索すると 参考になるコードがたくさんある。 • LINEヤフー社内 slack に help チャンネルがあり、困った時にすぐに聞ける環境 が存在する。(外部向けにも discord server があり、そこでも質問できる) • 便利機能が多くある。 50

Slide 51

Slide 51 text

Armeria 便利機能その1 • DocService: SwaggerのようなgRPC対応のドキュメンテーションサービス 51

Slide 52

Slide 52 text

Armeria 便利機能その2 • Decorator Decorator機能を使うことで、リクエストやレスポンスの前後に処理を挟むことができる 例 • Logging: リクエスト、レスポンスのログをいい感じに出してくれる。 • Retry: クライアントのリクエストでタイムアウトなどが発⽣した際にリトライをする機能。 • CircuitBreaker: リクエスト先のサーバーで何か異常があった時に、リクエストを遮断する機能。 52

Slide 53

Slide 53 text

Armeria ⼀旦 Spring Boot は置いといて、Armeria単体でどのようにサーバー実装 をするのかみてみましょう。 ブランチ: 3-armeria 53

Slide 54

Slide 54 text

Armeria with Spring Boot Armeriaは Spring Boot との統合も提供している。 何がいいか サーバーはArmeriaのものを使って • Dependency Injection • Transaction Manager • Data Binding といった 機能は Spring Boot のものを使うということができる。 54

Slide 55

Slide 55 text

Armeria with Spring Boot どのように Armeria と Spring Boot を統合させるかみてみましょう。 ブランチ: 4-armeria-spring 55

Slide 56

Slide 56 text

技術選定 56

Slide 57

Slide 57 text

HTTP Client 決済をするには外部決済代⾏会社にリクエストをする必要がある Armeria が HTTP Client を提供している。 • Retrofit • Spring WebClient 57

Slide 58

Slide 58 text

HTTP Client Retrofit • Square社が開発している Java や Android 向けの Http Client。 Non-Blocking に対応している。 • インターフェイスを定義する形で API リクエストを定義できる。 • Mock ライブラリなど、周辺ライブラリも充実している。 WebClient • Spring Framework の1つで Non-Blocking に対応した Http Client。 • プログラム的に API リクエストを設定する。 • Reactive Streamsに対応している。 58

Slide 59

Slide 59 text

Armeria with Retrofit Armeriaと統合することで何がいいか • Decoratorが使える • Retry機能 • Circuit breaker機能 59 ArmeriaRetrofit armeriaRetrofit = ArmeriaRetrofit.builder() .baseUrl("http://example.com") .decorator(LoggingClient.newDecorator()) .decorator(CircuitBreakerClient.newDecorator(circuitBreaker)) .decorator(RetryingClient.newDecorator(retryConfig)) .build();

Slide 60

Slide 60 text

技術選定 60

Slide 61

Slide 61 text

R2DBC (Reactive Relational Database Connectivity) Reactive Streams をベースにアクセスを Non-Blocking にするDBドライバ。 JDBCのようなドライバーでDBに Blocking アクセスをすると、DBアクセスの間 スレッドがブロックされて、パフォーマンス悪化の原因になる。 • r2dbc-mysql: MySQL ⽤のドライバー • r2dbc-pool: コネクションプーリングライブラリ • Spring-boot-starter-data-r2dbc: Spring Boot と R2DBC の統合 61

Slide 62

Slide 62 text

技術選定 62

Slide 63

Slide 63 text

出前館について システム刷新 gRPC と Non-Blocking 技術選定 苦労した点 01 02 03 04 05 63 ⽬次

Slide 64

Slide 64 text

苦労した点 • モダンな技術を使っているので、資料がたくさんあるわけではなく、理解するために 実際にコード読んだりするのが⼤変だった。 • 欲しい機能がない場合は実際 PR 作ってコントリビュートしたりした。 • https://github.com/line/armeria/pull/5555 • https://github.com/line/armeria/pull/5613 • https://github.com/line/armeria/pull/5617 • https://github.com/bufbuild/buf-gradle-plugin/pull/170 • でも総じてモダンな技術に触れられて楽しい︕ 64

Slide 65

Slide 65 text

宣伝 こんなモダンな技術を使える出前館に⼊社してみませんか︕︖ • 新卒採⽤ • エンジニア • 中途採⽤ • サーバーサイドエンジニア • シニアサーバーサイドエンジニア • フロントエンドエンジニア • インフラエンジニア • データベースエンジニア • SRE • QAエンジニア https://recruit.demae-can.co.jp/ 65

Slide 66

Slide 66 text

66