Shardingsphere整合Atomikos对XA分布式事务的支持(2)

Sharding-JDBC分库分表-分布式事务-5 本文介绍了Sharding JDBC提供的两种分布式事务解决方案:XA事务和BASE事务。XA事务基于X/OPEN组织的DTP模型,通过两阶段提交实现强一致性,适合短事务场景;BASE事务则追求最终一致性,适合高并发场景。文章重点演示了XA事务的Atomikos实现,包括依赖引入、事务配置和使用方法。通过测试案例验证了XA事务在分库分表环境中的正确回滚机制,确保了分布式事务的原子性。 阅读详情

Shardingsphere整合Atomikos对XA分布式事务的支持(2)

Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款相互独立,却又能够混合部署配合使用的产品组成。它们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

ShardingSphere 已于2020年4月16日成为 Apache 软件基金会的顶级项目。

咱们话不多,接上篇,我们直接进入正题。

Atomikos简单介绍

Atomikos(https://www.atomikos.com/),其实是一家公司的名字,提供了基于JTA规范的XA分布式事务TM的实现。其旗下最著名的产品就是事务管理器。产品分两个版本:

  • TransactionEssentials:开源的免费产品;

  • ExtremeTransactions:上商业版,需要收费。

这两个产品的关系如下图所示:

ExtremeTransactions在TransactionEssentials的基础上额外提供了以下功能(重要的):

  • 支持TCC:这是一种柔性事务

  • 支持通过RMI、IIOP、SOAP这些远程过程调用技术,进行事务传播。

  • 事务日志云存储,云端对事务进行恢复,并且提供了完善的管理后台。

org.apache.shardingsphere.transaction.xa.XAShardingTransactionManager详解

我们简单的来回顾下org.apache.shardingsphere.transaction.spiShardingTransactionManager

public interface ShardingTransactionManager extends AutoCloseable {

    /**
     * Initialize sharding transaction manager.
     *
     * @param databaseType database type
     * @param resourceDataSources resource data sources
     */
    void init(DatabaseType databaseType, Collection<ResourceDataSource> resourceDataSources);

    /**
     * Get transaction type.
     *
     * @return transaction type
     */
    TransactionType getTransactionType();

    /**
     * Judge is in transaction or not.
     * 
     * @return in transaction or not
     */
    boolean isInTransaction();

    /**
     * Get transactional connection.
     *
     * @param dataSourceName data source name
     * @return connection
     * @throws SQLException SQL exception
     */
    Connection getConnection(String dataSourceName) throws SQLException;

    /**
     * Begin transaction.
     */
    void begin();

    /**
     * Commit transaction.
     */
    void commit();

    /**
     * Rollback transaction.
     */
    void rollback();
}

我们重点县关注init方法,从它的命名,你就应该能够看出来,这是整个框架的初始化方法,让我们来看看它是如何进行初始化的。

 private final Map<String, XATransactionDataSource> cachedDataSources = new HashMap<>();

 private final XATransactionManager xaTransactionManager = XATransactionManagerLoader.getInstance().getTransactionManager();

    @Override
    public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources) {
        for (ResourceDataSource each : resourceDataSources) {
            cachedDataSources.put(each.getOriginalName(), new XATransactionDataSource(databaseType, each.getUniqueResourceName(), each.getDataSource(), xaTransactionManager));
        }
        xaTransactionManager.init();
    }
  • 首先SPI的方式加载XATransactionManager的具体实现类,这里返回的就是org.apache.shardingsphere.transaction.xa.atomikos.manager.AtomikosTransactionManager。

  • 我们在关注下 new XATransactionDataSource() , 进入 org.apache.shardingsphere.transaction.xa.jta.datasource。XATransactionDataSource类的构造方法。

public XATransactionDataSource(final DatabaseType databaseType, final String resourceName, final DataSource dataSource, final XATransactionManager xaTransactionManager) {
        this.databaseType = databaseType;
        this.resourceName = resourceName;
        this.dataSource = dataSource;
        if (!CONTAINER_DATASOURCE_NAMES.contains(dataSource.getClass().getSimpleName())) {
            // 重点关注 1 ,返回了xaDatasource
            xaDataSource = XADataSourceFactory.build(databaseType, dataSource);
            this.xaTransactionManager = xaTransactionManager;
            // 重点关注2 注册资源
            xaTransactionManager.registerRecoveryResource(resourceName, xaDataSource);
        }
    }
  • 我们重点来关注 XADataSourceFactory.build(databaseType, dataSource),从名字我们就可以看出,这应该是返回JTA规范里面的XADataSourc,在ShardingSphere里面很多的功能,可以从代码风格的命名上就能猜出来,这就是优雅代码(吹一波)。不多逼逼,我们进入该方法。

public final class XADataSourceFactory {

    public static XADataSource build(final DatabaseType databaseType, final DataSource dataSource) {
        return new DataSourceSwapper(XADataSourceDefinitionFactory.getXADataSourceDefinition(databaseType)).swap(dataSource);
    }
}
  • 首先又是一个SPI定义的 XADataSourceDefinitionFactory,它根据不同的数据库类型,来加载不同的方言。然后我们进入 swap方法。

 public XADataSource swap(final DataSource dataSource) {
        XADataSource result = createXADataSource();
        setProperties(result, getDatabaseAccessConfiguration(dataSource));
        return result;
    }
  • 很简明,第一步创建,XADataSource,第二步给它设置属性(包含数据的连接,用户名密码等),然后返回。

  • 返回 XATransactionDataSource 类,关注xaTransactionManager.registerRecoveryResource(resourceName, xaDataSource); 从名字可以看出,这是注册事务恢复资源。这个我们在事务恢复的时候详解。

  • 返回 XAShardingTransactionManager.init() ,我们重点来关注:
    xaTransactionManager.init();,最后进入AtomikosTransactionManager.init()

public final class AtomikosTransactionManager implements XATransactionManager {

    private final UserTransactionManager transactionManager = new UserTransactionManager();

    private final UserTransactionService userTransactionService = new UserTransactionServiceImp();

    @Override
    public void init() {
        userTransactionService.init();
    }

}
  • 进入UserTransactionServiceImp.init()

