Seata分布式事务环境搭建

kyaa111 8月前 ⋅ 229 阅读

下载seata服务端

https://github.com/seata/seata/releases

修改registry.conf

这里使用nacos做注册中心和配置中心, 也就不需要服务端的file.conf了

但是使用nacos时, nacos的密码不能有特殊符号, 否则seata可能连接不上

registry {
  type = "nacos"
  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = "e794b575-4231-4935-8271-145c5840d392"
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
}

config {
  type = "nacos"
  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = "e794b575-4231-4935-8271-145c5840d392"
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
  }
}

seata服务端需要的几个表: https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql

其他的一些相关的脚本https://github.com/seata/seata/tree/develop/script

nacos建立命名空间

新增配置文件

Data ID: seataServer.properties

Group: SEATA_GROUP

seataServer.properties配置内容

### seata
store.mode=db
store.publicKey=
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
store.db.datasource=druid
## mysql/oracle/postgresql/h2/oceanbase etc.
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
store.db.url=jdbc:mysql://192.168.101.128:3309/seata?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&&serverTimezone=Asia/Shanghai
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=100
store.db.globalTable = global_table
store.db.branchTable = branch_table
store.db.lockTable =lock_table
store.db.queryLimit = 100
store.db.maxWait = 5000



## transport
# tcp udt unix-domain-socket
transport.type=TCP
#NIO NATIVE
transport.server=NIO
#enable heartbeat
transport.heartbeat=true

transport.serialization=seata
transport.compressor=none

transport.threadFactory.bossThreadPrefix = NettyBoss
transport.threadFactory.workerThreadPrefix = NettyServerNIOWorker
transport.threadFactory.serverExecutorThread-prefix = 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



server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000

单体服务多库事务

SpringBoot项目引入依赖

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.4.2</version>
</dependency>

项目配置文件

seata:
  application-id: test #这里填你应用的id
  service:
    grouplist:
      # seata-server地址
      default: 127.0.0.1:8091
    # 分组事务
    vgroup-mapping:
      global_tx_group: default
      enable-degrade: false
      disable-global-transaction: false
  # 是否开启spring-boot自动装配
  enabled: true
  # 是否启用数据源 bean 的自动代理
  enable-auto-data-source-proxy: true
  tx-service-group: global_tx_group
  client:
    tm:
      # 一阶段全局提交结果上报TC重试次数 默认1次,建议大于1
      commit-retry-count: 3
      # 一阶段全局回滚结果上报TC重试次数 默认1次,建议大于1
      rollback-retry-count: 3
    rm:
      # 是否上报一阶段成功 true、false,从1.1.0版本开始,默认false.true用于保持分支事务生命周期记录完整,false可提高不少性能
      report-success-enable: true
      # 自动刷新缓存中的表结构 默认false
      table-meta-check-enable: true
      # 一阶段结果上报TC重试次数
      report-retry-count: 5
      # 异步提交缓存队列长度 默认10000。 二阶段提交成功,RM异步清理undo队列
      async-commit-buffer-limit: 1000
      lock:
        # 校验或占用全局锁重试间隔 默认10,单位毫秒
        retry-interval: 10
        # 分支事务与其它全局回滚事务冲突时锁策略 默认true,优先释放本地锁让回滚成功
        retry-policy-branch-rollback-on-conflict: true
        # 校验或占用全局锁重试次数
        retry-times: 30
    undo:
      # 自定义undo表名 默认undo_log
      log-table: seata_undo_log
      # 二阶段回滚镜像校验
      data-validation: true
      # undo log序列化方式
      log-serialization: jackson

  transport:
    type: TCP
    server: NIO
    heartbeat: true
    # client和server通信编解码方式 seata(ByteBuf)、protobuf、kryo、hession、fst,默认seata
    serialization: seata
    # client和server通信数据压缩方式 none、gzip,默认none
    compressor: none
    thread-factory:
      boss-thread-prefix: NettyBoss
      client-worker-thread-prefix: NettyServerNIOWorker
      server-executor-thread-prefix: NettyServerBizHandler
      client-selector-thread-size: 1
      client-selector-thread-prefix: NettyClientWorkerThread

简单使用, 配合dynamic-datasource-spring-boot-starter使用

@Autowired
StaffMapper staffMapper;
    
@Override
@GlobalTransactional(rollbackFor = Exception.class)
public void globalTx() {
    userService.updateMaster();
    userService.updateIndependent();
	//模拟异常回滚
    int i = 1 / 0;
}

@DS("master")
@Transactional(rollbackFor = Exception.class)
public void updateMaster() {
    User user1 = baseDao.selectById(1);
    user1.setAge(999);
    baseDao.updateById(user1);

    User user2 = baseDao.selectById(2);
    user2.setAge(999);
    baseDao.updateById(user2);
}


@DS("independent")
@Transactional(rollbackFor = Exception.class)
public void updateIndependent() {
    Staff staff1 = staffMapper.selectById(1);
    staff1.setAge(999);
    staffMapper.updateById(staff1);


    Staff staff2 = staffMapper.selectById(2);
    staff2.setAge(999);
    staffMapper.updateById(staff2);
}

可以观察到seata_undo_log中的undo记录

