applepie-umai / SpringFest2019

0 stars 0 forks source link

[LINE公式アカウントのチャットシステムにおけるSpringおよびWebFluxの活用事例] [吉田] #17

Open yoshidaki opened 4 years ago

yoshidaki commented 4 years ago

[LINE公式アカウントのチャットシステムにおけるSpringおよびWebFluxの活用事例]

概要

このセッションに関連するキーワード

セッションの内容

話の流れ

LINE公式アカウントとは企業/事業者向けに開設可能なLINEアカウントである。 ブロードキャストやターゲティング配信などの機能を有しており、一般ユーザ向けのチャットシステムとは異なる「LINEチャット」を利用してLINE公式アカウントと一般ユーザがチャットできる。

チャットシステムとして「LINEチャット」に求められるものは以下のものである。

リアルタイム通信

リアルタイム通信の実現については、検討時期にはPolling、Long Polling、Server Sent Events(SSE)、WebSocketの4択となっていたが、SSEを採用することにした。 また、採用したSpring WebFluxはNon BlockingなWebフレームワークである。 1Request=1ThreadなServlet APIと異なり、1つのEvent Loop Treadで複数のRequestをさばけることから、サーバ台数を削減できる狙いがあった。

ユーザ操作起因のコアビジネスロジック

ユーザ操作起因のコアビジネスロジックの実現については、Spring WebMVCを採用した。 開発時期がSpring Boot 2.0.0.M1だったということもあったが、いきなりSpring WebFluxを全適用するのではなく、小さくて確実に向いているコンポーネントから試していこうという判断があった。

LINEユーザが送信した画像・動画・音声の取得

LINEユーザが送信した画像・動画・音声の取得については、Spring WebFluxを採用した。 画像・動画・音声ファイルは社内CDNにあり、認証等の事情によりアカウントオーナーにアクセスさせるにはProxyを掛ける必要があったが、Spring WebFluxのContent Proxyによりスレッドを占有することなくProxyを掛けることができた。

以下は開発時に困った点やノウハウのTips

Netty OOMEの発生(Nettyに報告・改修確認済)

WebFlux Clientを用いたコードでAPI callのtimeoutが大量に発生するとOutOfMemoryError(OOME)でサーバが落ちることがあった。 NettyのReadTimeoutExceptionがシングルトンであったため、ReadTimeoutExceptionが保持しているsuppressedExceptions(List)が肥大化してOOMEが発生した。 現在はIssueを起票して修正済である。

参考:

https://github.com/netty/netty/pull/9152

Blocking DNS Resolver

Reactor NettyではInetSocketAddressを使ったBlocking DNS Resolverを使用しているので、NettyのNon-Blocking DNS Resolverを使うように変更する必要がある。 また、ごくまれにTreadがハングしてしまう現象があったためIssueを起票済。(現在0.10.x向けBacklog) また、デフォルトをNon-Blocking DNS Resolverとする案もReactor Nettyに上がっている。

参考:

https://github.com/reactor/reactor-netty/issues/710
https://github.com/reactor/reactor-netty/issues/569

Reactor hooks with MDC

ServletのようにMDCにRequest情報を渡してログに出力させたかったがWebFluxのEvent Loop Threadと相性が悪かった。 WebFilter、Reactor Context、Hooksを活用して何とか実現した。

BlockHound

BlockHoundはNon BlockingスレッドにおけるBlockingコールを検出してくれるJavaエージェント。 あまり日本語で言及されている記事を見かけなかったので紹介。 GradleやMavenで依存関係に追加し、テストケースにてBlockHoundを有効化しておくと、テストケース実行時にBlockingコール発生部分でErrorを出力してくれる。 しばしば予期しないBlockingコールもあり、コードレビューで漏れることがあるので導入をお勧めする。

考察したこと

講義資料URL

https://speakerdeck.com/line_developers/examples-of-using-spring-and-webflux-in-the-chat-system-for-line-official-accounts

yoshidaki commented 4 years ago

MEMO

LINEではどのくらいSpringを使っているか? 5000projectくらい Gradle3000くらい、Maven2700くらい

