复习《微服务模式》第三章的进程间通信
简述
参加了Java读书会,虽然辛苦学习了一番,但是因为接连忘记了,所以我决定记录下留下印象的部分。
在使用微服务架构的第三章中,我们讨论了进程间通信的应用。
-
- マイクロサービスアーキクテチャでは、複数のサービスを組み合わせて、全体として一つのサービスを形成するので、各サービス間の通信方法は必然的に重要な要素となる
-
- この章では大きく同期・非同期と分けながら色々な概念を紹介しているが、最重要なのは「3.3.7 トランザクショナルメッセージング」
- 以後の章では、トランザクショナルメッセージングがある前提で話が進められる
同步调用
远程过程调用模式
-
- 略してRPI。同期的なリモート呼び出しを指す
-
- https://microservices.io/patterns/communication-style/rpi.html
-
- 具体例としてはRESTやgRPCが挙げられている
-
- SOAP、CORBAやJava RMIなどもこの類に入る
Java RMIのような言語固有のメカニズムを使うのは、様々な技術が入り乱れるマイクロサービスアーキクテチャには向かないとしている
この本としては、同期呼び出しは可用性を下げるので、あまりおすすめしてない
跳闸模式 zhá
-
- https://microservices.io/patterns/reliability/circuit-breaker.html
-
- 同期呼び出し向けのパターンで、あるサービスへの呼び出しが一定回数、連続で失敗したり応答を返さなかったりした場合、以降の呼び出しは、(おそらくクライアント側のプロキシのところで)即座にエラーを返すようにする
-
- 一定期間が過ぎたら再びリモート呼び出しを行うようにする
- Java向けにはNetflixのHystrixが紹介されている
服务发现
-
- 同期呼び出しの場合に通信相手をどうやって見つけるか?
-
- Self registrationパターンと、Client-side discoveryパターンの組み合わせ
Self registrationは、呼ばれる側が自らサービスレジストリに登録しにいく
https://microservices.io/patterns/self-registration.html
Client-side discoveryは、クライアント側がサービスレジストリから呼び出したい相手を見つけ出す。候補が複数ある場合は、ラウンドロビンなりランダムなりで負荷分散する
https://microservices.io/patterns/client-side-discovery.html
CORBAのNameServiceと同様な考え方
プラットフォームが提供するサービスディスカバリのパターン
3rd party registration
Kubernetes環境下では立ち上げたサービスをサービスレジストリに自動で登録する
https://microservices.io/patterns/3rd-party-registration.html
Server-side discovery
クライアントがサービスを呼ぶときは、サービスレジストリの手前にあるルーター(この文脈ではロードバランサと呼ぶべきか?)に対して呼び出しを行う。ルーターは上記の登録済みサービスを見つけ出してリクエストを転送する
https://microservices.io/patterns/server-side-discovery.html
异步消息传递
-
- Messagingパターン
https://microservices.io/patterns/communication-style/messaging.html
サービス間の通信は、チャネルを通じて行われる
非同期リクエスト・レスポンスの場合は、双方向のチャネルを使う
非同期メッセージングには、メッセージブローカーを使う場合と使わない場合がある
ブローカーなしだと、同期の場合と同様に可用性が下がるのと、サービスディスカバリが必要になるのでこの本ではおすすめしてない
メッセージブローカーありの場合は、ライブラリとしては、JMS、Apache Kafka、RabbitMQ、AWS Kinesis、AWS SQSなどが挙げられている
SQSの場合は、パブサブがない
この本としては、ブローカーありの方が、粗結合で可用性が高いのでおすすめとしている
多くのブローカーは、At least Onceしか保証してないため、重複メッセージの排除のために処理済みメッセージのidをDBに保存するなどの工夫が必要
事务性消息传递
-
- サービスが自身のDBに対する処理を行いつつ、他のサービスに対してメッセージを発行する場合の話
-
- 例えばOrderのレコードを作りつつ、OrderCreatedイベントを発行する場合(イベントでなくても他のサービスへのリクエスト発行でも良い)
-
- このようなケースではDBの整合性はトランザクションによって保たれるが、DBの処理とメッセージの発行を分けて実施するとイベントの順序が逆転してしまうことがある
-
- 例えばOrderがCancelされてから、Orderが生成されるというイベント順になったりする
-
- 解決方法としては、メッセージをキューイングするためのテーブル(Outboxテーブル)を用意して、元々のテーブル更新と、メッセージキューイングのテーブル更新を一つのトランザクションで行う
Transactional outboxパターン: https://microservices.io/patterns/data/transactional-outbox.html
Outboxテーブルに貯められたメッセージは以下のいずれかの方法で取り出され、その後ブローカーに渡される
Polling publisherパターン:https://microservices.io/patterns/data/polling-publisher.html
名前の通りOutboxテーブルのポーリング
Transaction log tailing: https://microservices.io/patterns/data/transaction-log-tailing.html
Outboxテーブルのトランザクションログを監視する。
成为现实的有轨电车
-
- 前記のようなTransactional Outboxパターンを実現しようと思うと、それなりにライブラリが必要になる
-
- 著者が調べた範囲では、既存のライブラリでは、低レベルすぎたり、イベント以外のメッセージが発行できなかったりと不便なものしかなかった
-
- おすすめは著者が自ら作った「Eventuate Tram」
Transactional outbox対応
Transaction log tailingとPolling publisherの両対応
ブローカーはApache Kafka
重複メッセージの検出対応
一方向メッセージ、イベントパブリッシュ、コマンドリプライなど多様なメッセージングパターンに対応
本書では、このあとはほとんどがEventuateベースのコードになります。
Eventuateの宣伝本だった?
REST API的实现方式
-
- サービス間の通信は非同期メッセージング推奨とは言っても、公開APIはRESTだったりする
反応の遅い非同期通信では、RESTに求められるパフォーマンスがでない懸念がある
解決法1: 例えばOrderのGETの場合は、顧客情報やレストラン情報などのミラーをOrderService内に抱えれば、サービス間の非同期通信自体しなくてすすむ
これは7章のCQRSでより発展した議論になる
解決法2:REST APIでは、不完全状態のOrderをつくって、IDだけ返す。あとはクライアントがOrderの状態をポーリングする
总结起来
-
- 色々な概念・パターンが登場したけど、重要なのは「トランザクショナルメッセージング」
-
- 昔、Mutexロックを持っている状態で、キューにメッセージを入れる実装をしたことがあったが、発想は同じ
-
- トランザクションもキューも処理をシリアライズする仕組みなので、組み合わせて使うと全体としてもシリアルになる
-
- 本書ではこれ以降の議論は、トランザクショナルメッセージングによって、メッセージやイベントの順序性が保たれていることに立脚しているので、ここの部分は極めて重要
-
- 著者が言うようにトランザクショナルメッセージングを実現する高水準なライブラリがEventuateしなないのであれば、マイクロサービスパターンを活用したマイクロサービスアーキテクチャを構築するには、事実上Eventuateを使う以外にないということになるのかもしれない。
代替品はないのか?