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


195

ユーザー、ウォレットRESTマイクロサービス、および物事を接着するAPIゲートウェイがあるとします。BobがWebサイトに登録するとき、APIゲートウェイは、Userマイクロサービスを介してユーザーを作成し、Walletマイクロサービスを介してWalletを作成する必要があります。

次に、問題が発生する可能性があるいくつかのシナリオを示します。

  • ユーザーBobの作成は失敗します。問題ありません。エラーメッセージをBobに返します。SQLトランザクションを使用しているため、システム内でBobを見た人はいません。すべて良し :)

  • ユーザーBobが作成されましたが、ウォレットを作成する前に、APIゲートウェイがハードクラッシュします。これで、ウォレットのないユーザー(データに一貫性がない)ができました。

  • ユーザーBobが作成され、ウォレットを作成すると、HTTP接続がドロップします。ウォレットの作成が成功したか、成功しなかった可能性があります。

この種のデータの不整合が発生しないようにするには、どのような解決策がありますか?トランザクションが複数のRESTリクエストにまたがることを可能にするパターンはありますか?私はこの問題に触れていると思われる2フェーズコミットのWikipediaページを読みましたが、実際にそれを適用する方法がわかりません。このAtomic Distributed Transactions:RESTfulなデザインペーパーも興味深いですが、まだ読んでいません。

あるいは、RESTがこのユースケースに適していない可能性があることも知っています。この状況を処理してRESTを完全に削除し、メッセージキューシステムのような別の通信プロトコルを使用する正しい方法でしょうか?または、アプリケーションコードに整合性を適用する必要がありますか(たとえば、不整合を検出して修正するバックグラウンドジョブを実行するか、ユーザーモデルに「作成」、「作成」の値などの「状態」属性を設定するなど)。


3
興味深いリンク:news.ycombinator.com/item?id
Olivier Lalonde

3
ウォレットなしではユーザーが意味をなさない場合、ウォレット用に別のマイクロサービスを作成する理由は何ですか?そもそも建築がおかしいのでは?ところでジェネリックAPIゲートウェイが必要なのはなぜですか?具体的な理由はありますか?
Vladislav Rastrusny

4
@VladislavRastrusnyは架空の例ですが、ウォレットサービスは、たとえばStripeによって処理されていると考えることができます。
Olivier Lalonde 2017

プロセスマネージャーを使用してトランザクションを追跡したり(プロセスマネージャーのパターン)、各マイクロサービスにロールバックをトリガーする方法(サーガマネージャーのパターン)を知らせたり、何らかの2フェーズコミット(blog.aspiresys.com/software-product-engineering)を実行したりできます。/ producteering /…
Andrew Pate

@VladislavRastrusny「ユーザーがウォレットなしでは意味をなさない場合、なぜそれのために別のマイクロサービスを作成するのか」-たとえば、ウォレットなしではユーザーが存在できないという事実は別として、共通のコードはありません。したがって、2つのチームがユーザーとウォレットのマイクロサービスを個別に開発してデプロイする予定です。それはそもそもマイクロサービスを行うことの要点ではないでしょうか?
Nik 2018

回答:


148

意味をなさないもの:

  • RESTサービスを使用した分散トランザクション。RESTサービスは本質的にステートレスであるため、複数のサービスにまたがるトランザクション境界の参加者であってはなりません。ユーザー登録のユースケースのシナリオは理にかなっていますが、ユーザーとウォレットのデータを作成するRESTマイクロサービスを使用した設計は適切ではありません。

あなたに頭痛を与えるもの:

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

