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;
}


message

マイクロサービスはブロードキャストに関係します。これはエンティティ

Message

とその周辺のすべてをカプセル化しています。

@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二相コミットプロトコル


https://en.wikipedia.org/wiki/Two-phase


commit

protocol[Two-phase commit protocol](または2PC)は、異なるソフトウェアコンポーネント(複数のデータベース、メッセージキューなど)にわたるトランザクションを実装するためのメカニズムです


3.1. 2PC

のアーキテクチャー

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

  • 準備段階 – この段階では、トランザクションのすべての参加者

コミットの準備をし、コーディネーターに準備ができていることを通知する
トランザクションを完了する
** コミットまたはロールバックフェーズ – このフェーズでは、コミットまたは

ロールバックコマンドは、トランザクションコーディネータによってすべての参加者に発行されます。

2PCの問題は、単一のマイクロサービスを操作する時間に比べてかなり遅いということです。

マイクロサービス間のトランザクションを調整することは、たとえそれらが同じネットワーク上にあったとしても、本当にシステムを遅くする可能性があるので** 、このアプローチは通常高負荷シナリオでは使用されません。


3.2. XA規格


XA規格

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

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

  • ただし、このメカニズムを利用するには、リソースを単一のJTAプラットフォームにデプロイする必要があります。これは、マイクロサービスアーキテクチャには必ずしも適していません。


3.3. REST-AT標準ドラフト

もう1つの提案された標準はhttps://github.com/jbosstm/documentation/tree/master/rts/docs[REST-AT]で、RedHatによる開発を経ていますが、まだドラフト段階から抜け出していません。ただし、WildFlyアプリケーションサーバーはそのまま使用できます。

この標準では、分散トランザクションを作成して参加させるための特定のREST APIを持つトランザクションコーディネーターとしてアプリケーションサーバーを使用できます。

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

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


4最終的な一貫性と補償

  • これまでのところ、マイクロサービス間で一貫性を処理するための最も実現可能なモデルの1つはhttps://en.wikipedia.org/wiki/Eventual__consistencyつき[常にの一貫性]です。

このモデルでは、マイクロサービス間での分散ACIDトランザクションは強制されません。代わりに、将来のある時点でシステムが最終的に整合性を保つようにするためのメカニズムを使用することを提案します。


4.1. 最終的な一貫性のためのケース

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

  • ユーザープロフィールを登録する

  • ユーザーが実際にアクセスできることを自動バックグラウンドチェックで確認する

システム

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

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

  • それを解決する1つの方法は、報酬を含むメッセージ駆動型のアプローチを使用することです。


  • user

    マイクロサービスはユーザープロファイルの登録を任されています

バックグラウンドチェックを行うことを課された

validation

マイクロサービス

永続キューをサポートするメッセージングプラットフォーム

メッセージングプラットフォームは、マイクロサービスによって送信されたメッセージが確実に保持されるようにすることができる。受信機が現在利用できなかった場合、それらは後で配信されるでしょう


4.2. ハッピーシナリオ

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


  • user

    マイクロサービスはユーザーを登録し、彼女に関する情報を保存します

ローカルデータベース内
**

user

マイクロサービスは、このユーザーにフラグを付けます。それは意味することができます

このユーザーはまだ検証されておらず、フルアクセス権がありません。
システム機能
** 登録確認がユーザーに送信されます。

システムのすべての機能にすぐにアクセスできるわけではありません
**

user

マイクロサービスは

validation

にメッセージを送信します。

ユーザーのバックグラウンドチェックを行うためのマイクロサービス
**

validation

マイクロサービスはバックグラウンドチェックを実行し、

検査結果を含む

user

マイクロサービスへのメッセージ


結果が肯定的であれば、

user

マイクロサービスはブロックを解除します

ユーザー


結果が否定的であれば、

user

マイクロサービスはユーザーを削除します

アカウント

これらのステップをすべて実行した後、システムは一貫した状態になるはずです。しかしながら、しばらくの間、ユーザエンティティは不完全な状態にあるように見えました。

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


4.3. 失敗シナリオ

それでは、いくつかの失敗シナリオを考えてみましょう。


  • validation

    マイクロサービスにアクセスできない場合は、メッセージング

永続キュー機能を備えたプラットフォームでは、

validation

マイクロサービスはしばらくしてからこのメッセージを受信します
** メッセージングプラットフォームが失敗し、次に

user

マイクロサービスが失敗したとします。

たとえば、次のようにしてメッセージをもう一度送信しようとします。
まだ検証されていないすべてのユーザーのスケジュールされたバッチ処理
**

validation

マイクロサービスがメッセージを受信した場合は、

メッセージングプラットフォームのため、ユーザーが回答を返送できない
失敗、

validation

マイクロサービスもメッセージの送信を再試行する
しばらくして
** メッセージの1つが紛失した場合、または他の何らかの障害が発生した場合は、


user

microserviceは、スケジュールされたバッチ処理によってすべての未検証ユーザーを見つけ、検証要求を再度送信します。

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

  • 考えられるすべての障害シナリオを慎重に検討することで、システムが最終的な一貫性の条件を満たすことを保証できます。同時に、コストのかかる分散トランザクションに対処する必要はありません。

しかし、最終的な一貫性を確保することは複雑な作業であることを認識しておく必要があります。すべての場合に単一の解決策があるわけではありません。


5結論

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

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