Oracle分布式事务(一)两等提交和JTA

原创文章,同步发自笔者个人博客 http://www.jasongj.com/big_data/two_phase_commit/

分布式事务

分布式事务简介

分布式事务是负会涉及到操作多单数据库(或者提供工作语义的系,如JMS)的业务。其实就是拿对同一数据库事务之概念扩大至了对多单数据库的事体。目的是以保证分布式系统中事务操作的原子性。分布式事务处理的根本是要出同等种方式好知晓事情在其他地方所举行的保有动作,提交或回滚事务之支配要有统一的结果(全部提交或任何回滚)。

分布式事务实现机制

如作者以《SQL优化(六) MVCC
PostgreSQL实现工作及多本出现控制的精华》一和被所出口,事务包含原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

PostgreSQL针对ACID的兑现技能如下表所示。

ACID
原子性(Atomicity)
一致性(Consistency)
隔离性
持久性

分布式事务之落实技能如下表所示。(以PostgreSQL作为工作与方也例)

分布式ACID
原子性(Atomicity)
一致性(Consistency)
隔离性
持久性

从上表可以观看,一致性、隔离性和持久性靠的凡各个分布式事务参与方自己原本的建制,而个别号提交主要承保了分布式事务之原子性。

片流提交

分布式事务如何保管原子性

当分布式系统中,各个节点(或者业务参与方)之间在大体上竞相独立,通过网络进行和谐。每个独立的节点(或机件)由于在业务机制,可以保其数量操作的ACID特性。但是,各节点内由相互之间独立,无法适用地掌握那个通过节点受到之事情执行情况,所以基本上节点内充分麻烦保证ACID,尤其是原子性。

万一要贯彻分布式系统的原子性,则须保证拥有节点的数额勾勒操作,要无遍且尽(生效),要么全部都未履行(生效)。但是,一个节点在实行本地工作之早晚无法清楚其他机器的地面工作的施行结果,所以它们便未明白本次事务到底应该commit还是
roolback。常规的解决办法是引入一个“协调者”的组件来归并调度所有分布式节点的行。

XA规范

XA是由于X/Open组织提出的分布式事务的正统。XA规范重大定义了(全局)事务管理器(Transaction
Manager)和(局部)资源管理器(Resource
Manager)之间的接口。XA接口是双向的系接口,在事务管理器(Transaction
Manager)以及一个或者多个资源管理器(Resource
Manager)之间形成通信桥梁。XA引入的事务管理器充当及文所述全局工作中之“协调者”角色。事务管理器控制正在全局工作,管理事务生命周期,并协调资源。资源管理器负责控制与管理实际上资源(如数据库或JMS队列)。目前,Oracle、Informix、DB2、Sybase和PostgreSQL等各级主流数据库都提供了针对XA的支持。

XA规范着,事务管理器主要通过以下的接口对资源管理器进行田间管理

  • xa_open,xa_close:建立与关闭及资源管理器的连接。
  • xa_start,xa_end:开始与竣工一个当地工作。
  • xa_prepare,xa_commit,xa_rollback:预提交、提交和回滚一个地方工作。
  • xa_recover:回滚一个已进行预提交的事情。

点滴路提交原理

次品提交的算法思路好包为:协调者询问参与者是否准备好了提交,并依据所有参与者的申报情况控制往所有参与者发送commit或者rollback指令(协调者向装有参与者发送相同的下令)。

所谓的个别独号是依

  • 准备阶段
    又如投票等。在当时同样等,协调者询问有参与者是否准备好交给,参与者要就准备好交给则回复Prepared,否则回复Non-Prepared
  • 提交阶段
    又如执行阶段。协调者如果在上一阶段收到所有参与者回复的Prepared,则在斯号向所有参与者发送commit一声令下,所有参与者就实施commit操作;否则协调者向有参与者发送rollback指令,参与者就施行rollback操作。

片路提交中,协调者和参与方的互动过程要下图所显示。
Oracle 1

有限品提交前提条件

  • 网络通信是可信的。虽然网并无可靠,但点滴号提交的显要对象并无是化解诸如拜占庭问题之大网问题。同时片品级提交的第一网络通信危险期(In-doubt
    Time)在作业提交等,而该阶段大缺乏。
  • 不无crash的节点最终还见面恢复,不见面直接处在crash状态。
  • 每个分布式事务参与方都生WAL日志,并且该日记存于稳定之囤上。
  • 列节点上之地方工作状态就撞机器crash都不过打WAL日志上过来。

少数阶段提交容错方式

个别等提交中之良要分为如下三栽状况

  1. 协调者正常,参与方crash
  2. 协调者crash,参与者正常
  3. 协调者和涉企方都crash

