4.分布式事务:Seate

4.分布式事务:Seate0.1分布式事务问题一句话:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题

0.1分布式事务问题

4.分布式事务:Seate

        一句话:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。

0.2Seata

        Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。seata提供了AT、TCC、SAGA和XA事务模式。

        官网:http://seata.io/zh-cn/

        下载地址:https://github.com/seata/seata/releases

        一个典型的分布式事务过程:

                分布式处理过程的1ID+3组件:

                        Transaction ID XID:全局唯一的事务ID

                        Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;

                        Transaction Manager(TM):事务管理器,控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;

                        Resource Manager(RM):资源管理器,控制分支事务,负责分支注册,状态汇报,并接受事务协调器的指令,驱动分支(本地)事务的提交或回滚;

                处理过程:

4.分布式事务:Seate

         使用:

                本地@Transactional,spring的

                全局@GlobalTransaction,springcloud alibaba seata的

4.分布式事务:Seate

0.2安装

        本次下载的是0.9.0windows版本的。

        步骤:

                1.修改conf目录下的file.conf文件,先拷贝一份副本。主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息

                2.service模块:自定义事务组名称

                3.store模块:事务日志存储模式为db+数据库连接信息

4.分布式事务:Seate

        4.新建seata数据库

        5.建表(建表db_store.sql在conf目录里面)

        6.修改conf下的registry.conf配置文件

4.分布式事务:Seate

         7.先启动nacos端口号8848

        8.再启动seata-server.bat

0.3订单/库存/账户业务数据库准备

        以下演示都需要先启动Nacos后启动Seata,保证两个都ok。

        分布式事务业务说明:

                这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。

                当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。

                该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

        创建业务数据库:

                seata_order:存储订单的数据库

                seata_storage:存储库存的数据库

                seata_account:存储账户信息的数据库

        按照上述3库分别键对应业务表

                seata_order下建t_order表

                seata_storage下建t_storage表

                seata_account库下键t_account表

        按照上述3库分别建对应的回滚日志表

                conf下的db_undo_log.sql

0.4订单/库存/账户业务微服务准备

        业务需求:下订单->减库存->扣余额->改(订单)状态

        (1)新建Maveb工程作为订单微服务(seata-order-service2001)

        (2)添加seate依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>seata-all</artifactId>
            <groupId>io.seata</groupId> <!--由于spring-cloud-starter-alibaba-seata自带一个seata,要去掉-->
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId> <!--引入自己下载seata的是哪个版本对应的seata-->
    <version>0.9.0</version>
</dependency>

        (3)application.yml

server:
  port: 2001
spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        #自定义事务组名称需要与seata-server中的对应,就是我们改的file.conf里的service模块里的,自己取得名称
        tx-service-group: fsp_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order
    username: root
    password: 222
feign:
  hystrix:
    enabled: false
logging:
  level:
    io:
      seata: info
