EIPはメッセージングのためのデザインパターンです。インターネットもメッセージングで成り立っていますので、インターネットそのものがEIPのコンテキストと言っても過言ではありません。EIPは今どきの分散するシステムを設計する開発者として知っておいて損はありません。そんな偉大なEIPは、私が日常的に利用している分散システムのためのツールキットAkkaでもよく目にします。今回はその代表例を挙げて、EIPの考え方がどんなところで有用なのかを解説します。
EIPとAKKAについてかとじゅん( )2022/02/09実践!インテグレーションパターン現場から学ぶモデル駆動設計@j5ik2o1
View Slide
自己紹介Chatwork社 テックリード副業では技術顧問として活動中FizzBuzzを書くためだけのプログラミング言語を作りました@j5ik2ohttps://github.com/j5ik2o/oni-comb-rs2
EIPとAKKAについて話します3
具体的には 配送保証(GUARANTEEDDELIVERY) のパターンを解説しますが、前提知識を少し話します。4
AGENDA通信は届かない(ことがある)メッセージ配送の保証配送保証(Guaranteed Delivery)Akkaの簡単な実装例とReliable Delivery5
送信したデータが相手に必ず届くと思い込んでませんか?6
通信は届かない(ことがある)少なくとも現時点では失敗のないネットワークを構築することは不可能と言われている。メッセージが欠落したり、遅延したりする可能性は必ずある。今の技術で避けられないよく考えられたシステムは、この問題をカバーするように設計されている(それでも限界がある)。そのような考慮がないシステムでは容易にメッセージの欠落や矛盾が起こる7
「なぜ届かないのか」を理解する8
アナロジー:二人の将軍問題信頼性の低い通信チャネルではコンセンサスに達することが不可能であるTwo Generals' Problem二人の将軍問題9
二人の将軍問題(1/4)赤が敵軍、緑が同盟軍。同盟軍A or Bが単独攻撃しても数で負けるが、AとB 一緒に攻撃すれば勝てる可能性がある。しかし、軍隊は谷間に位置しているため、旗などの遠隔通信で攻撃の時間を指示することは不可能彼らはコミュニケーションのために敵地を経由して使者(メッセンジャー)を送る。が、敵地を経由するので使者は捕まったり殺されたりする可能性がある⼭ ⼭敵軍同盟軍A同盟軍B10
この問題は「信頼性の低い通信路」で何が起きるかを示している11
二人の将軍問題(2/4)将軍Aから将軍Bに「14時に攻撃する」ということをメッセンジャーを通じて送る。無事に将軍Bにメッセージが届いた場合 メッセンジャーは無事に敵地を通り抜けて、将軍Bにメッセージを伝える。将軍A将軍A敵地敵地将軍B将軍B14時に攻撃する12
二人の将軍問題(3/4)将軍Bから将軍Aに返信がない場合、将軍Aはどう考えるか将軍Aはメッセンジャーが将軍Bに到着したかを知らない。敵地で殺されたかもしれない。将軍Bは「14時に攻撃する」を知らない可能性がある。将軍Bが攻撃の準備ができていないかもしれない将軍A将軍A敵地敵地将軍B将軍B14時に攻撃する了解なので、将軍Bは「了解」ということをメッセンジャーを通じて返信もらう必要がある(返信が来ない場合はリトライするかも)13
二人の将軍問題(4/4)が、これもメッセージが将軍Aに届かない可能性がある厳密には「了解」が伝わったかは、将軍Aからの「了解の了解」の返信を待つ必要がある…将軍A将軍A敵地敵地将軍B将軍B14時に攻撃する了解了解の了解??これでは 完全に合意するには無限のメッセージを送る必要がある…14
低信頼網では100%の合意に到達できないどんなに多くの要求を送っても、どんなに多くの返答を送っても、将軍Aも将軍Bも相手の将軍が攻撃の準備をしていることを100%確信することはできません。これが「二人の将軍問題」の核心です15
ネットワークを使ったメッセージ配送も信頼性の低い通信路を使った通信の一つ16
メッセージ配送にはこれらを考慮したモデルがある17
メッセージ配送の信頼性(1/2)At-Most-Once Deliveryメッセージは0回もしくは1回 配送される(tellの場合は)送ったけど相手に届かないことがあるaskによって返信を確認することはできるとはいえ、タイムアウトしたときリトライするなら、at-most-once以上のことをやろうとしている高パフォーマンス・低コスト18
メッセージ配送の信頼性(1/2)At-Least-Once Deliveryメッセージは少なくとも1回配送が成功する相手にメッセージが重複して届く可能性がある送信側に必ずディスクが必要になるExactly-Once Deliveryメッセージは正確に1回だけ配送される受信側にもディスクが必要になるデフォルトのメッセージ配送は「At-Most-Once Delivery」です。運がよかったら届くかもね!そんなインテグレーションで大丈夫か?!19
そこでEIPですよ20
配送保証(GUARANTEED DELIVERY)At-Least-Once Deliveryのために21
メッセージングシステムが故障しても、送信者がメッセージを確実に届けられるようにするには?メッセージを配送するシステム=メッセージングシステムmessaging systemsender receiver22
メッセージの保存と転送のプロセス非同期に動作するメッセージングは送信者・受信者、そしてそれらを繋ぐネットワークが同時に動作している必要がない(同期型のRPCは障害に弱い)メッセージングシステムがネットワークを利用できない場合、ネットワークが利用可能になるまでメッセージを保存すればよい。受信者が利用できない場合も、メッセージを保存しておき、受信者が利用できるようになるまでリトライする。23
受信者が故障で不通なら送信者はリトライする。リトライ中 送信者が故障しても大丈夫?24
いいえ当然リトライ中であることも忘れます25
メッセージが揮発しないようにメッセージを永続化するメッセージングシステムがノード障害などを起こせば、保持しているメッセージはすべて消失する。対策としてファイルやデータベースなどを使ってディスクに永続化する必要があるApache Kafkaがまさに同じようなことをやっています(ブローカーが仲介するので厳密には同じではないが)26
配送保証(GUARANTEED DELIVERY)のPROS/CONS配送保証パターンでは、メッセージングシステムはデータストアを使ってメッセージを保存します27
送信者が故障しても復旧後にメッセージは送信される送信側のPCのローカルに保存されます。メッセージ送信際、データストアにメッセージが保存されるまで送信操作は正常に完了しない。メッセージが正常に送信されたら、データストアからメッセージが削除される28
信頼性を得る代わりにパフォーマンスが犠牲になるメッセージの永続性は信頼性を高めるが、パフォーマンスが犠牲になる。メッセージ配送のクラッシュやシャットダウン時にメッセージが失われても構わない場合は、配送保証パターンを採用しないほうがパフォーマンスがよくなる(トレードオフ)29
FYI: EXACTLY-ONCE DELIVERY =配送保証(GUARANTEED DELIVERY)+べき等レシーバー(IDEMPOTENTRECEIVER)30
AKKAでどう実装するか31
簡単なモデルを示した例networkSender«Actor» Forwarder«PersistentActor» Receiver«Actor»32
登場人物ReceiverForwarderSenderhttps://github.com/j5ik2o/akka-at-least-once-delivery/blob/main/src/main/scala/example/simple/Receivehttps://github.com/j5ik2o/akka-at-least-once-delivery/blob/main/src/main/scala/example/simple/Forwarhttps://github.com/j5ik2o/akka-at-least-once-delivery/blob/main/src/test/scala/example/simple/AtLeastO33
この設計の問題点リトライ間隔やリトライ上限回数がないので、リトライによってネットワークに負荷をかける可能性があるAkkaでは指数関数バックオフに対応したBackoffSupervisorによって解決できるメッセージの追い越しできない場合は、メッセージを蓄積しつつフロー制御が必要になるAkkaのStashBufferでバッファリングしつつ、akka-persistenceの機能によって受け付けたメッセージを蓄積するこのサンプルでは、故障しやすいタスクをReceiverが担う場合、故障中のメッセージ配送をどうするかという問題があるEIPのように受信側にも故障しにくい仲介者が必要。仲介者としてSupervisorを間にいれるAkka Reliable Deliverも選択肢の一つ34
AKKA RELIABLE DELIVERYhttps://doc.akka.io/docs/akka/current/typed/reliable-delivery.htmlhttps://github.com/j5ik2o/akka-at-least-once-delivery/tree/main/src/main/scala/example/delivery35
まとめ一般的にメッセージ配送には保証がない(TCPもHTTPもat-most-once)その代わりに高パフォーマンス・低コストちゃんと届けるにはそれなりの仕組みが必要になるそれが配送保証(Guaranteed Delivery)配送保証(Guaranteed Delivery)によってat-leaset-onceを実現するAkkaには、配送保証(Guaranteed Delivery)の考え方が設計思想に組み込まれているPersistentActorでも実現可能Akka組込のReliable Deliveryを使うことも可能36
終わり37