1. 序章

この記事では、マイクロサービス間でトランザクションを実装するためのオプションについて説明します。

また、分散マイクロサービスシナリオでのトランザクションの代替案についても確認します。

2. マイクロサービス間でのトランザクションの回避

分散トランザクションは非常に複雑なプロセスであり、多くの可動部分が失敗する可能性があります。 また、これらのパーツが異なるマシンまたは異なるデータセンターで実行されている場合、トランザクションをコミットするプロセスは非常に長くなり、信頼性が低下する可能性があります。

これは、ユーザーエクスペリエンスとシステム全体の帯域幅に深刻な影響を与える可能性があります。 したがって、分散トランザクションの問題を解決する最良の方法の1つは、分散トランザクションを完全に回避することです。

2.1. トランザクションを必要とするアーキテクチャの例

通常、マイクロサービスは、それ自体が独立していて役立つように設計されています。 それはいくつかのアトミックなビジネスタスクを解決できるはずです。

システムをそのようなマイクロサービスに分割できれば、それらの間でトランザクションを実装する必要がまったくない可能性が高くなります。

たとえば、ユーザー間のブロードキャストメッセージングのシステムについて考えてみましょう。

user マイクロサービスは、次の基になるドメインクラスを持つユーザープロファイル(新しいユーザーの作成、プロファイルデータの編集など)に関係します。

@Entity
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Basic
    private String name;

    @Basic
    private String surname;

    @Basic
    private Instant lastMessageTime;
}

メッセージマイクロサービスはブロードキャストに関係します。 エンティティメッセージとその周辺のすべてをカプセル化します。

@Entity
public class Message implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Basic
    private long userId;

    @Basic
    private String contents;

    @Basic
    private Instant messageTimestamp;

}

各マイクロサービスには独自のデータベースがあります。 message マイクロサービスからユーザークラスにアクセスできないため、エンティティMessageからエンティティUserを参照していないことに注意してください。 IDのみでユーザーを参照します。

これで、UserエンティティにlastMessageTimeフィールドが含まれるようになりました。これは、彼女のプロファイルに最後のユーザーアクティビティ時間に関する情報を表示するためです。

ただし、ユーザーに新しいメッセージを追加して lastMessageTime を更新するには、マイクロサービス間でトランザクションを実装する必要があります。

2.2. トランザクションなしの代替アプローチ

マイクロサービスアーキテクチャを変更し、UserエンティティからフィールドlastMessageTimeを削除できます。

次に、メッセージマイクロサービスに個別のリクエストを発行し、このユーザーのすべてのメッセージの最大 messageTimestamp 値を見つけることで、この時間をユーザープロファイルに表示できます。

おそらく、 message マイクロサービスの負荷が高いか、さらにはダウンしている場合、ユーザーのプロファイルにユーザーの最後のメッセージの時刻を表示することはできません。

ただし、ユーザーのマイクロサービスが時間内に応答しなかったという理由だけで、メッセージを保存するための分散トランザクションのコミットに失敗するよりも、それは受け入れられる可能性があります。

もちろん、複数のマイクロサービスにまたがってビジネスプロセスを実装する必要があり、それらのマイクロサービス間の不整合を許容したくない場合は、より複雑なシナリオがあります。

3. 2フェーズコミットプロトコル

2フェーズコミットプロトコル(または2PC)は、さまざまなソフトウェアコンポーネント(複数のデータベース、メッセージキューなど)間でトランザクションを実装するためのメカニズムです。

3.1. 2PCのアーキテクチャ

分散トランザクションの重要な参加者の1つは、トランザクションコーディネーターです。 分散トランザクションは、次の2つのステップで構成されます。

  • 準備フェーズ—このフェーズでは、トランザクションのすべての参加者がコミットの準備をし、トランザクションを完了する準備ができていることをコーディネーターに通知します
  • コミットまたはロールバックフェーズ—このフェーズでは、コミットまたはロールバックコマンドがトランザクションコーディネーターによってすべての参加者に発行されます。

2PCの問題は、単一のマイクロサービスの操作に比べて非常に遅いことです。

マイクロサービス間のトランザクションを調整すると、同じネットワーク上にある場合でも、システムの速度が大幅に低下する可能性があるため、このアプローチは通常、高負荷のシナリオでは使用されません。

3.2. XA標準

XA標準は、サポートリソース全体で2PC分散トランザクションを実行するための仕様です。 JTA準拠のアプリケーションサーバー(JBoss、GlassFishなど)は、すぐに使用できます。

分散トランザクションに参加するリソースは、たとえば、2つの異なるマイクロサービスの2つのデータベースである可能性があります。

ただし、このメカニズムを利用するには、リソースを単一のJTAプラットフォームにデプロイする必要があります。 これは、マイクロサービスアーキテクチャで常に実行可能であるとは限りません。

3.3. REST-AT標準ドラフト

