分布式事务

分布式事务支持

Sharding-JDBC目前支持三种事务类型:

  • 本地事务
  • XA事务(默认使用Atomikos,支持使用SPI方式加载其他XA事务管理器)
  • BASE事务
    Sega:ShardingSphere的柔性事务已通过第三方SPI实现Saga事务,Saga引擎使用Servicecomb-Saga。
    Seata:Seata使用阿里巴巴Seata

如果将实现了ACID的事务要素的事务称为刚性事务的话,那么基于BASE事务要素的事务则称为柔性事务。 BASE是基本可用、柔性状态和最终一致性这三个要素的缩写。

  • 基本可用(Basically Available)保证分布式事务参与方不一定同时在线。
  • 柔性状态(Soft state)则允许系统状态更新有一定的延时,这个延时对客户来说不一定能够察觉。
  • 而最终一致性(Eventually consistent)通常是通过消息传递的方式保证系统的最终一致性。

在ACID事务中对隔离性的要求很高,在事务执行过程中,必须将所有的资源锁定。 柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。通过放宽对强一致性要求,来换取系统吞吐量的提升。
基于ACID的强一致性事务和基于BASE的最终一致性事务都不是一定,只有在最适合的场景中才能发挥它们的最大长处。

本地事务XA事务BASE事务
业务改造需要实现接口
一致性-强一致性最终一致性
隔离性-支持业务方保证
并发性能无影响严重衰退略微衰退
使用场景业务方处理不一致短事务低并发长事务高并发

XA

XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、MySQL这些数据库都实现了XA接口,而事务管理器作为全局的协调者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:
在这里插入图片描述
缺点

  • 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。
  • 协调者单点故障。一旦协调者发生故障。参与者会一直阻塞下去。
  • 数据不一致。当协调者向参与者发送commit请求后发生网络异常,会导致一部分收到commit请求,另外一部分未收到commit请求,这样会导致数据不一致。
  • 协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的。

ShardingSphere代码示例

<!-- XA事务模块 -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-transaction-core</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-transaction-xa-core</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>
<!-- BASE事务(Seata)模块 -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-transaction-base-seata-at</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>1.2.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-api</artifactId>
    <version>1.2.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.2.1</version>
</dependency>

TCC

TCC也是基于两阶段提交模型,属于补偿型柔性事务。TCC是使用侵入业务上的补偿及事务管理器的协调来达到全局事务的一起提交及回滚,全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的一阶段(prepare)和二阶段(commit/rollback)行为。
在这里插入图片描述
相对于AT模型使用基于本地ACID的事务支持,TCC 模式,不依赖于底层数据资源的事务支持:

  • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
  • 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
  • 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。

具体流程如下:

  1. 业务应用开启事务调用各个微服务的try()方法,并执行资源检查及预留操作
  2. 当每个微服务的try()方法均执行成功后,对由事物管理器调用每个微服务的confirm()方法进行全局事物提交
  3. 当任意一个微服务的方法try()失败(如:预留资源不足,网络异常,代码异常等),则由事物管理器调用每个微服务的cancle()方法进行全局事务回滚

在这里插入图片描述

Saga

1987年普林斯顿大学的Hector Garcia-Molina和Kenneth Salem发表了一篇Paper Sagas,讲述的是如何处理长时间运行的事务(Long-running-transaction)。Saga将一个长事务分解成可以交错运行的子事务集合,每个子事务都是一个保持数据库一致性的事务。
优点:

  • 一阶段提交本地事务,无锁,高性能
  • 事件驱动架构,参与者可异步执行,高吞吐
  • 补偿服务易于实现

缺点:

  • 不保证隔离性
    在这里插入图片描述
    在Saga模式中,是由一系列本地事务构成,业务流程中每个服务都只提交本地事务。当出现某一个服务失败则补偿前面已经成功的服务,一阶段正向服务和二阶段补偿服务都由业务开发实现。第一个事务由系统外部请求启动,然后每个后续步骤由前一个事件完成而触发。

