web-dev-qa-db-ja.com

RESTマイクロサービス間のトランザクション

User、Wallet RESTマイクロサービス、およびものを結び付けるAPIゲートウェイがあるとします。ボブが私たちのウェブサイトに登録するとき、私たちのAPIゲートウェイはUserマイクロサービスを通してWalletマイクロサービスを通してユーザーを作成する必要があります。

今ここに物事がうまくいかない可能性がありますいくつかのシナリオがあります:

  • ユーザーBobの作成は失敗します。それで問題ありません。Bobにエラーメッセージを返すだけです。私たちはSQLトランザクションを使っているので、誰もシステム内でボブを見たことがありません。すべて良し :)

  • ユーザーBobが作成されましたが、Walletを作成する前にAPIゲートウェイがハードクラッシュしました。財布のないユーザー(データの矛盾)があります。

  • ユーザーBobが作成され、Walletを作成しているときにHTTP接続が切断されます。ウォレットの作成は成功したか、または成功しなかった可能性があります。

この種のデータの不整合が発生しないようにするために利用できる解決策は何ですか?トランザクションが複数のREST要求にまたがることを可能にするパターンはありますか?私は Two-phase commit のウィキペディアのページを読みました。これはこの問題に触れているようですが、実際にそれを適用する方法がわかりません。この Atomic Distributed Transactions:RESTful設計 論文もまだ読んでいませんが、興味深いようです。

あるいは、RESTはこのユースケースには適さないかもしれません。おそらくこの状況に対処する正しい方法はRESTを完全に削除してメッセージキューシステムのような異なる通信プロトコルを使用することでしょうか?それとも、私のアプリケーションコードの一貫性を強化する必要がありますか(たとえば、矛盾を検出して修正するバックグラウンドジョブを使用するか、Userモデルに「作成」、「作成」値などの「state」属性を設定するなど)。

156
Olivier Lalonde

どうしても意味がありません:

  • RESTサービス付きの分散トランザクション。 RESTサービスは定義上ステートレスであるため、複数のサービスにわたるトランザクション境界の参加者になるべきではありません。あなたのユーザー登録のユースケースシナリオは理にかなっていますが、ユーザーとWalletデータを作成するためのRESTマイクロサービスを使ったデザインは良くありません。

何が頭痛の種になりますか

  • 分散トランザクションを持つEJB。それは理論的には機能するが実際には機能しないことの1つです。今のところ、私は分散トランザクションをJBoss EAP 6.3インスタンスにまたがるリモートEJBに対して機能させることを試みています。私たちは数週間にわたってRedHatサポートと話してきましたが、まだうまくいきませんでした。
  • 一般的な2フェーズコミットソリューション。私は 2PCプロトコル が素晴らしいアルゴリズムだと思います(何年も前に私はそれをCのRPCで実装しました)。リトライ、ステートリポジトリなど、包括的な障害回復メカニズムが必要です。複雑さはすべてトランザクションフレームワーク内に隠されています(例:JBoss Arjuna)。しかし、2PCは失敗防止ではありません。トランザクションが単純に完了できない状況があります。その後、データベースの不整合を手動で識別して修正する必要があります。運がよければ100万回の取引に1回起こるかもしれませんが、プラットフォームやシナリオによっては100回の取引に1回起こるかもしれません。
  • Sagas(補正トランザクション)。補正操作を作成するための実装オーバーヘッドと、最後に補正を有効にするための調整メカニズムがあります。しかし、補償も失敗の証拠ではありません。あなたはまだ矛盾を起こすかもしれません(=いくらかの頭痛)。

おそらく最善の選択肢:

  • 最終的な一貫性。 ACIDのような分散トランザクションも代償トランザクションもフェイルプルーフではなく、どちらも矛盾を引き起こす可能性があります。最終的な整合性は、「偶発的な不整合」よりも優れています。次のようなさまざまな設計ソリューションがあります。
    • 非同期通信を使用して、より堅牢なソリューションを作成できます。あなたのシナリオでは、Bobが登録すると、APIゲートウェイはNewUserキューにメッセージを送信し、「アカウント作成を確認するためのEメールを受信します」というユーザーへの即時返信を送信します。キューコンシューマサービスは、メッセージを処理し、単一のトランザクションでデータベースの変更を実行し、アカウントの作成を通知するためにEメールをBobに送信できます。
    • Userマイクロサービスは、ユーザーレコードand Walletレコード同一データベース内を作成します。この場合、Userマイクロサービス内のWalletストアは、Walletマイクロサービスからしか見えないマスターWalletストアのレプリカです。データ同期メカニズムがあり、これはトリガーベースであるか、定期的に開始してレプリカからマスターにデータの変更(新しいウォレットなど)を送信します。

