1.分布式事务的问题
在微服务的架构下,随着业务服务的拆分和数据库的拆分,会存在多个业务对应多个数据库的情况,如下图所示,订单和库存分别拆分成两个独立的数据库,当客户端发送一个下单操作时,需要在订单服务的数据库中创建订单,同时库存服务完成商品库存的扣减。由于每个数据库的事务执行情况只有自己知道,比如订单数据库并不知道库存数据库的执行情况,就会导致订单数据库和库存数据库数据不一致的问题。
2.seata
Seata一款开源的分布式事务解决方案,致力于在微服务架构下提高性能和简单易用的分布式事务服务。
state术语
TC:事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM:事务管理者
定义全局事务的范围:开始全局范围,提交或回滚全局事务
RM:资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
具体执行流程:
- TM向TC注册全局事务,并生成全局唯一的XID
- RM向TC注册分支事务,并将其纳入该XID对应的全局事务范围
- RM向TC汇报资源的准备状态
- TC汇总所有事务参与者的执行状态,决定该分布式事务是否全部回滚或提交
- TC通知所有RM提交或回滚事务。
下面结合例子来解释seata的操作过程,具体了解分布式事务的操作过程
1.seata-server的安装
官网下载解压
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
}
}
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
注册成功
5.开启测试
首先创建3个微服务,一个订单服务,一个库存服务,一个账户服务
当用户下单时,订单服务中生成一个订单,然后通过远程调用库存服务扣减库存,再通过远程调用扣减余额,最后在订单服务中修改订单的状态为已完成。
1.创建数据库和模块
模块的创建
导入依赖
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就是每一个数据库。