初见SpringCloud(其三)

SpringCloud Alibaba Seata

一次业务操作需要跨多个数据源或系统进行远程调用,就会产生分布式事务问题(即全局数据不统一)

此时可用Seata(网址: https://seata.io/zh-cn/)一款开源的分布式事务解决方案。

先下载seata-server-0.9.0.zip并解压

修改file-conf文件

#vgroup_mapping.my_test_tx_group = ""

vgroup_mapping.my_test_tx_group = "dio_order_tx_group"

#mode = "mode"

mode = "db"                            //日志存到数据库

#user = "mysql"

user = "root"

password = "xxx"

password ="123"

保存

创建数据库 "seata"

将seata文件夹下的db_store.sql所有语句复制到seata数据库即(lock_table,brach_table,global_table )三张表

修改seata文件夹下/conf/registry.conf配置注册中心

#type = "file"

type = "nacos"

#serverAddr = “localhost”

serverAddr = "localhost:8848"

先启动Nacos(8848)再打开seata/bin/seata-server.bat,启动Seata Server(默认8091)

例:使用seata ,order_service , account_service , storage_service 四张表完成库存业务,分别对应order,account,storage 3个模块

浏览器/客户端            微服务(order)
  
                                           Seata Server


      微服务(storage)                                   数据库                                                                                
                               seata ,order_service,account_service , storage_service    





     微服务(account)

全局数据的一致性通过Seata Server 保证

创建好order,account,storage三库后,还要分别为他们创建回滚日志(seata/conf/db_undo_log.sql)

开发storage_service微服务模块

pom.xml文件:

新加Openfeign starter场景启动器

con.alibaba.cloud
spring-cloud-starter-openfeign

新加seata starter

com.alibaba.cloud
spring-cloud-starter-alibaba-seata
<exclusion> 
 io.seata                 //排除自带的seata-all,引入自己的版本
 seata-all
</exclusion>

 io.seata                 
 seata-all
 0.9.0

application.yml

port:10010
name: seata_service

  
  seata:
   tx-service-group: order_group           #指定事务组名,得喝conf/file.conf一致

  sever-addr: localhost:8848               #注册到Nacos

   
  datasource:                              #配置数据库
   driver-class-name:...
   url:...

mybatis:
 mapperLocaltions: clsspath:mapper/*.xml   #指定mybatis映射地址

创建file.conf在resources下,进行相关配置,该文件从 seata/conf/file.conf拷贝

vgroup_mapping.my_test_tx_group = "dio_order_tx_group"

改为

vgroup_mapping.dio_order_tx_group = "default"

创建registry.conf,同样从那里拷贝

开发实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Storage{
  id,producetId,amount
  
}

Dao层

@Mapper
public interface StorageDao{
   
    void reduce(@Param("productId") Long productId,
                @Param("nums") Interger nums)
}

 在resources下创建StorageMapper.xml来实现接口

...映射

<update id="reduce">   //实现方法
   UPDATE
     storage...
</update>

实现Service层

public interface StorageService{
  void reduce(Long productId,Integer nums);

}

实现该接口

@Service
public class SrorageServiceImpl  implements StorageService{
    
    @Resource
    private StorageDao storageDao;

    @Override
    reduce(..){
     storageDao.reduce(productId,nums);
 }
}

控制层Controller

@RestContoller
StorageController{
  @Resource
  private StorageService s..

  @PostMapping("/storage/reduce")
  public Result reduce(Long producetId,Integer nums)
     sotrageService.reduce(p..Id,nums);
     return Result.success("成功",null)
}

再新建MyBatisConfig,用于Mybatis和dao关联

@Configuration
@MappingScan({"com.springcloud.dao"})
public class MyBatisConfig{
    

}

对数据源进行代理(使用seata)

@Configuration
public class DataSourceProxyConfig{
   @Value("${mybatis.mapperLacations}")
   private String mapperLocation;


   配置Druid数据源   

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

  
    配置代理
    @Bean
    public DataSourceProxy proxy(DataSource dataSource){
        return new DataSourceProxy(dataSource);
    }

    配置SqlSessionFactory...(不用记)
  }
}

主启动类

需要取消数据源的自动配置,而是使用seata代理数据源
@SpringBootApplication(exclude = DataSourceAutoConfigration.class)
@EnableDiscoveryClient
@EnableFeignClient
SeatSource10010

开发accout_service(10012)模块

修改yml文件

 port: 10012
 name: seata_accout_service

datasource:
    url:...

同样将file.conf和registry.conf引入

同样创建实体类

Accout{

   id,userid,money
}

主启动类,AccountDao接口,AccountMapper.xml, Account_Service,Account_ServiceImple,Controller,MybatisConfig及代理全部类似

开发Order_service(10008)模块

application.yml

port: 10008

name: order_service

dataSource:
 url: ...

经典file.conf和registry.conf到resources下

实体类

Order{
 id...

}

Dao类

@Mapping
interface OrderDao{

 #新建订单
 void save(Order order);

 #修改订单
 void update(@Param("userId") Long userId...)

}

OderMapper.xml实现接口方法

Service接口

public interface OrderService{

  void save(Order order);
}

OpenFeign接口调用Stroage

@FeignClient(value = "seata_storage_service")      //此为nacos注册中心服务名
public interface StorageService{

 
   
   @PostMapping("/storage/reduce")   //远程调用的url http://seata_servic/storage/reduce
   
  public Result reduce(Long productId,Integer nums) 
 
}

同样再建个调用AccountService的

还有OrderService,这是调用自己就不是远程调用了

OrderServiceImple  implements OrderService {

 @Resource
 private OrderService orderService;

 @Resource
 private AccountService accountService;

 @Resource
 private StorageService storageService;

 @Override
 public void save(Order order){
        orderDao.save(order);    //调用本地方法生成订单order
        accountService.reduce(order.getProductId,order.getNums());   //扣减余额

        storageService.reduce(..)                                 //扣除库存

        orderDao.update(order.getUserId(),1)                    //调用本地方法修改订单状态
    }
}

Order的Controller类

Order主启动类

PS:如果出现Service id not legal hostname是因为服务名称不能带下划线

@GlobalTransaction:完成分布式事务控制(出现异常也能保证数据一致性)

在Order的OrderServiceImpl加上可以控制全局事务




@GlobalTransaction(name = "save-order"                   //名称,自子指定
                   rollbackFor = Exception.class )      
               //指定发生什么异常就会回滚(这里是任何异常)

XID:全局唯一的事务ID

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

TM: 控制全局事务的边界,负责开启一个全局事务,并发起全局提交或全局回滚的决议

RM: 控制分值事务,负责分支注册,汇报状态,并接受事务协调器的指令,驱动分支(本地)事物的提交和回滚

RPC:远程调用

大致流程:

TM向TC申请开启一个全局事务并生成一个全局唯一XID,XID会在微服务调用链路上下文传播,RM向TC注册分支事务,将其纳入XID对应的全局事务管理,TM向TC发起针对XID的全局提交或回滚决议,TC调度XID管的全部分支事务完成提交或回滚请求

补:TM是被@GlobalTransactional标注的微服务(如上面的Order)

Mysql-Seata的global表会生成xid和事务组名称等;branch表管理各个微服务分支事务(RM);lock表给各个微服务事务加锁防止脏读

浏览器                    微服务A                           微服务B
 X       ====>         TM:事务发起者               ===>   操作相关表
                     @GlobalTransactional            
                        
                        ||          ||                       ||   ||
                        ||申请XID    ||提交本地事务结果         ||   ||
                        ||再RM      ||                       ||RM  || 提交本地事务结果
                        \/          \/                       ||    \/
                                                             ||
                                                             \/        

                         Mysql-Seata
                   global表,branch表,lock表
                     
                  如果各个RM都执行成功就全局提交,否则全局回滚

Seata的事务模式

AT(默认,也是用的最多的),TCC,SAGA,XA

一阶段加载:Seata会拦截sql业务,在业务变更前保存为"before image",更新后的业务保存为"after image" 最后生成行锁,这样保证原子性

二阶段提交:Seata框架将一阶段保存的数据和行锁删掉,完成数据清理

二阶段回滚:若TC发现并不是所以RM都成功就会回滚到before还原业务数据

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值