seata 1.1.0 入门,简介

 

1.SeaTa简介

SeaTa 是阿里开源的可供商用的分布式事务框架 前身Fescar , java程序

 

1.1亮点

  • 应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性;
  • 将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚;
  • 通过全局锁实现了写隔离与读隔离
  • 多种事务模式 : AT、TCC、SAGA 事务模式

 

1.2 SeaTa相关概念

  • TC :事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
  • TM:控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
  • RM:控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。
  • TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。

 

1.3执行流程

seata 1.1.0 入门,简介_第1张图片

 

  1. TM 向 TC 申请开启一个全局事务,TC 创建全局事务后返回全局唯一的 XID,XID 会在全局事务的上下文中传播;
  2. RM 向 TC 注册分支事务,该分支事务归属于拥有相同 XID 的全局事务;
  3. TM 向 TC 发起全局提交或回滚;
  4. TC 调度 XID 下的分支事务完成提交或者回滚

 

1.4 详细参数

https://seata.io/zh-cn/docs/user/configurations.html

 

1.5性能损耗

  • 一条Update的SQL,则需要全局事务xid获取(与TC通讯)
  • before image(解析SQL,查询一次数据库)
  • after image(查询一次数据库)
  • insert undo log(写一次数据库)
  • before commit(与TC通讯,判断锁冲突)

这些操作都需要一次远程通讯RPC,而且是同步的。另外undo log写入时blob字段的插入性能也是不高的。每条写SQL都会增加这么多开销,粗略估计会增加5倍响应时间(二阶段虽然是异步的,但其实也会占用系统资源,网络、线程、数据库)

 

 

 

2.准备工作

 

1.server端工作

 

1.TC (Seate-server ) 下载

  • https://github.com/seata/seata/releases
  • 官方钉钉群(群号:23171167,1群5000人已满,2群),qq群(群号:254657148)群文件共享下载

2.建表

  • TC需要新建三张表
    • db_store.sql
    • 各个表对应功能
      • 全局事务---global_table
      • 分支事务---branch_table
      • 全局锁-----lock_table

 

3.修改配置文件

  • seata/conf/file.conf server服务 的日志记录方式,数据库连接信息
## transaction log store, only used in seata-server
store {
  ## store mode: file、db 事务日志存储模式
  mode = "db"   
# 服务端配置
 service {
# 分组名称 需要和client端一致 chuangqi-steata
  vgroup_mapping.chuangqi-steata = "chuangqi-steata"
  chuangqi-steata.grouplist = "127.0.0.1:8091"
# 降级开关 默认关闭
  enableDegrade = false
  disable = false
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
}
  ## file store property 
  file {
    ## store location dir
    dir = "sessionStore"
    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    maxBranchSessionSize = 16384
    # globe session size , if exceeded throws exceptions
    maxGlobalSessionSize = 512
    # file buffer size , if exceeded allocate new buffer
    fileWriteBufferCacheSize = 16384
    # when recover batch read size
    sessionReloadReadSize = 100
    # async, sync
    flushDiskMode = async
  }




  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "dbcp"
    ## mysql/oracle/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://localhost:3306/seata"
    user = "root"
    password = "root"
    minConn = 1
    maxConn = 10
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
  

 

  • seata/conf/registry.conf 不同注册中心和配置中心 file 单机本地版
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "file"




  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}




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




  nacos {
    serverAddr = "localhost"
    namespace = ""
    group = "SEATA_GROUP"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    app.id = "seata-server"
    apollo.meta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  

 

4.启动

seata/bin/seata-server.sh

nohup sh seata-server.sh -h xx.xx.xx.xx -p 8091 -m db -n 1 -e test &
-h: 注册到注册中心的ip
-p: Server rpc 监听端口
-m: 全局事务会话信息存储模式,file、db,优先读取启动参数
-n: Server node,多个Server时,需区分各自节点,用于生成不同区间的transactionId,以免冲突
-e: 多环境配置参考 http://seata.io/en-us/docs/ops/multi-configuration-isolation.html

 

 

 

2.client端工作

 

2.1项目添加依赖 (单选) 

  • 依赖seata-all 手动配置较多
  • 依赖seata-spring-boot-starter,支持yml配置
  • 依赖spring-cloud-alibaba-seata,内部集成了seata,并实现了xid传递
  • client 版本与 server端版本一致

seata 1.1.0 入门,简介_第2张图片

 

 

2.2项目新建undo_log表

db_undo_log.sql

 

2.3 增加配置文件 (1.1.0版本)

  • yml文件
seata:
  enabled: true
  application-id: account-api  # 项目标识
  tx-service-group: chuangqi-steat # seata分组名称
  enable-auto-data-source-proxy: true # 开启数据源自动代理
  use-jdk-proxy: false # 使用的代理方式
  client:
    rm:
      async-commit-buffer-limit: 1000
      report-retry-count: 5
      table-meta-check-enable: false
      report-success-enable: false
      lock:
        retry-interval: 10
        retry-times: 30
        retry-policy-branch-rollback-on-conflict: true
    tm:
      commit-retry-count: 5
      rollback-retry-count: 5
    undo:
      data-validation: true
      log-serialization: jackson
      log-table: undo_log
    log:
      exceptionRate: 100
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
    #enable-degrade: false
    #disable-global-transaction: false
  transport:
    shutdown:
      wait: 3
    thread-factory:
      boss-thread-prefix: NettyBoss
      worker-thread-prefix: NettyServerNIOWorker
      server-executor-thread-prefix: NettyServerBizHandler
      share-boss-worker: false
      client-selector-thread-prefix: NettyClientSelector
      client-selector-thread-size: 1
      client-worker-thread-prefix: NettyClientWorkerThread
      worker-thread-size: default
      boss-thread-size: 1
    type: TCP
    server: NIO
    heartbeat: true
    serialization: seata
    compressor: none
    enable-client-batch-send-request: true
  config:
    type: file
    consul:
      server-addr: 127.0.0.1:8500
    apollo:
      apollo-meta: http://192.168.1.204:8801
      app-id: seata-server
      namespace: application
    etcd3:
      server-addr: http://localhost:2379
    nacos:
      namespace:
      serverAddr: localhost
      group: SEATA_GROUP
    zk:
      server-addr: 127.0.0.1:2181
      session-timeout: 6000
      connect-timeout: 2000
      username: ""
      password: ""
  registry:
    type: file
    consul:
      cluster: default
      server-addr: 127.0.0.1:8500
    etcd3:
      cluster: default
      serverAddr: http://localhost:2379
    eureka:
      application: default
      weight: 1
      service-url: http://localhost:8761/eureka
    nacos:
      cluster: default
      server-addr: localhost
      namespace:
    redis:
      server-addr: localhost:6379
      db: 0
      password:
      cluster: default
      timeout: 0
    sofa:
      server-addr: 127.0.0.1:9603
      application: default
      region: DEFAULT_ZONE
      datacenter: DefaultDataCenter
      cluster: default
      group: SEATA_GROUP
      addressWaitTime: 3000
    zk:
      cluster: default
      server-addr: 127.0.0.1:2181
      session-timeout: 6000
      connect-timeout: 2000
      username: ""
      password: ""

 

2.4 开启数据源代理

  • 关闭spring自动代理
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
  • 注入数据源
@Bean
@Autowired
public SqlSessionFactory sqlsessionfactory(HikariDataSource dataSource, Configuration configuration) throws Exception {
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dataSource);


    sqlSessionFactoryBean.setPlugins(new Interceptor[]{new PageInterceptor()});
    sqlSessionFactoryBean.setConfiguration(configuration);
    return sqlSessionFactoryBean.getObject();
}


