JAVA Architect's Distributed Transaction Solution, Health Project Actual

Keywords: Programming Database MySQL SQL Java


In traditional architectures, you can use spring's @Transactional for declarative or programmatic transaction management, but if multiple data source operations are involved in our code, spring's @Transactional transaction management mechanism will fail, so we can consider using a two-phase commit solution.
Let's take mysql as an example. After version 5.0, mysql supports the XA specification, which supports distributed transactions in the form of 2PC.

mysql XA

Related sql statements

XA start 'global_id','branch_id';
update user set age=22 where id=12;
update order set amount=1000.01 where id=1234;
XA end  'global_id','branch_id';

XA prepare   'global_id','branch_id';
XA RECOVER;  -- View all currently in preparation XA affair
XA commit;-- True Commit Transaction
XA rollback;-- Rollback transaction

Java Code

Use druid to manage connection pools, which support XA
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;

import;import com.mysql.jdbc.jdbc2.optional.MysqlXid;import javax.sql.XAConnection;import javax.transaction.xa.XAResource;import javax.transaction.xa.Xid;import java.sql.Connection;import java.sql.Statement;import java.util.Properties;/**
 * @author Jam Fang
 * @version Creation time: 2019/4/14 13:58
 */public class TwoPhaseCommitApplication {    public void multiDataSourceTest() throws Exception {        String propertyfile = "/";        Properties props = new Properties();
        props.load(getClass().getResourceAsStream(propertyfile));        //Initialize Data Source  DruidXADataSource  xaDataSource_1 = initXADataSource(props, "db1.);  //Initialize XA Connection  XAConnection xaConnection_1 = xaDataSource_1.getXAConnection(); //Initialize XA Resource  XARurce _1 = xaConnection_1.get XAResource (); \//Get a database connection * Connection connection_1 = xaConnection_1.getConnection();
        connection_1.setAutoCommit(false);        //Create XID  Xid xid_1 = new MysqlXid("globalid".getBytes(), "branch-1".getBytes(), 0);  //associated transaction start end
        xaResource_1.start(xid_1, XAResource.TMNOFLAGS);        Statement stmt = connection_1.createStatement();        String sql_1 = "INSERT INTO `order`(orderid,amount,product) values('00001','3000.00','Apple Notebook');";//"delete from test3 where pk_t=3;";
        xaResource_1.end(xid_1, XAResource.TMSUCCESS);        //Transaction Preparation  int result_1 = xaResource_1.prepare(xid_1);  DruidXADataSource xaDataSource_2 = initXADataSource(props,'db2.'); XAConnection_2 = xaDataSource_2.getXAConnection(); XARurce xaResource_2 = connection_2.getXAResource (); ConnectionNnection_2 = xaConnection_2.getConnection();
        connection_2.setAutoCommit(false);        Xid xid_2 = new MysqlXid("globalid".getBytes(), "branch-2".getBytes(), 0);
        xaResource_2.start(xid_2, XAResource.TMNOFLAGS);        Statement stmt2 = connection_2.createStatement();        String sql_2 = "update shipping set address='Beijing Huangpu River' where id=1;";
        xaResource_2.end(xid_2, XAResource.TMSUCCESS);        int result_2 = xaResource_2.prepare(xid_2);        //if (result_1 == XAResource.XA_OK &&
                result_2 == XAResource.XA_OK) {            //All return to OK for submission
            xaResource_1.commit(xid_1, false);
            xaResource_2.commit(xid_2, false);        } else {            //Rollback transaction
            xaResource_2.rollback(xid_2);        }    }    DruidXADataSource initXADataSource(Properties props, String prefix) {        DruidXADataSource xaDataSource = new DruidXADataSource();
        xaDataSource.setDbType(props.getProperty(prefix + "dbtype"));
        xaDataSource.setUrl(props.getProperty(prefix + "url"));
        xaDataSource.setUsername(props.getProperty(prefix + "username"));
        xaDataSource.setPassword(props.getProperty(prefix + "password"));        return xaDataSource;    }    public static void main(String args[]) {        try {            new TwoPhaseCommitApplication().multiDataSourceTest();        } catch (Exception e) {
            e.printStackTrace();        }    }} file




In this scenario, our code acts as TM, which is also the transaction resource coordinator, and two different data sources, mysql, which acts as the RM resource manager, judges the preparation of each transaction in our code, commits the transaction if both OK and rollback s the transaction if they are not ready.

Modify to a sql that does not execute properly to view its execution



Through breakpoint analysis, we find that the program will have an exception when it executes this sentence, that is, the SQL statement is executed before prepare, but we set the transaction not to commit automatically, so we can not see the result of sql_1 execution in the database.

Modify normal sql to add breakpoints during prepare phase



We look at transactions in the database because I'm working on a cross-database data source on a database server, so we can see two xa records



Let's move on to the commit statement, adding the first commit



At this point, you can see that the xa information for the first transaction is no longer available, that is, the first transaction branch has committed successfully.



You can see in the database that a new insert succeeded



This is when we try to modify the structure or insert a statement and we find the database locked



After we release the breakpoint, we can see that other statements execute normally, that is, xa locks the database during the commit phase. After further analysis, we find that xa locks the entire table after entering xa end, because the sql is an update statement, the whole table is locked until the xa end is committed or rolled back.




We can easily extend this XA mechanism to the situation of micro-services. Each micro-service needs to provide the corresponding mechanism, and each micro-service provides the corresponding prepare interface, commit interface, rollback interface.


Performance issues with xA

XA performance is low.A comparison of XA transaction performance between one database and multiple databases shows that the performance is about 10 times worse.Avoid XA transactions where possible, such as writing data locally and distributing data with high-performance messaging systems.Or use technologies such as database replication.XA should only be used if these are not possible and performance is not a bottleneck

This mechanism assumes that all prepare ok transactions can commit normally

That is, after entering the prepare return ok, there may be some exceptions to two transactions during the commit phase, such as the first normal commit, but the second one fails with some exceptions.

Posted by littlehelp on Tue, 07 Jan 2020 09:31:49 -0800