private void initialize() {
       //添加恢复资源 不用关心
        for (RecoverableResource resource : resources_) {
            Configuration.addResource ( resource );
        }
        for (LogAdministrator logAdministrator : logAdministrators_) {
            Configuration.addLogAdministrator ( logAdministrator );
        }
         //注册插件 不用关心
        for (TransactionServicePlugin nxt : tsListeners_) {
            Configuration.registerTransactionServicePlugin ( nxt );
        }
        //获取配置属性 重点关心
        ConfigProperties configProps = Configuration.getConfigProperties();
        configProps.applyUserSpecificProperties(properties_);
        //进行初始化
        Configuration.init();
    }
  • 我们重点关注,获取配置属性。最后进入com.atomikos.icatch.provider.imp.AssemblerImp.initializeProperties()方法

    @Override
    public ConfigProperties initializeProperties() {
         //读取classpath下的默认配置transactions-defaults.properties
        Properties defaults = new Properties();
        loadPropertiesFromClasspath(defaults, DEFAULT_PROPERTIES_FILE_NAME);
        //读取classpath下,transactions.properties配置,覆盖transactions-defaults.properties中相同key的值
        Properties transactionsProperties = new Properties(defaults);
        loadPropertiesFromClasspath(transactionsProperties, TRANSACTIONS_PROPERTIES_FILE_NAME);
        //读取classpath下,jta.properties,覆盖transactions-defaults.properties、transactions.properties中相同key的值
        Properties jtaProperties = new Properties(transactionsProperties);
        loadPropertiesFromClasspath(jtaProperties, JTA_PROPERTIES_FILE_NAME);

        //读取通过java -Dcom.atomikos.icatch.file方式指定的自定义配置文件路径,覆盖之前的同名配置
        Properties customProperties = new Properties(jtaProperties);
        loadPropertiesFromCustomFilePath(customProperties);
        //最终构造一个ConfigProperties对象,来表示实际要使用的配置
        Properties finalProperties = new Properties(customProperties);
        return new ConfigProperties(finalProperties);
    }
  • 接下来重点关注, Configuration.init(), 进行初始化。

ublic static synchronized boolean init() {
        boolean startupInitiated = false;
        if (service_ == null) {
            startupInitiated = true;
           //SPI方式加载插件注册,无需过多关心  
            addAllTransactionServicePluginServicesFromClasspath();
            ConfigProperties configProperties = getConfigProperties();
          //调用插件的beforeInit方法进行初始化话,无需过多关心
            notifyBeforeInit(configProperties);
          //进行事务日志恢复的初始化,很重要,接下来详解
            assembleSystemComponents(configProperties);
         //进入系统注解的初始化,一般重要
            initializeSystemComponents(configProperties);
            notifyAfterInit();
            if (configProperties.getForceShutdownOnVmExit()) {
                addShutdownHook(new ForceShutdownHook());
            }
        }
        return startupInitiated;
    }
  • 我们先来关注 assembleSystemComponents(configProperties); 进入它,进入com.atomikos.icatch.provider.imp.AssemblerImp.assembleTransactionService()方法:

@Override
    public TransactionServiceProvider assembleTransactionService(
            ConfigProperties configProperties) {
        RecoveryLog recoveryLog =null;
       //打印日志
        logProperties(configProperties.getCompletedProperties());
       //生成唯一名字
        String tmUniqueName = configProperties.getTmUniqueName();

        long maxTimeout = configProperties.getMaxTimeout();
        int maxActives = configProperties.getMaxActives();
        boolean threaded2pc = configProperties.getThreaded2pc();
      //SPI方式加载OltpLog ,这是最重要的扩展地方,如果用户没有SPI的方式去扩展那么就为null    
        OltpLog oltpLog = createOltpLogFromClasspath();
        if (oltpLog == null) {
            LOGGER.logInfo("Using default (local) logging and recovery..."); 
                        //创建事务日志存储资源
            Repository repository = createRepository(configProperties);     
            oltpLog = createOltpLog(repository);
            //??? Assemble recoveryLog
            recoveryLog = createRecoveryLog(repository);            
        }
        StateRecoveryManagerImp recoveryManager = new StateRecoveryManagerImp();
        recoveryManager.setOltpLog(oltpLog);
           //生成唯一id生成器,以后生成XID会用的到
        UniqueIdMgr idMgr = new UniqueIdMgr ( tmUniqueName );
        int overflow = idMgr.getMaxIdLengthInBytes() - MAX_TID_LENGTH;
        if ( overflow > 0 ) {
            // see case 73086
            String msg = "Value too long : " + tmUniqueName;
            LOGGER.logFatal ( msg );
            throw new SysException(msg);
        }
        return new TransactionServiceImp(tmUniqueName, recoveryManager, idMgr, maxTimeout, maxActives, !threaded2pc, recoveryLog);
    }
  • 我们重点来分析createOltpLogFromClasspath(), 采用SPI的加载方式来获取,默认这里会返回 null, 什么意思呢?
    就是当没有扩展的时候,atomikos,会创建框架自定义的资源,来存储事务日志。

private OltpLog createOltpLogFromClasspath() {
        OltpLog ret = null;
        ServiceLoader<OltpLogFactory> loader = ServiceLoader.load(OltpLogFactory.class,Configuration.class.getClassLoader());
        int i = 0;
        for (OltpLogFactory l : loader ) {
            ret = l.createOltpLog();
            i++;
        }
        if (i > 1) {
            String msg = "More than one OltpLogFactory found in classpath - error in configuration!";
            LOGGER.logFatal(msg);
            throw new SysException(msg);
        }
        return ret;
    }
  • 我们跟着进入 Repository repository = createRepository(configProperties);

    private CachedRepository createCoordinatorLogEntryRepository(
            ConfigProperties configProperties) throws LogException {
        //创建内存资源存储
        InMemoryRepository inMemoryCoordinatorLogEntryRepository = new InMemoryRepository();
       //进行初始化
        inMemoryCoordinatorLogEntryRepository.init();
       //创建使用文件存储资源作为backup
        FileSystemRepository backupCoordinatorLogEntryRepository = new FileSystemRepository();
       //进行初始化
        backupCoordinatorLogEntryRepository.init();
      //内存与file资源进行合并
        CachedRepository repository = new CachedRepository(inMemoryCoordinatorLogEntryRepository, backupCoordinatorLogEntryRepository);
        repository.init();
        return repository;
    }
  • 这里就会创建出 CachedRepository,里面包含了 InMemoryRepository 与 FileSystemRepository

  • 回到主线 com.atomikos.icatch.config.Configuration.init(), 最后来分析下notifyAfterInit();

    private static void notifyAfterInit() {
         //进行插件的初始化
        for (TransactionServicePlugin p : tsListenersList_) {
            p.afterInit();
        }
        for (LogAdministrator a : logAdministrators_) {
            a.registerLogControl(service_.getLogControl());
        }
         //设置事务恢复服务,进行事务的恢复
        for (RecoverableResource r : resourceList_ ) {
            r.setRecoveryService(recoveryService_);
        }

    }
  • 插件的初始化会进入com.atomikos.icatch.jta.JtaTransactionServicePlugin.afterInit()

    public void afterInit() {
        TransactionManagerImp.installTransactionManager(Configuration.getCompositeTransactionManager(), autoRegisterResources);
          //如果我们自定义扩展了 OltpLog ,这里就会返回null,如果是null,那么XaResourceRecoveryManager就是null
        RecoveryLog recoveryLog = Configuration.getRecoveryLog();
        long maxTimeout = Configuration.getConfigProperties().getMaxTimeout();
        if (recoveryLog != null) {
            XaResourceRecoveryManager.installXaResourceRecoveryManager(new DefaultXaRecoveryLog(recoveryLog, maxTimeout),Configuration.getConfigProperties().getTmUniqueName());
        }

    }
  • 重点注意 RecoveryLog recoveryLog = Configuration.getRecoveryLog(); ,如果用户采用SPI的方式,扩展了com.atomikos.recovery.OltpLog ,这里就会返回 null。如果是null,则不会对 XaResourceRecoveryManager 进行初始化。

  • 回到 notifyAfterInit(), 我们来分析 setRecoveryService。