对第一栽情况,若与方在准备等crash,则协调者收不顶Prepared过来,协调在未见面发送commit命令,事务不会见真的付诸。若与方在提交等提交,当她过来后可透过自旁参与方或者协调方获取工作是否合宜提交,并作出相应的响应。

老二栽状况,可以经选出新的协调者解决。

其三种情况,是鲜级提交无法到解决之图景。尤其是当协调者发送出commit令后,唯一收到commit命令的参与者也crash,此时别参与在未能够打协调者和已crash的参与者那儿了解工作提交状态。但犹上等同节约片号提交前提条件所述,两流提交的前提条件之一是有着crash的节点最终都见面还原,所以当接到commit的参与方恢复后,其它节点可自从她那里得到工作状态并作出相应操作。

JTA

JTA介绍

作为java平台上作业专业JTA(Java Transaction
API)也定义了针对XA事务的支持,实际上,JTA是基于XA架构上建模的。在JTA
中,事务管理器抽象为javax.transaction.TransactionManager接口,并透过底部工作服务(即Java
Transaction
Service)实现。像许多其它的Java规范一样,JTA就定义了接口,具体的落实则是由供应商(如J2EE厂商)负责提供,目前JTA的兑现重点出以下几种植:

  • J2EE容器所提供的JTA实现(如JBoss)。
  • 独立的JTA实现:如JOTM(Java Open Transaction
    Manager),Atomikos。这些实现好下在那些休采取J2EE应用服务器的条件里用来提供分布事事务保证。

PostgreSQL两品提交接口

  • PREPARE TRANSACTION transaction_id PREPARE TRANSACTION
    也目前政工之个别级提交做准备。
    在命令后,事务就不再与当前对话关联了;它的状态了保留于磁盘上,
    它交给成功产生死高的可能性,即使是以呼吁提交之前数据库有了崩溃也这样。这长达命令必须于一个用BEGIN显式开始的事务块里面用。
  • COMMIT PREPARED transaction_id
    提交已经进准备等的ID为transaction_id的事务
  • ROLLBACK PREPARED transaction_id
    回滚已上准备阶段的ID为transaction_id的事务

典型的行使方法如下

postgres=> BEGIN;
BEGIN
postgres=> CREATE TABLE demo(a TEXT, b INTEGER);    
CREATE TABLE
postgres=> PREPARE TRANSACTION 'the first prepared transaction';
PREPARE TRANSACTION
postgres=> SELECT * FROM pg_prepared_xacts;
 transaction |              gid               |           prepared            | owner | database 
-------------+--------------------------------+-------------------------------+-------+----------
       23970 | the first prepared transaction | 2016-08-01 20:44:55.816267+08 | casp  | postgres
(1 row)

自打者代码可看,使用PREPARE TRANSACTION transaction_id语句后,PostgreSQL会在pg_catalog.pg_prepared_xact发明中拿欠业务之transaction_id记于gid字段中,并拿拖欠事情之地头工作ID,即23970,存于transaction字段中,同时会记下该事务的缔造时间跟创造用户和数码库名。

继续执行如下命令

postgres=> \q
SELECT * FROM pg_prepared_xacts;
 transaction |              gid               |           prepared            | owner | database 
-------------+--------------------------------+-------------------------------+-------+----------
       23970 | the first prepared transaction | 2016-08-01 20:44:55.816267+08 | casp  | cqdb
(1 row)

cqdb=> ROLLBACK PREPARED 'the first prepared transaction';            
ROLLBACK PREPARED
cqdb=> SELECT * FROM pg_prepared_xacts;
 transaction | gid | prepared | owner | database 
-------------+-----+----------+-------+----------
(0 rows)

就算退时session,pg_catalog.pg_prepared_xact说明中有关已经入准备等的政工信息仍旧有,这同上文所陈述准备等后每节点会将业务信息存于磁盘中持久化相符。注:如果不使PREPARED TRANSACTION 'transaction_id',则早已BEGIN但还未COMMIT或ROLLBACK的事务会在session退出时自动ROLLBACK。

在ROLLBACK已进入准备阶段的作业时,必须指定其transaction_id

