1、了解CAP理论
1、一致性
实现目标:当数据分布在多个节点上,从任意节点读取都是最新的数据。
例如:数据库做了读写分离,写入master的数据,要在slave上查询出来的是最新的,也就是一致性
实现方式: 当写入主数据库时,将从数据库锁定,等写入完成,解锁从数据库。如果同步失败,返回错误信息
2、可用性
实现目标:从数据库中查询可以立刻查询出结果,不允许出现超时或报错
实现方式:保证可用性,就不可以将数据库锁定。返回数据时,即使是旧的数据,也要返回,就是不能返回错误信息
3、分区容忍性
实现目标:当项目分布在多个服务器上,需要用http请求访问,可能访问时出现问题,但是即使出现错误,也要可以对外提供服务
实现方式:1、尽量使用异步来操作。2、部署主从数据库,保证一个节点挂,其他节点可以顶上
4、结论
以上可知,分区容忍性是必须具备的。在保证分区容忍性之后。来看a和c。
c是一致性,要保证服务查询到的是最新的数据,就要锁库,但是a是可用性,可用性特点是即使是旧数据也要正常返回,那么就是冲突的,我们只能保证其中一个,以下为组合方式:
1、AP(常用)
保证可用性和分区容忍性
例如:转账,今日转账,明日才能收到金额。用户也是可以接收的。
2、CP
保证一致性和分区容忍性
数据一定会同时进行操作,这边打钱,那边实时收到款
3、CA
数据库不进行分区,也就不再是一个分布式系统了
2、base理论
1、理解强一致性和最终一致性的区别
1、强一致性:
a银行扣款成功,b银行要实时收到钱。即使中间出现了错误,也要返回一个错误信息,同时回滚
2、最终一致性:
a银行扣款成功,b银行可以不实时收到钱,状态会变成转账中,如果转账成功,b银行实时收到钱,如果失败,a银行退回金额
2、base理论理解
base理论是基本可用,软状态,最终一致性的缩写。是对ap理论的拓展,允许数据一段时间内是不一致性的,但要达到最终一致性。这种方式可以理解为 柔性事务
基本可用:
当其中一个服务出现故障时,其他服务可以正常使用
软状态:
例如支付中,扣款中等状态
最终一致性
最终状态会变成扣款成功和扣款失败
3、seata使用
1、先了解几个概念
事务协调器(TC):单独部署,协调各个事务的提交和回滚操作
事务管理器(TM):嵌入服务中,负责开启事务,也就是添加事务注解,向事务协调器发送提交和回滚的指令
服务(RM):每个参与事务的服务
2、seata执行流程
案例:a向b用户转账
正常流程:
增加注解到发起方:a发起的扣款,所以a负责开启事务,service方法上加入@GlobalTransactional开启全局事务,并返回全局事务id。至此,全局事务开启完成
1、a服务开始扣减金额操作前,携带全局事务id向事务协调器注册一个分支事务并返回一个分支事务id。
2、有了分支事务id,开始执行扣减金额操作,执行后写入undo_log表中数据(表中主要记录分支事务id,全局事务id,修改前到修改后等信息),并提交分支事务,扣减金额完成
3、a服务执行完成后,向b服务发出增加金额请求,请求中携带全局事务id
4、b服务执行增加金额前,携带全局事务id向事务协调器注册一个分支事务并返回一个分支事务id。
5、执行增加金额操作,记录到undo_log中,提交分支事务。
6、所有子服务执行完成,事务管理器提交全局事务,提交完成后,删除每个服务中对应的undo_log记录
异常流程:
1、如果有服务执行中出现了异常,就会向事务协调器发送回滚消息。之后执行所有服务的回滚操作
2、回滚:由于每个undo_log中存储了每个服务的修改前消息和修改后消息。执行每个服务的反向操作,也就是自动生成一条反向的sql
‘’
3、启动流程
1、启动事务协调器
cmd下: seata-server.bat -p 8888 -m file
2、每个服务引入seata包
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
3、服务的数据库创建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,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=169 DEFAULT CHARSET=utf8
4、将seata-server-0.7.1\conf下的file.conf和registry.conf拷到服务resources下
file.conf修改:
service {
#vgroup_mapping.修改为项目服务名称-fescar-service-group
#default 为协调器名称
vgroup_mapping.seata-demo-bank1-fescar-service-group = "default"
#事务协调器地址
default.grouplist = "127.0.0.1:8888"
#不开启降级
enableDegrade = false
#disable
disable = false
}
5、application.yml中添加mysql数据源配置
spring:
##################### DB #####################
datasource:
ds0:
url: jdbc:mysql://localhost:3306/bank1?useUnicode=true
username: root
password: 1234
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT user()
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
connection-properties: druid.stat.mergeSql:true;druid.stat.slowSqlMillis:5000
6、添加DatabaseConfiguration.java 来使用该配置并与事务协调器进行交互
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
public class DatabaseConfiguration {
private final ApplicationContext applicationContext;
public DatabaseConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.ds0")
public DruidDataSource ds0() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
@Primary
@Bean
public DataSource dataSource(DruidDataSource ds0) {
DataSourceProxy pds0 = new DataSourceProxy(ds0);
return pds0;
}
}
7、在service方法上增加@GlobalTransactional和@transactional注解即可
4、了解TCC理论
1、什么是TCC
tcc是try(预处理),confirm(确认),cancel(取消)。要求每个事务都有三个操作
try:业务检查阶段
confirm:业务确认阶段,只要try成功,confirm肯定会成功,如果不成功需要重试,还不行只能人工参与
cancel:业务取消阶段,只要try成功,cancel肯定会成功,如果不成功需要重试,还不行只能人工参与
2、案例
a向b转账,a服务和b服务都会有try,confirm, cancel,tcc需要做严密的幂等操作,也就是要保证每次访问接口都是同样的结果
a服务:
try:
判断是否已经执行try,如果执行过,就不再执行
判断是否已经执行了cancel和confirm,如果执行了,就不执行了
扣减金额,如果扣减失败,就抛异常到cancel
confirm:
空,因为try已经执行了扣减金额操作
cancel:
判断是否已经执行了try,没有执行就不执行了
判断有没有执行过cancel,执行过就不执行了
把扣除的金额添加回来
b服务:
try:
空,a服务已经扣减成功,所以b服务必须成功
confirm:
判断是否已经增加过
增加金额
cancel:
空
3、编写代码(hmily框架实现)
1、导入依赖
<dependency>
<groupId>org.dromara</groupId>
<artifactId>hmily-springcloud</artifactId>
<version>2.0.4-RELEASE</version>
</dependency>
2、创建hmily数据库,记录hmily框架的数据。hmily框架运行时会自动创建表
CREATE DATABASE `hmily` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
3、添加配置
org:
dromara:
hmily :
serializer : kryo
recoverDelayTime : 30
retryMax : 30
scheduledDelay : 30
scheduledThreadMax : 10
repositorySupport : db
started: true #事务发起方为true,接收方为false
hmilyDbConfig :
driverClassName : com.mysql.jdbc.Driver
url : jdbc:mysql://localhost:3306/hmily?useUnicode=true
username : root
password : 1234
4、发起方添加注解@EnableAspectJAutoProxy
5、调用方feign接口上增加@Hmily,将全局事务id发送给被调用者
6、在service上增加@Hmily(confirmMethod = "confirmMethod ", cancelMethod = "cancelMethod ")
在本类中增加confirmMethod和cancelMethod两个方法
5、消息中间件的最终一致性
可以使用rocketmq来解决消息中间件,实现事务
参考链接:https://blog.csdn.net/qq_38384460/article/details/114692078
6、最大努力通知
1、交互流程
1、b服务完成加钱后,通知a系统转账成功,如果通知失败,则按照策略进行重复通知
2、a服务收到结果后,修改状态为转账成功
3、如果还是没有通知,需要给a服务提供是否转账成功的接口。