1、原子性(A) 在整个事务中的所有操作,要么全部完成,要么全部不做,没有中间状态。对于事务在执行中发生错误,所有的操作都会被回滚,整个事务就像从没被执行过一样。
2、一致性(C) 事务的执行必须保证系统的一致性
3、隔离性(I) 所谓的隔离性就是说,事务与事务之间不会互相影响,一个事务的中间状态不会被其他事务感知。
4、持久性(D) 所谓的持久性,就是说一单事务完成了,那么事务对数据所做的变更就完全保存在了数据库中,即使发生停电,系统宕机也是如此。
这种特性简称刚性事物
1、脏读:事务T1读取到事务T2修改了但是还未提交的数据,之后事务T2又回滚其更新操作,导致事务T1读到的是脏数据。
2、不可重复读:事务T1读取某个数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。
3、幻影读:事务T1读取在读取某范围数据时,事务T2又插入一条数据,当事务T1再次数据这个范围数据时发现不一样了,出现了一些“幻影行”。
不可重复读和脏读的区别:脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
幻读和不可重复读的异同:都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体。
分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同布式系统的不同节点之上。以上是百度百科的解释,简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
CAP由Eric Brewer在2000年PODC会议上提出[1][2],是Eric Brewer在Inktomi[3]期间研发搜索引擎、分布式web缓存时得出的关于数据一致性(consistency)、服务可用性(availability)、分区容错性(partition-tolerance)的猜想:
(1)数据一致性(consistency):如果系统对一个写操作返回成功,那么之后的读请求都必须读到这个新数据;如果返回失败,那么所有读操作都不能读到这个数据,对调用者而言数据具有强一致性(strong consistency) (又叫原子性 atomic、线性一致性 linearizable consistency) (2)服务可用性(availability):所有读写请求在一定时间内得到响应,可终止、不会一直等待 (3)分区容错性(partition-tolerance):在网络分区的情况下,被分隔的节点仍能正常对外服务、BASE理论是指,Basically Available(基本可用)、Soft-state( 软状态/柔性事务)、Eventual Consistency(最终一致性)。是基于CAP定理演化而来,是对CAP中一致性和可用性权衡的结果。核心思想:即使无法做到强一致性,但每个业务根据自身的特点,采用适当的方式来使系统达到最终一致性。
1、基本可用:指分布式系统在出现故障的时候,允许损失部分可用性,保证核心可用。但不等价于不可用。比如:搜索引擎0.5秒返回查询结果,但由于故障,2秒响应查询结果;网页访问过大时,部分用户提供降级服务,等。 2、软状态:软状态是指允许系统存在中间状态,并且该中间状态不会影响系统整体可用性。即允许系统在不同节点间副本同步的时候存在延时。 3、最终一致性:系统中的所有数据副本经过一定时间后,最终能够达到一致的状态,不需要实时保证系统数据的强一致性。最终一致性是弱一致性的一种特殊情况。BASE理论面向的是大型高可用可扩展的分布式系统,通过牺牲强一致性来获得可用性。ACID是传统数据库常用的概念设计,追求强一致性模型让我们想象一下传统的单个应用程序。它的业务由3个模块组成。他们使用单个本地数据源。
自然,本地事务将保证数据的强一致性。 微服务架构已发生了变化。上面提到的3个模块被设计为在3种不同数据源之上的3种服务。本地事务已经无法保证模块之间调用数据的一致性。
1、Seata是阿里开发的一个用于微服务架构的高性能易使用的分布式事务框架。分布式事务是由一批分支事务组成的全局事务,通常分支事务只是本地事务。
2、Seata有3个基本组成部分:
**事务协调器(TC):**维护全局事务和分支事务的状态,驱动全局提交或回滚。
**事务管理器(TM):**用于开启全局事务、提交或者回滚全局事务,是全局事务的开启者。
**资源管理器(RM):**用于分支事务上的资源管理,向TC注册分支事务,上报分支事务的状态,接受TC的命令来提交或者回滚分支事务。 3、Seata管理的分布式事务的典型生命周期:
(1)TM向TC请求发起一个全局事务,TC返回一个代表这个全局事务的XID。
(2)XID通过微服务的调用链传播。
(3)每个RM拿到XID后向TC发起一个分支事务,TC返回一个代表这个分支事务的XID。
(4)RM完成本地分支的业务,提交本地分支,并且报告给TC。
(5)全局事务调用链处理完毕,TM根据有无异常向TC发起全局事务的提交或者回滚。
(6)假设某个RM本地事务失败。该RM自身驱动本地事务回滚,并且报告给TC。
(7)TM检测到了某个分支事务失败,向TC发起全局事务回滚。
(8)TC给每一个RM发送消息,通知它们全部回滚。
(9)TC将全局事务回滚的结果发送给TM,全局事务结束。
注册中心:eureka
服务间调用:feign
持久层:mybatis
数据库:mysql 5.7.20
Springboot:2.1.7.RELEASE
Springcloud:Greenwich.SR2
jdk:1.8
seata:0.8
demo分为四个项目,单独启动。
eureka:作为注册中心order:订单服务,用户下单后,会创建一个订单添加在order数据库,同时会扣减库存storage,扣减账户account;storage:库存服务,用户扣减库存;account:账户服务,用于扣减账户余额;注:使用时版本需要相对应
order服务关键代码如下:
@Override @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class) //此注解开启全局事务 public void create(Order order) { //本地方法 创建订单 orderDao.create(order); //远程方法 扣减库存 storageApi.decrease(order.getProductId(),order.getCount()); //远程方法 扣减账户余额 可在accountServiceImpl中模拟异常 accountApi.decrease(order.getUserId(),order.getMoney()); }seata-server中,/conf目录下,有两个配置文件,需要结合自己的情况来修改:
里面有事务组配置,锁配置,事务日志存储等相关配置信息,由于此demo使用db存储事务信息,我们这里要修改store中的配置:
## transaction log store store { ## store mode: file、db mode = "db" 修改这里,表明事务信息用db存储 ## file store 当mode=db时,此部分配置就不生效了,这是mode=file的配置 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 mode=db时,事务日志存储会存储在这个配置的数据库里 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.jdbc.Driver" url = "jdbc:mysql://116.62.62.26/seat-server" 修改这里 user = "root" 修改这里 password = "root" 修改这里 min-conn = 1 max-conn = 3 global.table = "global_table" branch.table = "branch_table" lock-table = "lock_table" query-limit = 100 } }由于此demo我们使用db模式存储事务日志,所以,我们要创建三张表:global_table,branch_table,lock_table,建表sql在上面下载的seata-server的/conf/db_store.sql中;
由于存储undo_log是在业务库中,所以在每个业务库中,还要创建undo_log表,建表sql在/conf/db_undo_log.sql中。
由于我自定义了事务组名称,所以这里也做了修改:
service { #vgroup->rgroup vgroup_mapping.fsp_tx_group = "default" 修改这里,fsp_tx_group这个事务组名称是我自定义的,一定要与client端的这个配置一致!否则会报错! #only support single node default.grouplist = "127.0.0.1:8091" 此配置作用参考:https://blog.csdn.net/weixin_39800144/article/details/100726116 #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" }其他的可以先使用默认值。
registry{}中是注册中心相关配置,config{}中是配置中心相关配置。seata中,注册中心和配置中心是分开实现的,是两个东西。
我们这里用eureka作注册中心,所以,只用修改registry{}中的:
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "eureka" 修改这里,指明注册中心使用什么 nacos { serverAddr = "localhost" namespace = "" cluster = "default" } eureka { serviceUrl = "http://localhost:8761/eureka" 修改这里 application = "default" weight = "1" } redis { serverAddr = "localhost:6379" db = "0" } zk { cluster = "default" serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } consul { cluster = "default" serverAddr = "127.0.0.1:8500" } etcd3 { cluster = "default" serverAddr = "http://localhost:2379" } sofa { serverAddr = "127.0.0.1:9603" application = "default" region = "DEFAULT_ZONE" datacenter = "DefaultDataCenter" cluster = "default" group = "SEATA_GROUP" addressWaitTime = "3000" } file { name = "file.conf" } }其他的配置可以暂时使用默认值。
client端的几个服务,都是普通的springboot整合了springCloud组件的正常服务,所以,你需要配置eureka,数据库,mapper扫描等,即使不使用seata,你也需要做,这里不做特殊说明,看代码就好。
以order服务为例,除了常规配置外,这里还要配置下事务组信息:
spring: application: name: order-server cloud: alibaba: seata: tx-service-group: fsp_tx_group 这个fsp_tx_group自定义命名很重要,server,client都要保持一致自己新建的项目是没有这个配置文件的,copy过来,修改下面配置:
service { #vgroup->rgroup vgroup_mapping.fsp_tx_group = "default" 这个fsp_tx_group自定义命名很重要,server,client都要保持一致 #only support single node default.grouplist = "127.0.0.1:8091" #degrade current not support enableDegrade = false #disable disable = false disableGlobalTransaction = false }使用eureka做注册中心,仅需要修改eureka的配置即可:
registry { # file 、nacos 、eureka、redis、zk type = "eureka" 修改这里 nacos { serverAddr = "localhost" namespace = "public" cluster = "default" } eureka { serviceUrl = "http://localhost:8761/eureka" 修改这里 application = "default" weight = "1" } redis { serverAddr = "localhost:6381" db = "0" } zk { cluster = "default" serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } file { name = "file.conf" } }其他的使用默认值就好。
这个是要特别注意的地方,seata对数据源做了代理和接管,在每个参与分布式事务的服务中,都要做如下配置:
/** * 数据源代理 */ @Configuration public class DataSourceConfiguration { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); return druidDataSource; } @Primary @Bean("dataSource") public DataSourceProxy dataSource(DataSource druidDataSource){ return new DataSourceProxy(druidDataSource); } @Bean public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy)throws Exception{ SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath*:/mapper/*.xml")); sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); return sqlSessionFactoryBean.getObject(); } }(1).启动eureka;
(2).启动seata-server;
(3).启动order,storage,account服务; DEFAULT为seata服务的默认名字,也可通过修改配置文件自定义名字
(5).访问:http://localhost:8080/order/create?userId=1&productId=1&count=10&money=100
然后可以模拟正常情况,异常情况,超时情况等,观察数据库即可,发现事务控制成功
(2)访问:http://localhost:8080/order/create?userId=1&productId=1&count=10&money=100,发现事务回滚