しかし、同期応答が必要な場合はどうしますか?

  • マイクロサービスを改造します。サービスコンシューマがすぐに応答を必要としているためにキューを使用したソリューションが機能しない場合は、User機能とWallet機能を同じサービス(または少なくとも同じVM)に配置するように変更します。分散トランザクションを避けるため)はい、それはマイクロサービスからより遠くモノリスに近い一歩ですが、頭痛からあなたを救うでしょう。
119
Paulo Merson

これは私が最近インタビューの間に尋ねた古典的な質問です。どうやって複数のWebサービスを呼び出し、それでもタスクの途中である種のエラー処理を保存するか。今日、高性能コンピューティングでは、2フェーズコミットを避けています。私は何年も前に取引のための「スターバックモデル」と呼ばれていたものについての論文を読みました。あなたがコーヒーを受け取るまでの全過程は、含まれるすべてのステップのための単一の包装取引であることを提案します。しかし、このモデルでは、すべての従業員はあなたがコーヒーを飲むまで待って仕事をやめることになります。あなたは絵が見えますか?

代わりに、「スターバックモデル」は「ベストエフォート」モデルに従い、プロセス内のエラーを補正することでより生産的になります。まず、彼らはあなたが支払うことを確認します!そして、あなたの注文がカップに添付されたメッセージキューがあります。コーヒーが届かなかったり、注文したものではないなど、プロセスで問題が発生した場合は、補償プロセスに入り、必要なものが得られるようにするか、払い戻します。これが最も効率的なモデルです。生産性向上のため。

時々、スターバックスはコーヒーを無駄にしていますが、全体的なプロセスは効率的です。 Webサービスを構築するときには、それらを何度も呼び出すことができ、それでも同じ最終結果が得られるように設計するなど、他にもトリックを考慮する必要があります。だから、私のお勧めは:

  • あなたのWebサービスを定義するときはあまりにも罰金をかけないでください(私は最近のマイクロサービスの大騒ぎについて確信していません:行き過ぎるリスクが多すぎる)。

  • 非同期はパフォーマンスを向上させるため、非同期であることを好み、可能な限り通知を電子メールで送信します。

  • よりインテリジェントなサービスを構築して、何度もそれらを「呼び戻す」ことができます。最後まで注文の最後まで続くuidまたはtaskidで処理し、各ステップのビジネスルールを検証します。

  • メッセージキュー(JMSなど)を使用し、反対の操作を適用することによって操作を「ロールバック」に適用するエラー処理プロセッサに迂回します。ところで、非同期順序で作業するには、プロセスの現在の状態を検証するために何らかのキューが必要になります。そう考えてみてください。

  • 最後の手段として、(頻繁には起こらないかもしれないので)手動でエラーを処理するためにキューに入れます。

投稿された最初の問題に戻りましょう。アカウントを作成してウォレットを作成し、すべてが完了したことを確認します。

Webサービスが操作全体を調整するために呼び出されるとしましょう。

Webサービスの擬似コードは次のようになります。

  1. アカウント作成マイクロサービスを呼び出し、情報と固有のタスクIDを渡します。1.1アカウント作成マイクロサービスは、まずそのアカウントがすでに作成されているかどうかを確認します。タスクIDはアカウントのレコードに関連付けられています。マイクロサービスはアカウントが存在しないことを検出し、アカウントを作成してタスクIDを保存します。注:このサービスは2000回呼び出すことができます、それは常に同じ結果を実行します。サービスは「必要に応じて元に戻す操作を実行するための最小限の情報を含むレシート」で応答します。

  2. アカウントIDとタスクIDを渡して、Wallet creationを呼び出します。条件が無効でウォレットの作成ができないとしましょう。呼び出しはエラーで戻りますが、何も作成されませんでした。

  3. オーケストレーターにエラーが通知されます。アカウントの作成を中止する必要があることはわかっていますが、それ自体では行われません。手順1の最後に受け取った「最小の元に戻す」を渡して、ウォレットサービスにそれを実行するように依頼します。

  4. Accountサービスは取り消し領収書を読み、操作を取り消す方法を知っています。取り消しレシートには、ジョブの一部を実行するために自分自身を呼び出した可能性がある別のマイクロサービスに関する情報も含まれる場合があります。この状況で、元に戻す領収書には、アカウントIDと、場合によっては反対の操作を実行するために必要な追加情報が含まれる可能性があります。私たちの場合、物事を簡単にするために、単にそのアカウントIDを使用してアカウントを削除するとしましょう。

  5. さて、Webサービスがアカウント作成の取り消しが実行されたという成功または失敗(この場合)を受け取らなかったとしましょう。単にアカウントのアンドゥサービスを再度呼び出すだけです。このサービスは、アカウントが存在しなくなることを目的としているため、通常は失敗することはありません。それでそれはそれが存在するかどうかチェックし、それを元に戻すために何もすることができないのを見ます。したがって、操作は成功したことになります。

  6. Webサービスは、アカウントを作成できなかったことをユーザーに返します。