mybatis:
  mapperLocations: classpath:mapper/*.xml

             (4)resources目录下file.conf

transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  #thread factory for netty
  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"
    # netty boss thread size,will not be used for UDT
    boss-thread-size = 1
    #auto default pin or 8
    worker-thread-size = 8
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}

service {

  vgroup_mapping.fsp_tx_group = "default"

  default.grouplist = "127.0.0.1:8091"
  enableDegrade = false
  disable = false
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
  disableGlobalTransaction = false
}


client {
  async.commit.buffer.limit = 10000
  lock {
    retry.internal = 10
    retry.times = 30
  }
  report.retry.count = 5
  tm.commit.retry.count = 1
  tm.rollback.retry.count = 1
}

## transaction log store
store {
  ## store mode: file、db
  mode = "db"

  ## file store
  file {
    dir = "sessionStore"

    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    max-branch-session-size = 16384
    # globe session size , if exceeded throws exceptions
    max-global-session-size = 512
    # file buffer size , if exceeded allocate new buffer
    file-write-buffer-cache-size = 16384
    # when recover batch read size
    session.reload.read_size = 100
    # async, sync
    flush-disk-mode = async
  }

  ## database store
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "dbcp"
    ## mysql/oracle/h2/oceanbase etc.
    db-type = "mysql"
    driver-class-name = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "root"
    password = "123456"
    min-conn = 1
    max-conn = 3
    global.table = "global_table"
    branch.table = "branch_table"
    lock-table = "lock_table"
    query-limit = 100
  }
}
lock {
  ## the lock store mode: local、remote
  mode = "remote"

  local {
    ## store locks in user's database
  }

  remote {
    ## store locks in the seata's server
  }
}
recovery {
  #schedule committing retry period in milliseconds
  committing-retry-period = 1000
  #schedule asyn committing retry period in milliseconds
  asyn-committing-retry-period = 1000
  #schedule rollbacking retry period in milliseconds
  rollbacking-retry-period = 1000
  #schedule timeout retry period in milliseconds
  timeout-retry-period = 1000
}

transaction {
  undo.data.validation = true
  undo.log.serialization = "jackson"
  undo.log.save.days = 7
  #schedule delete expired undo_log in milliseconds
  undo.log.delete.period = 86400000
  undo.log.table = "undo_log"
}

## metrics settings
metrics {
  enabled = false
  registry-type = "compact"
  # multi exporters use comma divided
  exporter-list = "prometheus"
  exporter-prometheus-port = 9898
}

support {
  ## spring
  spring {
    # auto proxy the DataSource bean
    datasource.autoproxy = false
  }
}

             (5)resources目录下registry.conf

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

  nacos {
    serverAddr = "localhost:8848"
    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 = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    app.id = "seata-server"
    apollo.meta = "http://192.168.1.204:8801"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

              (6)Config配置

//使用seata对数据源进行代理
@Configuration
public class DateSourceProxyConfig {
    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }
}

                (7)Service在业务代码开头方法上面加@GlobalTransactional

/**
     * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
     */
    @Override
    @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class) //name随便取,但是要唯一。rollbackFor:发生什么异常要回滚
    public void create(Order order) {
        log.info("----->开始新建订单");
        orderDao.create(order);

        log.info("----->订单微服务开始调用库存,做扣减count");
        storageService.decrease(order.getProductId(),order.getCount());
        log.info("----->订单微服务开始调用库存,做扣减结束");

        log.info("----->订单微服务开始调用账户,做扣减money");
        accountService.decrease(order.getUserId(),order.getMoney());
        log.info("----->订单微服务开始调用账户,做扣减结束");

        //修改订单状态,从零到1,1代表已经完成
        log.info("----->修改订单状态开始");
        orderDao.update(order.getUserId(),0);
        log.info("----->修改订单状态结束");

        log.info("----->下订单结束了");
    }

                (8)启动类

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动创建的配置
public class SeataOrderMainApp2001{
    public static void main(String[] args)
    {
        SpringApplication.run(SeataOrderMainApp2001.class, args);
    }
}

                (9)新建Maven工程作为库存微服务(seata-order-service2002)

                        差不多和订单微服务配置一致

                (10)新建Maven工程作为账户微服务(seata-order-service2003)

                        差不多和订单微服务配置一致

                (11)测试

                        正常下单:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

                        超时异常,没加@GlobalTransaction:

                                库存和账户余额被扣减了,订单状态并没有设置为已经完成,没有从0改为1,而且由于feign的重试机制,账户余额还有可能被多次扣减。

                        超时异常:为2001业务入口方法上加@GlobalTransactional:

                                前台页面还是报错,但数据库没有任何改变,记录都没有被修改,事务成功

0.5Seata之原理简介

        1.分布式事务的执行流程

                TM开启分布式事务(TM向TC注册全局事务记录)

                换业务场景,编排数据库,服务等事务内资源(RM向TC汇报资源准备状态)

                TM结束分布式事务,事务一阶段结束(TM通知TC提交/回滚分布式事务)

                TC汇总事务信息,决定分布式事务是提交还是回滚

                TC通知所有RM提交/回滚资源,事务二阶段结束。

        2.AT模式如何做到对业务的无侵入

4.分布式事务:Seate

                 4.分布式事务:Seate

 4.分布式事务:Seate

4.分布式事务:Seate

今天的文章4.分布式事务:Seate分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/71326.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注