The author | | xiao-ming liu source alibaba cloud native public number

Seata-golang is a distributed transaction framework, which implements BOTH AT mode and TCC mode. Compared with TCC mode, AT mode is less intrusive to code and requires fewer interfaces to be developed. However, the AT mode holds a global lock on the transaction data, and TCC mode performs better in this regard.

In SEATA’s AT mode, the global lock is placed on the transaction coordinator, and the storage implementation depending on the specific lock interface can be file/ DB/REDis, instead of the database lock. The database lock is immediately released when each branch transaction is committed. In this way, the pressure on the database is reduced and the performance of the database is improved. Seata AT mode and TCC mode see: [What is SEATA?]

The following uses seats- Golang samples as an example to explain how the AT mode and TCC mode are connected to business.

Access in AT mode

Under the samples/ AT directory, there are three microservices: product_svc, order_svc, and Aggregation_svc.

  • Product_svc is responsible for reducing inventory when creating an order.

  • Order_svc is responsible for writing the order master table and the order detail table when creating the order.

  • Aggregation_svc calls the interfaces of order_SVC and Product _SVC through HTTP requests.

1. Global transaction broker

Those familiar with the Seata Java framework know that it dynamically generates AOP facets by scanning @GlobalTransactional annotations, proxies methods marked by @GlobalTransactional, Start, commit, or roll back global transactions.

Unlike as interpreted language Java, Go is a compiled language, so seata – golang using the function of reflection technology to realize dynamic proxy, the proxy objects need to implement GlobalTransactionProxyService interface.

type GlobalTransactionProxyService interface { GetProxyService() interface{} GetMethodTransactionInfo(methodName string)  *TransactionInfo }Copy the code

The Svc struct in Aggregation_SVC has a method, CreateSo, which creates orders and deducts inventory through calls to order_svc and product_svc. To delegate the *Svc object, seata-Golang creates a delegate object in which the propped method is treated as an empty method member, waiting for Seata-Golang to implement it dynamically.

type ProxyService struct {
	*Svc
	CreateSo func(ctx context.Context, rollback bool) error
}
Copy the code

ProxyService is built into the proxied object Svc by combination. After the developer calls tm.Implement(svc.proxysvc), Seata – golang will pass the Svc implementation GlobalTransactionProxyService interface to get dynamically create CreateSo method need transaction information, then according to the transaction information to dynamically create CreateSo method: Enable transactions -> Execute the CreateSo method logic of the prosteed *Svc object -> Decide whether to commit or rollback based on the error message returned by the prosteed CreateSo method.

2. Pass the global transaction ID

The global transaction ID can be passed in three ways.

1) Http

In the aggregation_svc service, Seata-golang passes XID(global transaction ID) to order_svC and product_svc via request header (req.header.set (“XID”, rootContext.getxID ())), Order_svc and product_svc fetch XID (c.equest.header.get (“XID”)) from the Request Header for branch transactions.

2) Dubbo

If the DUBBo protocol RPC communication is used, the XID needs to be injected into the Attachment to be passed downstream.

If you use the Dubbo-Go framework, dubbo-Go will read the attachment from the context and pass it serialized to the server. You can pass the XID as follows:

context.WithValue(ctx, "attachment", map[string]string{
		"XID": rootContext.GetXID(),
}
Copy the code

The Dubo-Go server takes the XID from the Attachment and injects it into the context. The business method of the branch transaction can get the XID from the context for the branch transaction.

// SeataFilter ... type SeataFilter struct { } // Invoke ... func (sf *SeataFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { xid := invocation.AttachmentsByKey("XID", "") if xid ! = "" { return invoker.Invoke(context.WithValue(ctx, "XID", xid), invocation) } return invoker.Invoke(ctx, invocation) } // OnResponse ... func (sf *SeataFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } // GetSeataFilter ... func getSeataFilter() filter.Filter { return &SeataFilter{} }Copy the code

The above filter can be injected into dubbo-Go’s filter chain through the extension.setFilter (“SEATA”, getSeataFilter) method.

3) GRPC

GRPC can transmit Xids through metadata.

Client first places XID into MD := metadata.Pairs(“XID”, rootContext.getxID ()) and passes metadata to context: Metadata. NewOutgoingContext (context. The Background (), md).

