seata笔记—处理分布式事务

1.分布式事务的问题

在微服务的架构下,随着业务服务的拆分和数据库的拆分,会存在多个业务对应多个数据库的情况,如下图所示,订单和库存分别拆分成两个独立的数据库,当客户端发送一个下单操作时,需要在订单服务的数据库中创建订单,同时库存服务完成商品库存的扣减。由于每个数据库的事务执行情况只有自己知道,比如订单数据库并不知道库存数据库的执行情况,就会导致订单数据库和库存数据库数据不一致的问题。


image.png

2.seata

Seata一款开源的分布式事务解决方案,致力于在微服务架构下提高性能和简单易用的分布式事务服务。

state术语

TC:事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM:事务管理者
定义全局事务的范围:开始全局范围,提交或回滚全局事务
RM:资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。


image.png

具体执行流程:

  • TM向TC注册全局事务,并生成全局唯一的XID
  • RM向TC注册分支事务,并将其纳入该XID对应的全局事务范围
  • RM向TC汇报资源的准备状态
  • TC汇总所有事务参与者的执行状态,决定该分布式事务是否全部回滚或提交
  • TC通知所有RM提交或回滚事务。

下面结合例子来解释seata的操作过程,具体了解分布式事务的操作过程

1.seata-server的安装

官网下载解压


image.png

2.修改conf下的file.conf文件,修改里面的service和store,并添加数据库seata

service {
  #vgroup->rgroup
  vgroup_mapping.my_test_tx_group = "fsp_tx_group"//起一个名称
  #only support single node
  default.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
}
//----------------------------------------------------//

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.cj.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC"
    user = "root"
    password = "123"
    min-conn = 1
    max-conn = 3
    global.table = "global_table"
    branch.table = "branch_table"
    lock-table = "lock_table"
    query-limit = 100
  }
}
image.png

3.修改register.conf下的配置文件

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos" //指明注册中心是nacos

  nacos {
    serverAddr = "localhost:8848" //修改服务地址
    namespace = ""
    cluster = "default"
  }

4.启动nacos和seata

注册成功


image.png

image.png

5.开启测试

首先创建3个微服务,一个订单服务,一个库存服务,一个账户服务
当用户下单时,订单服务中生成一个订单,然后通过远程调用库存服务扣减库存,再通过远程调用扣减余额,最后在订单服务中修改订单的状态为已完成。

1.创建数据库和模块

image.png

image.png

image.png
image.png

模块的创建


image.png

导入依赖

      
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-seata
            
            
                
                    io.seata
                    seata-all
                
            
        
        
        
            io.seata
            seata-all
            1.1.0
        

这里主要使用order模块对storage和account模块进行操作

storage层业务代码

@RestController
public class StorageController {
    @Autowired
    private StorageService storageService;

    @RequestMapping("/storage/decrease")
    public CommonResult decrease(@RequestParam("productId")Long productId, @RequestParam("count")Integer count){
        storageService.decrease(productId,count);
        return new CommonResult(200,"扣减库存成功!");
    }
}

service层

@Service
@Slf4j
public class StorageServiceImpl implements StorageService {
    @Resource
    private StorageDao storageDao;
    @Override
    public void decrease(Long productId, Integer count) {
        log.info("------->扣减库存开始");
        storageDao.decrease(productId,count);
        log.info("------->扣减库存完成");
    }
}

account层业务代码类似,并且需要配置file.conf和register.conf,因为seata默认不支持yml配置方式,可以使用文件的方式进行配置。

file.conf文件下的修改

service {
  #vgroup->rgroup
  vgroup_mapping.fsp_tx_group = "default" //*******这里注意修改
  #only support single node
  default.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
}

register.conf文件不变

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

  nacos {
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "default"
  }

我们知道,对于事务的处理,最重要的是要拿到数据源,因为通过数据源我们可以控制事务什么时候回滚或提交,所以数据源我们需要让seata来代理,在我们的启动注解上排除自动加载的数据源@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

@Configuration
public class DataSourceProxyConfig {
    @Value("${mybatis.mapper-locations}")
    private String mapperLocations;

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

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

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

}

下面主要看order层的业务代码。

service层的业务结构



AccountService和StorageService的业务代码

@FeignClient(value = "seata-account-service")
public interface AccountService {
    @PostMapping("/account/decrease")
    CommonResult decrease(@RequestParam("userId")Long userId, @RequestParam("money")BigDecimal money);
}
@FeignClient(value = "seata-storage-service")
public interface StorageService {
    @PostMapping("/storage/decrease")
    CommonResult decrease(@RequestParam("productId")Long productId,@RequestParam("count")Integer count);
}

orderService的实现类

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderDao orderDao;
    @Resource
    private StorageService storageService;
    @Resource
    private AccountService accountService;

    @Override
    @GlobalTransactional(name="fsp-create-order",rollbackFor = Exception.class) //这里名字不唯一,处理异常回滚
    public void create(Order order) {
        log.info("------>开始新建订单");
        orderDao.create(order);
        log.info("------>订单微服务开始调用库存");
        storageService.decrease(order.getProductId(),order.getCount());
        log.info("------->订单微服务开始调用库存,做扣减end");
        accountService.decrease(order.getUserId(),order.getMoney());
        //修改订单的状态
        log.info("------->修改订单");
        orderDao.update(order.getUserId(),0);
        log.info("------->订单完成");
    }
}

最后执行操作,完成。

总结

TC:事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM:事务管理者
定义全局事务的范围:开始全局范围,提交或回滚全局事务
RM:资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

这里TC就等于seata服务器,TM就是添加@GlobalTransactional注解的事务发起方,RM就是每一个数据库。

你可能感兴趣的:(seata笔记—处理分布式事务)