web-dev-qa-db-ja.com

マイクロサービスとのDBの整合性

マイクロサービスベースのシステムでDBの一貫性を実現するための最良の方法は何ですか?

ベルリンのGOTO で、Martin Fowlerはマイクロサービスについて話していました。彼が言及した「ルール」の1つは、「サービスごとの」データベースを保持することでした。つまり、サービスは「所有」するDBに直接接続できません。別のサービス。

これはとても素敵でエレガントですが、実際には少し注意が必要です。いくつかのサービスがあるとします。

  • フロントエンド
  • 注文管理サービス
  • ロイヤルティプログラムサービス

これで、顧客がフロントエンドで購入を行います。これにより、注文管理サービスが呼び出され、DBにすべてが保存されます。問題ありません。この時点で、ロイヤルティプログラムサービスへの呼び出しもあり、アカウントからポイントを貸方/借方に記入します。

これで、すべてが同じDB/DBサーバー上にある場合、1つのトランザクションですべてを実行できるため、すべてが簡単になります。ポイントプログラムサービスがDBへの書き込みに失敗した場合、すべてをロールバックできます。

複数のサービス全体でDB操作を行う場合、1つの接続に依存しない/単一のトランザクションの実行を利用するため、これは不可能です。物事の一貫性を保ち、幸せな生活を送るための最良のパターンは何ですか?

私はあなたの提案を聞いてとても熱心です!..そして事前に感謝します!

30
odino

これはとても素敵でエレガントですが、実際には少しトリッキーになります

「実際に」とは、次のルールに従うときに必要なビジネスの一貫性が満たされるようにマイクロサービスを設計する必要があることを意味します。

そのサービスは、別のサービスが「所有する」DBに直接接続することはできません。

言い換えれば、彼らの責任について何の仮定もせず、それを機能させる方法が見つかるまで、必要に応じて境界を変更してください。

さて、あなたの質問に:

物事の一貫性を保ち、幸せな生活を送るための最良のパターンは何ですか?

即時の一貫性を必要とせず、ポイントの更新がそのカテゴリに分類されるように思われる場合は、信頼できるpub/subパターンを使用して、あるマイクロサービスからイベントをディスパッチして他のマイクロサービスで処理できます。信頼できるビットは、イベント処理のものに対して、適切な再試行、ロールバック、およびべき等(またはトランザクション性)が必要なことです。

.NETで実行している場合、この種の信頼性をサポートするインフラストラクチャの例には、 NServiceBus および MassTransit が含まれます。完全な開示-私はNServiceBusの創設者です。

更新:ポイントに関する懸念に関するコメントに続いて:「残高の更新が遅れて処理される場合、顧客は実際には彼らよりも多くのアイテムを注文できる可能性がありますのポイントがあります。

多くの人々は、強い一貫性のためにこれらの種類の要件に苦労しています。問題は、これらの種類のシナリオは通常、ユーザーが負のロイヤルティポイントで通知する場合など、追加のルールを導入することで対処できるということです。ポイントが整理されずにTが経過した場合は、コンバージョン率に基づいてMが請求されることをユーザーに通知します。このポリシーは、顧客がポイントを使用して商品を購入するときに表示される必要があります。

11
Udi Dahan

私は通常マイクロサービスを扱っていません。これは物事を行うための良い方法ではないかもしれませんが、ここにアイデアがあります。

問題を言い換えると、システムは、フロントエンド、注文管理バックエンド、およびロイヤルティプログラムバックエンドの3つの独立しているが通信する部分で構成されています。フロントエンドは、注文管理バックエンドとポイントプログラムバックエンドの両方で何らかの状態が確実に保存されるようにしたいと考えています。

考えられる解決策の1つは、ある種の 2フェーズコミット を実装することです。

  1. まず、フロントエンドは、すべてのデータを含むレコードを独自のデータベースに配置します。これをフロントエンドレコードと呼びます。
  2. フロントエンドは注文管理バックエンドにトランザクションIDを要求し、アクションを完了するために必要なデータをすべて渡します。注文管理バックエンドは、このデータをステージング領域に保存し、新しいトランザクションIDを関連付けて、フロントエンドに返します。
  3. 注文管理トランザクションIDは、フロントエンドレコードの一部として保存されます。
  4. フロントエンドは、ロイヤルティプログラムのバックエンドにトランザクションIDを要求し、アクションを完了するために必要なデータをすべて渡します。ポイントプログラムのバックエンドは、このデータをステージング領域に保存し、新しいトランザクションIDを関連付けて、フロントエンドに返します。
  5. ポイントプログラムのトランザクションIDは、フロントエンドレコードの一部として保存されます。
  6. フロントエンドは、フロントエンドが格納したトランザクションIDに関連付けられたトランザクションを完了するように注文管理バックエンドに指示します。
  7. フロントエンドは、ロイヤルティプログラムのバックエンドに、フロントエンドに保存されているトランザクションIDに関連付けられたトランザクションを完了するように指示します。
  8. フロントエンドはフロントエンドレコードを削除します。