Server by md, ok: = metadata. FromIncomingContext (CTX) access to the metadata, then take out XID.

3. Transaction branching

In addition to the methods that initiate global transactions, the AT pattern also needs to proxy the data source.

Seata parses SQL statements through proxy data sources to obtain the data before and after the modification for transaction coordinator rollback. To broker the data source, just inject the SQL driver instance you created into the DB operation object of Seata-Golang:

Db, err := exec.newdb (config.getatConfig (), {your SQL driver instance})Copy the code

If you are using XORm or GORM, you can take the SQL Driver instance from the XORM object or GORM object and use the above method to construct the DB operation object of Seata-Golang. This means that you can use both the ORM framework and seata-Golang framework to execute SQL statements using seata-Golang db operation objects when your operations require transactions.

As described in the previous section, developers can already get xids from upstream on the server. To add the branch transaction to the global transaction group, the developer needs to construct a RootContext using the obtained XID:

RootContext := &context. rootContext {context: CTX} rootContext.Bind("{upstream fetch XID}")Copy the code

When a branch transaction is started, the call flow is as follows:

  • Call the Begin method of seata-Golang’s DB operation object to get the branch transaction objecttx, err := dao.Begin(ctx).
  • The Exec method of the branch transaction object TX is used to execute the SQL statementfunc (tx *Tx) Exec(query string, args ... interface{}) (sql.Result, error).
  • After executing the SQL operation logic, you can call the command based on the returned resulttx.Commit()tx.Rollback()To commit or roll back the branch operation.

Finally, whether the whole branch transaction is committed successfully or not, a result is returned to the caller, the initiator of the global transaction, and the Transaction Manager decides whether to commit or roll back the whole global transaction based on the result returned.

TCC mode access

The TCC mode has more constraints than the AT mode. The TCC pattern first requires the developer to implement the TccService interface and encapsulate the parameters of the interface’s three methods into a BusinessActionContext.

The developer calls the Try method, and the Seata-Golang framework calls the Confirm/Cancel method. The framework decides whether to initiate a global commit or rollback based on whether all branch transaction Try methods have executed successfully. A global commit automatically invokes the Confirm method for each transaction branch, and a global rollback invokes the Cancel method for all transaction branches that joined the transaction group.

type TccService interface {
	Try(ctx *context.BusinessActionContext) (bool, error)
	Confirm(ctx *context.BusinessActionContext) bool
	Cancel(ctx *context.BusinessActionContext) bool
}
Copy the code

Before calling the Try method, the Transaction branch must be added to the Transaction group, and the BusinessActionContext, the execution context of the Try method, must be stored in the Transaction coordinator. In this way, when the framework commits or rolls back, In order to pass the BusinessActionContext argument to the Confirm, cancel methods, this part of the logic is still implemented through the agent. So developers also need to create a proxy class and implement the TccProxyService interface:

type TccProxyService interface {
	GetTccService() TccService
}
Copy the code

The framework implements the above logic for the proxy class by calling the tcc.implementtcc ({proxy class instance}) method. Developers can see examples of the TCC schema in the samples/ TCC directory.

summary

In addition to samples in the project structure directory, there is an example of Dubo-Go, Dubo-go-seata. For the above mentioned access method, but also hope that the reader combined with the code more understanding, integration.

Currently, SeATa-Golang is fully integrated with the latest SeATA Java version 1.4 protocol. If you have companies that use both Java and Golang on the technology stack, you can access the SeATA framework to solve your distributed transaction concerns.

If you have any questions, welcome to join the communication group [Nail Group Number 33069364]

The resources

  • Seata official: Seata.io
  • Seata for Java: github.com/seata/seata
  • Seata-golang Project address: github.com/opentrx/sea…
  • Seata – golang go night stand share b: www.bilibili.com/video/BV1oz…
  • Seata-golang Communication Model based on Getty: Seata.io /zh-cn/blog/…

Author’s brief introduction

Liu Xiaomin (GitHubID DK-lockdown) currently works for H3C Chengdu. He is skilled in Java/Go and has an interest in both cloud native and microservices related technologies. He currently specializes in distributed transactions.