public void setRecoveryService ( RecoveryService recoveryService )
            throws ResourceException
    {

        if ( recoveryService != null ) {
            if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( "Installing recovery service on resource "
                    + getName () );
            this.branchIdentifier=recoveryService.getName();
            recover();
        }
    }
  • 我们进入 recover() 方法:

 public void recover() {
        XaResourceRecoveryManager xaResourceRecoveryManager = XaResourceRecoveryManager.getInstance();
        //null for LogCloud recovery
        if (xaResourceRecoveryManager != null) { 
            try {
                xaResourceRecoveryManager.recover(getXAResource());
            } catch (Exception e) {
                refreshXAResource(); //cf case 156968
            }

        }
    }
  • 看到最关键的注释了吗,如果用户采用SPI的方式,扩展了com.atomikos.recovery.OltpLog,那么XaResourceRecoveryManager 为null,则就会进行云端恢复,反之则进行事务恢复。事务恢复很复杂,我们会单独来讲。

到这里atomikos的基本的初始化已经完成。

atomikos事务begin流程

我们知道,本地的事务,都会有一个 trainsaction.begin, 对应XA分布式事务来说也不另外,我们再把思路切换回XAShardingTransactionManager.begin(), 会调用com.atomikos.icatch.jta.TransactionManagerImp.begin()

  public void begin ( int timeout ) throws NotSupportedException,
            SystemException
    {
        CompositeTransaction ct = null;
        ResumePreviousTransactionSubTxAwareParticipant resumeParticipant = null;

        ct = compositeTransactionManager.getCompositeTransaction();
        if ( ct != null && ct.getProperty (  JTA_PROPERTY_NAME ) == null ) {
            LOGGER.logWarning ( "JTA: temporarily suspending incompatible transaction: " + ct.getTid() +
                    " (will be resumed after JTA transaction ends)" );
            ct = compositeTransactionManager.suspend();
            resumeParticipant = new ResumePreviousTransactionSubTxAwareParticipant ( ct );
        }

        try {
      //创建事务补偿点
            ct = compositeTransactionManager.createCompositeTransaction ( ( ( long ) timeout ) * 1000 );
            if ( resumeParticipant != null ) ct.addSubTxAwareParticipant ( resumeParticipant );
            if ( ct.isRoot () && getDefaultSerial () )
                ct.setSerial ();
            ct.setProperty ( JTA_PROPERTY_NAME , "true" );
        } catch ( SysException se ) {
            String msg = "Error in begin()";
            LOGGER.logError( msg , se );
            throw new ExtendedSystemException ( msg , se );
        }
        recreateCompositeTransactionAsJtaTransaction(ct);
    }
  • 这里我们主要关注 compositeTransactionManager.createCompositeTransaction(),

public CompositeTransaction createCompositeTransaction ( long timeout ) throws SysException
    {
        CompositeTransaction ct = null , ret = null;

        ct = getCurrentTx ();
        if ( ct == null ) {
            ret = getTransactionService().createCompositeTransaction ( timeout );
            if(LOGGER.isDebugEnabled()){
                LOGGER.logDebug("createCompositeTransaction ( " + timeout + " ): "
                    + "created new ROOT transaction with id " + ret.getTid ());
            }
        } else {
             if(LOGGER.isDebugEnabled()) LOGGER.logDebug("createCompositeTransaction ( " + timeout + " )");
            ret = ct.createSubTransaction ();

        }

        Thread thread = Thread.currentThread ();
        setThreadMappings ( ret, thread );

        return ret;
    }
  • 创建了事务补偿点,然后把他放到了用当前线程作为key的Map当中,这里思考,为啥它不用 threadLocal。

到这里atomikos的事务begin流程已经完成。大家可能有些疑惑,begin好像什么都没有做,XA start 也没调用?别慌,下一节继续来讲。

XATransactionDataSource getConnection() 流程

我们都知道想要执行SQL语句,必须要获取到数据库的connection。让我们再回到 XAShardingTransactionManager.getConnection() 最后会调用到org.apache.shardingsphere.transaction.xa.jta.datasourceXATransactionDataSource.getConnection()

 public Connection getConnection() throws SQLException, SystemException, RollbackException {
      //先检查是否已经有存在的connection,这一步很关心,也是XA的关键,因为XA事务,必须在同一个connection
        if (CONTAINER_DATASOURCE_NAMES.contains(dataSource.getClass().getSimpleName())) {
            return dataSource.getConnection();
        }
      //获取数据库连接
        Connection result = dataSource.getConnection();
      //转成XAConnection,其实是同一个连接
        XAConnection xaConnection = XAConnectionFactory.createXAConnection(databaseType, xaDataSource, result);
      //获取JTA事务定义接口
        Transaction transaction = xaTransactionManager.getTransactionManager().getTransaction();
        if (!enlistedTransactions.get().contains(transaction)) {
      //进行资源注册
            transaction.enlistResource(new SingleXAResource(resourceName, xaConnection.getXAResource()));
            transaction.registerSynchronization(new Synchronization() {
                @Override
                public void beforeCompletion() {
                    enlistedTransactions.get().remove(transaction);
                }

                @Override
                public void afterCompletion(final int status) {
                    enlistedTransactions.get().clear();
                }
            });
            enlistedTransactions.get().add(transaction);
        }
        return result;
    }
  • 首先第一步很关心,尤其是对shardingsphere来说,因为在一个事务里面,会有多个SQL语句,打到相同的数据库,所以对相同的数据库,必须获取同一个XAConnection,这样才能进行XA事务的提交与回滚。

  • 我们接下来关心 transaction.enlistResource(new SingleXAResource(resourceName, xaConnection.getXAResource()));, 会进入com.atomikos.icatch.jta.TransactionImp.enlistResource(), 代码太长,截取一部分。

try {
                restx = (XAResourceTransaction) res
                        .getResourceTransaction(this.compositeTransaction);

                // next, we MUST set the xa resource again,
                // because ONLY the instance we got as argument
                // is available for use now !
                // older instances (set in restx from previous sibling)
                // have connections that may be in reuse already
                // ->old xares not valid except for 2pc operations

                restx.setXAResource(xares);
                restx.resume();
            } catch (ResourceException re) {
                throw new ExtendedSystemException(
                        "Unexpected error during enlist", re);
            } catch (RuntimeException e) {
                throw e;
            }

            addXAResourceTransaction(restx, xares);
  • 我们直接看 restx.resume();

