分散トランザクション、Alibaba が TCC を好むのはなぜですか?

分散トランザクション、Alibaba が TCC を好むのはなぜですか?

[[404481]]

この記事はWeChatの公開アカウント「プログラマーjinjunzhu」から転載したもので、著者はjinjunzhuです。この記事を転載する場合は、プログラマーjinjunzhuの公式アカウントまでご連絡ください。

分散トランザクションの実装方法の中で、TCC はよく知られたモデルです。しかし、このモデルには考慮すべき問題がたくさんあるため、私はこのモデルを決して好きではありません。

以前、TCC の多くの欠点について書いた記事を書いたのですが、後に Alibaba の幹部が私を友達として追加し、私の見解を訂正してくれたため、その記事を削除しました。

どうもありがとうございます!

1 TCCの概要

簡単に言えば、TCC モードは、トランザクション全体を 2 つの段階に分けて送信することです。試行段階ではリソースを予約します。すべてのブランチが正常に予約されると、コミット ステージに入り、すべてのブランチ トランザクションが送信されます。それ以外の場合は、すべてのブランチ トランザクションをキャンセルするために cancel が実行されます。

電子商取引システムを例にとると、注文、在庫、アカウントの 3 つのサービスがある場合、顧客が商品を購入すると、注文サービスが注文を追加し、在庫サービスが在庫を差し引き、アカウントサービスが金額を差し引きます。これら 3 つの操作はアトミックである必要があり、すべてが成功するか、すべてが失敗するかのいずれかになります。

トライフェーズ

以下のように表示されます。

注文サービスは注文を追加し、在庫サービスは注文の在庫を凍結し、アカウントサービスは注文の金額を凍結します。

注文、在庫、アカウントの 3 つのサービスは、分散トランザクション全体のブランチ トランザクションであり、ローカル トランザクションは try フェーズで送信する必要があります。上記の在庫とアカウントの凍結は、この注文に対応する在庫と数量が他の取引で使用できなくなるため、ローカル取引を送信する必要があることを意味します。

ただし、この送信では、実際にはグローバル トランザクションが送信されず、リソースが中間状態に転送されます。この中間状態は、try メソッドのビジネス コードに実装する必要があります。たとえば、口座から差し引かれた金額は、まず中間口座に保管されます。

ローカル トランザクションが try フェーズでコミットされない場合、何が起こりますか?他のトランザクションでは、試行フェーズ中にユーザーのアカウントに十分な金額があることがわかっても、コミット フェーズ中に金額が不足していることがわかってしまう可能性があります。コミット フェーズでの推論は失敗する可能性があります。このとき、他の 2 つのブランチ トランザクションは正常に送信されますが、アカウント サービスのブランチ トランザクションは送信に失敗し、最終データが不整合になります。

コミットステージ

以下のように表示されます。

コミットフェーズでは、データが中間状態から最終状態に転送されます。たとえば、注文金額は中間アカウントから最終アカウントに転送されます。

キャンセル フェーズはコミット フェーズと似ており、たとえば、注文金額は中間アカウントから顧客アカウントに返されます。

2 問題コード

次のコードは、試行フェーズで接続を保持し、分岐トランザクションをコミットせず、コミット フェーズで分岐トランザクションをコミットする TCC として理解することもできます。コードは次のとおりです。控除口座を例に挙げてみましょう。まず、接続を保持するための 2 つの変数を定義します。

  1. プライベート Map<String, Statement> statementMap = new ConcurrentHashMap<>(100);
  2. プライベート Map<String, Connection > connectionMap = 新しい ConcurrentHashMap<>(100);

try メソッドのコードは次のとおりです。

  1. パブリックブール型 try(String xid, Long userId, BigDecimal payAmount) {
  2. LOGGER.info( "減少、xid:{}" 、xid);
  3. LOGGER.info( "------->アカウント開始アカウントからの減額を試みます" );
  4.  
  5. 試す {
  6. //口座金額を差し引こうとしましたが、取引は送信されません
  7. 繋がり 接続= hikariDataSource.getConnection();
  8. 接続.setAutoCommit( false );
  9. 文字列 sql = "UPDATE アカウント SET 残高 = 残高 - ?、使用済み = 使用済み + ?、user_id = ?" ;
  10. PreparedStatement stmt = connection .prepareStatement(sql);
  11. stmt.setBigDecimal(1, 支払額);
  12. stmt.setBigDecimal(2, 支払額);
  13. stmt.setLong(3, ユーザーID);
  14. stmt.executeUpdate();
  15. ステートメントマップを put(xid, stmt);
  16. connectionMap.put(xid、接続);
  17. } キャッチ (例外 e) {
  18. LOGGER.error( "パラメータの減少失敗:" , e);
  19. 戻る 間違い;
  20. }
  21.  
  22. LOGGER.info( "------->アカウント終了までアカウントを減算しようとします" );
  23.  
  24. 戻る 真実;
  25. }

