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

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

推薦する

Apple、2013年に最も人気があったアプリとゲームを発表

Apple は本日、2013 年に最も人気のある無料 iOS アプリ (iPhone および iPa...

#DoubleTwelve# dogyun: 全品30%オフ、月額27.16元から、ダイナミッククラウドサーバー(IP切り替えは10元のみ)、香港CN2\CMI\BGP+ドイツCN2+日本ソフトバンク

Dogyun(狗云)は、ダブル12の最新プロモーションを実施しました。全製品が30%オフで、更新時に...

美団と大衆点評のマーケティング戦略の類似点と相違点に関する2つの分析ポイント

原題: Meituan と Dianping のマーケティング戦略の類似点と相違点に関する 2 つの...

hiformance-$7/KVM/6G メモリ/6 コア/20gSSD/4T トラフィック/3IPv4/ロサンゼルス/Windows

米国のホスティング業界の新参者である hiformance.com は現在、ダラス、オグデン (ユタ...

ノルウェー VPS: xhostfire - $10/kvm/1g メモリ/15g ハードディスク/1T トラフィック

xhostfire は、ノルウェーのデータ センターに KVM 仮想化に基づく新しい VPS を立ち...

エッジ コンピューティングの 10 の問題点にどう対処するか?

ネットワークと統合からフェイルオーバー、資産管理、セキュリティまで、エッジ テクノロジーへの適切なサ...

ソフト記事SEO最適化技術の2つの側面

SEO テクニックは数多くあります。筆者だけでも 10 種類以上を知っています。実際、他にも最適化テ...

権威あるインターネットアカウントを構築するためのいくつかの原則

現在、ほぼすべてのインターネット サイトがユーザー登録とログイン機能を提供しており、これによってすべ...

質問: AI と機械学習はクラウド アプリケーションのセキュリティにどのような影響を与えますか?

他の多くの新興テクノロジーと同様に、AI は諸刃の剣であり、クラウド コンピューティングのセキュリテ...

DigitalOcean ロンドン データセンター/DigitalOcean ロンドン VPS レビュー

Digitalocean は、英国ロンドンの新しいデータセンターに移転しました。HostCat の ...

、WeChatでお金を稼ぐための基本的な公式

ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービス01草の根起業家が成功す...

Photonvps が全面的に 50% オフ。この機会をお見逃しなく

Photonvps は全品 50% オフ。この機会をお見逃しなく。クーポンコード: HALFOFF ...

巨大企業のクラウドコンピューティングの前編:公式の歴史の裏に隠された「浮き沈み」と「不合理」

インターネット大手によるクラウドコンピューティング開発の歴史は、何千回も書き記されてきました。しかし...

分散システムの一貫性保証ソリューションの概要

導入インターネットシステムでは、理想的には、システムが「一貫性」、「可用性」、および「パーティション...