おそらく最良の代替策は何ですか?

  • 結果の一貫性。ACIDのような分散トランザクションも補償トランザクションも失敗を防ぐものではなく、どちらも不整合を引き起こす可能性があります。結果の整合性は、「時折の不整合」よりも優れていることがよくあります。次のようなさまざまな設計ソリューションがあります。
    • 非同期通信を使用して、より堅牢なソリューションを作成できます。シナリオでは、Bobが登録すると、APIゲートウェイがNewUserキューにメッセージを送信し、「アカウントの作成を確認するためのメールが届きます」とユーザーにすぐに返信します。キューコンシューマーサービスは、メッセージを処理し、データベースの変更を単一のトランザクションで実行し、アカウントの作成を通知するためにメールをボブに送信できます。
    • Userマイクロサービスは、同じデータベースにユーザーレコードウォレットレコード作成します。この場合、ユーザーマイクロサービスのウォレットストアは、ウォレットマイクロサービスにのみ表示されるマスターウォレットストアのレプリカです。トリガーに基づく、または定期的に起動してデータの変更(たとえば、新しいウォレット)をレプリカからマスターに、またはその逆に送信するデータ同期メカニズムがあります。

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

  • マイクロサービスを再構築します。サービスコンシューマーがすぐに応答を必要とするためにキューを含むソリューションが機能しない場合は、ユーザーとウォレットの機能を同じサービス(または少なくとも同じVM内)に配置するように改造し、分散トランザクションを回避します)。はい、これはマイクロサービスから一歩離れて、モノリスに近い段階ですが、頭痛の種からあなたを救います。

4
最終的な一貫性は私のために働いた。この場合、「NewUser」キューは可用性が高く、回復力がある必要があります。
Ram Bavireddi 2017

@RamBavireddi KafkaまたはRabbitMQは回復力のあるキューをサポートしますか?
v.oddou 2018年

@ v.oddouはい、そうです。
Ram Bavireddi、2018年

2
@PauloMerson結果の一貫性に対する補償トランザクションの違いがわかりません。もしあなたの最終的な一貫性において、ウォレットの作成が失敗した場合はどうなりますか?
バルシック2018年

2
@balsick結果整合性設定の課題の1つは、設計の複雑さの増加です。多くの場合、整合性チェックと修正イベントが必要です。ソリューションの設計はさまざまです。答えとして、メッセージブローカー経由で送信されたメッセージを処理するときに、データベースにウォレットレコードが作成される状況をお勧めします。この場合、デッドレターチャネルを設定できます。つまり、そのメッセージの処理でエラーが発生した場合は、メッセージをデッドレターキューに送信して、「ウォレット」の担当チームに通知できます。
Paulo Merson

66

これは最近のインタビューで私が尋ねられた典型的な質問です。複数のWebサービスを呼び出し、タスクの途中で何らかのエラー処理を維持する方法。今日、高性能コンピューティングでは、2フェーズコミットを回避しています。私は何年も前に、トランザクションの「スターバックモデル」と呼ばれるものについての論文を読みました。スターバックで注文したコーヒーの注文、支払い、準備、受け取りのプロセスについて考えてみてください...単純化しすぎますが、2フェーズコミットモデルではあなたがあなたのコーヒーを受け取るまで、プロセス全体が含まれるすべてのステップのための単一のラッピングトランザクションであることを提案します。ただし、このモデルでは、コーヒーを手に入れるまですべての従業員が待機し、作業を停止します。あなたは絵を見ますか?

その代わり、「Starbuckモデル」は「ベストエフォート」モデルに従い、プロセスのエラーを補正することで、より生産性を高めます。まず、彼らはあなたが支払うことを確認します!次に、注文がカップに添付されたメッセージキューがあります。あなたがコーヒーを手に入れなかった、それがあなたが注文したものではない、などのプロセスで何かがうまくいかない場合、私たちは補償プロセスに入り、あなたが欲しいものを手に入れるか、あなたに返金することを確認します、これは最も効率的なモデルです生産性を向上させます。

時々、スターバックはコーヒーを浪費していますが、全体的なプロセスは効率的です。Webサービスを構築するときに、何度でも呼び出すことができ、同じ最終結果を提供できるようにWebサービスを設計する場合など、他にも考えなければならないトリックがあります。だから、私の推薦は:

  • Webサービスを定義するときに細かくなりすぎないでください(最近のマイクロサービスの誇大宣伝については確信がありません。行き過ぎのリスクが多すぎます)。

  • 非同期はパフォーマンスを向上させるため、非同期であることが優先され、可能な場合はいつでも電子メールで通知を送信します。

  • よりインテリジェントなサービスを構築し、何度でも「呼び出し可能」にして、最後まで注文の下から上に続くuidまたはtaskidで処理し、各ステップでビジネスルールを検証します。

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

  • 最後の手段として(頻繁には発生しない可能性があるため)、エラーを手動で処理するためにキューに入れます。

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

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

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

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

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

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

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

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

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

