分布式事务(壹)两阶段提交及JTA

分布式事务

两等第提交

两阶段提交容错格局

两等第提交中的相当重要分为如下三种景况

  1. 协调者符合规律,参加方crash
  2. 协调者crash,出席者不荒谬
  3. 协调者和参与方都crash

对于第3种境况,若插足方在准备阶段crash,则协调者收不到Prepared过来,协调方不会发送commit指令,事务不会真的付诸。若参预方在提交阶段提交,当它过来后方可通过从别的参预方也许协调方获取职业是或不是应当提交,并作出相应的响应。

第两种景况,能够由此选出新的协调者化解。

其几种情景,是两阶段提交不或然周详化解的意况。尤其是当协调者发送出commit一声令下后,唯一收到commit命令的加入者也crash,此时此外参与方不可能从协调者和曾经crash的出席者那儿理解职业提交状态。但就像是上一节两品级提交前提条件所述,两阶段提交的前提条件之壹是全部crash的节点最终都会回复,所以当接到commit的参加方苏醒后,别的节点可从它那里获得工作状态并作出相应操作。

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的两等第提交。

分布式事务完结机制

犹如小编在《SQL优化(6) MVCC
PostgreSQL落成工作和多版本出现调控的优美
》一文中所讲,事务包涵原子性(Atomicity)、1致性(Consistency)、隔开性(Isolation)和持久性(Durability)。

PostgreSQL针对ACID的落成才能如下表所示。

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

分布式事务的实现才干如下表所示。(以PostgreSQL作为工作加入方为例)

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

从上表能够见见,1致性、隔开分离性和持久性靠的是各分布式事务出席方本身固有的机制,而两品级提交重要承接保险了分布式事务的原子性。

分布式事务简单介绍

分布式事务是指会涉及到操作几个数据库(或许提供业务语义的系统,如JMS)的工作。其实就是将对一样数据库事务的定义扩充到了对多少个数据库的事务。目标是为着保障分布式系统湖北中华南理工科业余大学学学程集团作操作的原子性。分布式事务处理的重若是必须有1种方法能够精通事情在任哪个地方方所做的全体动作,提交或回滚事务的决定必须产生统壹的结果(全体付出或任何回滚)。

XA规范

XA是由X/Open协会提议的分布式事务的行业内部。XA规范重大定义了(全局)事务管理器(Transaction
Manager)和(局地)财富管理器(Resource
Manager)之间的接口。XA接口是双向的系统接口,在事情管理器(Transaction
Manager)以及二个或八个财富管理器(Resource
Manager)之间产生通讯桥梁。XA引进的政工管理器充当上文所述全局工作中的“协调者”剧中人物。事务管理器调节着全局工作,管管事人务生命周期,并协调整工财富。能源管理器负责调整和保管实际财富(如数据库或JMS队列)。目前,Oracle、Informix、DB二、Sybase和PostgreSQL等各主流数据库都提供了对XA的支撑。

XA规范中,事务管理器首要透过以下的接口对能源管理器进行政管理制

  • xa_open,xa_close:建立和倒闭与能源管理器的连接。
  • xa_start,xa_end:开端和完工2个本地职业。
  • xa_prepare,xa_commit,xa_rollback:预提交、提交和回滚一个当地工作。
  • xa_recover:回滚二个已实行预备提拔交的业务。

JTA

JTA完成PostgreSQL两等第提交

正文使用Atomikos提供的JTA完结,利用PostgreSQL提供的两等第提交本性,完结了分布式事务。本文中的分布式事务使用了一个分裂机器上的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>

两等第提交原理

二阶段提交的算法思路能够归纳为:协调者询问出席者是不是准备好了交给,并基于全数参预者的反馈景况决定向全体加入者发送commit只怕rollback指令(协调者向具备参预者发送同样的指令)。

所谓的多少个等第是指

  • 准备阶段
    又称投票阶段。在那1等第,协调者询问全部参与者是或不是准备好交给,加入者借使已经准备好交给则回复Prepared,不然回复Non-Prepared
  • 提交阶段
    又称施行等第。协调者固然在上1阶段收到全部加入者回复的Prepared,则在此阶段向具有参预者发送commit命令,全体参预者登时施行commit操作;不然协调者向全体到场者发送rollback命令,到场者马上实行rollback操作。

两等第提交中,协调者和到场方的并行进程如下图所示。
图片 1

两品级提交前提条件

  • 互联网通讯是可靠的。纵然互联网并不保障,但两等第提交的主要对象并不是涸泽而渔诸如拜占庭难题的网络难点。同时两阶段提交的要害互联网通讯危急期(In-doubt
    Time)在事情提交阶段,而该阶段极短。
  • 富有crash的节点最后都会恢复,不会一贯处在crash状态。
  • 各样分布式事务参加方都有WAL日志,并且该日志存于牢固的蕴藏上。
  • 各节点上的地面工作状态固然遇见机器crash都可从WAL日志上恢复生机。

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

JTA介绍

用作java平台上作业专业JTA(Java Transaction
API)也定义了对XA事务的支撑,实际上,JTA是基于XA架构上建立模型的。在JTA
中,事务管理器抽象为javax.transaction.TransactionManager接口,并经过后面部分工作服务(即Java
Transaction
Service)达成。像多数任何的Java规范同样,JTA仅仅定义了接口,具体的实现则是由供应商(如J二EE厂商)负责提供,目前JTA的兑现重点有以下两种:

  • J二EE容器所提供的JTA实现(如JBoss)。
  • 独立的JTA落成:如JOTM(Java Open Transaction
    Manager),Atomikos。这么些完毕能够选拔在那3个不选取J2EE应用服务器的环境里用来提供分布事事务保障。

分布式事务怎样保障原子性

在分布式系统中,种种节点(或许业务加入方)之间在情理上互动独立,通过网络开始展览协调。各个独立的节点(或机件)由于存在工作机制,能够保障其数量操作的ACID性子。然则,各节点之间由于互动独立,不可能适用地明白其经节点中的事务执市场价格况,所以多节点之间很难保障ACID,尤其是原子性。

要是要落成分布式系统的原子性,则须保障全数节点的多寡写操作,要不全体都实施(生效),要么全部都不实行(生效)。不过,1个节点在实行业地职业的时候不能够知道其余机器的地点工作的试行结果,所以它就不清楚此番事务到底应该commit依旧roolback。常规的消除办法是引进1个“协调者”的组件来归并调度全数分布式节点的试行。

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

相关文章