public synchronized void resume() throws ResourceException {
        int flag = 0;
        String logFlag = "";
        if (this.state.equals(TxState.LOCALLY_DONE)) {// reused instance
            flag = XAResource.TMJOIN;
            logFlag = "XAResource.TMJOIN";
        } else if (!this.knownInResource) {// new instance
            flag = XAResource.TMNOFLAGS;
            logFlag = "XAResource.TMNOFLAGS";
        } else
            throw new IllegalStateException("Wrong state for resume: "
                    + this.state);

        try {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.logDebug("XAResource.start ( " + this.xidToHexString
                        + " , " + logFlag + " ) on resource "
                        + this.resourcename
                        + " represented by XAResource instance "
                        + this.xaresource);
            }
            this.xaresource.start(this.xid, flag);

        } catch (XAException xaerr) {
            String msg = interpretErrorCode(this.resourcename, "resume",
                    this.xid, xaerr.errorCode);
            LOGGER.logWarning(msg, xaerr);
            throw new ResourceException(msg, xaerr);
        }
        setState(TxState.ACTIVE);
        this.knownInResource = true;
    }
  • 哦多尅,看见了吗,各位,看见了 this.xaresource.start(this.xid, flag); 了吗????,我们进去,假设我们使用的Mysql数据库:

 public void start(Xid xid, int flags) throws XAException {
        StringBuilder commandBuf = new StringBuilder(300);
        commandBuf.append("XA START ");
        appendXid(commandBuf, xid);
        switch(flags) {
        case 0:
            break;
        case 2097152:
            commandBuf.append(" JOIN");
            break;
        case 134217728:
            commandBuf.append(" RESUME");
            break;
        default:
            throw new XAException(-5);
        }

        this.dispatchCommand(commandBuf.toString());
        this.underlyingConnection.setInGlobalTx(true);
    }
  • 组装XA start Xid SQL语句,进行执行。

到这里,我们总结下,在获取数据库连接的时候,我们执行了XA协议接口中的 XA start xid

atomikos事务commit流程

好了,上面我们已经开启了事务,现在我们来分析下事务commit流程,我们再把视角切换回XAShardingTransactionManager.commit(),最后我们会进入com.atomikos.icatch.imp.CompositeTransactionImp.commit() 方法:

 public void commit () throws HeurRollbackException, HeurMixedException,
            HeurHazardException, SysException, SecurityException,
            RollbackException
    {
       //首先更新下事务日志的状态
        doCommit ();
        setSiblingInfoForIncoming1pcRequestFromRemoteClient();

        if ( isRoot () ) {
         //真正的commit操作
          coordinator.terminate ( true );
        }
    }
  • 我们关注coordinator.terminate ( true );

 protected void terminate ( boolean commit ) throws HeurRollbackException,
            HeurMixedException, SysException, java.lang.SecurityException,
            HeurCommitException, HeurHazardException, RollbackException,
            IllegalStateException

    {    
        synchronized ( fsm_ ) {
            if ( commit ) {
                     //判断有几个参与者,如果只有一个,直接提交
                if ( participants_.size () <= 1 ) {
                    commit ( true );
                } else {
                                //否则,走XA 2阶段提交流程,先prepare, 再提交
                    int prepareResult = prepare ();
                    // make sure to only do commit if NOT read only
                    if ( prepareResult != Participant.READ_ONLY )
                        commit ( false );
                }
            } else {
                rollback ();
            }
        }
    }
  • 首先会判断参与者的个数,这里我们可以理解为MySQL的database数量,如果只有一个,退化成一阶段,直接提交。
    如果有多个,则走标准的XA二阶段提交流程。

  • 我们来看 prepare (); 流程,最后会走到com.atomikos.icatch.imp.PrepareMessage.send() ---> com.atomikos.datasource.xa.XAResourceTransaction.prepare()

int ret = 0;
        terminateInResource();

        if (TxState.ACTIVE == this.state) {
            // tolerate non-delisting apps/servers
            suspend();
        }

        // duplicate prepares can happen for siblings in serial subtxs!!!
        // in that case, the second prepare just returns READONLY
        if (this.state == TxState.IN_DOUBT)
            return Participant.READ_ONLY;
        else if (!(this.state == TxState.LOCALLY_DONE))
            throw new SysException("Wrong state for prepare: " + this.state);
        try {
            // refresh xaresource for MQSeries: seems to close XAResource after
            // suspend???
            testOrRefreshXAResourceFor2PC();
            if (LOGGER.isTraceEnabled()) {
                LOGGER.logTrace("About to call prepare on XAResource instance: "
                        + this.xaresource);
            }
            ret = this.xaresource.prepare(this.xid);

        } catch (XAException xaerr) {
            String msg = interpretErrorCode(this.resourcename, "prepare",
                    this.xid, xaerr.errorCode);
            if (XAException.XA_RBBASE <= xaerr.errorCode
                    && xaerr.errorCode <= XAException.XA_RBEND) {
                LOGGER.logWarning(msg, xaerr); // see case 84253
                throw new RollbackException(msg);
            } else {
                LOGGER.logError(msg, xaerr);
                throw new SysException(msg, xaerr);
            }
        }
        setState(TxState.IN_DOUBT);
        if (ret == XAResource.XA_RDONLY) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.logDebug("XAResource.prepare ( " + this.xidToHexString
                        + " ) returning XAResource.XA_RDONLY " + "on resource "
                        + this.resourcename
                        + " represented by XAResource instance "
                        + this.xaresource);
            }
            return Participant.READ_ONLY;
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.logDebug("XAResource.prepare ( " + this.xidToHexString
                        + " ) returning OK " + "on resource "
                        + this.resourcename
                        + " represented by XAResource instance "
                        + this.xaresource);
            }
            return Participant.READ_ONLY + 1;
        }
  • 终于,我们看到了这么一句 ret = this.xaresource.prepare(this.xid); 但是等等,我们之前不是说了,XA start xid 以后要先 XA end xid 吗?答案就在 suspend(); 里面。

public synchronized void suspend() throws ResourceException {

        // BugzID: 20545
        // State may be IN_DOUBT or TERMINATED when a connection is closed AFTER
        // commit!
        // In that case, don't call END again, and also don't generate any
        // error!
        // This is required for some hibernate connection release strategies.
        if (this.state.equals(TxState.ACTIVE)) {
            try {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.logDebug("XAResource.end ( " + this.xidToHexString
                            + " , XAResource.TMSUCCESS ) on resource "
                            + this.resourcename
                            + " represented by XAResource instance "
                            + this.xaresource);
                }
                 //执行了 xa end 语句
                this.xaresource.end(this.xid, XAResource.TMSUCCESS);

            } catch (XAException xaerr) {
                String msg = interpretErrorCode(this.resourcename, "end",
                        this.xid, xaerr.errorCode);
                if (LOGGER.isTraceEnabled())
                    LOGGER.logTrace(msg, xaerr);
                // don't throw: fix for case 102827
            }
            setState(TxState.LOCALLY_DONE);
        }
    }

到了这里,我们已经执行了 XA start xid -> XA end xid --> XA prepare xid, 接下来就是最后一步 commit

  • 我们再回到 terminate(false) 方法,来看 commit()流程。其实和 prepare流程一样,最后会走到 com.atomikos.datasource.xa.XAResourceTransaction.commit()。commit执行完,数据提交

//繁杂代码过多,就显示核心的
this.xaresource.commit(this.xid, onePhase);

思考:这里的参与者提交是在一个循环里面,一个一个提交的,如果之前的提交了,后面的参与者提交的时候,挂了,就会造成数据的不一致性。

Atomikos rollback() 流程

