囲碁におけるTCC分散トランザクションのベストプラクティス


この記事は、TCCタイプ取引の正確な理解を読者に与える完全なTCC例を提示するでしょう

ビジネスシナリオ


典型的な分散トランザクションシナリオは、銀行間の資金を転送する必要がある銀行間転送である.仮説的要求シナリオは、AからBへの両方の転送が成功し、失敗する可能性があることである.
また、ロールバックがある場合、サガモードは、そのバランスが差し引かれたことを発見する結果となりますが、受取人、Bは、大きな苦痛を引き起こすバランスを受け取るのが遅いです.ビジネスはこの状況を避けることを好む

TCCコンポーネント


TCCは3部に分けられる
  • トライパート:実行しようとすると、すべてのビジネスチェック(一貫性)を完了し、必要なビジネスリソースを予約します.
  • 確認:すべての支店がトライフェーズで成功した場合、確認フェーズに移動します.確認フェーズでは、try - phase
  • で予約されているビジネスリソースのみを使用して、ビジネスチェックなしで実際にビジネスを実行します.
  • をキャンセルします:すべてのブランチのTrysの1つが失敗するならば、我々はキャンセル段階に行きます.
  • 我々が別々のマイクロサービスのトランスアウトとトランスインで、我々が銀行インターバンク転送に類似したトランザクションを実行することになっているならば、正常に完成したTCCトランザクションのための典型的なタイミング図は以下の通りです.

    私たちのソリューションは、分散トランザクションのソリューションに専用の優れたプロジェクトは、分散トランザクションフレームワークdtmに基づいています.

    コアオペレーション


    最初に、アカウントバランス表を作成します.ここで、tradingchen balanceは、凍結された量を示します.
    create table if not exists dtm_busi.user_account(
      id int(11) PRIMARY KEY AUTO_INCREMENT,
      user_id int(11) UNIQUE,
      balance DECIMAL(10, 2) not null default '0',
      trading_balance DECIMAL(10, 2) not null default '0',
      create_time datetime DEFAULT now(),
      update_time datetime DEFAULT now(),
      key(create_time),
      key(update_time)
    );
    
    まず最初にコアコードを書きましょう.凍結/非凍結資金操作は、制約のバランスがとれていない場合+ tradingchen balance >= 0をチェックします
    func tccAdjustTrading(db dtmcli.DB, uid int, amount int) error {
        affected, err := dtmimp.DBExec(db, "update dtm_busi.user_account set trading_balance=trading_balance+?       where user_id=? and trading_balance + ? + balance >= 0", amount, uid, amount)
        if err == nil && affected == 0 {
            return fmt.Errorf("update error, maybe balance not enough")
        }
        return err
    }
    
    func tccAdjustBalance(db dtmcli.DB, uid int, amount int) error {
        affected, err := dtmimp.DBExec(db, "update dtm_busi.user_account set trading_balance=trading_balance-?,          balance=balance+? where user_id=? ", amount, amount, uid)
        if err == nil && affected == 0 {
            return fmt.Errorf("update user_account 0 rows")
        }
        return err
    }
    
    特定のtry/確認/キャンセルハンドラ関数を書きましょう
    app.POST(BusiAPI+"/TccBTransOutTry", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
      bb := MustBarrierFromGin(c)
      return bb.Call(txGet(), func(tx *sql.Tx) error {
        return tccAdjustTrading(tx, TransOutUID, -req.Amount)
      })
    }))
    app.POST(BusiAPI+"/TccBTransOutConfirm", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
      bb := MustBarrierFromGin(c)
      return bb.Call(txGet(), func(tx *sql.Tx) error {
        return tccAdjustBalance(tx, TransOutUID, -reqFrom(c).Amount)
      })
    }))
    app.POST(BusiAPI+"/TccBTransOutCancel", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
      bb := MustBarrierFromGin(c)
      return bb.Call(txGet(), func(tx *sql.Tx) error {
        return tccAdjustTrading(tx, TransOutUID, req.Amount)
      })
    }))
    app.POST(BusiAPI+"/TccBTransInTry", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
      bb := MustBarrierFromGin(c)
      return bb.Call(txGet(), func(tx *sql.Tx) error {
        return tccAdjustTrading(tx, TransInUID, req.Amount)
      })
    }))
    app.POST(BusiAPI+"/TccBTransOutConfirm", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
      bb := MustBarrierFromGin(c)
      return bb.Call(txGet(), func(tx *sql.Tx) error {
        return tccAdjustBalance(tx, TransInUID, reqFrom(c).Amount)
      })
    }))
    app.POST(BusiAPI+"/TccBTransInCancel", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
      bb := MustBarrierFromGin(c)
      return bb.Call(txGet(), func(tx *sql.Tx) error {
        return tccAdjustTrading(tx, TransInUID, -req.Amount)
      })
    }))
    
    これらの関数のコアロジックは、バランスを凍結調整することです

    TCC取引


    それから、TCCトランザクションはつくられます、そして、分岐呼び出しは作られます
    // TccGlobalTransaction will open a global transaction
    _, err := dtmcli.TccGlobalTransaction(DtmServer, func(tcc *dtmcli.Tcc) (rerr error) {
      // CallBranch will register the Confirm/Cancel of the transaction branch to the global transaction, and then call Try directly
      res1, rerr := tcc.CallBranch(&TransReq{Amount: 30}, host+"/api/TccBTransOutTry", host+"/api/TccBTransOutConfirm", host+"/api/ TccBTransOutCancel"
      if err ! = nil {
        return resp, err
      }
      return tcc.CallBranch(&TransReq{Amount: 30}, host+"/api/TccBTransInTry", host+"/api/TccBTransInConfirm", host+"/api/TccBTransInCancel")
    })
    
    この時点で、完全なTCC分散トランザクションが終了します.

    ラン


    成功した例を実行したい場合は、次の手順に従います.
  • DTMを実行する
  • git clone https://github.com/dtm-labs/dtm && cd dtm
    go run main.go
    
  • 例を実行する
  • git clone https://github.com/dtm-labs/dtm-examples && cd dtm-examples
    go run main.go http_tcc_barrier
    

    ネットワーク例外の処理


    DTMにコミットされたトランザクションがこれらのステップの1つで簡単に失敗すると仮定してください.DTMは不完全な操作を再試行します.そして、グローバルトランザクションの下位トランザクションを必要とします.DTMフレームワークは、サブトランザクションバリア技術を開拓しました.この関数の内部での動作を一度に一度呼び出すことを保証する関数コールを提供します.
    func (bb *BranchBarrier) Call(tx *sql.Tx, busiCall BarrierBusiFunc) error
    
    このブランチバリアは、自動的に無効化だけでなく、ヌル補償と絞り込み問題を扱うことができます.詳細についてはexceptions and solutionsを参照してください.

    TCCのロールバック


    銀行がユーザー2に量を移す準備をしていて、ユーザー2のアカウントが異常であるとわかるならば、何が起こるか、失敗を返しますか?この状況をシミュレートするコードを変更します.
    app.POST(BusiAPI+"/TccBTransInTry", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
      return dtmcli.ErrFailure
    }))
    
    これはトランザクション障害の相互作用のタイミング図です

    これと成功したTCCの違いは、子トランザクションが失敗を返すと、その後グローバルトランザクションがロールバックされ、各トランザクションのキャンセル操作を呼び出してグローバルトランザクションがすべてロールバックされることを保証します.
    transintryのフォワード操作は何もせずに失敗を返しました、この点でTransincancel補償操作を呼ぶことは逆の調整が間違って行く原因になります?
    心配しないで、前の副トランザクションバリア技術は、それがコミットの前に起こるならば、transintry誤りがヌル操作として補償されるのを確実にします、そして、コミットの後、TransIntry誤りが起こるならば、補償操作はデータをコミットします.
    あなたはbb.Callに変更することができます
    app.POST(BusiAPI+"/TccBTransInTry", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
      bb := MustBarrierFromGin(c)
      bb.Call(txGet(), func(tx *sql.Tx) error {
        return tccAdjustTrading(tx, TransInUID, req.Amount)
      })
      return dtmcli.ErrFailure
    }))
    
    最終的な結果のバランスはまだ正しいでしょう、詳細はExceptions and Solutionsを見てください.

    概要


    この記事は完全なTCCトランザクション解決を与えます.あなたはこの例にいくつかの簡単な変更を使用して実際の問題を解決するために使用することができます
    TCCの原理の詳細については、TCC
    プロジェクトを訪問する歓迎