spring cloud alibaba开发笔记十二(Seata:分布式事务解决方案)

@Transactional注解解读

◆@Transactional 是Spring 事务管理提供的注解,在一个方法中加上了这个注解,那么这个方法就将是有事务的,方法内的操作要么一起提交、要么一起回滚。

spring cloud alibaba开发笔记十二(Seata:分布式事务解决方案)_第1张图片propagation事务的传播级别

Propagation.REQUIRED(默认传播行为):支持当前事务;如果当前没有事务,则新建一个事务

Propagation.REQUIRES_NEW :新建事务;如果当前存在事务,则把当前事务挂起

Propagation.SUPPORTS :支持当前事务;否则将以非事务方式执行

Propagation.MANDATORY :支持当前事务 ;否则将抛出IllegalTransactionStateException异常

Propagation.NOT_SUPPORTED :不支持当前事务,而是始终以非事务的方式执行

Propagation.NEVER:以非事务方式执行;如果当前存在事务,则抛出IlegalTransactionStateException 异常

Propagation.NESTED :如果当前存在事务,则对于该传播行为修饰的方法会依然使用当前事务

@Transactional注解失效场景

◆把注解标注在非public修饰的方法上

◆propagation (传播行为)属性配置错误(不合理)

◆rollbackFor 属性设置错误

◆在同一个类中方法调用,导致事务失效

◆自己主动去catch ,代表「没有出现」异常,导致事务失效

◆数据库引擎本身就不支持事务(例如MyISAM) ,当然也不会生效

分布式事务

分布式事务是来源于微服务的(或类似的场景) ,服务之间存在着调用,且整个调用链路上存在着多处(分布在不同的微服务上)写数据表的行为,那么,分布式事务就要保证这些操作要么全部成功,要么全部失败。

分布式事务可能追求的一致性条件不同(业务特性)

1.强一致性:任何一次读都能读到某个数据的最近一次写的数据(要求最高)

2.弱一致性:数据更新后,如果能容忍后续的访问只能访问到部分或者全部访问不到,则是弱一致性(绝大多数的业务场景都不允许)

3.最终一致性:不保证在任意时刻数据都是完整的(状态一致) ,但是,随时时间的推移(会有个度量) , 数据总是会达到一致的状态

最常用的分布式事务的解决方案:两阶段提交(强一致性)

◆两阶段指的是分两步提交;存在一个中央协调器负责协调各 个分支事务

spring cloud alibaba开发笔记十二(Seata:分布式事务解决方案)_第2张图片最常用的分布式事务的解决方案:本地消息表(最终一致性)

◆该方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行

spring cloud alibaba开发笔记十二(Seata:分布式事务解决方案)_第3张图片

 分布式事务解决方案Seata AT模式

Seata 中的三个重要角色: TC、TM、RM:

TM :事务的发起者,用于通知TC ,全局事务的开始、提交、回滚

RM:事务资源,每-个RM都会作为一个分支事务注册在TC .上

TC :事务协调者,也就是中央协调器;用于接收事务的注册、提交、回滚

Seata下载地址:下载中心

Seata目录结构

spring cloud alibaba开发笔记十二(Seata:分布式事务解决方案)_第4张图片

 使用默认配置搭建单机Seata Server

◆启动命令: nohup sh bin/seata-server.sh -h 127.0.0.1 -p 8091 -m file &

-h :指定在注册中心注册的IP ;不指定时获取当前的IP ,外部访问部署在云环境和容器中的server建议指定

-p :端口号;默认端口号是8091

-m :事务日志存储方式,支持file、db、redis ,默认为file

单机Seata Server ,数据库存储事务日志

需要修改配置文件file.conf,配置数据库的连接

## transaction log store, only used in seata-server
store {
  ## store mode: file、db、redis
  mode = "db"

  ## 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)/HikariDataSource(hikari) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false"
    user = "root"
    password = "root"
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

  ## redis store property
  redis {
    host = "127.0.0.1"
    port = "6379"
    password = ""
    database = "0"
    minConn = 1
    maxConn = 10
    maxTotal = 100
    queryLimit = 100
  }

}