上面我们已经分析了commit流程,其实rollback流程和commit流程一样,我们在把目光切换回 org.apache.shardingsphere.transaction.xa.XAShardingTransactionManager.rollback() ,最后会执行到com.atomikos.icatch.imp.CompositeTransactionImp.rollback()。

    public void rollback () throws IllegalStateException, SysException
    {
        //清空资源,更新事务日志状态等
        doRollback ();
        if ( isRoot () ) {
            try {
                coordinator.terminate ( false );
            } catch ( Exception e ) {
                throw new SysException ( "Unexpected error in rollback: " + e.getMessage (), e );
            }
        }
    }
  • 重点关注 coordinator.terminate ( false ); ,这个和 commit流程是一样的,只不过在 commit流程里面,参数传的是true。

 protected void terminate ( boolean commit ) throws HeurRollbackException,
            HeurMixedException, SysException, java.lang.SecurityException,
            HeurCommitException, HeurHazardException, RollbackException,
            IllegalStateException

    {    
        synchronized ( fsm_ ) {
            if ( commit ) {
                if ( participants_.size () <= 1 ) {
                    commit ( true );
                } else {
                    int prepareResult = prepare ();
                    // make sure to only do commit if NOT read only
                    if ( prepareResult != Participant.READ_ONLY )
                        commit ( false );
                }
            } else {
                 //如果是false,走的是rollback
                rollback ();
            }
        }
    }
  • 我们重点关注 rollback() ,最后会走到com.atomikos.datasource.xa.XAResourceTransaction.rollback()。

public synchronized void rollback()
            throws HeurCommitException, HeurMixedException,
            HeurHazardException, SysException {
        terminateInResource();

        if (rollbackShouldDoNothing()) {
            return;
        }
        if (this.state.equals(TxState.TERMINATED)) {
            return;
        }

        if (this.state.equals(TxState.HEUR_MIXED))
            throw new HeurMixedException();
        if (this.state.equals(TxState.HEUR_COMMITTED))
            throw new HeurCommitException();
        if (this.xaresource == null) { 
            throw new HeurHazardException("XAResourceTransaction "
                    + getXid() + ": no XAResource to rollback?");
        }

        try {
            if (this.state.equals(TxState.ACTIVE)) { // first suspend xid
                suspend();
            }

            // refresh xaresource for MQSeries: seems to close XAResource after
            // suspend???
            testOrRefreshXAResourceFor2PC();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.logDebug("XAResource.rollback ( " + this.xidToHexString
                        + " ) " + "on resource " + this.resourcename
                        + " represented by XAResource instance "
                        + this.xaresource);
            }
            this.xaresource.rollback(this.xid);
  • 先在supend()方法里面执行了 XA end xid 语句, 接下来执行 this.xaresource.rollback(this.xid); 进行数据的回滚。

Atomikos 事务恢复流程

在说,事务恢复流程之前,我们来讨论下,会啥会出现事务恢复?,XA 2阶段提交协议不是强一致性的吗?。要解答这个问题,我们就要来看看XA 二阶段协议有什么问题?

问题一 :单点故障

由于协调者的重要性,一旦协调者TM发生故障。参与者RM会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)

问题二 :数据不一致

数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象。

如何解决?

解决的方案简单,就是我们在事务的操作的每一步,我们都需要对事务状态的日志进行人为的记录,我们可以把日志记录存储在我们想存储的地方,可以是本地存储,也可以中心化的存储。atomikos的开源版本,我们之前也分析了,它是使用内存 + file的方式,存储在本地,这样的话,如果在一个集群系统里面,如果有节点宕机,日志又存储在本地,所以事务不能及时的恢复(需要重启服务)。

Atomikos 多场景下事务恢复。

Atomikos 提供了二种方式,来应对不同场景下的异常情况。

  • 场景一:服务节点不宕机,因为其他的原因,产生需要事务恢复的情况。这个时候才要定时任务进行恢复。
    具体的代码 com.atomikos.icatch.imp.TransactionServiceImp.init() 方法,会初始化一个定时任务,进行事务的恢复。

public synchronized void init ( Properties properties ) throws SysException
    {
        shutdownInProgress_ = false;
        control_ = new com.atomikos.icatch.admin.imp.LogControlImp ( (AdminLog) this.recoveryLog );
        ConfigProperties configProperties = new ConfigProperties(properties);
        long recoveryDelay = configProperties.getRecoveryDelay();  
       recoveryTimer = new PooledAlarmTimer(recoveryDelay);  
        recoveryTimer.addAlarmTimerListener(new AlarmTimerListener() {

            @Override
            public void alarm(AlarmTimer timer) {
                //进行事务恢复
                performRecovery();

            }


        });

        TaskManager.SINGLETON.executeTask(recoveryTimer);

        initialized_ = true;


    }
  • 最终会进入com.atomikos.datasource.xa.XATransactionalResource.recover() 方法。

   public void recover() {
        XaResourceRecoveryManager xaResourceRecoveryManager = XaResourceRecoveryManager.getInstance();
        if (xaResourceRecoveryManager != null) { //null for LogCloud recovery
            try {
                xaResourceRecoveryManager.recover(getXAResource());
            } catch (Exception e) {
                refreshXAResource(); //cf case 156968
            }

        }
    }
  • 场景二: 当服务节点宕机重启动过程中进行事务的恢复。具体实现在com.atomikos.datasource.xa.XATransactionalResource.setRecoveryService()方法里面

 @Override
    public void setRecoveryService ( RecoveryService recoveryService )
            throws ResourceException
    {

        if ( recoveryService != null ) {
            if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( "Installing recovery service on resource "
                    + getName () );
            this.branchIdentifier=recoveryService.getName();
         //进行事务恢复
            recover();
        }

    }

com.atomikos.datasource.xa.XATransactionalResource.recover() 流程详解。

    public void recover(XAResource xaResource) throws XAException {
      // 根据XA recovery 协议获取 xid
        List<XID> xidsToRecover = retrievePreparedXidsFromXaResource(xaResource);
        Collection<XID> xidsToCommit;
        try { 
            // xid 与日志记录的xid进行匹配
            xidsToCommit = retrieveExpiredCommittingXidsFromLog();
            for (XID xid : xidsToRecover) {
                if (xidsToCommit.contains(xid)) {
            //执行 XA commit xid 进行提交                 
                    replayCommit(xid, xaResource);
                } else {
                    attemptPresumedAbort(xid, xaResource);
                }
            }
        } catch (LogException couldNotRetrieveCommittingXids) {
            LOGGER.logWarning("Transient error while recovering - will retry later...", couldNotRetrieveCommittingXids);
        }
    }
  • 我们来看一下如何根据 XA recovery 协议获取RM端存储的xid。进入方法 retrievePreparedXidsFromXaResource(xaResource), 最后进入 com.atomikos.datasource.xa.RecoveryScan.recoverXids()方法。