OR MapperはMyBatisをよく使う モニタリングはプロメテウス

LINE公式アカウントとは? 企業や事業者が解説するアカウント ビジネス向けの機能がある、ブロードキャスト、ターゲッティング配信……

Lineチャット LINE公式アカウントの運用者向けのチャットシステム(普通のLINEのチャットとは別) タグやラベルを貼ったり、定型文をつくったり

SpringとWebFluxの活用事例

システム構成、設計 リアルタイム通信(チャットだもの) CGI時代はiframeを自動リロード…… Server Sent Eventsを採用! 双方向通信不要な案件、テキストしか使わないし…… シンプルな構成も良し

SSEはサーバと繋ぎっぱなしになる、Servletではスケールしない(リクエストとスレッドが同じ紐付き) サーバが多く必要……Blocking I/O ↓ WebFluxを採用!Non Blocking I/O Nettyつかう WebFluxでは各リクエストをNon Blockingで処理するので1台で多く処理できる

Talk Server→Event Receiver→Kafka→Event Processer→Redis Cluster→SSE

長時間Eventがないと接続が切れるので定期的にPINGを送ったりしている

過去メッセージの検索とかはRESTful API(Spring-webmvc) いきなり全部WebFluxにするのはビビったとのこと (今ならWebFluxでもいいかも)

画像動画音声の配信、取得 公式アカウントを直接LINE用のサーバに繋げられなかったのでProxyを用意した。

一年WebFluxをつかって……

困ったこと Netty OOME(timeout多いとOOMEになった。。) NettyのReadTimeoutExceptionがシングルトンだったのでどんどんsuppressedExceptionが肥大化した。 既に解消済み。

Blocking DNS Resolver Rractor nettyではBlocking DNS ResolverつかってたのでNettyのNon〜〜をつかう

Reactor hooks with MDC WebFluxではEvent Loop Eventとは相性が悪い →WebFilterでリクエスト情報をコンテキストに詰め込む、各種onXXXXメソッドをラップしたメソッドを実装、Hooksに登録する

BlockHound あまり日本語記事ないけど。 Blockingな実装を発見できる。

ogawaeri commented 4 years ago

1Request=1ThreadなServlet APIと異なり、1つのEvent Loop Treadで複数のRequestをさばけることから、サーバ台数を削減できる狙いがあった。

この処理方式を導入できたのは、以下の理由だからと認識しました。 ・今回は双方向通信が不要。テキスト形式で問題ない ・"Blockするコード(直列な処理)"が不要だったから

電話以外ぱっと双方向通信が思い浮かばないので、ほとんどのアプリとかで採用可能なのかなと思いました。処理方式でこんなにも変わるんですね。 (メッセージの受信、コンテンツの受信、ユーザ認証、という部分は知らない言葉を追うので精いっぱいでした。。難しかった。)

yoshidaki commented 4 years ago

この処理方式を導入できたのは、以下の理由だからと認識しました。 ・今回は双方向通信が不要。テキスト形式で問題ない ・"Blockするコード(直列な処理)"が不要だったから

これに関しては講義スライドにある通りその通りですね。 直列処理を廃することがリアクティブプログラミングには必要なことなので、やり始めると結構こだわらないといけないかもしれませんが、うまく作れるとスレッド数をぎゅっと圧縮できそうですよね。

ogawaeri commented 4 years ago

この処理方式を導入できたのは、以下の理由だからと認識しました。 ・今回は双方向通信が不要。テキスト形式で問題ない ・"Blockするコード(直列な処理)"が不要だったから

これに関しては講義スライドにある通りその通りですね。 直列処理を廃することがリアクティブプログラミングには必要なことなので、やり始めると結構こだわらないといけないかもしれませんが、うまく作れるとスレッド数をぎゅっと圧縮できそうですよね。

リアクティブプログラミングは"情報を受けたら処理をするもの"と勘違していました。。調べたら並列処理にしていく流れになるね。ありがとうございます! 難しかったけどレポートを見れてよかったです。調べるきっかけになりました。