これは同期の例です。システムにエラーを完全に回復させたくない場合は、別の方法で管理し、ヘルプデスクを対象としたメッセージキューに入れてもかまいません」。ヘルプデスクは、正常に実行されたことを含むメッセージを受信し、アンドゥの受信を完全に自動化された方法で使用するのと同じように問題を解決するのに十分な情報を持っていました。

私は検索を実行しましたが、マイクロソフトのWebサイトにこのアプローチのパターンの説明があります。これは補正取引パターンと呼ばれます。

補償取引パターン

49
user8098437

すべての分散システムはトランザクションの一貫性に問題があります。これを行う最良の方法はあなたが言ったように、2フェーズコミットをすることです。 Walletとユーザーを保留状態で作成します。作成したら、別の電話をかけてユーザーをアクティブにします。

この最後の呼び出しは安全に再現できるはずです(接続が切断された場合)。

これは最後の呼び出しが両方のテーブルについて知っていることを必要とするでしょう(それがそれが単一のJDBCトランザクションで行われることができるように)。

あるいは、なぜあなたが財布のないユーザーを心配しているのかを考えたいと思うかもしれません。これが問題を引き起こすと思いますか?もしそうなら、多分それらを別々の休息呼び出しとして持つのは悪い考えです。ユーザーがWalletなしで存在してはならない場合は、おそらくユーザーにWalletを追加する必要があります(元のPOSTユーザーの作成コール)。

26
Rob Conklin

私見マイクロサービスアーキテクチャの重要な側面の1つは、トランザクションが個々のマイクロサービスに限定されていることです(単一責任の原則)。

現在の例では、ユーザーの作成は独自のトランザクションになります。ユーザーが作成すると、USER_CREATEDイベントがイベントキューにプッシュされます。 WalletサービスはUSER_CREATEDイベントをサブスクライブしてWalletを作成します。

9
mithrandir

私の財布がユーザーと同じSQLデータベース内の単なる別のレコードの集まりであった場合、私はおそらくユーザーと財布の作成コードを同じサービスに配置し、それを通常のデータベーストランザクション機能を使用して処理します。

財布作成コードで他のシステムに触れる必要があるときに何が起こるかについて質問しているのではないでしょうか。 Idは、すべてが作成プロセスの複雑さや危険性にかかっていると言っています。

他の信頼できるデータストア(sqlトランザクションに参加できないものなど)に触れるだけの場合は、システム全体のパラメータによっては、2回目の書き込みが行われないという可能性が非常に小さい可能性があります。私は何もしないかもしれませんが、例外を発生させ、矛盾したデータを代償的なトランザクションまたは何らかの特別な方法で対処します。私が開発者にいつも言っているように、「このようなことがアプリで起こっても、それは気付かれないでしょう」。

ウォレット作成の複雑さとリスクが増すにつれて、関連するリスクを軽減するための対策を講じる必要があります。いくつかの手順では、複数のパートナーのAPIを呼び出す必要があるとしましょう。

この時点で、部分的に構築されたユーザーやWallet、あるいはその両方の概念と共にメッセージキューを導入することができます。

エンティティが最終的に正しく構築されるようにするための簡単で効果的な戦略は、ジョブが成功するまでジョブを再試行させることですが、多くはアプリケーションのユースケースに依存します。

私は私のプロビジョニングプロセスで失敗しがちなステップがあった理由についても長くそして懸命に考えるでしょう。

7
Robert Moskal

この種のデータの不整合が発生しないようにするために利用できる解決策は何ですか?

伝統的には、分散トランザクションマネージャが使用されています。数年前のJava EEの世界では、これらのサービスを EJB sとして作成し、それらをさまざまなノードにデプロイし、APIゲートウェイでそれらのEJBに対してリモート呼び出しを行っていました。アプリケーションサーバ(正しく設定されている場合)は、2フェーズコミットを使用して、トランザクションが各ノードでコミットまたはロールバックされることを自動的に保証するので、一貫性が保証されます。しかし、そのためには、すべてのサービスを同じ種類のアプリケーションサーバーに展開し(互換性を持たせる)、実際には1社が展開したサービスでしか機能しないことが必要です。

トランザクションが複数のREST要求にまたがることを可能にするパターンはありますか?

SOAP(はい、RESTではありません)の場合、 WS-AT という仕様がありますが、これまでに統合しなければならなかったサービスはサポートされていません。 RESTに関しては、JBossは パイプラインの中にある を持っています。そうでなければ、 "パターン"はあなたがあなたのアーキテクチャにプラグインできる製品を見つけるか、あるいはあなた自身の解決策を構築することです(推奨されません)。

