ビジネスの急速な発展とビジネスの複雑さの増大に伴い、ほぼすべての企業のシステムはモノリシックから分散へと移行します。マイクロサービスアーキテクチャへ。その場合、必然的に分散トランザクションの問題に遭遇することになります。
この記事では、最初に関連する基本理論を紹介し、次に最も古典的なトランザクションの解決策を要約し、最後にサブトランザクションのアウトオブオーダー実行 (冪等性、ヌル補償、およびハング問題) の解決策を示します。みんなでシェアしましょう。
基本理論
具体的なソリューションを説明する前に、まず分散トランザクションに関連する基本的な理論的知識を理解しましょう。
送金を例に挙げます。A は B に 100 元を送金する必要があります。すると、A への残高は -100 元、B への残高は 100 元になります。送金全体で A-100 が保証される必要があります。と B 100 が同時に成功するか、両方が同時に失敗します。さまざまなシナリオでこの問題がどのように解決されるかを見てみましょう。
トランザクション
複数のステートメントをまとめて操作する機能をデータベーストランザクションと呼びます。データベース トランザクションでは、トランザクションの範囲内のすべての操作が成功または失敗する可能性があることが保証されます。
トランザクションには、原子性、一貫性、分離性、耐久性という 4 つの属性があります。これら 4 つのプロパティは、多くの場合、ACID プロパティと呼ばれます。
- 原子性 (アトミック性): トランザクション内のすべての操作は完全に完了するか、完了しないかのいずれかであり、中間リンクで終了することはありません。トランザクションの実行中にエラーが発生した場合は、トランザクションが実行されなかったかのように、トランザクションが開始される前の状態に復元されます。
- Consistency (一貫性): トランザクションの開始前とトランザクションの終了後にデータベースの整合性が破壊されません。外部キー制約、アプリケーション定義の制約などを含む整合性は破壊されません。
- 分離: データベースでは、複数の同時トランザクションが同時にデータの読み取り、書き込み、変更を行うことができます。分離により、複数のトランザクションが同時に実行される場合のクロス実行によるデータの不整合を防ぐことができます。
- 耐久性: トランザクション完了後のデータへの変更は永続的であり、システムに障害が発生した場合でも失われることはありません。
当社のビジネス システムが複雑でなく、1 つのデータベースと 1 つのサービスでデータの変更と転送を完了できる場合は、データベース トランザクションを使用して転送ビジネスを正しく完了することができます。
分散トランザクション
銀行間送金ビジネスは典型的な分散トランザクション シナリオです。A が銀行をまたいで B に送金する必要があるとします。この場合、これには 2 つの銀行のデータが含まれるため、通過することはできません。 1 つのデータベースのローカル データベース トランザクション保証転送の ACID は、分散トランザクションを通じてのみ解決できます。
分散トランザクションとは、トランザクションの開始者、リソースとリソース マネージャー、およびトランザクション コーディネーターが分散システムの異なるノードに配置されていることを意味します。上記転送業務では、ユーザA−100の業務とユーザB100の業務は同一ノード上に存在しない。本質的に、分散トランザクションは、分散シナリオでのデータ操作の正しい実行を保証することを目的としています。
分散環境における分散トランザクションは、可用性、パフォーマンス、劣化したサービスのニーズを満たし、一貫性と分離の要件を軽減するために、一方では BASE 理論 (BASE 関連の理論、多くの内容が含まれています。興味のある学生は BASE 理論を参照してください):
- Basic Business Availability (Basic Availability)
- Soft state (Soft state)
- Eventual Consistency一貫性)
同様に、分散トランザクションも ACID 仕様に部分的に準拠します。
- 原子性: 厳密に準拠します
- 一貫性: トランザクション完了後の一貫性は次のとおりです。厳密に守られます。トランザクションの一貫性は適切に緩和できます。
- 分離: 並列トランザクションは影響を受けません。中間トランザクション結果の可視性によりセキュリティを緩和できます
- 永続性: 厳密に守られます
分散トランザクション ソリューション
分散トランザクション ソリューションのため、完全な ACID 保証は達成できません。すべてのビジネス上の問題を解決できる完璧なソリューションはありません。したがって、実際のアプリケーションでは、ビジネスのさまざまな特性に基づいて最適な分散トランザクション ソリューションが選択されます。
Two-phase commit/XA
XA は、X/Open 組織によって提案された分散トランザクション仕様です。XA 仕様では、主に (グローバル) トランザクション マネージャー (TM) と (ローカル) トランザクション マネージャーが定義されています。リソース マネージャー (RM) 間のインターフェイス。 mysql などのローカル データベースは、XA
XA で RM の役割を果たします。
XA は 2 つのフェーズに分かれています。
最初のフェーズ (準備): つまり、すべての参加者がトランザクションの実行とロックの準備をします。生きていくために必要な資源。参加者の準備が完了すると、参加者は準備ができたことを TM に報告します。
現在主流のデータベースは、mysql、oracle、sqlserver、postgres などの XA トランザクションを基本的にサポートしています
###XA トランザクションは、1 つ以上のリソース マネージャー (RM)、トランザクション マネージャー (TM)、およびアプリケーション プログラム (ApplicationProgram) で構成されます。 )。 ###ここでの RM、TM、AP の 3 つの役割は古典的な役割分担であり、Saga や Tcc などの後続のトランザクション モードで実行されます。
上記の転送を例として、正常に完了した XA トランザクション シーケンス図は次のとおりです。
参加者のいずれかが準備に失敗した場合、TM はすべての完了を通知します。 Prepare の参加者はロールバックを実行します。
XA トランザクションの特徴は次のとおりです。
- シンプルで理解しやすく、開発が容易です
- リソースの長期ロック、低い同時実行性
XA、Go 言語、PHP、Python、Java、C#、Node などをさらに詳しく学習したい場合は、DTM
SAGA
Saga はこのデータベース ペーパーを参照してください。サガで言及された解決策。基本的なアイデアは、ロング トランザクションを複数のローカル ショート トランザクションに分割し、Saga トランザクション コーディネーターによって調整されることです。正常に終了した場合は正常に完了します。ステップが失敗した場合は、補償操作が逆の順序で 1 回呼び出されます。
上記の転送を例として、正常に完了した SAGA トランザクション シーケンス図は次のとおりです。
Saga がキャンセル ステージに到達したら、キャンセルします。ビジネスロジックの失敗は許されない。ネットワークまたはその他の一時的な障害により成功が返されない場合、TM はキャンセルが成功を返すまで再試行を続けます。
Saga トランザクションの特徴:
- 高い同時実行性、XA トランザクションのように長時間リソースをロックする必要がない
- 通常操作と補正操作を定義する必要があり、開発量が XA Big より大きい
- 一貫性が弱く、送金の場合、ユーザー A がお金を差し引いてしまい、最終的な送金が再び失敗する可能性があります
この文書には、2 種類のリカバリを含む多くの SAGA コンテンツが含まれています。戦略には、ブランチ トランザクションの同時実行が含まれます。ここでの説明には、最も単純な SAGA のみが含まれています。
SAGA は、多くのシナリオ、長いトランザクション、およびビジネス シナリオに適用できます
#読者が SAGA をさらに詳しく学びたい場合は、SAGA の成功と失敗のロールバックの例や、さまざまなネットワーク例外の処理が含まれる DTM を参照できます。 TCCTCC (試行-確認-キャンセル) の概念は、2007 年にパット ヘランドによって「分散トランザクションを超えた生活: 背教者の意見」というタイトルの記事で初めて発表されました。 TCC は 3 つのフェーズに分かれています。- トライ フェーズ: 実行を試み、すべてのビジネス チェックを完了する (一貫性)、必要なビジネス リソースを確保する (準分離)
- 確認フェーズ: ビジネス チェックなしで実行が実際にビジネスを実行し、試行フェーズで予約されたビジネス リソースのみを使用することを確認します。確認操作には冪等な設計が必要であり、確認が失敗した後は再試行が必要です。
- キャンセル フェーズ: 実行をキャンセルし、Try フェーズで確保されたビジネス リソースを解放します。キャンセルフェーズの例外処理スキームは基本的に確認フェーズの例外処理スキームと同じであり、冪等な設計が必要です。
- 高い同時実行性と長期的なリソース ロックなし。
- 開発量が大きいため、試行/確認/キャンセルのインターフェイスを提供する必要があります。
- 一貫性が高く、SAGAで引き落とされたのに送金に失敗するといった事態は発生しません。
- TCCは受注型ビジネスや納期に制約のあるビジネスに適しています。中間ステータス
- 残高控除トランザクションが失敗した場合、トランザクションは後続の手順なしで直接ロールバックされます
- シーケンス生成メッセージが失敗した場合、残高増加トランザクションは失敗します。再試行
- #ローカル メッセージ テーブルの機能:
#長いトランザクションは複数のタスクに分割するだけで済み、使いやすい
- プロデューサーには追加のメッセージ テーブルの作成が必要です
- ##各ローカル メッセージ テーブルをポーリングする必要があります ##コンシューマ ロジックが再試行によって成功しない場合は、操作をロールバックするための追加のメカニズムが必要です
- 非同期に実行でき、後続の操作にロールバックが必要ないビジネスに適用可能
- メッセージの送信 (ハーフ メッセージ)
- サーバーはメッセージを保存し、メッセージの書き込み結果に応答します
- 送信結果に基づいてローカル トランザクションを実行します (書き込みが失敗した場合、この時点では半分のメッセージはビジネスに表示されず、ローカル ロジックは実行されません)
- コミットまたはロールバックを実行ローカル トランザクション ステータスに基づいて (コミット操作によりメッセージがパブリッシュされ、メッセージが消費されます) 表示されます)
- 非同期で実行されるビジネスと後続の操作にロールバックが必要ないアプリケーションに適用されます
- ベスト エフォート通知は、ビジネス通知タイプに適しています。たとえば、WeChat トランザクションの結果は、ベスト エフォート通知を通じて各販売者に通知されます。コールバック通知とトランザクション クエリ インターフェイスの両方があります。
- ビジネス プロセスがリクエスト 4 を実行する場合、 Try の前に Cancel が実行されます。 handle empty rollback
- 業務処理要求 6、Cancel を繰り返し実行する場合、冪等である必要がある
- 業務処理要求 8、Cancel 後に Try を実行し、一時停止処理が必要な場合
トランザクション メッセージ
上記のローカル メッセージ テーブル ソリューションでは、プロデューサーは追加のメッセージ テーブルを作成してローカル メッセージ テーブルをポーリングする必要があり、これにより大きなビジネス負担が生じます。 Alibaba のオープン ソース RocketMQ 4.3 以降ではトランザクション メッセージが正式にサポートされており、このトランザクション メッセージは基本的に RocketMQ 上にローカル メッセージ テーブルを配置し、運用側でのメッセージ送信とローカル トランザクション実行のアトミック性の問題を解決します。
トランザクション メッセージの送信と送信:
通常の送信フローチャートは次のとおりです:
#補償プロセス: コミット/ロールバック メッセージ (保留ステータスのメッセージ) のないトランザクションの場合、サーバーから「レビュー」を開始します。
プロデューサーはレビュー メッセージを受信し、ローカルのステータスを返します。メッセージに対応するトランザクション (コミットまたはロールバック)
トランザクション メッセージ スキームとローカル メッセージ テーブルのメカニズムは非常に似ていますが、主な違いは、元の関連するローカル テーブルの操作がリバース クエリ インターフェイスに置き換えられることです。 ##トランザクション メッセージの特性は次のとおりです:
長いトランザクションは複数のタスクに分割する必要があるだけであり、単純な
- If コンシューマーのロジックが再試行に失敗した場合、操作をロールバックするには追加のメカニズムが必要です。
メッセージ校正メカニズム。最善の努力にもかかわらず受信者が通知されない場合、または受信者がメッセージを消費した後に再度そのメッセージを消費したい場合、受信者は要求を満たすためにメッセージ情報をノーティファイアに積極的に問い合わせることができます。
先ほど紹介したローカルメッセージテーブルとトランザクションメッセージはどちらも信頼できるメッセージですが、ここで紹介したベストエフォート通知とはどう違うのでしょうか?
信頼できるメッセージの一貫性。通知の開始側は、メッセージが送信され、通知の受信側に送信されることを確認する必要があります。メッセージの信頼性の鍵は、通知の開始側によって保証されます。
通知の受信者がインターフェースを通じて業務処理の結果を照会できるようにインターフェースを提供する
- メッセージキュー ACK メカニズム、メッセージ キューは、通知に必要な時間ウィンドウの上限に達するまで、1 分、5 分、10 分、30 分、1 時間、2 時間、5 時間、10 時間の間隔で通知間隔を徐々に増やします。後で通知する必要はありません
空のロールバック:
TCC リソースが呼び出されない場合Try メソッドの場合、2 段階の Cancel メソッドが呼び出されます。Cancel メソッドは、これが空のロールバックであることを認識して、直接成功を返す必要があります。
これは、ブランチトランザクションでサービスダウンやネットワーク異常が発生した場合、ブランチトランザクションの呼び出しが失敗として記録されるためで、このとき実際にはTryフェーズは実行されず、障害が復旧すると、分散トランザクションはロールバックされ、2 段階の Cancel メソッドが呼び出され、空のロールバックが行われます。インポテンス
:どのリクエストにもネットワーク異常やリクエストの繰り返しが存在する可能性があるため、すべての分散トランザクション ブランチは冪等性を確保する必要があります
一時停止:
一時停止とは、分散トランザクションの場合、Try インターフェイスの前に第 2 段階の Cancel インターフェイスが実行されることを意味します。
理由は、RPC がブランチ トランザクション try を呼び出すと、最初にブランチ トランザクションが登録されてから RPC 呼び出しが実行されるためで、このとき RPC が呼び出すネットワークが混雑している場合、RPC タイムアウト後に TM が RM に通知します。おそらくロールバックが完了した後、Try の RPC リクエストがパーティシパントに到達し、実際に実行されます。
上記の問題をよりよく理解するために、ネットワーク異常のタイミング図を見てみましょう。
上記の複雑なネットワーク異常に直面して、さまざまな企業が現在提案している解決策は、ビジネス パーティが一意のキーを使用して、関連する操作が完了したかどうかを問い合わせ、完了している場合は、その操作を照会するというものです。成功を直接返します。関連する判断ロジックは複雑でエラーが発生しやすく、ビジネス上の負担が大きくなります。
サブトランザクションバリア
プロジェクト https://github.com/yedf/dtm で、サブトランザクションバリア技術が登場しました。この技術を使用すると、この効果を実現できます。概略図:
これらのリクエストがすべてサブトランザクション バリアに到達すると、異常なリクエストはフィルタリングされ、通常のリクエストはバリアを通過します。開発者がサブトランザクション バリアを使用すると、上記のすべての例外が適切に処理されるため、ビジネス開発者は実際のビジネス ロジックに集中するだけで済み、負担が大幅に軽減されます。
サブトランザクション バリアは、ThroughBarrierCall メソッドを提供します。このメソッドのプロトタイプは次のとおりです:
func ThroughBarrierCall(db *sql.DB, transInfo *TransInfo, busiCall BusiFunc)
ビジネス開発者は、busiCall に独自の関連ロジックを記述して、この関数を呼び出すことができます。 ThroughBarrierCall は、空のロールバックや一時停止などのシナリオで BusiCall が呼び出されないことを保証します。ビジネスが繰り返し呼び出される場合、ビジネスが 1 回だけ送信されることを保証する冪等な制御があります。
サブトランザクション バリアは TCC、SAGA、トランザクション メッセージなどを管理し、他の領域にも拡張できます
サブトランザクション バリアの原則
原則サブトランザクション バリア技術の特徴は、ローカル データベースにブランチ トランザクション ステータス テーブル sub_trans_barrier を確立することです。一意のキーは、グローバル トランザクション ID-サブトランザクション ID-サブトランザクション ブランチ名 (try|confirm|cancel)
です。- トランザクションを開く
- If Try ブランチの場合、insertignore が gid-branchid-try に挿入されます。正常に挿入されると、バリア内のロジックが呼び出されます。
- 確認ブランチの場合は、gid-branchid-confirm に insertignore が挿入されます。正常に挿入された場合は、バリア内のロジックを呼び出します。
- キャンセル ブランチの場合は、挿入します。無視して gid-branchid-try に挿入し、gid-branchid-cancel を挿入します。try が挿入されず、cancel が正常に挿入された場合は、バリア内のロジックを呼び出します。
- バリア内のロジックは成功を返し、トランザクションを返し、成功を返します。
- バリア内のロジックはエラーを返し、トランザクションをロールバックし、エラーを返します。
このメカニズムでは、ネットワーク例外は解決されます。関連する問題
- 空の補償制御 -- Try が実行されず、Cancel が直接実行された場合、バリア内のロジックを使用せずに gid-branchid-try に挿入された Cancel が成功し、空の補償制御が保証されます。 インポテント コントロール -- どのブランチにも一意のキーを繰り返し挿入することはできないため、繰り返し実行されないことが保証されます。
- アンチハング コントロール -- Try は Cancel の後に実行され、挿入された gid- Branchid -try が失敗した場合は実行されず、ハング防止制御が保証されます。