これは同期の例です。別の方法で管理し、システムにエラーを完全に回復させたくない場合は、ヘルプデスクを対象とするメッセージキューにケースを入れることもできます。これが十分ではない会社で実行されているのを見てきました。ヘルプデスクは、正常に実行された内容を含むメッセージを受け取り、完全に自動化された方法で取り消しレシートを使用できるように、問題を修正するのに十分な情報を得ました。

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

補償トランザクションパターン


2
この答えを拡張して、OPにさらに具体的なアドバイスを提供できると思いますか。現状では、この回答はやや曖昧で理解しにくいものです。スターバックスでコーヒーがどのように提供されるかは理解していますが、RESTサービスでこのシステムのどの側面をエミュレートする必要があるのか​​は不明です。
jwg 2017年

最初の投稿で最初に提供されたケースに関連する例を追加しました。
user8098437 2017年

2
Microsoftが説明するように、補償トランザクションパターンへのリンクを追加しました。
user8098437 2017

3
私にとっては、これが最良の答えです。とてもシンプル
オスカーネバレス

1
トランザクションの補償は、特定の複雑なシナリオでは完全に不可能になる可能性があることに注意してください(Microsoftドキュメントで見事に強調表示されています)。この例では、ウォレットの作成が失敗する前に、アカウントサービスでGET呼び出しを実行することにより、関連付けられたアカウントの詳細を誰かが読み取ることができると想像してください。アカウントの作成が失敗したため、そもそも存在しないはずです。これにより、データの不整合が発生する可能性があります。この分離の問題は、SAGASパターンでよく知られています。
Anmol Singh Jaggi

32

すべての分散システムは、トランザクションの一貫性に問題があります。これを行う最善の方法は、あなたが言ったように、2フェーズコミットを行うことです。ウォレットとユーザーを保留状態で作成します。作成後、別の呼び出しを行ってユーザーをアクティブ化します。

この最後の呼び出しは、安全に繰り返すことができます(接続が切断された場合)。

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

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


提案をありがとう。ユーザー/ウォレットサービスは、要点を説明するためだけに架空のものでした。ただし、トランザクションの必要性をできるだけ回避するようにシステムを設計する必要があることに同意します。
Olivier Lalonde 2015年

7
私は第二の見方に同意します。このオペレーションはアトミックな作業単位を表すため、ユーザーを作成するマイクロサービスもウォレットを作成する必要があります。また、このeaipatterns.com/docs/IEEE_Software_Design_2PC.pdf
Sattar

2
これは実際には素晴らしいアイデアです。アンドゥは頭痛の種です。しかし、保留中の状態で何かを作成することは、それほど侵襲的ではありません。すべてのチェックが実行されましたが、確定的なものはまだ作成されていません。これで、作成したコンポーネントをアクティブにするだけで済みます。おそらくそれを非トランザクションで行うこともできます。
ティモ

10

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

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


1
2PCをすべて避けたいと想定し、ユーザーサービスがデータベースに書き込むと想定すると、ユーザーがメッセージをイベントキューにプッシュしてトランザクションに対応させることはできません。ウォレットサービス。
Roman

@RomanKharkovski確かに重要なポイントです。これに取り組む1つの方法は、トランザクションを開始し、ユーザーを保存し、(トランザクションの一部ではなく)イベントを発行してから、トランザクションをコミットすることです。(最悪の場合、非常にまれに、コミットが失敗し、イベントに応答するユーザーがユーザーを見つけることができなくなります。)
Timo

1
次に、イベントをデータベースとエンティティに格納します。保存されたイベントを処理してメッセージブローカーに送信するようにスケジュールされたジョブを用意します。stackoverflow.com/a/52216427/4587961
Yan Khonski、

7

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

