1 Spring下的本地事务
详见Spring文档
Spring
2 事务的ACID
事务是由一组SQL语句组成的逻辑处理单元,事务具有4属性,通常称为事务的ACID属性。
原子性(Actomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以操持完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
eg:有3个人进行转账操作,为了保证一致性(即3个人 的账号金额总数不变),那在我写代码的时候,如果写了代码:A=A-5000;此时数据时不一致的。那就必须要写上,B=B+5000,或者是C=C+5000,这样的代码才能保证了数据库的一致性状态。隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。具体看下面的几个隔离级别和并发问题。持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持
3V+3高
3 互联网下架构新需求
CAP
C:consistency,数据在多个副本中能保持一致的状态。
A:Availability,整个系统在任何时刻都能提供可用的服务,通常达到99.99%四个九可以称为高可用
P:Partition tolerance,分区容错性,在分布式中,由于网络的原因无法避免有时候出现数据不一致的情况,系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择,换句话说,系统可以跨网络分区线性的伸缩和扩展。
CAP理论的核心:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。
- CA:单点集群,满足一致性,可用性的系统,通常在可扩展上不太强大。应用:传统的Oracle数据库
- CP:满足一致性,分区容错性的系统,通常性能不是特别高。应用:Redis,MongoDB,银行
- AP:满足可用性,分区容错性,通常可能对一致性要求低一些。应用:大多数网站架构的选择

CAP理论就是说在分布式存储系统中,最多只能实现上面的两个。而由于当前的网络硬件肯定会出现延迟丢包等问题。所以分区容忍性是我们必须需要实现的
所以我们只能在一致性和高可用之间进行权衡,没有NoSQL系统能同时保证三点。为什么呢?
为何CAP三者不可兼得
现在我们就来证明一下,为什么不能同时满足三个特性?
假设有两台服务器,一台放着应用A和数据库V,一台放着应用B和数据库V,他们之间的网络可以互通,也就相当于分布式系统的两个部分。
在满足一致性的时候,两台服务器 N1和N2,一开始两台服务器的数据是一样的,DB0=DB0。在满足可用性的时候,用户不管是请求N1或者N2,都会得到立即响应。在满足分区容错性的情况下,N1和N2有任何一方宕机,或者网络不通的时候,都不会影响N1和N2彼此之间的正常运作。
当用户通过N1中的A应用请求数据更新到服务器DB0后,这时N1中的服务器DB0变为DB1,通过分布式系统的数据同步更新操作,N2服务器中的数据库V0也更新为了DB1,这时,用户通过B向数据库发起请求得到的数据就是即时更新后的数据DB1。
上面是正常运作的情况,但分布式系统中,最大的问题就是网络传输问题,现在假设一种极端情况,N1和N2之间的网络断开了,但我们仍要支持这种网络异常,也就是满足分区容错性,那么这样能不能同时满足一致性和可用性呢?
假设N1和N2之间通信的时候网络突然出现故障,有用户向N1发送数据更新请求,那N1中的数据DB0将被更新为DB1,由于网络是断开的,N2中的数据库仍旧是DB0;
如果这个时候,有用户向N2发送数据读取请求,由于数据还没有进行同步,应用程序没办法立即给用户返回最新的数据DB1,怎么办呢?有二种选择,第一,牺牲数据一致性,响应旧的数据DB0给用户;第二,牺牲可用性,阻塞等待,直到网络连接恢复,数据更新操作完成之后,再给用户响应最新的数据DB1。
上面的过程比较简单,但也说明了要满足分区容错性的分布式系统,只能在一致性和可用性两者中,选择其中一个。也就是说分布式系统不可能同时满足三个特性。这就需要我们在搭建系统时进行取舍了。
Base
Base就是为了解决关系型数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。
Base其实是下面三个术语的缩写:
- 基本可用(Basically Available)
- 软状态(Soft state)状态可以有一段时间不同步
- 最终一致(Eventually consistent)最终数据是一致的就可以了,而不是时时保持强一致
它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法。
以案例转账为例,我们把用户A给用户B转账分成四个阶段,第一个阶段用户A准备转账,第二个阶段从用户A账户扣减余额,第三个阶段对用户B增加余额,第四个阶段完成转账。系统需要记录操作过程中每一步骤的状态,一旦系统出现故障,系统能够自动发现没有完成的任务,然后,根据任务所处的状态,继续执行任务,最终完成任务,达到一致的最终状态。
在实际应用中,上面这个过程通常是通过持久化执行任务的状态和环境信息,一旦出现问题,定时任务会捞取未执行完的任务,继续未执行完的任务,直到执行完成为止,或者取消已经完成的部分操作回到原始状态。这种方法在任务完成每个阶段的时候,都要更新数据库中任务的状态,这在大规模高并发系统中不会有太好的性能,一个更好的办法是用Write-Ahead Log(写前日志),这和数据库的Bin Log(操作日志)相似,在做每一个操作步骤,都先写入日志,如果操作遇到问题而停止的时候,可以读取日志按照步骤进行恢复,并且继续执行未完成的工作,最后达到一致。写前日志可以利用机械硬盘的追加写而达到较好性能,因此,这是一种专业化的实现方式,多数业务系系统还是使用数据库记录的字段来记录任务的执行状态,也就是记录中间的“软状态”,一个任务的状态流转一般可以通过数据库的行级锁来实现,这比使用Write-Ahead Log实现更简单、更快速。
分布式和集群
分布式:不同的多台服务器上面部署不同的服务模块(工程)
集群:不同的多台服务器上面部署相同的服务模块。通过分布式调度软件进行统一的调度,对外提供服务和访问。
4 分布式事务的几种方案
4.1 2PC模式(XA事务)
数据库支持的2PC【2二阶段提交】,又叫做XA Transactions
支持情况:mysql从5.5版本开始支持,SQLserver2005开始支持,Oracle7开始支持。
其中,XA是一个两阶段提交协议,该协议分为以下两个阶段:
- 第一阶段:事务协调器要求每个涉及到事务的数据库预提交(P090此操作,并反映是否可以提交
- 第二阶段:事务协调器要求每个数据库提交数据。
其中,如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此事务中的那部分信息
如图所示,如果有订单服务和库存服务要求分布式事务,要求有一个总的事务管理器
总的事务管理让事务分为两个阶段:
- 第一个阶段是预备(log)
- 第二个阶段是正式提交(commit)
总事务管理器接收到两个服务都预备好了log(收到ack),就告诉他们commit,如果有一个没准备好,就回滚所有人。
总结2PC
- XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。
- 性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景
- XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录阶段日志,主备切换回导致主库与备库数据不一致。许多nosql没有支持XA,这让XA的应用场景变得非常狭隘。
也有3PC,引入了超时机制(无论协调者还是参与者,在向对方发送请求后,若长时间未收到回应则做出相应处理)
4.2 柔性事务(TCC事务补偿性方案)
刚性事务:遵循ACID原则,强一致性
- 柔性事务:遵循BASE理论,最终一致性
与刚性事务不同,柔性事务允许一定时间内,不同节点的数据不一致,但要求最终一致
- 一阶段prepare行为:调用自定义的prepare逻辑
- 二阶段commit行为:调用自定义的commit逻憬
- 二阶段rollback行为:调用自定义的rollback逻辑
TCC模式,是指支持 自定义的 分支事务纳入到全局事务的管理中。
http://www.tianshouzhi.com/api/tutorials/distributed_transaction/388
4.3 柔性事务(最大努力通知型方案)
按规律进行通知,不保证数据一定能通知成功,但会提供可查询操作接囗进行核对。这种方案主要用在与第三方系统通讯时,比如:调用微信或支付宝支付后的支付结果通知。这种方案也是结合MQ进行实现,例如:通过MQ发送就请求,设置最大通知次数。达到通知次数后即不再通知。
案例:银行通知、商户通知等(各大交易业务平台间的商户涌知:多次通知、查询校对、对账文件),支付宝的支付成功异步回调。
大业务调用订单,库存,积分。最后积分失败,则一遍遍通知他们回滚让子业务监听消息队列如果收不到就重新发。
4.4 柔性事务(可靠消息+最终一致性方案:异步确保型)
实现:业务处理服务在业务事务提交之前,向实时消息服务请求发送捎息,实时消息服务只记录消息数据,而不是真正的发送。业务处理服务在业务事务提交之后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才会真正发送。
5 Seata解决分布式事务问题
概述
概述
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案,AT模式的Seata是2PC模式模式的进阶版本。
快速开始:seata-官网
重点:在高并发场景如下单,Seata的AT模式会加很多锁,影响效率。所以不使用AT(2PC模式)、TCC(柔性事务-TCC事务补偿),而是使用消息队列(柔性事务-最大努力通知、柔性事务-最终一致性)
1+3组套件
一套事务Tansaction ID(XID):全局唯一的事务ID三大组件TC (Transaction Coordinator) 。事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。TM (Transaction Manager) 。事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。RM (Resource Manager) 。资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
- TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID。
- XID在微服务调用链路的上下文中传播。
- RM向TC注册分支事务,将其纳入XID对应全局事务的管辖。
- TM向TC发起针对XID的全局提交或回滚决议。
- TC调度XID下管辖的全部分支事务完成提交或回滚请求。

案例
这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。当用户下单时,会在订单服务中创建一个订单, 然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
- 建表
seata需要的seata库,三个业务库,每个业务库里面各有一个业务表和额外的seata需要的undo_log回滚表。
- 配置信息
```java
//1.0 yaml配置文件信息
spring:
application:
name: seata-order-service
cloud:
alibaba:
seata
nacos: discovery:tx-service-group: fsp_tx_group //拿到seata服务器file.conf里面同名的组,该file.conf文件
datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: jdbc:mysql://000.000.000.000:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 feign: hystrix: enabled: falseserver-addr: localhost:8848
如上在业务模块里面指定的seata组<br />
3. 请求方法
package com.atguigu.springcloud.alibaba.service.impl; import com.atguigu.springcloud.alibaba.dao.OrderDao; import com.atguigu.springcloud.alibaba.domain.Order; import com.atguigu.springcloud.alibaba.service.AccountService; import com.atguigu.springcloud.alibaba.service.OrderService; import com.atguigu.springcloud.alibaba.service.StorageService; import io.seata.spring.annotation.GlobalTransactional; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service @Slf4j public class OrderServiceImpl implements OrderService { @Resource private OrderDao orderDao; @Resource private StorageService storageService; @Resource private AccountService accountService;
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
@Override
public void create(Order order) {
log.info("-------->开始创建新订单");
orderDao.create(order);
log.info("--------订单微服务开始调用库存,做扣减");
storageService.decrease(order.getProductId(),order.getCount());
log.info("-------订单微服务开始调用库存,做扣减end");
log.info("-------订单微服务开始调用账户,做扣减");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("-------订单微服务开始调用账户,做扣减end");
log.info("-------修改订单状态");
orderDao.update(order.getUserId(),0);
log.info("-------修改订单状态结束");
log.info("--------下订单结束了,哈哈哈哈");
}
} ```
