目录
分布式介绍以及遇到的难题:
Seata能干嘛:
Seata怎么用:
编码:
数据库准备:
三个微服务准备:
接下来进入测试环节:
分布式介绍以及遇到的难题:
产生的问题:多个微服务数据一致性无法保证!!!
所以强大的阿里巴巴就打造了一款处理全局数据统一的框架Seata来解决此问题。
Seata能干嘛:
分布式事务处理过程中的ID+三组件模型:
处理过程:
Seata怎么用:
Seata的用法非常简单,甚至不用理解Seata的任何逻辑,但是如果你是大厂男孩,还是要懂下底层的hhh!!!
Seata的安装这里就不讲了,因为Bean觉得不是重点,而且也没什么技术含量!!
Seata的原理和思想上面介绍完啦,那我们就进入代码编写环节吧!
编码:
以下演示都需要先开启Nacos再开启Seata,务必确保两者启动成功!
分布式事务业务说明:
数据库准备:
CREATE DATAbase seata_order;
CREATE DATAbase seata_storage;
CREATE DATAbase seata_account;
根据上面的三个库建三张表:
CREATE TABLE t_order(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`count` INT(11) DEFAULT NULL COMMENT '数量',
`money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
`status` INT(1) DEFAULT NULL COMMENT '订单状态:0:创建中; 1:已完结'
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
SELECt * FROM t_order;
CREATE TABLE t_storage(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`'total` INT(11) DEFAULT NULL COMMENT '总库存',
`used` INT(11) DEFAULT NULL COMMENT '已用库存',
`residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO seata_storage.t_storage(`id`,`product_id`,`total`,`used`,`residue`)
VALUES('1','1','100','0','100');
SELECt * FROM t_storage;
CREATE TABLE t_account(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
`used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
`residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO seata_account.t_account(`id`,`user_id`,`total`,`used`,`residue`) VALUES('1','1','1000','0','1000')
SELECt * FROM t_account;
三个库中都需要建立各自的回滚日志表,对应seata中的conf目录下的db_undo_log.sql。
在这三个库中执行一次此sql即可!!!
最终效果:
三个微服务准备:
业务需求:下订单->减库存->扣余额->改(订单)状态
新建一个微服务模块seata-order-service2001
pom:
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discoverycom.alibaba.cloud spring-cloud-starter-alibaba-seataseata-all io.seata io.seata seata-all0.9.0 org.springframework.cloud spring-cloud-starter-openfeignorg.springframework.boot spring-boot-starter-weborg.springframework.boot spring-boot-starter-actuatormysql mysql-connector-java5.1.37 com.alibaba druid-spring-boot-starter1.1.10 org.mybatis.spring.boot mybatis-spring-boot-starter2.0.0 org.springframework.boot spring-boot-starter-testtest org.projectlombok lomboktrue
yml
server:
port: 2001
spring:
application:
name: seata-order-service
cloud:
alibaba:
seata:
#自定义事务组名称需要与seata-server中的对应
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: 1111111
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper
@Override
public void create(Order order){
log.info("----->开始新建订单");
//新建订单
orderDao.create(order);
//扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(),order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");
//扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减end");
//修改订单状态,从零到1代表已经完成
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("----->修改订单状态结束");
log.info("----->下订单结束了");
}
}
controller
@RestController
public class OrderController{
@Resource
private OrderService orderService;
@GetMapping("/order/create")
public CommonResult create(Order order)
{
orderService.create(order);
return new CommonResult(200,"订单创建成功");
}
}
config
@Configuration
public class DataSourceProxyConfig {
@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();
}
}
主启动类:
@EnableDiscoveryClient
@MapperScan({"com.atguigu.springcloud.alibaba.dao"})
@EnableFeignClients
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动创建的配置
public class SeataOrderMainApp2001{
public static void main(String[] args)
{
SpringApplication.run(SeataOrderMainApp2001.class, args);
}
}
库存模块和账户模块就自己写吧,这里Bean就不写出来了,逻辑很简单(体力活)!!!
接下来进入测试环节:
在AccountServiceImpl添加超时异常
@Override
public void decrease(Long userId, BigDecimal money) {
LOGGER.info("------->account-service中扣减账户余额开始");
try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
accountDao.decrease(userId,money);
LOGGER.info("------->account-service中扣减账户余额结束");
}
然后访问:localhost:2001/order/create?userId=1&productId=1&count=10&money=100
发现数据库产生了数据不一致的情况,
解决方案:
在业务类前面加个 @GlobalTransactional注解(如下图)
@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("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(),order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");
//扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减end");
//修改订单状态,从零到1代表已经完成
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("----->修改订单状态结束");
log.info("----->下订单结束了");
}
}
再次访问:localhost:2001/order/create?userId=1&productId=1&count=10&money=100
发现数据都未能插入数据库,保证了全局数据的一致性!!!
好了,今天的Seata总结就到这啦!!!