SELECT CAST(rollback_info AS char) FROM seata_undo_log
{
    "@class": "io.seata.rm.datasource.undo.BranchUndoLog",
    "xid": "192.168.101.1:8091:6593516322371825665",
    "branchId": 6593516322371825668,
    "sqlUndoLogs": [
        "java.util.ArrayList",
        [
            {
                "@class": "io.seata.rm.datasource.undo.SQLUndoLog",
                "sqlType": "UPDATE",
                "tableName": "staff",
                "beforeImage": {
                    "@class": "io.seata.rm.datasource.sql.struct.TableRecords",
                    "tableName": "staff",
                    "rows": [
                        "java.util.ArrayList",
                        [
                            {
                                "@class": "io.seata.rm.datasource.sql.struct.Row",
                                "fields": [
                                    "java.util.ArrayList",
                                    [
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "id",
                                            "keyType": "PRIMARY_KEY",
                                            "type": 4,
                                            "value": [
                                                "java.lang.Long",
                                                1
                                            ]
                                        },
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "name",
                                            "keyType": "NULL",
                                            "type": 12,
                                            "value": "1"
                                        },
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "age",
                                            "keyType": "NULL",
                                            "type": 4,
                                            "value": 2
                                        }
                                    ]
                                ]
                            }
                        ]
                    ]
                },
                "afterImage": {
                    "@class": "io.seata.rm.datasource.sql.struct.TableRecords",
                    "tableName": "staff",
                    "rows": [
                        "java.util.ArrayList",
                        [
                            {
                                "@class": "io.seata.rm.datasource.sql.struct.Row",
                                "fields": [
                                    "java.util.ArrayList",
                                    [
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "id",
                                            "keyType": "PRIMARY_KEY",
                                            "type": 4,
                                            "value": [
                                                "java.lang.Long",
                                                1
                                            ]
                                        },
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "name",
                                            "keyType": "NULL",
                                            "type": 12,
                                            "value": "1"
                                        },
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "age",
                                            "keyType": "NULL",
                                            "type": 4,
                                            "value": 999
                                        }
                                    ]
                                ]
                            }
                        ]
                    ]
                }
            },
            {
                "@class": "io.seata.rm.datasource.undo.SQLUndoLog",
                "sqlType": "UPDATE",
                "tableName": "staff",
                "beforeImage": {
                    "@class": "io.seata.rm.datasource.sql.struct.TableRecords",
                    "tableName": "staff",
                    "rows": [
                        "java.util.ArrayList",
                        [
                            {
                                "@class": "io.seata.rm.datasource.sql.struct.Row",
                                "fields": [
                                    "java.util.ArrayList",
                                    [
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "id",
                                            "keyType": "PRIMARY_KEY",
                                            "type": 4,
                                            "value": [
                                                "java.lang.Long",
                                                2
                                            ]
                                        },
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "name",
                                            "keyType": "NULL",
                                            "type": 12,
                                            "value": "2"
                                        },
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "age",
                                            "keyType": "NULL",
                                            "type": 4,
                                            "value": 3
                                        }
                                    ]
                                ]
                            }
                        ]
                    ]
                },
                "afterImage": {
                    "@class": "io.seata.rm.datasource.sql.struct.TableRecords",
                    "tableName": "staff",
                    "rows": [
                        "java.util.ArrayList",
                        [
                            {
                                "@class": "io.seata.rm.datasource.sql.struct.Row",
                                "fields": [
                                    "java.util.ArrayList",
                                    [
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "id",
                                            "keyType": "PRIMARY_KEY",
                                            "type": 4,
                                            "value": [
                                                "java.lang.Long",
                                                2
                                            ]
                                        },
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "name",
                                            "keyType": "NULL",
                                            "type": 12,
                                            "value": "2"
                                        },
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "age",
                                            "keyType": "NULL",
                                            "type": 4,
                                            "value": 999
                                        }
                                    ]
                                ]
                            }
                        ]
                    ]
                }
            }
        ]
    ]
}

可以看到该表内存储了数据操作前和操作后的记录

微服务项目分布式事务

如果是微服务项目, 需要分布式事务支持, 配置如下

引入依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

配置和单体服务多库事务是一样的, seata.application-id可以不填, 默认取当前应用id

每个模块都需要配置, 因为seata需要代理数据源

但实际1.4.2版本使用jackson/fastjson序列化Date字段时会失败(https://github.com/seata/seata/issues/3883), 可以替换序列化方式为kryo

需要额外引入依赖

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-serializer-kryo</artifactId>
    <version>1.4.2</version>
</dependency>

简易demo

@Autowired
RoleFeignClient roleFeignClient;

@Autowired
StaffFeignClient staffFeignClient;
    
@GlobalTransactional(rollbackFor = Exception.class)
public RestResult<Boolean> globalTxTest() {
    log.info("xid: {}", RootContext.getXID());

    roleFeignClient.updateRole();

    staffFeignClient.updateUser();

    int i = 1 / 0;
    return RestResult.ok();
}
@Override
@Transactional(rollbackFor = Exception.class)
public RestResult<Boolean> updateRole() {

    log.info("xid: {}", RootContext.getXID());

    RolePO role1 = roleDAO.selectById(1);
    role1.setName("1111111111");
    roleDAO.updateById(role1);

    RolePO role2 = roleDAO.selectById(2);
    role2.setName("2222222");
    roleDAO.updateById(role2);

    return RestResult.ok();
}
@Override
@Transactional(rollbackFor = Exception.class)
public RestResult<Boolean> updateUser() {

    log.info("xid: {}", RootContext.getXID());

    StaffPO staff1 = baseMapper.selectById(1);
    staff1.setAge(999);
    baseMapper.updateById(staff1);

    StaffPO staff2 = baseMapper.selectById(2);
    staff2.setAge(999);
    baseMapper.updateById(staff2);
    return RestResult.ok();
}