Use go to easily complete a saga distributed transaction, nanny level tutorial

Don't startle the magpie 2021-10-14 06:47:14

Inter bank transfer is a typical distributed transaction scenario , hypothesis A Inter bank transfer is required to B, Then it involves the data of two banks , Transfer cannot be guaranteed through local transactions in a database ACID, Can only be solved through distributed transactions .

Distributed transactions

Distributed transactions in a distributed environment , To meet availability 、 Performance and the need for degraded Services , Reduce requirements for consistency and isolation , On the one hand, follow BASE theory :

  • Basic business availability (Basic Availability)

  • The state of flexibility (Soft state)

  • Final consistency (Eventual consistency)

On the other hand , Distributed transactions also partially follow ACID standard :

  • Atomicity : Follow strictly

  • Uniformity : Consistency after completion of a transaction strictly follows ; Consistency in transactions can be relaxed

  • Isolation, : Parallel transactions cannot affect ; Transaction intermediate result visibility allows security to be relaxed

  • persistence : Follow strictly


Saga This is a database paper SAGAS A distributed transaction scheme mentioned above . Its core idea is to split long transactions into multiple local short transactions , from Saga Transaction coordinator coordination , If each local transaction completes successfully, it completes normally , If a step fails , The compensation operation is called once in reverse order .

Now it can be used for SAGA Open source framework of , Mainly for Java Language , Among them seata As a representative . Our example uses go Language , The distributed transaction framework used is, Its support for distributed transactions is very elegant . Let's talk about it in detail SAGA The composition of :

DTM In the transaction framework , Yes 3 A character , And classic XA Like distributed transactions :

  • AP/ Applications , Initiate a global transaction , Defines which transaction branches are included in a global transaction

  • RM/ Explorer , Be responsible for the management of various resources of branch affairs

  • TM/ Transaction manager , Responsible for coordinating the correct execution of global transactions , Include SAGA positive / Execution of reverse operation

Let's look at a successful SAGA Sequence diagram , It's easy to understand SAGA Distributed transactions :


SAGA practice

For the example of bank transfer we want to make , We will be in forward operation , Transfer in and out , In compensation operation , Make the opposite adjustment .

First, we create an account balance table :

CREATE TABLE dtm_busi.`user_account` ( `id` int(11) AUTO_INCREMENT PRIMARY KEY, `user_id` int(11) not NULL UNIQUE , `balance` decimal(10,2) NOT NULL DEFAULT '0.00', `create_time` datetime DEFAULT now(), `update_time` datetime DEFAULT now());

Let's write the core business code first , Adjust the user's account balance

func qsAdjustBalance(uid int, amount int) (interface{}, error) { _, err := dtmcli.SdbExec(sdbGet(), "update dtm_busi.user_account set balance = balance + ? where user_id = ?", amount, uid) return dtmcli.ResultSuccess, err}

Let's write a specific forward operation / Processing function of compensation operation

app.POST(qsBusiAPI+"/TransIn", common.WrapHandler(func(c *gin.Context) (interface{}, error) { return qsAdjustBalance(2, 30) })) app.POST(qsBusiAPI+"/TransInCompensate", common.WrapHandler(func(c *gin.Context) (interface{}, error) { return qsAdjustBalance(2, -30) })) app.POST(qsBusiAPI+"/TransOut", common.WrapHandler(func(c *gin.Context) (interface{}, error) { return qsAdjustBalance(1, -30) })) app.POST(qsBusiAPI+"/TransOutCompensate", common.WrapHandler(func(c *gin.Context) (interface{}, error) { return qsAdjustBalance(1, 30) }))

At this point, the processing functions of each sub transaction have been OK 了 , Then turn on SAGA Business , Make branch calls

req := &gin.H{"amount": 30} // The load of microservices  // DtmServer by DTM Address of service  saga := dtmcli.NewSaga(DtmServer, dtmcli.MustGenGid(DtmServer)). // Add one TransOut Sub business of , Forward operation is url: qsBusi+"/TransOut", Reverse operation is url: qsBusi+"/TransOutCompensate" Add(qsBusi+"/TransOut", qsBusi+"/TransOutCompensate", req). // Add one TransIn Sub business of , Forward operation is url: qsBusi+"/TransOut", Reverse operation is url: qsBusi+"/TransInCompensate" Add(qsBusi+"/TransIn", qsBusi+"/TransInCompensate", req) // Submit saga Business ,dtm Will complete all sub transactions / Roll back all sub transactions  err := saga.Submit()