私はJava EEのためのそのような製品を公開しました: https://github.com/maxant/genericconnector

あなたが参照した論文によると、AtomikosからのTry-Cancel/Confirmパターンと関連するProductもあります。

BPELエンジンは、補償を使用してリモートに配置されたサービス間の一貫性を処理します。

あるいは、RESTはこのユースケースには適さないかもしれません。おそらくこの状況に対処する正しい方法はRESTを完全に削除してメッセージキューシステムのような異なる通信プロトコルを使用することでしょうか?

非トランザクションリソースをトランザクションに「バインド」する方法はたくさんあります。

  • お勧めのとおり、トランザクションメッセージキューを使用することもできますが、非同期になるため、応答に依存している場合は面倒になります。
  • バックエンドサービスを呼び出す必要があるという事実をデータベースに書き込んでから、バッチを使用してバックエンドサービスを呼び出すことができます。繰り返しますが、非同期なので、面倒になることがあります。
  • ビジネスプロセスエンジンをAPIゲートウェイとして使用して、バックエンドマイクロサービスを編成することができます。
  • 冒頭で述べたように、リモートEJBを使用することができます。これは、そのままで分散トランザクションをサポートするためです。

それとも、私のアプリケーションコードの一貫性を強化する必要がありますか(たとえば、矛盾を検出して修正するバックグラウンドジョブを使用するか、Userモデルに「作成」、「作成」値などの「state」属性を設定するなど)。

悪魔を演じることを主張する:なぜあなたに代わってそれをする製品があるのに(上を見て)、そのようなものを作るのか、そしておそらくそれはあなたができるよりも上手くやるか?

3
Ant Kutschera

簡単な解決策の1つは、ユーザーサービスを使用してユーザーを作成し、ユーザーサービスがそのイベントを発行するメッセージングバスを使用し、Wallet Serviceがメッセージングバスに登録し、User Createdイベントをリッスンし、ユーザーのWalletを作成することです。その間に、ユーザーが自分のWalletを表示するためにWallet UIにアクセスした場合は、ユーザーが作成されたばかりかどうかを確認して、作成中であることを確認してください。

2
techagrammer

個人的には私はMicro Services、ユースケースで定義されたモジュールのアイデアが好きですが、あなたの質問が言及するように、彼らは銀行、保険、電気通信などのような古典的なビジネスに適応問題を抱えています...

多くの人が述べたように、分散トランザクションは良い選択ではありません、人々は現在最終的に一貫したシステムにもっと行くが、私はこれが銀行、保険、その他にうまくいくかどうかわからない。

私は私の提案した解決策についてブログを書きました、これはあなたを助けることができるかもしれません....

https://mehmetsalgar.wordpress.com/2016/11/05/micro-services-fan-out-transaction-problems-and-solutions-with-spring-bootjboss-and-netflix-eureka/ =

2
posthumecaver

最終的な一貫性がここで重要です。

  • サービスの1つが、イベントの主な処理者になるように選択されています。
  • このサービスはシングルコミットで元のイベントを処理します。
  • 一次ハンドラは、二次効果を他のサービスに非同期的に伝達する責任を負います。
  • プライマリハンドラは他のサービス呼び出しのオーケストレーションを行います。

指揮官が分散トランザクションを担当し、制御を引き受けます。それは実行される命令を知っており、それらの実行を調整します。ほとんどのシナリオでは2つの命令しかありませんが、複数の命令を処理できます。

指揮官はすべての命令の実行を保証する責任を負い、それは引退を意味します。コマンダーがリモートアップデートを実行しようとしても応答がない場合、再試行はありません。このようにして、システムは障害を起こしにくいように構成でき、自己修復します。

再試行があるので、冪等性があります。冪等性は、最終結果が一度だけ行われた場合と同じ結果になるように、何かを二度行うことができるという特性です。リモートサービスまたはデータソースでのべき等性が必要です。そうすれば、それが複数回命令を受け取った場合に、それが一度だけそれを処理するようになります。

最終的な一貫性これは分散トランザクションの課題のほとんどを解決しますが、ここでいくつかの点を考慮する必要があります。失敗したすべてのトランザクションの後に再試行が続きます。再試行の試行回数はコンテキストによって異なります。

一貫性は、例えば顧客が本を注文し、支払いをし、次に在庫数量を更新した場合など、再試行中にシステムが一貫性のない状態になる間に、最終的に生じる。在庫更新操作が失敗し、それが利用可能な最後の在庫であると想定している場合、在庫更新の再試行操作が成功するまでブックは引き続き利用可能です。再試行が成功した後、あなたのシステムは安定しているでしょう。

0