PostgreSQL两流提交注意事项

  • PREPARE TRANSACTION transaction_id一声令下后,事务状态了保存于磁盘上。
  • PREPARE TRANSACTION transaction_id指令后,事务就不再跟目前对话关联,因此当前session可继续执行其它工作。
  • COMMIT PREPAREDROLLBACK PREPARED而是于另会话中履行,而连无要求以提交准备的对话中施行。
  • 勿容许对那些履行了关联临时表或者是创造了带动WITH HOLD游标的业务进行PREPARE。
    这些特征和当下对话绑定得实际是最最紧密了,因此于一个备选好之事体里没什么可用的。
  • 苟事情用SET修改了运转时参数,这些职能在PREPARE TRANSACTION以后保留,并且不会见于别以后的COMMIT PREPAREDROLLBACK PREPARED所影响,因为SET的生效范围是时session。
  • 由性质的角度来拘禁,把一个政工长时已于备选好的状态是勿明智的,因为她会潜移默化VACUUM回收存储的力。
  • 早就准备好之事务会继续保有它们获取的吊,直到该事务让commit或者rollback。所以要是都跻身准备阶段的事体一直未吃拍卖,其它工作可能会见以落不至锁而受block或者失败。
  • 默认情况下,PostgreSQL并无上马起来两等提交,可以通过当postgresql.conf文件被设置max_prepared_transactions布起开启PostgreSQL的有数号提交。

JTA实现PostgreSQL两流提交

本文使用Atomikos提供的JTA实现,利用PostgreSQL提供的星星点点阶段提交特性,实现了分布式事务。本文中之分布式事务使用了2独不等机器上的PostgreSQL实例。

本例所示代码可自从作者Github获取。

package com.jasongj.jta.resource;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Path("/jta")
public class JTAResource {
  private static final Logger LOGGER = LoggerFactory.getLogger(JTAResource.class);

  @GET
  public String test(@PathParam(value = "commit") boolean isCommit)
      throws NamingException, SQLException, NotSupportedException, SystemException {
    UserTransaction userTransaction = null;
    try {
      Context context = new InitialContext();
      userTransaction = (UserTransaction) context.lookup("java:comp/UserTransaction");
      userTransaction.setTransactionTimeout(600);

      userTransaction.begin();

      DataSource dataSource1 = (DataSource) context.lookup("java:comp/env/jdbc/1");
      Connection xaConnection1 = dataSource1.getConnection();

      DataSource dataSource2 = (DataSource) context.lookup("java:comp/env/jdbc/2");
      Connection xaConnection2 = dataSource2.getConnection();
      LOGGER.info("Connection autocommit : {}", xaConnection1.getAutoCommit());

      Statement st1 = xaConnection1.createStatement();
      Statement st2 = xaConnection2.createStatement();
      LOGGER.info("Connection autocommit after created statement: {}", xaConnection1.getAutoCommit());


      st1.execute("update casp.test set qtime=current_timestamp, value = 1");
      st2.execute("update casp.test set qtime=current_timestamp, value = 2");
      LOGGER.info("Autocommit after execution : ", xaConnection1.getAutoCommit());

      userTransaction.commit();
      LOGGER.info("Autocommit after commit: ",  xaConnection1.getAutoCommit());
      return "commit";

    } catch (Exception ex) {
      if (userTransaction != null) {
        userTransaction.rollback();
      }
      LOGGER.info(ex.toString());
      throw new WebApplicationException("failed", ex);
    }
  }
}

于上示代码中好看看,虽然采取了Atomikos的JTA实现,但坐使用了面向接口编程特性,所以只有现出了JTA相关的接口,而无显式使用Atomikos相关类。具体的Atomikos使用是以WebContent/META-INFO/context.xml中配置。

<Context>
  <Transaction factory="com.atomikos.icatch.jta.UserTransactionFactory" />
    <Resource name="jdbc/1"
    auth="Container"
    type="com.atomikos.jdbc.AtomikosDataSourceBean"
    factory="com.jasongj.jta.util.EnhancedTomcatAtomikosBeanFactory"
    uniqueResourceName="DataSource_Resource1"
    minPoolSize="2"
    maxPoolSize="8"
    testQuery="SELECT 1"
    xaDataSourceClassName="org.postgresql.xa.PGXADataSource"
    xaProperties.databaseName="postgres"
    xaProperties.serverName="192.168.0.1"
    xaProperties.portNumber="5432"
    xaProperties.user="casp"
    xaProperties.password=""/>

    <Resource name="jdbc/2"
    auth="Container"
    type="com.atomikos.jdbc.AtomikosDataSourceBean"
    factory="com.jasongj.jta.util.EnhancedTomcatAtomikosBeanFactory"
    uniqueResourceName="DataSource_Resource2"
    minPoolSize="2"
    maxPoolSize="8"
    testQuery="SELECT 1"
    xaDataSourceClassName="org.postgresql.xa.PGXADataSource"
    xaProperties.databaseName="postgres"
    xaProperties.serverName="192.168.0.2"
    xaProperties.portNumber="5432"
    xaProperties.user="casp"
    xaProperties.password=""/>  
</Context>

相关文章