コミットメソッドのコードは次のとおりです。

  1. パブリックブールコミット(BusinessActionContext actionContext){
  2. 文字列 xid = actionContext.getXid();
  3. PreparedStatement ステートメント = (PreparedStatement) statementMap.get(xid);
  4. 繋がり 接続= connectionMap.get(xid);
  5. 試す {
  6. if ( null !=接続){
  7. 繋がり専念();
  8. }
  9. } キャッチ (SQLException e) {
  10. LOGGER.error( "アカウントの引き落としに失敗しました:" , e);
  11. 戻る 間違い;
  12. }ついに {
  13. 試す {
  14. ステートメントマップを削除します(xid);
  15. 接続マップを削除します。(xid);
  16. if ( null != ステートメント){
  17. ステートメント.close () ;
  18. }
  19. if ( null !=接続){
  20. 接続を閉じます
  21. }
  22. } キャッチ (SQLException e) {
  23. LOGGER.error( "アカウントを差し引いてトランザクションを送信した後、接続プールを閉じることができませんでした:" , e);
  24. }
  25. }
  26. 戻る 真実;
  27. }

キャンセルメソッドのコードは次のとおりです。

  1. パブリックブールロールバック(BusinessActionContext actionContext){
  2. 文字列 xid = actionContext.getXid();
  3. PreparedStatement ステートメント = (PreparedStatement) statementMap.get(xid);
  4. 繋がり 接続= connectionMap.get(xid);
  5. 試す {
  6. 接続.ロールバック( ) ;
  7. } キャッチ (SQLException e) {
  8. 戻る 間違い;
  9. }ついに {
  10. 試す {
  11. if ( null != ステートメント){
  12. ステートメント.close () ;
  13. }
  14. if ( null !=接続){
  15. 接続を閉じます
  16. }
  17. ステートメントマップを削除します(xid);
  18. 接続マップを削除します。(xid);
  19. } キャッチ (SQLException e) {
  20. LOGGER.error( "アカウントを減算してトランザクションをロールバックした後、接続プールを閉じることができませんでした:" , e);
  21. }
  22. }
  23. 戻る 真実;
  24. }

このコードは問題のあるコードです、使用できません、使用できません、使用できません

このコードには 2 つの問題があります。

2.1 ブロッキング待機

アカウント サービスなどの現在のトランザクションがコミットされていない場合は、リソースをロックすることと同じであり、後続のトランザクションはリソースが解放されるのを待つことしかできません。

2.2 サービスクラスター

注文サービスを例に挙げてみましょう。注文サービスが 3 台のマシンのクラスターである場合は、次のようになります。

コーディネーション ノードは、登録センター クライアントを使用して注文サービスを呼び出します。 try 要求が注文サービス 1 に送信され、commit 要求が注文サービス 2 に送信された場合、注文サービス 2 の connectionMap には xid=123 の接続が存在せず、送信は失敗します。

TCC の 3 つの問題点

上記の問題コードは、概要を示すためのものです。本当に接続を維持したいのであれば、それは TCC の理念の実現とみなすことができます。しかし、システム上これを実行できないため、問題コードと呼ばれます。

3.1 空のロールバック

次の図に示すように、注文サービス 1 ノードに障害が発生します。再試行を考慮しない場合、try メソッドは失敗します。

試行は失敗しましたが、グローバル トランザクションは開始されており、フレームワークはグローバル トランザクションを終了状態にプッシュする必要があります。そのためには、注文サービスのキャンセル メソッドを呼び出してロールバックする必要があります。その結果、注文サービスはキャンセル メソッドを無駄に実行します。

この問題を解決するには、トランザクション制御テーブルを記録して、グローバル トランザクション xid とブランチ トランザクション branchId を保存します。 try フェーズが実行されたことを示すレコードが try フェーズに挿入されます。キャンセルメソッドはレコードを読み取ります。レコードが存在する場合は、通常どおりロールバックされます。レコードが存在しない場合は、空のロールバックになります。