public static List<XID> recoverXids(XAResource xaResource, XidSelector selector) throws XAException {
        List<XID> ret = new ArrayList<XID>();

        boolean done = false;
        int flags = XAResource.TMSTARTRSCAN;
        Xid[] xidsFromLastScan = null;
        List<XID> allRecoveredXidsSoFar = new ArrayList<XID>();
        do {
            xidsFromLastScan = xaResource.recover(flags);
            flags = XAResource.TMNOFLAGS;
            done = (xidsFromLastScan == null || xidsFromLastScan.length == 0);
            if (!done) {

                // TEMPTATIVELY SET done TO TRUE
                // TO TOLERATE ORACLE 8.1.7 INFINITE
                // LOOP (ALWAYS RETURNS SAME RECOVER
                // SET). IF A NEW SET OF XIDS IS RETURNED
                // THEN done WILL BE RESET TO FALSE

                done = true;
                for ( int i = 0; i < xidsFromLastScan.length; i++ ) {
                    XID xid = new XID ( xidsFromLastScan[i] );
                    // our own XID implements equals and hashCode properly
                    if (!allRecoveredXidsSoFar.contains(xid)) {
                        // a new xid is returned -> we can not be in a recovery loop -> go on
                        allRecoveredXidsSoFar.add(xid);
                        done = false;
                        if (selector.selects(xid)) {
                            ret.add(xid);
                        }
                    }
                }
            }
        } while (!done);

        return ret;
    }
  • 我们重点关注xidsFromLastScan = xaResource.recover(flags); 这个方法,如果我们使用MySQL,那么久会进入 MysqlXAConnection.recover()方法。执行 XA recovery xid 语句来获取 xid

 protected static Xid[] recover(Connection c, int flag) throws XAException {
        /*
         * The XA RECOVER statement returns information for those XA transactions on the MySQL server that are in the PREPARED state. (See Section 13.4.7.2, ???XA
         * Transaction States???.) The output includes a row for each such XA transaction on the server, regardless of which client started it.
         * 
         * XA RECOVER output rows look like this (for an example xid value consisting of the parts 'abc', 'def', and 7):
         * 
         * mysql> XA RECOVER;
         * +----------+--------------+--------------+--------+
         * | formatID | gtrid_length | bqual_length | data |
         * +----------+--------------+--------------+--------+
         * | 7 | 3 | 3 | abcdef |
         * +----------+--------------+--------------+--------+
         * 
         * The output columns have the following meanings:
         * 
         * formatID is the formatID part of the transaction xid
         * gtrid_length is the length in bytes of the gtrid part of the xid
         * bqual_length is the length in bytes of the bqual part of the xid
         * data is the concatenation of the gtrid and bqual parts of the xid
         */

        boolean startRscan = ((flag & TMSTARTRSCAN) > 0);
        boolean endRscan = ((flag & TMENDRSCAN) > 0);

        if (!startRscan && !endRscan && flag != TMNOFLAGS) {
            throw new MysqlXAException(XAException.XAER_INVAL, Messages.getString("MysqlXAConnection.001"), null);
        }

        //
        // We return all recovered XIDs at once, so if not  TMSTARTRSCAN, return no new XIDs
        //
        // We don't attempt to maintain state to check for TMNOFLAGS "outside" of a scan
        //

        if (!startRscan) {
            return new Xid[0];
        }

        ResultSet rs = null;
        Statement stmt = null;

        List<MysqlXid> recoveredXidList = new ArrayList<MysqlXid>();

        try {
            // TODO: Cache this for lifetime of XAConnection
            stmt = c.createStatement();

            rs = stmt.executeQuery("XA RECOVER");

            while (rs.next()) {
                final int formatId = rs.getInt(1);
                int gtridLength = rs.getInt(2);
                int bqualLength = rs.getInt(3);
                byte[] gtridAndBqual = rs.getBytes(4);

                final byte[] gtrid = new byte[gtridLength];
                final byte[] bqual = new byte[bqualLength];

                if (gtridAndBqual.length != (gtridLength + bqualLength)) {
                    throw new MysqlXAException(XAException.XA_RBPROTO, Messages.getString("MysqlXAConnection.002"), null);
                }

                System.arraycopy(gtridAndBqual, 0, gtrid, 0, gtridLength);
                System.arraycopy(gtridAndBqual, gtridLength, bqual, 0, bqualLength);

                recoveredXidList.add(new MysqlXid(gtrid, bqual, formatId));
            }
        } catch (SQLException sqlEx) {
            throw mapXAExceptionFromSQLException(sqlEx);
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException sqlEx) {
                    throw mapXAExceptionFromSQLException(sqlEx);
                }
            }

            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException sqlEx) {
                    throw mapXAExceptionFromSQLException(sqlEx);
                }
            }
        }

        int numXids = recoveredXidList.size();

        Xid[] asXids = new Xid[numXids];
        Object[] asObjects = recoveredXidList.toArray();

        for (int i = 0; i < numXids; i++) {
            asXids[i] = (Xid) asObjects[i];
        }

        return asXids;
    }
  • 这里要注意如果Mysql的版本 <5.7.7 ,则不会有任何数据,在以后的版本中Mysql进行了修复,因此如果我们想要使用MySQL充当RM,版本必须 >= 5.7.7 ,原因是:

MySQL 5.6版本在客户端退出的时候,自动把已经prepare的事务回滚了,那么MySQL为什么要这样做?这主要取决于MySQL的内部实现,MySQL 5.7以前的版本,对于prepare的事务,MySQL是不会记录binlog的(官方说是减少fsync,起到了优化的作用)。只有当分布式事务提交的时候才会把前面的操作写入binlog信息,所以对于binlog来说,分布式事务与普通的事务没有区别,而prepare以前的操作信息都保存在连接的IO_CACHE中,如果这个时候客户端退出了,以前的binlog信息都会被丢失,再次重连后允许提交的话,会造成Binlog丢失,从而造成主从数据的不一致,所以官方在客户端退出的时候直接把已经prepare的事务都回滚了!

  • 回到主线,假设我们获取到需要进行事务恢复的XID,再从自己记录的事务日志里面获取XID,如果前者包含在后者之中,则进行commit,否则进行rollback.

List<XID> xidsToRecover = retrievePreparedXidsFromXaResource(xaResource);
        Collection<XID> xidsToCommit;
        try {
            xidsToCommit = retrieveExpiredCommittingXidsFromLog();
            for (XID xid : xidsToRecover) {
                if (xidsToCommit.contains(xid)) {
                    replayCommit(xid, xaResource);
                } else {
                    attemptPresumedAbort(xid, xaResource);
                }
            }
        } catch (LogException couldNotRetrieveCommittingXids) {
            LOGGER.logWarning("Transient error while recovering - will retry later...", couldNotRetrieveCommittingXids);
        }
  • replayCommit 方法如下:

private void replayCommit(XID xid, XAResource xaResource) {
        if (LOGGER.isDebugEnabled()) LOGGER.logDebug("Replaying commit of xid: " + xid);
        try {
            xaResource.commit(xid, false);
            log.terminated(xid);
        } catch (XAException e) {
            if (alreadyHeuristicallyTerminatedByResource(e)) {
                handleHeuristicTerminationByResource(xid, xaResource, e, true);
            } else if (xidTerminatedInResourceByConcurrentCommit(e)) {
                log.terminated(xid);
            } else {
                LOGGER.logWarning("Transient error while replaying commit - will retry later...", e);
            }
        }
    }
  • attemptPresumedAbort(xid, xaResource); 方法如下:

private void attemptPresumedAbort(XID xid, XAResource xaResource) {
        try {
            log.presumedAborting(xid);
            if (LOGGER.isDebugEnabled()) LOGGER.logDebug("Presumed abort of xid: " + xid);
            try {
                xaResource.rollback(xid);
                log.terminated(xid); 
            } catch (XAException e) {
                if (alreadyHeuristicallyTerminatedByResource(e)) {
                    handleHeuristicTerminationByResource(xid, xaResource, e, false);
                } else if (xidTerminatedInResourceByConcurrentRollback(e)) {
                    log.terminated(xid);
                } else {
                    LOGGER.logWarning("Unexpected exception during recovery - ignoring to retry later...", e);
                }
            }
        } catch (IllegalStateException presumedAbortNotAllowedInCurrentLogState) {
            // ignore to retry later if necessary
        } catch (LogException logWriteException) {
            LOGGER.logWarning("log write failed for Xid: "+xid+", ignoring to retry later", logWriteException);
        }
    }

文章到此,已经写的很长很多了,我们分析了ShardingSphere对于XA方案,提供了一套SPI解决方案,对Atomikos进行了整合,也分析了Atomikos初始化流程,开始事务流程,获取连接流程,提交事务流程,回滚事务流程,事务恢复流程。
希望对大家理解XA的原理有所帮助。

作者介绍:肖宇,Apache ShardingSphere Committer,开源hmily分布式事务框架作者,
开源soul网关作者,热爱开源,追求写优雅代码。目前就职于京东数科,参与ShardingSphere的开源建设,以及分布式数据库的研发工作。

扫码关注

ShardingSphere

ShardingSphere与Atomikos XA的分布式事务实现示例 最后,我们配置了一个XA事务管理器,指定了使用的数据源。通过以上示例代码,我们演示了如何使用ShardingSphere和Atomikos XA来实现分布式事务。通过合理配置和结合两者的功能,我们可以轻松处理跨多个数据库节点的事务操作。方法中,我们获取ShardingSphere和Atomikos的连接,并在各自的连接上执行操作。首先,我们需要初始化ShardingSphere的数据源和Atomikos的事务管理器。请注意,以上示例代码仅用于演示目的,实际使用时需要根据具体的业务需求进行适当的调整和优化。 阅读详情

相关推荐

shardingsphere-jdbc整合springboot实现读写分离

shardingsphere-jdbc整合springboot实现读写分离,持久层使用mybatisplus,下载就能直接使用,适合新手小白,开箱即用

shardingsphere com.atomikos.icatch.jta.JtaTransactionServicePlugin.beforeInit()V

shardingsphere com.atomikos.icatch.jta.JtaTransactionServicePlugin.beforeInit()V 今天使用shardingsphere 5.1.0版本做分库分表,同时测试了他的分布式事务XA的解决方案,项目启动的时候就报上面这个错误; 引入包 <dependency> <groupId>org.apache.shardingsphere</groupId> &l

以后的今天的博客 1157

2015年全国大学生电子设计大赛综合测评题