Saga事务有两种不同的方式来实现:

  1. 事件/编排Choreography:没有事务协调器(没有单点风险)时,每个服务生产并监听其他服务的事件,并执行相应处理流程。
  2. 命令/协调orchestrator:由事务协调器负责集中处理事件的决策和业务逻辑排序。

事件/编排

在Events/Choreography中,Saga事务参与者主要通过交换事件进行沟通,参与者订阅彼此的事件并做出相应的响应。
如:第一个服务执行一个事务,然后发布一个事件。该事件被一个或多个服务进行监听,这些服务再执行本地事务并发布(或不发布)新的事件。
当最后一个服务执行本地事务并且不发布任何事件时,意味着分布式事务结束;或者它发布的事件没有被任何Saga参与者监听都意味着事务结束。
应用场景示例:
该示例中,除了订单服务以外的其他服务都是订单服务的子服务,也就是说,为完成一个订单服务,需要经过这些步骤,订单服务与这些服务是包含与被包含关系。因此,订单服务在业务上天然是一个协调器。

  1. 订单服务保存新订单,并发布“下单事件”。
  2. 支付服务监听“下单事件”,并发布“支付事件”。
  3. 物流服务监听“支付事件”,在完成物流过程之后,发布“物流事件”。
  4. 最后,订单服务监听“物流事件”并设置订单的状态为“已完成”。

在这里插入图片描述

命令/协调

在Saga控制类中集中Saga的协调逻辑(Saga协调器),Saga控制类使用命令/异步回复的方式与参与者进行通信。当启动事务时,Saga协调器必须选择并告知第一个Saga参与者执行本地事务。一旦该事务完成,Saga协调器会选择并调用下一个Saga参与者,这个过程一直持续到Saga执行了所有步骤。
在这里插入图片描述

Seata

Seata(Simple Extensible Autonomous Transaction Architecture) 是阿里巴巴开源的分布式事务中间件,以高效并且对业务零侵入的方式解决分布式事务问题。
Seat提供了4种事务模式:

  • AT模式
  • XA模式
  • SEGA模式
  • TCC模式

AT模式

Seata AT模式是基于XA事务演进而来的一个分布式事务中间件。
AT模式基于两阶段提交协议的演变如下:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

  • 二阶段:提交异步化,非常快速地完成。回滚通过一阶段的回滚日志进行反向补偿。

    在这里插入图片描述

  • TC - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。

  • TM - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。

  • RM - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

工作流程

一阶段

  • 首先Seata会对业务SQL进行解析,根据解析后的SQL在更新前生成数据快照;
  • 当数据更新完成后,再根据之前的镜像通过主键生成更新后的数据快照。
  • 根据前后数据镜像和SQL关键信息生成回滚日志(undo log)。
  • 本地提交前先向 TC 注册分支,并向TC申请全局锁(一阶段本地事务提交前,需要确保先拿到全局锁 。拿不到全局锁,不能提交本地事务)。
  • 业务数据和回滚日志记录在同一个本地事务中提交。

二阶段

  • 当RM收到TC的提交请求,由于此时分支事务已经完成提交,所以立即把结果返回给TC,并将请求放入异步队列。而异步队列的任务只需要清理回滚日志,因此该过程可以非常快速地完成。
  • 当RM收到TC的回滚请求,会通过XID和Branch ID找到相应的回滚日志记录,再根据回滚记录中更新前的镜像数据生成反向的SQL并执行。提交本地事务完成后,把执行结果上报给TC。

使用示例

pom.xml

<!-- 事务配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- XA事务模块 -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-transaction-core</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-transaction-xa-core</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>
<!-- BASE事务(Seata)模块 -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-transaction-base-seata-at</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>
<!-- Alibaba Seata必要依赖 -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>1.2.0</version>
</dependency>
<!-- Alibaba Nacos必要依赖 -->
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-api</artifactId>
    <version>1.2.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.2.1</version>