もう1つの提案された標準は、 REST-AT です。これは、RedHatによって開発されましたが、ドラフト段階から抜け出すことはできませんでした。 ただし、WildFlyアプリケーションサーバーですぐにサポートされます。

この標準では、分散トランザクションを作成および参加するための特定のRESTAPIを備えたトランザクションコーディネーターとしてアプリケーションサーバーを使用できます。

2フェーズトランザクションに参加したいRESTfulWebサービスも、特定のRESTAPIをサポートする必要があります。

残念ながら、分散トランザクションをマイクロサービスのローカルリソースにブリッジするには、これらのリソースを単一のJTAプラットフォームにデプロイするか、このブリッジを自分で作成するという重要なタスクを解決する必要があります。

4. 結果整合性と補償

これまでのところ、マイクロサービス全体の整合性を処理するための最も実現可能なモデルの1つは、結果整合性です。

このモデルは、マイクロサービス間で分散ACIDトランザクションを強制しません。 代わりに、システムが将来のある時点で結果整合性を持つことを保証するいくつかのメカニズムを使用することを提案します。

4.1. 結果整合性の事例

たとえば、次のタスクを解決する必要があるとします。

  • ユーザープロファイルを登録する
  • ユーザーが実際にシステムにアクセスできることを自動バックグラウンドチェックします

2番目のタスクは、たとえば、このユーザーが何らかの理由でサーバーから禁止されていないことを確認することです。

ただし、時間がかかる可能性があるため、別のマイクロサービスに抽出したいと思います。 彼女が正常に登録されたことを知るためだけにユーザーを長く待たせるのは合理的ではありません。

これを解決する1つの方法は、補償を含むメッセージ駆動型のアプローチを使用することです。次のアーキテクチャについて考えてみましょう。

  • ユーザープロファイルの登録を担当するuserマイクロサービス
  • 検証マイクロサービスは、身元調査を行うことを任務としています
  • 永続キューをサポートするメッセージングプラットフォーム

メッセージングプラットフォームは、マイクロサービスによって送信されたメッセージが永続化されることを保証できます。 その後、受信機が現在利用できない場合は、後で配信されます

4.2. ハッピーシナリオ

このアーキテクチャでは、幸せなシナリオは次のようになります。

  • user マイクロサービスはユーザーを登録し、そのユーザーに関する情報をローカルデータベースに保存します
  • user マイクロサービスは、このユーザーにフラグを付けます。 このユーザーはまだ検証されておらず、システムの全機能にアクセスできないことを示している可能性があります
  • 登録の確認がユーザーに送信され、システムのすべての機能にすぐにアクセスできるわけではないという警告が表示されます
  • userマイクロサービスはvalidationマイクロサービスにメッセージを送信して、ユーザーの身元調査を行います
  • validation マイクロサービスはバックグラウンドチェックを実行し、チェックの結果を含むメッセージをuserマイクロサービスに送信します
    • 結果が正の場合、userマイクロサービスはユーザーのブロックを解除します
    • 結果が否定的な場合、userマイクロサービスはユーザーアカウントを削除します

これらの手順をすべて実行すると、システムは一貫した状態になります。 ただし、しばらくの間、ユーザーエンティティは不完全な状態にあるように見えました。

ユーザーマイクロサービスが無効なアカウントを削除する最後のステップは、補償フェーズです

4.3. 障害シナリオ

次に、いくつかの障害シナリオを考えてみましょう。

  • validation マイクロサービスにアクセスできない場合、永続キュー機能を備えたメッセージングプラットフォームにより、validationマイクロサービスが後でこのメッセージを受信することが保証されます。
  • メッセージングプラットフォームに障害が発生した場合、 user マイクロサービスは、たとえば、まだ検証されていないすべてのユーザーのスケジュールされたバッチ処理によって、後でメッセージを再送信しようとします。
  • validation マイクロサービスがメッセージを受信し、ユーザーを検証したが、メッセージングプラットフォームの障害のために応答を返送できない場合、validationマイクロサービスも後でメッセージの送信を再試行します
  • メッセージの1つが失われた場合、またはその他の障害が発生した場合、 user マイクロサービスは、スケジュールされたバッチ処理によって検証されていないすべてのユーザーを検出し、検証のリクエストを再度送信します

一部のメッセージが複数回発行された場合でも、これはマイクロサービスのデータベース内のデータの整合性に影響を与えません。

考えられるすべての障害シナリオを注意深く検討することにより、システムが結果整合性の条件を満たすことを保証できます。 同時に、コストのかかる分散トランザクションを処理する必要はありません。

ただし、結果整合性を確保することは複雑な作業であることに注意する必要があります。 すべての場合に単一のソリューションがあるわけではありません。

5. 結論

この記事では、マイクロサービス間でトランザクションを実装するためのメカニズムのいくつかについて説明しました。

また、そもそもこのスタイルのトランザクションを実行するためのいくつかの代替案についても検討しました。