これが実装されている場合、変更は必ずしもアトミックである必要はありませんが、結果整合性になります。失敗する可能性のある場所について考えてみましょう。

  • 最初のステップで失敗した場合、データは変更されません。
  • 2番目、3番目、4番目、または5番目に失敗した場合、システムがオンラインに戻ると、すべてのフロントエンドレコードをスキャンして、関連付けられたトランザクションID(いずれかのタイプ)のないレコードを探すことができます。そのようなレコードに遭遇した場合、ステップ2から再生できます(ステップ3または5で障害が発生した場合、バックエンドにいくつかの破棄されたレコードが残りますが、ステージング領域から移動されることはありません。大丈夫です。)
  • 6番目、7番目、または8番目のステップで失敗した場合、システムがオンラインに戻ると、両方のトランザクションIDが入力されたすべてのフロントエンドレコードを検索できます。次に、バックエンドにクエリを実行して、これらのトランザクションの状態(コミット済みまたは非コミット済み)を確認できます。 。どちらがコミットされているかに応じて、適切なステップから再開できます。
7
icktoofay

分散トランザクションの場合でも、トランザクションの途中で参加者の1人がクラッシュすると、「トランザクションが疑わしいステータス」になる可能性があります。サービスをべき等操作として設計すると、生活が少し楽になります。 XAなしでビジネス条件を満たすプログラムを書くことができます。 Pat Hellandは、これについて「LifeBeyondXA」と呼ばれる優れた論文を書いています。基本的に、アプローチは、リモートエンティティについて可能な限り最小限の仮定を行うことです。彼はまた、ビジネスプロセスをモデル化するためのOpen Nested Transactions( http://www.cidrdb.org/cidr2013/Papers/CIDR13_Paper142.pdf )と呼ばれるアプローチを説明しました。この特定のケースでは、購入トランザクションはトップレベルのフローになり、ロイヤルティと注文管理は次のレベルのフローになります。秘訣は、きめ細かいサービスを補償ロジックを備えたべき等サービスとして作成することです。したがって、フローのどこかで何かが失敗した場合、個々のサービスがそれを補うことができます。だから例えば何らかの理由で注文が失敗した場合、ロイヤルティはその購入の未払いポイントを差し引くことができます。

他のアプローチは、CALMまたはCRDTを使用して結果整合性を使用してモデル化することです。実生活でCALMを使用することを強調するブログを書きました http://shripad-agashe.github.io/2015/08/Art-Of-Disorderly-Programming それはあなたを助けるかもしれません。

0
Shripad

@UdiDahanの言ったことに同意します。彼の答えに追加したいだけです。

ロイヤルティプログラムへのリクエストを永続化して、失敗した場合に別の時点で実行できるようにする必要があると思います。 Word /これを行うにはさまざまな方法があります。

1)ロイヤルティプログラムのAPI障害を回復可能にします。つまり、リクエストが失われないように永続化でき、後で回復(再実行)できるということです。

2)ロイヤルティプログラムリクエストを非同期で実行します。つまり、最初にリクエストをどこかに永続化してから、サービスがこの永続化されたストアからリクエストを読み取れるようにします。正常に実行された場合にのみ、永続ストアから削除します。

3)Udiが言ったことを実行し、それを適切なキューに配置します(正確にはpub/subパターン)。これには通常、サブスクライバーが次の2つのいずれかを実行する必要があります...キューから削除する前にリクエストを永続化する(goto 1)-または-最初にキューからリクエストを借用し、リクエストを正常に処理した後、リクエストを取得しますキューから削除されました(これが私の好みです)。

3つすべてが同じことを達成します。リクエストは、正常に完了するまで作業できる永続的な場所に移動します。リクエストが失われることはなく、必要に応じて、満足のいく状態に達するまで再試行されます。

リレーの例を使うのが好きです。各サービスまたはコードは、前のコードがリクエストを手放す前に、リクエストの所有権を保持する必要があります。渡された後、現在の所有者は、要求が処理されるか、他のコードに渡されるまで、要求を失ってはなりません。

0
Jose Martinez