thus , A complete SAGA Distributed transaction writing is complete .

If you want to run a successful example completely , Then according to the yedf/dtm Project description after setting up the environment , Run with the following command saga An example of :

go run app/main.go quick_start

Handle network exceptions

Suppose submitted to dtm In the transaction of , When calling the carry in operation , What if there is a short fault ? according to SAGA Transaction agreement ,dtm The incomplete operation will be retried , How do we deal with ? The fault may be a network fault after the transfer in operation is completed , It is also possible that the machine is down when the transfer in operation is completed . How to deal with it to ensure that the adjustment of account balance is correct ?

DTM Provides sub transaction barrier function , Ensure multiple retries , There will only be one successful submission .( The sub transaction barrier not only guarantees idempotence , It can also solve problems such as null compensation , For details, refer to the sub transaction barrier of the seven classic solutions of distributed transactions )

We adjust the handler to :

func sagaBarrierAdjustBalance(sdb *sql.Tx, uid int, amount int) (interface{}, error) { _, err := dtmcli.StxExec(sdb, "update dtm_busi.user_account set balance = balance + ? where user_id = ?", amount, uid) return dtmcli.ResultSuccess, err
func sagaBarrierTransIn(c *gin.Context) (interface{}, error) { return dtmcli.ThroughBarrierCall(sdbGet(), MustGetTrans(c), func(sdb *sql.Tx) (interface{}, error) { return sagaBarrierAdjustBalance(sdb, 1, reqFrom(c).Amount) })}
func sagaBarrierTransInCompensate(c *gin.Context) (interface{}, error) { return dtmcli.ThroughBarrierCall(sdbGet(), MustGetTrans(c), func(sdb *sql.Tx) (interface{}, error) { return sagaBarrierAdjustBalance(sdb, 1, -reqFrom(c).Amount) })}

there dtmcli.TroughBarrierCall The call uses sub transaction barrier technology , Ensure that the callback function in the third parameter is processed only once

You can try calling this more than once TransIn service , Only one balance adjustment . You can run the following command , Run a new process :

go run app/main.go saga_barrier

Process rollback

If the bank prepares to transfer the amount to the user 2 when , Discover users 2 Your account is abnormal , Return failed , What will happen? ? We adjust the handler , Let the transfer in operation return failure

func sagaBarrierTransIn(c *gin.Context) (interface{}, error) { return dtmcli.ResultFailure, nil}

We give the sequence diagram of transaction failure interaction


Here's a little ,TransIn The forward operation of does nothing , It returns a failure , Call at this time TransIn Compensation operation of , Will it lead to an error in reverse adjustment ?

Never mind , The previous sub transaction barrier technology , Can guarantee TransIn If the error occurs before submission , Then the compensation is null ;TransIn If the error occurs after submission , Then the compensation operation will submit the data once .

You can return the wrong TransIn Change to :

func sagaBarrierTransIn(c *gin.Context) (interface{}, error) { dtmcli.ThroughBarrierCall(sdbGet(), MustGetTrans(c), func(sdb *sql.Tx) (interface{}, error) { return sagaBarrierAdjustBalance(sdb, 1, 30) }) return dtmcli.ResultFailure, nil}

In the end, the balance will still be right , The principle can refer to : The sub transaction barrier of the seven classic solutions of distributed transaction


In this article , We introduced SAGA Theoretical knowledge of , Also through an example , A complete program is given SAGA The course of the business , Covers normal successful completion , Abnormal situation , And successful rollback . I believe that readers will pass this article , Yes SAGA Already have a deep understanding .

Used in this article dtm It's new and open source Golang Distributed transaction management framework , Powerful , Support TCC、SAGA、XA、 Transaction patterns such as transaction messages , Support Go、python、PHP、node、csharp Languages like . At the same time, it provides a very simple and easy-to-use interface .

After reading this dry goods , Welcome to the project, Support a star !

Click on the bottom left corner " Read the original ", You can access the project directly

Please bring the original link to reprint ,thank
Similar articles