目录
9.1 什么是弱一致性 ?
弱⼀致性舍弃了强⼀致性,表示允许在某个时间点 甚⾄某个时间段 主从节点的数据不⼀致性的情况。但是这种不⼀致性只是暂时的,但是数据最终会变的⼀致性。
9.2 Seata的弱一致性
seata的四种模式 除了XA模式是强⼀致性之外 其他的三种⽐如 Saga TCC AT 都属于弱⼀致性,但是除了这些弱⼀致性的解决⽅案之外,还有可以利⽤可靠消息队列实现最终⼀致性的效果等等
AT 是Seata强烈建议使用的方式,95%的人使用Seata都会使用AT模式
9.3 Seata的AT模式介绍
AT模式和XA模式差不多,是由XA模式演化⽽来的,是Seata推荐的⼀种分布式解决⽅案,AT模式最早来源于阿⾥中间件团队发布的TXC服务。 AT模式不再像XA那样,AT模式下数据库不需要⽀持XA协议。并且AT模式和XA模式从编码模型上⼏乎⼀样,可以这样说,会XA的编码 就会AT的编码。
9.4 AT模式流程图
9.5 AT模式注意点
9.6 全局锁的理解
1、认识全局锁
全局锁由表名和操作记录的主键 按照⼀定的规律组成。 seata的AT模式 全局锁保存在TC端(seata-server端) TC端保存全局锁可以在如下三个位置 redis mysql file(默认)
2、注册全局锁
RM 向TC注册分⽀事务,在注册分⽀事务之后,将会操作数据库修改数据,提交事务 之前,将会把修改的表和主键信息封装成全局锁,发送到TC服务器进⾏注册,如果TC服务器发现已经有这个主键 的全局锁 证明有其他事务正在执⾏这条数据 则会跑出全局锁冲突异常,客户端会循环等待并且重试
3、校验(获取)全局锁
提交本地事务时 先要获取这个全局锁,当能获取到全局锁 则会提交本地事务 如果全局锁被占⽤则表示有其他事务在操作这条数据。客户端等待 重试获取锁
4、释放锁
第⼆阶段是异步执⾏的,在TC向RM(客户端)发送 branch Commit请求后,客户端仅将分⽀提交信息插⼊内存列队中,可以理解为 如果第⼀阶段成功,第⼆阶段不出异常的情况下,第⼆阶段⼀开始就会释放全局锁,不会锁定到第⼆阶段执⾏结束才会释放全局锁
5、结论
全局锁将会被客户端持有到第⼆阶段的开始 所以性能不如本地事务⾼
9.7 AT的多数据源场景
9.8 AT的分库分表场景
1、环境搭建
见 gitee at_sharding_demo 模块下
2、shardingsphere 整合 Seata
- 所有的库中添加 undo表
-- 数据库aaa和bbb都要添加undo_log表
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
- 添加依赖
<!-- seata -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/
sharding-transaction-base-seata-at -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-base-seata-at</artifactId>
<version>4.1.1</version>
</dependency>
2)在资源路径下创建seata.conf
## 默认值可以不配置
sharding.transaction.seata.at.enable=true
## 配置值和 application-id ⼀致
client.application.id=xyz
## 配置值和 tx-service-group的值保持⼀致
client.transaction.service.group=shangma
3)yaml中编写 seata配置
seata:
application-id: xyz
service:
vgroup-mapping:
shangma: default
grouplist:
default: 127.0.0.1:8091
tx-service-group: shangma
- 添加注解@Transactional + @ShardingTransactionType(TransactionType.BASE)
@GetMapping("/transfer")
@Transactional
@ShardingTransactionType(TransactionType.BASE)
public String transfer(Long fromId, Long toId, double money) {
User fromUser = userMapper.findById(fromId);
User toUser = userMapper.findById(toId);
fromUser.setMoney(fromUser.getMoney() - money);
toUser.setMoney(toUser.getMoney() + money);
userMapper.updateUser(fromUser);
int i = 1 / 0;
userMapper.updateUser(toUser);
return "success";
}
请求: http://localhost:8080/transfer?fromId=1&toId=2&money=100
9.9 AT的微服务场景
1、微服务环境搭建
见 jitee : at-cloud-master
2、AT模式解决微服务分布式事务
1) 添加 undo 表
三个库都要添加undo表
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
2)common 添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/io.seata/seata-spring-boot
-starter -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<optional>true</optional>
</dependency>
3)四个服务都要添加seata配置
seata:
service:
vgroup-mapping:
huige: default
grouplist:
default: 127.0.0.1:8091
tx-service-group: huige
4)common工程添加 druid数据源配置
(上面说过使用默认的hakari数据源,分布式事务不生效)
@Configuration
@ConditionalOnClass(PlatformTransactionManager.class)
public class DbConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DruidDataSource();
}
}
5)business系统排除 TCC
@SpringBootApplication(exclude = SeataTCCFenceAutoConfiguration.class)
@EnableFeignClients
public class ATBusinessApp {
public static void main(String[] args) {
SpringApplication.run(ATBusinessApp.class, args);
}
}
6) 业务系统添加 Global 注解
@GlobalTransactional
public void placeOrder(int accountId, int goodId, int num) {
Good good = goodFeignClient.findById(goodId);
double amount = good.getGoodPrice() * num;
Order order = new Order().setGoodId(goodId).setGoodNum(num).setAccountId(accountId).setOrderAmount(amount).setStatus(OrderStatus.CREATE.getValue());
// 创建订单
orderFeignClient.addOrder(order);
// 减库存
goodFeignClient.reduceStock(goodId, num);
// 减余额
accountFeignClient.reduceMoney(accountId, amount);
}