ウォレット作成コードで他のシステムにタッチする必要がある場合にどうなるかについて質問しているようですね。それはすべて、作成プロセスがどれほど複雑でリスクが高いかに依存していると言います。

それが別の信頼できるデータストア(たとえば、SQLトランザクションに参加できないもの)に触れるだけの問題である場合、システム全体のパラメーターによっては、2番目の書き込みが発生しないという非常に小さな可能性を危険にさらす可能性があります。私は何もしないかもしれませんが、例外を発生させ、補償トランザクションまたはいくつかのアドホックな方法で一貫性のないデータを処理します。開発者にいつも言っているように、「この種のことがアプリで起こっていても、見過ごされることはありません」。

ウォレット作成の複雑さとリスクが高まるにつれ、関連するリスクを改善するための対策を講じる必要があります。一部のステップで複数のパートナーAPIを呼び出す必要があるとしましょう。

この時点で、部分的に構築されたユーザーやウォレットの概念とともにメッセージキューを導入できます。

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

また、プロビジョニングプロセスで失敗しがちなステップが発生した理由についても、長く懸命に考えます。


4

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


3

この種のデータの不整合が発生しないようにするには、どのような解決策がありますか?

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

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

SOAP(RESTではなく)の場合、WS-AT仕様がありますが、これまでに統合する必要があったサービスでサポートされていません。RESTの場合、JBossはパイプラインに何かを持っています。それ以外の場合、「パターン」は、アーキテクチャにプラグインできる製品を見つけるか、独自のソリューションを構築することです(非推奨)。

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

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

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

あるいは、RESTがこのユースケースに適していない可能性があることも知っています。この状況を処理してRESTを完全に削除し、メッセージキューシステムのような別の通信プロトコルを使用する正しい方法でしょうか?

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

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

または、アプリケーションコードに整合性を適用する必要がありますか(たとえば、不整合を検出して修正するバックグラウンドジョブを実行するか、ユーザーモデルに「作成」、「作成」の値などの「状態」属性を設定するなど)。

悪魔の擁護者を演じる:あなたのためにそれを行う製品がある場合(上記を参照)、なぜそのようなものを構築し、おそらく彼らは試されてテストされているので、あなたができるよりもうまくいくでしょうか?


2

個人的にはマイクロサービス、ユースケースで定義されたモジュールのアイデアが好きですが、質問にあるように、銀行、保険、通信などの古典的なビジネスには適応の問題があります...

多くの人が言及したように、分散トランザクションは良い選択ではありません。人々は今や最終的に一貫したシステムを求めていますが、これが銀行や保険などで機能するかどうかはわかりません...

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

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


0

ここでは、結果の一貫性が重要です。

  • サービスの1つがイベントのプライマリハンドラーとして選択されます。
  • このサービスは、単一のコミットで元のイベントを処理します。
  • プライマリハンドラは、セカンダリエフェクトを他のサービスに非同期で通信する責任を負います。
  • プライマリハンドラは、他のサービス呼び出しのオーケストレーションを行います。

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

指揮官は、すべての命令の実行を保証する責任を負います。つまり、退職します。司令官がリモート更新を実行しようとして応答がない場合、再試行は行われません。このようにして、障害が発生しにくくなるようにシステムを構成でき、システム自体が修復されます。

再試行があるため、べき等性があります。べき等性は、最終結果が1回だけ行われた場合と同じになるように、2回何かを行うことができる特性です。リモートサービスまたはデータソースでべき等性が必要なため、複数回命令を受信した場合に1回しか処理されません。

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

一貫性は最終的なものです。つまり、たとえば顧客が本を注文し、支払いを行ってから在庫数を更新した場合など、システムが再試行中に一貫性のない状態になります。在庫更新操作が失敗し、それが利用可能な最後の在庫であると仮定した場合、在庫更新の再試行操作が成功するまで、書籍は引き続き利用できます。再試行が成功した後、システムは一貫したものになります。


-2

スクリプト/プログラミングをサポートするAPI管理(APIM)プラットフォームを使用しないのはなぜですか?したがって、マイクロサービスを妨げることなく、APIMで複合サービスを構築できます。私はこの目的のためにAPIGEEを使用して設計しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.