3.2 冪等性

べき等性とは、コミット/キャンセル フェーズで、TC がブランチ トランザクションから応答を受信しなかったため再試行する必要があり、そのためにはブランチ トランザクションがべき等性をサポートしている必要があることを意味します。注文サービスを例に挙げてみましょう。以下のように表示されます。

冪等性をサポートするために、トランザクション制御テーブルを記録して、グローバル トランザクション xid、ブランチ トランザクション branchId、およびブランチ トランザクション ステータスを保存できます。第 2 フェーズでコミット/キャンセルする前に、ブランチ トランザクションのステータスがすでに確定しているかどうかを確認します。そうでない場合は、第 2 フェーズのロジックを実行します。

3.3 サスペンション

中断とは、try メソッドの前にトランザクションの cancel メソッドが実行されることを意味します。上記で、seata の使用中に空のロールバックが発生することが説明されました。空のロールバックが発生した場合、キャンセル メソッドの実行後にグローバル トランザクションは終了します。ただし、ネットワークの問題により、注文サービスは再試行リクエストを再度受信します。 try メソッドが実行されると、予約されたリソースは成功し、最終的にこれらのリソースを解放することはできません。

この問題の解決策は、キャンセル メソッドで xid に対応するブランチ トランザクション ロールバック レコードを記録することです。 try フェーズを実行するときは、まずブランチ トランザクションがロールバックされているかどうかを判断します。ロールバックレコードがある場合は、直接終了します。

3.4 ビジネスコード侵入

TCC の try/commit/cancel はビジネス コードに侵入し、各メソッドはローカル トランザクションになります。さらに、べき等性、空のロールバック、中断などを考慮する必要があり、コードの侵入が高くなります。

4.TCCの利点

ここでは、XA、SAGA、TCC、AT を含む、seata によって実装された 4 つのモードを比較します。

効率

TCC モードを使用する場合、ローカル トランザクションは try フェーズでコミットされ、リソースはロックされないため、追加のパフォーマンス オーバーヘッドは発生しません。比較のために、他のいくつかのモードを見てみましょう。

  • AT モードでは、undolog を記録する必要があるため、パフォーマンスが大幅に低下します。
  • XA モードでは、xa start | を実行した後、 sql | xa が終了し、コミット/ロールバックを実行する前に、リソースがロックされ、後続のトランザクションは待機する必要があります。

サガパターン

プロセスの長いビジネス シナリオに適しています。

5. パフォーマンスの最適化

参考文献[1]

5.1 非同期送信

最適化の考え方は、試行フェーズが成功した後、確認/キャンセル フェーズがすぐに実行されるのではなく、システムがアイドル状態のときに非同期的に実行されるというものです。以下のように表示されます。

このように、try フェーズが終了した後、グローバル トランザクションが終了したとみなされ、2 番目のフェーズが固定時間 (たとえば 10 分) で非同期的に実行されるため、パフォーマンスが大幅に向上します。

もちろん、グローバル トランザクションがロールバックされると、一時的にデータの不整合が発生するという問題があります。たとえば、控除のシナリオでは、非同期タスクが 10 分ごとに実行されます。第2段階がキャンセルされた場合、お客様は10分以内に金額をご利用いただけなくなります。

この非同期実行の時間も、ビジネスに基づいて決定できます。たとえば、中間アカウントから最終アカウントにデータをタイムリーに転送する必要がない場合は、より長く設定できます。

5.2 同じデータベースモード

まず、TCC のさまざまな役割を確認しましょう。

  • TMは、グローバルトランザクションの開始、グローバルトランザクションのコミット/ロールバックなど、グローバルトランザクションを管理します。
  • RMは支店業務を管理する
  • TCはグローバル取引と支店取引のステータスを管理します

まず、最適化前の通信モデルを見てみましょう。

最適化の前に、TM がグローバル トランザクションを開始すると、RM は登録のために TC に RPC メッセージを送信する必要があり、TC はブランチ トランザクションのステータスを保存します。 TM がコミットまたはロールバックを要求すると、TC はコミットまたはロールバックのために RPC メッセージを RM に送信する必要があります。 2 つのブランチ トランザクションを含むこの分散トランザクションでは、TC と RM の間に 4 つの RPC が存在します。