2015年全国大学生电子设计大赛综合测评题 题目如下: 一、555产生可调方波 555定时器是一种集成电路芯片,常被用于定时器、脉冲产生器和振荡电路。555可被作为电路中的延时器件、触发器或起振元件。 555定时器可组成单稳态电路、多稳态电路和无稳态电路。产生方波其实就是制造一个无稳态电路。 555定时器的引脚功能: 其中: DIS引脚与OUT脚电平一致 TRIG: 当此引脚电压降至1/3VCC(或由控制端决定的阈值电压)时输出端给出高电平。 THR:当此引脚电压升至2/3VCC(或由控制端决定的阈值电压

m0_47066484的博客 3万+

shardingsphere 5.2.1 分布式事务

shardingsphere 分布式事务

weixin_39024325的博客 2004

Claude Code+Cursor+Claude 4三剑合璧!5步搞定Magentic-UI的OAuth2.0集成,开发效率飙升500%,新手秒变全栈工程师

本文详细介绍了如何利用Claude Code、Cursor和Claude 4三款AI工具高效集成Magentic-UI的OAuth2.0认证系统。通过5个步骤的实战演练,开发者可快速完成从环境搭建、项目分析、代码生成到前端集成的全过程,显著提升开发效率。文章还提供了JWT令牌管理、安全配置及常见问题解决方案。

weixin_29234509的博客 453

中国30米分辨率城市建成区数据集 (1972-2023年)

全国城市建成区空间分布数据(1972-2023年),利用Google Earth Engine平台,基于Landsat系列遥感影像提取了1972–2023年城市不透水面,后经过不透水面聚集密度计算方法计算了不透水面聚集密度,通过阈值划分提取了城市建成区,并设置了建成区面积阈值,去除了噪点,获取了全国城市建成区空间分布数据集。该提取能准确刻画城市形态,提取精度高,保持了建成区的完整性。数据集包括41期数据,1972年,1978年,1985-2023年逐年,分辨率为30米,有栅格和矢量两种格式。

地球资源数据云的官方博客 5825

Shardingsphere整合Atomikos对XA分布式事务的支持(1)

Shardingsphere整合Atomikos对XA分布式事务的支持(1)Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC...

ShardingSphere官微 1668

2024“华为杯”中国研究生数学建模竞赛(A题)深度剖析_数学建模完整过程+详细思路+代码全解析

目标是优化风力涡轮机的功率调度,使其在给定的实时风速数据下,最小化涡轮机关键组件(如塔架和主轴)的疲劳损伤。问题的挑战在于,考虑通信延迟和测量噪声的同时,要确保功率输出与风速变化的响应快速且准确。

2401_82549447的博客 6630

ShardingSphere分布式事务详解

ShardingJDBC支持的分布式事务方式有三种 LOCAL, XA , BASE,这三种事务实现方式都是采用的对代码无侵入的方式实现的。

Zhuxiaoyu_91的博客 2317

使用TMS320F28335控制四位共阳数码管

简要阐述利用F28335控制四位共阳数码管的整个过程,包括原理、思路、部分代码以及调试过程。

liujunhaozuishuai的博客 2660

PCL 加权局部最优投影(WLOP)简化算法

WLOP算法是一种点云重采样方法,通过迭代优化实现点云均匀化和特征保留。算法包含数据项(加权平均位置保持几何特征)和正则化项(排斥邻近点实现均匀分布)两部分。代码实现基于PCL库,通过KD树加速邻域搜索,使用高斯权重函数计算点位置更新。参数包括输出点比例、邻域半径、正则化权重和迭代次数。该算法不依赖网格信息,适合处理大规模扫描数据的预处理优化。

点云侠的博客 631

Java-125 深入浅出 MySQL 深入解析 XA 分布式事务:ShardingSphere 的整合原理与实践

传统方式依赖数据库厂商提供的 XA 驱动和应用服务器的事务管理器,但存在部署复杂、成本高、资源消耗大的问题。现代系统则多采用 嵌入式事务管理器(如 Atomikos、Bitronix),以轻量级 jar 包形式集成,简化了运维。ShardingSphere 在此基础上提供跨库 XA 事务支持,采用 两阶段提交协议(2PC) 保证强一致性,并具备故障恢复能力。其执行流程涵盖 SQL 路由、XA 资源注册、prepare 阶段和 commit/rollback 协调,确保分布式事务要么全部成功,要么全部回滚。通

谢谢你的喜欢 我们一起无限进步 1315

黑猫带你学eMMC协议第4篇:eMMC概述

1 前言 声明: 本文依据eMMC JEDEC5.1协议及个人工作经验整理而成,如有错误请留言。 文章为个人辛苦整理,付费内容,禁止私自转载。 本文大约3000字,从emmc的内部结构框架、供电、总线、寄存器等各方面进行框架性的介绍,本章节内容不需要深入研究,读完本篇文章,只要能对emmc有个框架下的了解,就可以了。 2 内部结构框架 由图可以看出,emmc内部由两块组成:控制器 + 存储介质。emmc内部存储介质绝大部分为nand flash。 众所周知,我们需要存储数据到nand flash中,并

黑猫奥利奥的博客 3341

ShardingSphere之Sharding-JDBC整合XA模式、Saga模式、Seata原理介绍以及代码演示

ShardingSphere之Sharding-JDBC整合XA模式、Saga模式、Seata原理介绍以及代码演示

weixin_44795847的博客 2595

基于Requests的百度图片搜索爬取技术详解

本文介绍了一种高效获取百度图片搜索数据的混合请求爬虫技术。该技术结合HTML解析与JSON API调用,通过分析百度图片搜索的网络通信模式,实现了首页HTML解析获取初始参数、后续页面直接调用JSON接口的双重机制。系统采用模块化设计,包含Cookies解析、智能请求分发、数据提取、图片下载等核心模块,支持自定义关键词和爬取页数配置。关键技术包括动态参数管理、正则表达式提取、JSON数据处理等,相比传统爬虫方法具有效率高、资源消耗低的优势,为计算机视觉研究提供了可靠的大规模图片数据采集方案。

那些看似不起波澜的日复一日,会在某天让你看到坚持的意义 420

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

ShardingSphere分布式事务:XA与Saga模式解析与实践

分布式事务是微服务架构中的关键技术,用于解决跨数据库或跨服务的数据一致性问题。其核心原理包括两阶段提交(XA)和最终一致性(Saga/TCC)两种模式,前者保证强一致性但性能较低,后者通过补偿机制实现高可用。在技术价值层面,分布式事务中间件如ShardingSphere通过统一抽象层,帮助开发者屏蔽底层复杂度,典型应用场景包括电商订单、金融支付等高并发系统。本文以ShardingSphere为例,深入解析其XA事务的原子性保障机制与Saga模式的自动SQL反向生成特性,并给出混合事务模式的电商实战案例,其中

weixin_30770783的博客 442

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

ShardingSphere集成springBoot实现分布式事务

XA事务是典型的强一致性事务,也就是完全遵循事务的ACID设计原则。这里的BASE来自基本可用(BasicallyAvailable)、软状态(SoftState)和最终一致性(EventualConsistency)这三个概念实现柔性事务如阿里巴巴提供一些优秀的框架Seata,ShardingSphere内部也集成了对Seata的支持,可以根据需要集成其他分布式事务类开源框架,并基于微内核架构嵌入到ShardingSphere运行时环境中。//抛出异常事务回滚。...

weixin_39643007的博客 3107

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

SpringBoot+ShardingSphere - 高并发库存扣减场景的分布式事务模型

SpringBoot+ShardingSphere - 高并发库存扣减场景的分布式事务模型

weixin_46619605的博客 1716

(高级)ShardingSphere源码分析如何实现分布式事务

在分析事务之前,需要先明白事务与链接的关系,一个事务可以包含多个链接(如读写分离的实现),一个链接也可以对应多个事务(如链接池化技术)。 ShardingTransactionManagerEngine shardingsphere与事务相关的工程有以下几个。 从命名上,我们不难看出 sharding-transaction-core 应该包含了分布式事务相关的一些基础核心类,而 sharding-transaction-2pc 和 sharding-transaction-base 分别基于强一致性.

gonghaiyu的博客 1672

SpringBoot与ShardingSphere整合,重构高并发库存扣减场景的分布式事务模型

订单创建和库存扣减操作必须保证原子性,避免出现超卖或欠卖的情况,利用 ShardingSphere 的分布式事务管理功能,确保跨多个数据库的操作具有一致性。XA 事务支持:ShardingSphere 提供了 XA 协议的支持,确保跨多个数据库的操作具有强一致性,即使在分布式环境下也能保持数据的一致性。柔性事务:除了 XA 事务外,ShardingSphere 还支持柔性事务解决方案,如 Saga 和 BASE 模型,以适应不同的业务场景。灵活的分片策略:可以根据不同的业务需求选择合适的分片算法(如。

qq_18244417的博客 547

ShardingSphere事务

ShardingSphere支持将普通的数据库连接池,转换为支持XA事务的连接池,对HikariCP, Druid和DBCP2连接池内置支持,无需额外配置。其它连接池需要用户实现的SPI接口进行扩展。

Mamba Out 892

Sharding-JDBC分布式事务实战与优化指南

分布式事务是微服务架构中的关键技术,用于确保跨数据库操作的数据一致性。其核心原理包括两阶段提交(2PC)和柔性事务(BASE理论),前者保证强一致性但性能较低,后者通过最终一致性提升系统吞吐量。Sharding-JDBC作为Apache ShardingSphere生态的核心组件,整合了XA、Saga、Seata等多种事务模式,特别适合分库分表场景下的数据一致性保障。在实际工程中,开发者需要根据业务场景选择合适的事务模型:金融支付等强一致性需求推荐XA模式,而电商订单等长流程业务可采用Saga模式配合补偿机

weixin_34195364的博客 373

ShardingSphpere分库分表二之XA事务

ShardingSphere分表分库之XA事务

flying86的博客 1141

<span class=“js_title_inner“>SpringBoot与ShardingSphere整合,重构高并发库存扣减场景的分布式事务模型</span>

订单创建和库存扣减操作必须保证原子性,避免出现超卖或欠卖的情况,利用 ShardingSphere 的分布式事务管理功能,确保跨多个数据库的操作具有一致性。XA 事务支持:ShardingSphere 提供了 XA 协议的支持,确保跨多个数据库的操作具有强一致性,即使在分布式环境下也能保持数据的一致性。柔性事务:除了 XA 事务外,ShardingSphere 还支持柔性事务解决方案,如 Saga 和 BASE 模型,以适应不同的业务场景。灵活的分片策略:可以根据不同的业务需求选择合适的分片算法(如。

qq_18244417的博客 339

Atomikos

 使用 &lt;dependency&gt; &lt;groupId&gt;io.shardingsphere&lt;/groupId&gt; &lt;artifactId&gt;sharding-transaction-2pc-xa&lt;/artifactId&gt; &lt;version&gt;3....

qq_14945847的博客 1937

Sharding-JDBC分布式事务实战

简介 ShardingSphere整合了XA、Saga和Seata模式后,为分布式事务控制提供了极大的便利,我们可以在 应用程序编程时,采用以下统一模式进行使用。 实战 引入Maven依赖 //XA模式 <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-transaction-xa-core</artifactId> &l

jiqiren1994的博客 1400
上一篇: 万字长文详解Shardingsphere对XA分布式事务的支持
下一篇: ​Atomikos-XA 事务恢复(3)
ShardingSphere
博客等级 码龄7年 449粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值