</dependency>

回滚表配置

需要在每一个分片数据库实例中创建undo_log表。建表语句如下(MySQL):

CREATE TABLE `undo_log` (
	`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'increment id',
	`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
	`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
	`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
	`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
	`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
	`log_created` DATETIME NOT NULL COMMENT 'create datetime',
	`log_modified` DATETIME NOT NULL COMMENT 'modify datetime',
	PRIMARY KEY (`id`),
	UNIQUE INDEX `ux_undo_log` (`xid`, `branch_id`)
)
COMMENT='AT transaction mode undo table'
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=11
;

客户端配置

seata.conf

该文件是使用Seata分布式事务必须的配置文件,需要放入到项目resources目录下。

client {
  # 应用名称
  application.id = sharding-tx-test
  # 事务服务组名称
  transaction.service.group = my_test_tx_group
}
registry.conf

该文件是Seata服务器注册中心和配置中心关键文件,同时也需要放入到项目resources目录下。

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    username = "nacos"
    password = "nacos"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
    password = ""
    timeout = "0"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
  type = "nacos"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

服务端配置

nacos-config

该文件默认放在$SEATA_HOME(seata-server根目录下)

transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
store.mode=file
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
store.db.user=username
store.db.password=password
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
client.undo.dataValidation=true
client.undo.logSerialization=jackson
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
导入命令

上面这些配置项需要使用 nacos-conf.sh 导入的nacos配置中心,导入方式如下:

# -h 用户指定nacos的服务器地址
# -p 用户指定nacos的服务器端口号
$ ./nacos-conf.sh -h Nacos_IP -p Nacos_Port

nacos-conf.sh脚本如下:

#!/usr/bin/env bash
# Copyright 1999-2019 Seata.io Group.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at、
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

while getopts ":h:p:g:t:" opt
do
  case $opt in
  h)
    host=$OPTARG
    ;;
  p)
    port=$OPTARG
    ;;
  g)
    group=$OPTARG
    ;;
  t)
    tenant=$OPTARG
    ;;
  ?)
    echo " USAGE OPTION: $0 [-h host] [-p port] [-g group] [-t tenant] "
    exit 1
    ;;
  esac
done

if [[ -z ${host} ]]; then
    host=localhost
fi
if [[ -z ${port} ]]; then
    port=8848
fi
if [[ -z ${group} ]]; then
    group="SEATA_GROUP"
fi
if [[ -z ${tenant} ]]; then
    tenant=""
fi

nacosAddr=$host:$port
contentType="content-type:application/json;charset=UTF-8"

echo "set nacosAddr=$nacosAddr"
echo "set group=$group"

failCount=0
tempLog=$(mktemp -u)
function addConfig() {
  curl -X POST -H "${1}" "http://$2/nacos/v1/cs/configs?dataId=$3&group=$group&content=$4&tenant=$tenant" >"${tempLog}" 2>/dev/null
  if [[ -z $(cat "${tempLog}") ]]; then
    echo " Please check the cluster status. "
    exit 1
  fi
  if [[ $(cat "${tempLog}") =~ "true" ]]; then
    echo "Set $3=$4 successfully "
  else
    echo "Set $3=$4 failure "
    (( failCount++ ))
  fi
}

count=0
for line in $(cat $(dirname "$PWD")/config.txt | sed s/[[:space:]]//g); do
  (( count++ ))
        key=${line%%=*}
  value=${line#*=}
        addConfig "${contentType}" "${nacosAddr}" "${key}" "${value}"
done

echo "========================================================================="
echo " Complete initialization parameters,  total-count:$count ,  failure-count:$failCount "
echo "========================================================================="

if [[ ${failCount} -eq 0 ]]; then
        echo " Init nacos config finished, please start seata-server. "
else
        echo " init nacos config fail. "
fi
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值