最適化されたモデルは次のとおりです。

TM がグローバル トランザクションを開始すると、ブランチ トランザクションを TC に登録する必要がなくなり、ブランチ トランザクションのステータスがローカルに保存されます。 TM が TC にコミットまたはロールバック メッセージを送信すると、TC はグローバル トランザクションのステータスを保存します。 RM は非同期スレッドを開始して、ローカル レコード内のコミットされていないブランチ トランザクションを検出し、TC に RPC メッセージを送信して全体的なトランザクション ステータスを取得し、ローカル トランザクションをコミットするかロールバックするかを決定します。最適化後、RPC の数が 50% 削減され、パフォーマンスが大幅に向上していることがわかります。

6. まとめ

TCC には確かに多くの問題がありますが、ビジネス コードへの侵入の問題を除いて、他の問題には対応する解決策があります。

Alibaba は、第 2 フェーズの非同期送信や同一データベース モードなど、TCC にいくつかの最適化を施し、パフォーマンスを大幅に向上させました。

<<:  Kubernetes がコンテナのベストプラクティスになった経緯

>>:  ハイブリッド マルチクラウド管理プラットフォームのホワイト ペーパー

推薦する

5Gがクラウドコンピューティングをさらに進化させる方法

2020年の突然の流行により人々の仕事や生活は一変し、多くの組織の従業員は自宅からリモートワークをし...

Oracle Marketing Cloud が Royal FrieslandCampina の精密マーケティングの成功を支援

消費の高度化と新小売時代の到来により、中国は世界第2位の乳幼児消費市場となった。乳児用調製粉乳業界に...

パブリッククラウドの導入によりSD-WANのメリットが増大

パブリック クラウドの導入の増加により、企業は SD-WAN の利点を活用する方法の調査を開始せざる...

この男は金儲けのために他人のウェブサイトにリンクを張っていたが、刑務所に行くとは思っていなかった。

若者はコンピューター技術に精通しており、多くのチャンスを見つけることができますが、臨安には自分の技術...

2013 トピック: SEO を通じてウェブサイトのランキングを向上させる方法

今日は、SEO 最適化を通じてウェブサイトのインクルージョンとランキングを向上させる方法についてお話...

長巴とテンセントWeibo: 7日間で500万人のユーザーが戻ってきた典型的なケーススタディ

【はじめに】 製品の細部にこだわることで大きな利益を生む典型的な事例です。背景データシステムから見る...

Kubernetes にアプリケーション (Nginx) をデプロイする方法は 2 つあります。どちらがお好みですか?

k8s でアプリケーションを公開するには 2 つの方法があります。 Kubernetesダッシュボー...

2018年のクラウド予測

12月はテクノロジー業界における「予言の季節」です。専門家の予測を読むのはいつも楽しいが、これらの予...

Baidu Webmaster PlatformからSEOスキルを学ぶ: ウェブサイトのIP変更の心配はもうありません

周知のとおり、SEO では、ウェブサイトのコンテンツに頻繁に更新された価値のあるコンテンツが含まれる...

共同購入は粗利益の活路を模索:住宅コミュニティにサービスを提供するために製品単価を上げる

不安定な共同購入業界は、再び「寒波」に見舞われているようだ。共同購入ナビゲーションサイト「Tuan8...

新しいウェブサイトやドメイン名を公開後半月以内にホームページに掲載する秘訣

こんにちは、ウェブマスターの皆さん、今日は私のウェブサイトが半月も経たないうちにホームページに掲載さ...

グラフィック説明: Discuz フォーラム セクション設定 2 番目の部分

Discuzフォーラムには多くの機能があり、バックグラウンドで設定する必要があるものがたくさんありま...

Red Hat、ソフトウェアサプライチェーンからエッジまでのセキュリティを強化する新機能をリリース

オープンソース ソリューションの世界的大手プロバイダーである Red Hat は最近、オープン ハイ...

UCloudのXu Liang氏との独占インタビュー:UCloudの仮想ネットワークの進化

[51CTO.com からのオリジナル記事] 今日、ほぼすべての IT インフラストラクチャがクラウ...

IP変更後にウェブサイトが削除またはダウングレードされた理由の分析

ウェブサイトの IP アドレスを変更すると、検索エンジンの権威が失われ、インデックスの数が減少するこ...