Seata 分布式事务初体验

阿里技术(如何选择分布式事务解决方案?) https://mp.weixin.qq.com/s/2AL3uJ5BG2X3Y2Vxg0XqnQ## 起初看了这个分布式相关的信息,对于seata 有点想了解一下实现的精髓,之前公司内部也有大佬分享过seata 还不止一次的分享过 内部也实现了一个简单的分布式事务,借此机会了解一下这个鬼东西。 Seata (Simple Extensible Autonomous Transaction Architecture) AT (Automatic Transaction) 模式


官方的文档: https://seata.io/zh-cn/docs/overview/what-is-seata.html

一、AT (Automatic Transaction) 模式

AT 模式 完全是根据 上面 https://mp.weixin.qq.com/s/2AL3uJ5BG2X3Y2Vxg0XqnQ## 这篇博客copy 下来的,感觉比官方文档说明更加的清楚,两阶段提交的变异品,总体来说还是十分不错。Seata支持很多种模式 AT、TCC、XA、SAGA 但是AT具有创新性,我们就了解这个吧。

AT 模式的运行机制

下面我们重点说一下 AT 模式的运行机制:

  • 全局事务依然是基于各个分支事务来完成。Seata Server 协调各个分支事务要么一起提交,要么一起回滚。

  • 各个分支事务在运行时,Seata Client 通过对 SQL 执行的代理和拦截,通过解析 SQL 定位到行记录,记录下 SQL 执行前后的行数据快照,beforeImage 和 afterImage 共同构成了回滚日志,回滚日志记录在独立的表中。回滚日志的写入和业务数据的更改在在同一个本地事务中提交。

  • 分支事务完成后,立即释放对本地资源的锁,然后给 Seata 协调器上报事务执行的结果。

  • Seata 协调器汇总各个分支事务的完成情况**,生成事务提交或者回滚的决议,将决议下发给 Seata Client**。

  • 如果决议是提交事务,则 Seata Client 异步清理回滚日志;如果决议是回滚事务,则 Seata Client 根据回滚日志进行补偿操作,补偿前会对比当前数据快照和 afterImage 是否一致,如果不一致则回滚失败,需要人工介入。
    Seata 分布式事务初体验_第1张图片

AT模式限制

AT 模式通过自动生成回滚日志的方式,使得业务方接入成本低,对业务入侵度很低,但是应用 AT 模式也有一些限制:

  • AT 模式只支持基于 ACID 事务的关系数据库。
  • AT 模式是通过对 SQL 解析来完成的,对 SQL 语法的支持有限,使用复杂 SQL 时需要考虑兼容性。
  • 目前不支持复合主键,业务表在设计时注意添加自增主键。
  • 全局事务默认的隔离级别是读未提交,但是通过 SELECT…FOR UPDATE 等语句,可以实现读已提交的隔离级别。通过全局排它写锁,可以做到的隔离级别介于读未提交和读已提交之间

二、实践

2.1 准备工作

  • java 环境变量

  • mysql 本地安装一台

  • nacos 安装,下载 nacos-1.2.1

    https://github.com/alibaba/nacos/releases
    启动脚本 ./bin startup.sh -m standalone
    访问地址: http://127.0.0.1:8848/nacos/#
    Seata 分布式事务初体验_第2张图片

  • seata server 安装

    https://github.com/seata/seata/releases
    参考这个文档: https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html
    本地启动seata-server 使用db-server sql 初始化脚本
    https://github.com/seata/seata/blob/1.2.0/script/server/db/mysql.sql
    修改server的配置文件 模式修改为db,修改数据库密码
    启动脚本 sh ./bin/seata-server.sh -p 8091 -h 127.0.0.1 -m db

    Seata 分布式事务初体验_第3张图片

    at 模式客户端也需要undo_log 数据库创建,github seata example 目录下 也有sql
    https://github.com/seata/seata/blob/1.2.0/script/client/at/db/mysql.sql

  • 下载github seata example

https://github.com/seata/seata-samples/tree/master/springboot-dubbo-seata/
然后根据官方的文档: https://seata.io/zh-cn/docs/user/quickstart.html 即可体验一下子。


这里 客户端的服务端都使用一个数据库拉~

Seata 分布式事务初体验_第4张图片

2.2 实践