@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariDataSource dataSource() {
   return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSource(DataSource druidDataSource){
        return new DataSourceProxy(druidDataSource);
    }

 

2.4 初始化GlobalTransactionScanner

  • 手动
       public GlobalTransactionScanner globalTransactionScanner() {
           String applicationName = this.applicationContext.getEnvironment().getProperty("spring.application.name");
           String txServiceGroup = this.seataProperties.getTxServiceGroup();
           if (StringUtils.isEmpty(txServiceGroup)) {
               txServiceGroup = applicationName + "-fescar-service-group";
               this.seataProperties.setTxServiceGroup(txServiceGroup);
           }
   
           return new GlobalTransactionScanner(applicationName, txServiceGroup);
       }
  • 自动,引入seata-spring-boot-starter、spring-cloud-alibaba-seata等jar

 

 

 

2.5根据项目架构 配置 XID 传递

  • 手动 参考源码integration文件夹下的各种rpc实现 module
  • 自动 springCloud用户可以引入spring-cloud-alibaba-seata,内部已经实现xid传递

 

 

2.6 使用

  • 1.@GlobalTransaction 全局事务注解 项目中要实现分布式事务的接口加入
  • 2.@GlobalLock 防止脏读和脏写,又不想纳入全局事务管理时使用。(不需要rpc和xid传递等成本)

 

3.注意事项

 

1.server端

  • file模式为单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高;
  • db模式为高可用模式,全局事务会话信息通过db共享,相应性能差些
  • file模式可直接启动 , 无需多余配置

 

2.client端

  • restTemplate RPC调用时 : SeataFilter ,SeataRestTemplateAutoConfiguration 需要交给spring管理 , 扫描包路径记得添加

 

 

4.各个模式介绍

 

1.AT模式

 

1.0简介

  • 简单上手
  • 二阶段提交

 

1.1 前提

  • 基于支持本地 ACID 事务的关系型数据库。
  • Java 应用,通过 JDBC 访问数据库

 

1.2实现

  • 二阶段提交演变 , 整体基于本地事务 + 全局锁 + 本地锁 实现 回滚通过undo_log进行反向补偿
  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

 

 

1.3 步骤

  • 一阶段开启事务时 首先获取本地锁 然后开始走业务逻辑
    • 本地锁获取失败 不断重试
  • 一阶段提交前 , 首先尝试获取该条记录的全局锁
    • 获取全局锁失败 不能提交本地事务 , 重试获取全局锁 (获取 默认 10ms一次 , 重试30次)
      • client.rm.lock.retryInterval 校验或占用全局锁重试间隔 默认10 单位ms
      • client.rm.lock.retryTimes 校验或占用全局锁重试次数 默认30
      • 重试后仍未获取到全局锁 , 回滚本地事务 , 释放本地锁
    • 获取成功 一阶段事务执行
  • 二阶段事务执行
    • commit : 直接执行
    • rollback : 首先重新获取 该条记录的本地锁 , 然后执行反向补偿操作 实现回滚

seata 1.1.0 入门,简介_第3张图片

seata 1.1.0 入门,简介_第4张图片

 

 

1.4 脏写,脏读问题

1.写隔离

  • 上述案例, 在tx1 二阶段提交之前 tx2可以获取到tx1还未全局提交的数据 ,tx2提交事务会脏写
    • 解决 :
    • tx1二阶段提交前一直拥有全局锁 , 回滚时需要获取本地锁 ,
    • tx2 拥有本地锁 , 一阶段提交前需要获取到全局锁
    • tx1回滚获取本地锁会不断重试 , 那么tx2获取全局锁会超时
    • tx2超时 -> 回滚本地事务 , 释放本地锁
    • tx1获取到本地锁 -> 事务回滚

2.读隔离

  • 上述案例 , tx2是否可以读到 tx1还未全局提交的数据
    • 在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。
    • 通过 SELECT FOR UPDATE实现
    • SELECT FOR UPDATE 会去申请全局锁
      • 获取失败 则重试获取 直到全局锁获取到
  • 对性能消耗较大
  • @GlobalLock 注解解决脏读幻读问题 (不生成undo_log)

 

1.5工作原理

# 分支事务的sql
update product set name = 'GTS' where name = 'TXC';

一阶段

seata 1.1.0 入门,简介_第5张图片

  • 解析sql , 通过条件获取到查询前镜像数据
    • select id, name, since from product where name = 'TXC'; 获取到查询前镜像
  • 执行sql
  • 获取执行后镜像 , 根据查询前镜像结果 , 通过主键 定位查询后镜像
    • select id, name, since from product where id = 1`;
    • seata 1.1.0 入门,简介_第6张图片
  • 把镜像前后数据 以及业务sql等信息 组成一条 回滚记录 插入到undo_log中
{
	"branchId": 641789253,
	"undoItems": [{
		"afterImage": {
			"rows": [{
				"fields": [{
					"name": "id",
					"type": 4,
					"value": 1
				}, {
					"name": "name",
					"type": 12,
					"value": "GTS"
				}, {
					"name": "since",
					"type": 12,
					"value": "2014"
				}]
			}],
			"tableName": "product"
		},
		"beforeImage": {
			"rows": [{
				"fields": [{
					"name": "id",
					"type": 4,
					"value": 1
				}, {
					"name": "name",
					"type": 12,
					"value": "TXC"
				}, {
					"name": "since",
					"type": 12,
					"value": "2014"
				}]
			}],
			"tableName": "product"
		},
		"sqlType": "UPDATE"
	}],
	"xid": "xid:xxx"
}
  • 提交前,向 TC 注册分支:申请 product 表中,主键值等于 1 的记录的 全局锁 。
  • 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
  • 将本地事务提交的结果上报给 TC。

 

二阶段-回滚

  • 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
  • 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
  • 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理
  • 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
update product set name = 'TXC' where id = 1;
  • 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。

 

二阶段-提交

  • 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
  • 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。

 

1.6特点

  • 改造成本低 , cloud项目基本只需要添加配置文件 , 新增注解
  • 普通springboot项目 - 添加配置文件 , 实现XID传递
  • 隔离性

 

2.TCC模式

 

2.0简介

  • 二阶段提交

 

2.1前提

  • 每个分支事务需要具备
    • 一阶段 prepare 方法 (本地提交)
    • 二阶段 commit 或 rollback 方法
  • TCC模式不依赖底层数据源的事务支持
    • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
    • 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
    • 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
  • TCC其实就是 自定义本地事务 加入了全局事务的管理

 

2.2特点

  • 性能好 , 没有多余的操作, 只有TC管理
  • 隔离性
  • 业务改动大 , 开发困难

 

 

3.Saga模式

 

3.0简介

  • SEATA提供的长事务解决方案
  • 业务流程中每个参与者都提交本地事务
  • 当出现某一个参与者失败则补偿前面已经成功的参与者
  • 一阶段正向服务 和 二阶段补偿服务都由业务开发实现

 

3.1前提

  • 开发一阶段正向业务 和 二阶段补偿业务

 

3.2特点

  • 适合长事务
  • 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口
  • 一阶段提交本地事务,无锁,高性能
  • 事件驱动架构,参与者可异步执行,高吞吐
  • 不保证隔离性

 

3.3实现

 

你可能感兴趣的:(分布式事务)