◆启动命令: nohup sh bin/seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 &

-n :用于指定seata-server节点ID ;如1、2、3...默认为1

注意:需要在MySQL中创建库和表

CREATE DATABASE `seata`;

CREATE TABLE IF NOT EXISTS `seata`.`global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `seata`.`branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME,
    `gmt_modified`      DATETIME,
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `seata`.`lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(96),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

集群Seata Server ,高可用模式

 高可用模式除了存储方式使用DB之外,还需要注册到注册中心

需要修改配置文件file.conf,配置数据库的连接

同上

需要修改配置文件registry.conf,配置nacos的连接

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = "1bc13fd5-843b-4ac0-aa55-695c25bc0ac6"
    cluster = "default"
    username = ""
    password = ""
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  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 = "127.0.0.1:8848"
    namespace = "1bc13fd5-843b-4ac0-aa55-695c25bc0ac6"
    group = "SEATA_GROUP"
    username = ""
    password = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
    apolloAccesskeySecret = ""
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

启动命令:

nohup sh bin/seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 &

nohup sh bin/seata-server.sh -h 127.0.0.1 -p 8092 -m db -n 2 &

nohup sh bin/seata-server.sh -h 127.0.0.1 -p 8093 -m db -n 3 &

Client(微服务)使用Seata(AT模式)的步骤

◆pom.xml 中引入依赖: spring-cloud-starter alibaba-seata, HikariCP


        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-seata
        

        
            com.zaxxer
            HikariCP
            true
        

◆创建undo_ log 表(如果业务使用了多个数据库,每一个数据库都要有这张表)

CREATE TABLE IF NOT EXISTS `ecommerce`.`undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

◆配置事务分组(配置文件file.conf,registry.conf)

file.conf

## transaction log store, only used in seata-server
store {
  ## store mode: file、db、redis
  mode = "db"

  ## 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)/HikariDataSource(hikari) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false"
    user = "root"
    password = "root"
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

  ## redis store property
  redis {
    host = "127.0.0.1"
    port = "6379"
    password = ""
    database = "0"
    minConn = 1
    maxConn = 10
    maxTotal = 100
    queryLimit = 100
  }

}
##事务分组
service {
  vgroupMapping.imooc-ecommerce = "default"
  default.grouplist = "127.0.0.1:8091"
}
client {
  async.commit.buffer.limit = 10000
  lock {
    retry.internal = 10
    retry.times = 30
  }
}

registry.conf

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

  file {
    name = "file.conf"
  }

}

config {
  type = "file"

  file {
    name = "file.conf"
  }
}

bootstrap.yml

spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: imooc-ecommerce # seata 全局事务分组

◆配置Seata数据源代理(思考下,为什么需要这个? )

import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

/**
 * 

Seata 所需要的数据源代理配置类

* */ @Configuration public class DataSourceProxyAutoConfiguration { private final DataSourceProperties dataSourceProperties; public DataSourceProxyAutoConfiguration(DataSourceProperties dataSourceProperties) { this.dataSourceProperties = dataSourceProperties; } /** *

配置数据源代理, 用于 Seata 全局事务回滚

* before image + after image -> undo_log * */ @Primary @Bean("dataSource") public DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(dataSourceProperties.getUrl()); dataSource.setUsername(dataSourceProperties.getUsername()); dataSource.setPassword(dataSourceProperties.getPassword()); dataSource.setDriverClassName(dataSourceProperties.getDriverClassName()); return new DataSourceProxy(dataSource); } }

◆加载拦截器SeataHandlerInterceptor,实现微服务之间xid的传递

    /**
     * 

添加拦截器配置

* */ @Override protected void addInterceptors(InterceptorRegistry registry) { // 添加用户身份统一登录拦截的拦截器 registry.addInterceptor(new LoginUserInfoInterceptor()) .addPathPatterns("/**").order(0); // Seata 传递 xid 事务 id 给其他的微服务 // 只有这样, 其他的服务才会写 undo_log, 才能够实现回滚 registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns("/**"); }

◆>将@GlobalTransactional注解标注在需要分布式事务的方法上

你可能感兴趣的:(spring,cloud,学习)