此时,我们已经下载好了 seata example 、启动好了 seata server、nacos等等。
官方的体验文档: https://seata.io/zh-cn/docs/user/quickstart.html 这里的前提是修改好了 数据库密码

依次启动服务

然后去nacos 可以看到在线的服务

Seata 分布式事务初体验_第5张图片

模拟买买买

然后去查看数据库,水杯的数据的变化
http://localhost:8104/business/dubbo/buy

{
  "userId": "1",
  "commodityCode": "C201901140001",
  "name": "demoData",
  "count": 1,
  "amount": 1
}

Seata 分布式事务初体验_第6张图片

买买买的拦截Aop

从seata的逻辑流程图中可以看到一丝的痕迹,拦截要做一些的处理,传递context 信息,为后续业务接口的调用传递上下文信息。eg xid

   /**
     * init global transaction scanner
     *
     * @Return: GlobalTransactionScanner
     */
    @Bean
    public GlobalTransactionScanner globalTransactionScanner(){
        return new GlobalTransactionScanner("dubbo-gts-seata-example", "my_test_tx_group");
    }

Seata 分布式事务初体验_第7张图片

连接池的代理

io.seata.rm.datasource.DataSourceProxy,为啥要代理连接池?这个是执行sql的地方啊!从AT 模式: https://seata.io/zh-cn/docs/overview/what-is-seata.html 这个实践的原理中有过介绍 ,业务发起方会将分布式事务的上下文进行传递,如果当前执行事务有分布式的上下文信息,那么就会解析sql,查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据,执行业务 SQL:更新这条记录,询后镜像:根据前镜像的结果,通过 主键 定位数据。

这里对于sql的解析, 兼容性真的需要水平。(AT 模式是通过对 SQL 解析来完成的,对 SQL 语法的支持有限,使用复杂 SQL 时需要考虑兼容性。目前不支持复合主键,业务表在设计时注意添加自增主键。)

 /**
     * init datasource proxy
     * @Param: druidDataSource  datasource bean instance
     * @Return: DataSourceProxy  datasource proxy
     */
    @Bean
    public DataSourceProxy dataSourceProxy(DruidDataSource druidDataSource){
        return new DataSourceProxy(druidDataSource);
    }

    @Bean
    public DataSourceTransactionManager transactionManager(DataSourceProxy dataSourceProxy) {
        return new DataSourceTransactionManager(dataSourceProxy);
    }

断点看看 undo_log


{
    "@class": "io.seata.rm.datasource.undo.BranchUndoLog",
    "xid": "192.168.0.104:8091:2012950778",
    "branchId": 2012950785,
    "sqlUndoLogs": [
        "java.util.ArrayList",
        [
            {
                "@class": "io.seata.rm.datasource.undo.SQLUndoLog",
                "sqlType": "UPDATE",
                "tableName": "t_storage",
                "beforeImage": {
                    "@class": "io.seata.rm.datasource.sql.struct.TableRecords",
                    "tableName": "t_storage",
                    "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": 1
                                        },
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "count",
                                            "keyType": "NULL",
                                            "type": 4,
                                            "value": 999
                                        }
                                    ]
                                ]
                            }
                        ]
                    ]
                },
                "afterImage": {
                    "@class": "io.seata.rm.datasource.sql.struct.TableRecords",
                    "tableName": "t_storage",
                    "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": 1
                                        },
                                        {
                                            "@class": "io.seata.rm.datasource.sql.struct.Field",
                                            "name": "count",
                                            "keyType": "NULL",
                                            "type": 4,
                                            "value": 998
                                        }
                                    ]
                                ]
                            }
                        ]
                    ]
                }
            }
        ]
    ]
}

注释打开看看回滚的效果

Seata 分布式事务初体验_第8张图片

三、总结

从程序的角度实践,简单的了解了一下seata的使用,了解了AT 模式对于二阶段提交的改进的使用。动手实践一下还是不错的,GlobalTransactionScanner 拦截全局事务,注册TM client、RM client 通过netty活server端通信,其中还有动态从配置中读出配置等等接入,简单的看看一下源代码,学习了一波。结合 https://mp.weixin.qq.com/s/2AL3uJ5BG2X3Y2Vxg0XqnQ## 这篇文章,理论和实践都玩了一下,seata example 中还玩了一下TCC 模式嗯。

你可能感兴趣的:(seata)