分散トランザクション、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 がコンテナのベストプラクティスになった経緯

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

推薦する

ウェブマスターは、Baidu がサイト検索エンジンとして衰退していることにどう対処すべきでしょうか?

Baiduで「デジタルマルチメーター」を検索してください。検索結果の最初のページを見てみましょう。 ...

トラフィックのコンバージョン率を高めるために広告ランディングページを巧みに設計する

インターネットの発展に伴い、オンラインマーケティングは徐々に企業のマーケティングに参入してきました。...

一般的な分散トランザクション フレームワークを実装するにはどうすればよいでしょうか?

もちろん、TCC トランザクション フレームワークでは、分散トランザクションの管理を解決する必要があ...

ウェブサイト分析ハック: エントリー、エグジット、バウンスレポート (パート 1)

この記事は、Web 分析の第一人者である Eric T. Peterson 氏の著書「Web Sit...

ウェブサイトランキングの安定性に影響を与える6つの要素

諺にあるように、帝国を征服するのは困難だが、それを維持するのはもっと困難である。弊社のウェブサイトで...

Tongwei CIO 周勇: 低コストでユニバーサルなクラウド災害復旧が可能に

[51CTO.comより引用] 3年ぶりに、成都のTongwei本社で、Tongwei Co., L...

ウェブサイトの最適化で見落としがちな6つの詳細

ウェブサイトの最適化に関しては、多くのウェブマスターは、ウェブサイトの全体的な方向性や SEO の基...

intovps: ルーマニア VPS、50% オフ、無料の DirectAdmin パネル、ハイエンド製品、OpenStack+KVM+NVMe+2*40G 帯域幅

intovps のルーマニア データ センターに新しく追加された VPS は、2G メモリの VPS...

spinservers: 月額 99 ドル、シリコンバレー サーバー、e3-1280v5/32G メモリ/1T M.2/10T トラフィック/10Gbps 帯域幅

spinserversは、米国シリコンバレー(サンノゼ)データセンターにHostCat専用の特別価格...

raksmart: 大規模トラフィックサーバー、回線: CN2/本土最適化/国際 BGP

ビデオ サーバー、CDN サーバー、ダウンロード サーバー、さまざまなストリーミング メディア サー...

Kubernetes エコシステムの実装における選択と落とし穴

収益の増加とコストの削減は、企業が利益を増やすための 2 つの主要な方向性です。多くの場合、コスト削...

韓国専用サーバー: ZjiNet、20% オフ、500 元、2*e5-2620/32g メモリ/480gSSD/10M 帯域幅 (CN2+BGP)

zji.net は現在、韓国のソウル データ センターにある韓国のサーバー (韓国の独立サーバー、韓...

ウェブサイトの SEO 分析: キーワードが長いほど正確になるか?

検索習慣は人それぞれ全く異なるため、キーワードも大きく異なります。 「洗濯機」で検索する人が絶対に洗...

サーバーレスコンピューティングはビジネスの成功に必要

企業内のテクノロジーや運用方法は、頻繁に変化します。もちろん、大きな変化が起こるたびに市場の動向に追...

加速クラウド:四川徳陽高防御、825元/16コア/16gメモリ/200g SSD/50M帯域幅/100G防御(CC攻撃を無視)

加速クラウド(中華人民共和国付加価値通信事業許可証 B1-5344)は、四川省徳陽電信のコンピュータ...