分布式中间件
⭐分布式系统有哪些组件?
- 网关(Gateway)
- 微服务应用(Microservice)
- 消息队列(Message broker)
- 数据库(DB)
- 分布式缓存
- 分布式锁
- 分布式事务
通过以下中间件实现:Spring Cloud APP、Nginx、Zookeeper、RabbitMQ、Kafka、Netty、Dubbo等。
Zookeeper的应用场景
略
Kafka的应用场景
略
⭐网关
nginx的原理及配置
略
⭐分布式应用
企业级常见架构
- SSM(Spring、SpringMVC、Mybatis)
- SpringBoot、Dubbo、Zookeeper
- SpringCloud
什么是UMP系统
UMP系统是由阿里巴巴提出的一套云数据库解决方案。 用到的开源组件包括:
- mnesia(分布式数据库管理系统)
- LVS(Linux虚拟服务器)
- RabbitMQ(消息队列服务器)
- Zookeeper(协同工作系统)
springcloud应用有哪些组件
- Eureka(服务注册与发现)
- Ribbon(负载均衡,在RestTemplate上加@LoadBalance)
- Feign(负载均衡)
- Hystrix(熔断器)
- Zuul(网关)
- Config(配置)
什么是幂等性?如何保证幂等性?
幂等性满足如下条件:
- 请求后对资源没有副作用;(如select操作)
- 第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会再对资源产生副作用;
保证幂等性的方法:
- 请求被处理之后,有地方将结果ID保存起来(例如支付请求,支付完成后将订单ID与支付请求关联起来)
- 请求被处理之后,有地方将结果状态保存起来(例如支付请求,支付完成后将订单状态改为“已支付”)
Eureka Client调用Server的服务的流程
- Eureka Server启动时,会注册信息到Registry(注册中心);
- Eureka Server会定期向注册中心发送心跳信息(每30s),表明自己可用;
- Eureka Server发送心跳的同时,会获取最新的注册表信息;
- 因此,集群里可用的Eureka Server都维护着一张注册表(虽然同一时间内注册表数据可能不一致)
- Eureka Client第一次调用服务时,会先从Registry(注册中心)获取服务提供者地址(注册表),并缓存到本地;然后取其中一个Server进行调用。下次调用则可直接调用那个Server的服务;
- Eureka Client也会定期去Server端更新注册表;
- 当注册表中Eureka Server有多个时,Eureka Client会通过 Ribbon 自动进行负载均衡。
- 当Client发现调用的Server宕机时,Client会重新从本地的注册表中取新的Server调用地址,并调用。
Eureka服务调用失败后,有哪些处理方案
- 熔断(默认状态下,当某个服务接受的20个请求里有50%都失败时,触发熔断,再请求该服务会直接返回失败,直到5s后,重新检测是否该打开该服务)
- 降级(当服务熔断后,客户端调用微服务失败,此时客户端自己准备一个Fallback函数,返回一个缺省值或者报错)
- 限流
- 排队
心跳机制 & 注册中心的剔除机制、自我保护机制
- 心跳机制:client每30s向server发送心跳数据
- 剔除机制:server一定时间内(默认90s)没有接收到心跳数据,则认为该client已下线,会从自己的缓存中移除该client实例的信息
- 自我保护机制:
- 触发开启:server检查15分钟以内所有Eureka实例的正常心跳比,若低于85%,开启自我保护模式
- 开启状态下:
- 发现长时间没发送心跳的client,也不会对其进行剔除;
- 仍然能接收client的注册,但不会同步到其他server中;
- 待网络正常后,才将client的注册信息同步到其他server中。
Eureka启动一个服务,客户端是怎么从注册中心发现这个服务的,流程说一下
Eureka Client在注册的同时,会启动一个轮询,该轮询会定期从注册中心获取其他服务的信息(IP、port、实例id等),并缓存起来; 调用服务时便会直接从缓存中获取服务的信息。 调用方法:
- Ribbon参与时,根据spring.application.name:http://eurakaclientsample1/sample/getData
- Ribbon没有参与时,根据IP+端口:http://localhost:10000/sample/getData
如何让Ribbon参与?在RestTemplate上添加@LoadBalance注解。
zookeeper和eureka的区别,以及cap原则
CAP原则:
- Consistency(一致性):分布式系统中每个节点中的数据是否总是一致
- Availability(可用性):分布式系统中,部分节点挂掉了,这个集群是否还能使用
- Partition tolerance(分区容错性):因某些原因,导致某些机房上部署的系统与其他系统不能通信(不连通),则这些机房便形成了“分区”。分区容错性便是指如果形成了“分区”,如何保证集群的一致性和可用性。
若要满足”分区容错性“,办法是将不同机房中的数据备份到其他机房的节点上。这样数据分布在各个机房中,某个机房挂了,该数据还有副本提供访问;但这样会带来一致性问题。
若要满足”一致性“,每次写操作后,要等待其他节点全部写成功,然后才能读;但这样又会带来可用性问题。
因此,分布式系统一般只能满足CAP中的两个原则。而分区容错性又是必须的,所以一般分布式系统都是再C和A中权衡。
eureka -->AP,即高可用,但不满足一致性 如何体现是AP? Eureka Server每个节点都是平等的,每台Server都维护着Server信息注册表,集群中只要有一台服务Server没有宕机,集群也是可用的;
zookeeper -->CP,即数据一致,但不满足高可用 如何体现是CP? zookeeper server集群至少需要3台服务器(leader选举算法需要)。 zookeeper server集群中的节点分为leader和follow节点。 当leader宕机时,集群需要重新选出leader,此时的集群暂时不可用。
cas算法
略
⭐消息队列
rabbitmq有哪些组件,项目中怎么用的,为什么这么用
主要有生产者、消费者、队列、还有交换机。
用法:
- 异步。可以将函数调用中某些非关键操作异步化。
- 解耦。当发现某个函数以后可能会发送别的消息到其他系统时,可以用消息队列的模式设计。
⭐分布式数据库
1.为什么需要分布式数据库?分布式数据库的优点有哪些?
当数据库遇到以下问题,便要考虑对数据库进行扩展:
1.容量不足
2.性能变差
扩容的方案:当遇到以上问题,可以通过以下方式解决:
1.升级服务器的CPU、硬盘、内存等方式解决(这种方式称为scale-up)。
2.采用分布式数据库(这种方式称为scale-out)
采用分布式数据库的优点:
灵活,方便,上限高
2. 什么时候该分库分表?
当单表数据量达1000w或100G时
3. 有哪些分库分表的方法?
分库分表常用中间件: shardingsphere(当当,有sharding-proxy、sharding-jdbc、sharding-sidecar三种解决方案) Atlas(360) Cobar(Alibaba,已停止维护) Mycat(Alibaba,需要部署代理服务器)
两种方式:垂直切分和水平切分
垂直切分:
(1)垂直分库:将一个数据库切分为多个数据库。所有表也被分类到不同的数据库里。在分类时,将关联度低的表存储到不同的数据库中。 (2)垂直分表:对某个字段太多的表进行拆分,拆分成两个甚至多个表,其中每个表只有部分的字段。
优点: 解决业务层面的耦合,业务清晰; 缺点: 部分表无法join; 需要处理分布式事务; 仍然存在单表数据量太大的问题。
水平切分:
(1)库内分表:当某个单表数据量太大时,拷贝多个该表(有同样的列),将这些数据按某种条件分布到这些表中,从而使单个表的数据量减少。
(2)分库分表:将单表的数据库分到其他机器的表中(与该单表有同样的列)。
优点: 减少单表的数据量; 应用端改造较少? 缺点: 难以保证事务一致性; 跨库的关联查询性能较差;
4.水平切分后,同一张表的数据会被分到多个库/多张表中。有哪些数据分片的规则?
1.根据时间区间或ID区间区分。 例如,按时间区间区分,将数据分为历史数据和热点数据,即“冷热数据分离”。业务功能上只提供热点数据的查询。
优点:易于扩展,当想对集群扩容时,只要增加机器,无需对其他分片的数据进行迁移。 缺点:热机会被频繁读写,冷机则很少被查询,导致资源的浪费。
2.根据数值取模 采用hash取模的方式,将数据平均分到不同的表中。
优点: 不易出现热点数据和并发访问的瓶颈; 缺点: 若集群想要扩容时,需要对分片的数据进行迁移(用一致性hash算法可避免该问题);
5.分库分表带来的问题
- 分布式事务 当在一个事务中更新数据时,可能需要同时对不同库的不同表进行更新,此时会面临跨库事务的问题。
解决方案:XA事务、两阶段提交。
- 跨节点join查询 不同库中的表不能再进行sql join查询了。
解决方案:
(1)全局表:一些大部分表都会用到的表(字典),为了避免跨库join查询,可以在每个数据库都保存一份这个表。
(2)字段冗余,直接避免sql join查询;
(3)数据组装:分开查询,并在代码层面对数据进行组装;
(4)ER分片:将有关联关系的表放到同一个分片; 3. 跨节点分页、排序问题
分库分表需要单独设计全局主键。常见主键生成策略: (1)UUID(32个16进制字符,即0-f); (2)专门创建一个生成主键的表。 (3)snowflake分布式自增ID算法。
数据迁移、扩容问题
⭐缓存
redis常用数据类型及使用、缓存雪崩及缓存穿透相关解决方案
- string(key-value),可保存字符串和数字
- 增/改
- 普通增 SET key value
- 设置过期时间 SETEX key seconds value
- 增加多对键值对 MSET key value key value...
- 自增一 INCR key
- 自增N INCRBY key increment
- 自减N DECR key
- 删
- 查
- 普通查 GET key
- 查子字符串 GETRANGE key start end
- 返回多个值 MGET key key ...
- 增/改
- hash(哈希表)
- 增
- 往哈希表插入键值对 HSET key field value
- 插入多对键值对 HMSET key field value field value...
- 自增N HINCRBY key field increment
- 删
- 删除键值对 HDEL key field field ...
- 查
- 查某个键对应的值 HGET key field
- 查所有键值对 HGETALL key
- 查所有键 HKEYS key
- 查所有值 HVALS key
- 增
- list(数组)
- 增
- 插入多个值到列表头部 LPUSH key value value ...
- 插入多个值到列表尾部 RPUSH key value value ...
- 通过索引修改值 LSET key index value ...
- 删
- 移出并获取第一个元素 LPOP key
- 移出并获取最后一个元素 RPOP key
- 查
- 查找数组中第i个元素 LINDEX key i
- 增
- set(集合)
- 增
- 删
- 查
- zset(有序集合)
- 增
- 删
- 查
1.缓存雪崩
缓存雪崩是指,如果所有key的失效时间一致,便会造成在某一时间段所有key失效;此时查询请求会直接去到数据库中。若请求太多甚至会使数据库挂掉。 处理方法:为key的失效时间加个随机值。
2.缓存穿透
缓存穿透是指,用户不断查询缓存中没有的数据,导致请求直接去到数据库中。 处理方法: 1.增加参数校验,不符合规格的入参直接返回报错。 2.Redis自带的布隆过滤器。
3.缓存击穿
与缓存雪崩类似,缓存击穿是有一个Key并发特别大,当该key失效时,大量的请求直接去到数据库,拖垮数据库的性能。 处理方法: 1.设置热点数据永远不过期。 2.加上互斥锁。
⭐ 分布式锁
1.什么时候需要分布式锁
分布式锁的使用在分布式系统中较为常见,当某个变量需要被不同机器上部署的系统共享使用时,就需要用到分布式锁来控制共享变量的访问,尽量做到不出现数据一致性问题。
2.如何实现分布式锁?
基于数据库实现分布式锁; 创建一个表,表id即为锁。插入一条数据就是一次获取锁的操作。 利用了主键唯一的特性,当已获取了id为1的锁后,再次获取(即插入id=1的数据)会报错,通过此机制实现分布式锁。
基于缓存(Redis等)实现分布式锁; 使用redisson框架。
基于Zookeeper实现分布式锁。 使用zk的临时节点(set -e /lock/...)。 临时节点是当session关闭后,节点会被删除。因此可以当做分布式锁来用。
⭐分布式事务
1.有哪些事务的解决方案?有哪些分布式事务的解决方案?
单数据库事务: 每种商用数据库基本上都有自己的事务框架。
分布式事务: 多个数据库共用一个事务,全部操作成功则提交事务,若其中一个失败则全部需要回滚。
有以下几种实现分布式事务的模式:
强一致性(严格保证ACID原则)
- 2PC(两阶段提交)
- 3PC(三阶段提交)
- TCC()
最终一致性(不保证一直保持ACID原则,但最终总会达到ACID)
- 本地消息表
- 消息事务(MQ)
- 最大努力通知
先从强一致性的模式开始说。
2PC模式 原理:
假设某个函数需要向N个数据库插入数据,会经历以下两个阶段。
准备阶段(prepare): 各个数据库开启自己的事务,执行插入语句; 执行完后,不提交,而是通过XA接口告诉事务管理器已准备好(prepare);
提交阶段(commit): 当所有数据库已准备好(prepare),事务管理器才通过XA接口告诉所有数据库,可以提交事务(submit); 反之,若有某个数据库没有返回“准备好(prepare)”,则事务管理器告诉所有数据库,需要回滚事务(rollback)。
优点:
- 能保证强一致性
- 现实分布式事务的耗时相对比较少(只需要一次prepare、一次commit即可)
缺点:
- 同步阻塞资源。分布式事务开启期间会锁住资源,导致其他事务需要等待。
- 单点故障。当某节点参与分布式事务时突然宕机,没提交prepare,则其他节点都会处于等待commit状态。
- 数据不一致。当事务管理器接收到所有prepare,并返回submit(可以提交)时,部分节点宕机,则会导致实际上有的事务没有被提交,导致数据不一致。
3PC模式 原理:
在2PC的基础上,增加了询问阶段;且增加了“超时没接到回复则默认失败”的机制。
询问阶段(CanCommit): 【事务管理器】 事务管理器向各个参与者发出CanCommit的询问,问是否可以顺利执行事务; 【参与者】 参与者觉得可以,则返回yes;不可以则返回no。
预提交阶段(PreCommit):
【事务管理器】 两种情况。
事务管理器接收到所有参与者的回复都是yes,则向所有参与者发送PreCommit消息;
有参与者回复no,或者等待参与者消息超时,则向所有参与者发送abort消息(中断事务);
【参与者】
接收到PreCommit:就会像2PC的准备阶段一样,开启事务,执行SQL语句,并将undo和redo记录下来; 参与者执行完后,执行成功则返回preCommit ACK;执行失败则返回no;
接收到Abort:执行事务的中断。
超时没接收到事务管理器的回复:执行事务的中断。
提交阶段(doCommit): 【事务管理器】 当所有参与者都返回ACK,才进入到该阶段。事务管理器向所有参与者发送doCommit请求。
【参与者】 接收到doCommit请求,正式提交语句,并释放占用资源。 提交完后,向事务管理器返回doCommit ack;
若超时没有收到doCommit请求,参与者也会提交语句,并释放占用资源,返回doCommit ack;
【事务管理器】 接收到所有ack后,完成事务。
若没有接收到所有ack,则向所有参与者发送abort消息(中断事务);
【参与者】 收到abort消息后,使用undo日志回滚事务,回滚完后释放所有资源; 回滚完后,返回ack
【事务管理器】 接收到所有ack后,完成回滚。
2PC模式、3PC模式最终都有可能因为宕机、网络问题,导致数据不一致的情况。此时只能用人工补偿(比如SQL脚本检查)去达到最终一致性了。 2PC有相应的支持(XA),3PC没有或者不常用。
XA模式(2PC的实现) XA模式将系统大致分为两个角色:事务管理器(TransactionManager),本地资源管理器(一般指数据库,以下均称为数据库)。 数据库自己可以提供事务,因此,若只对一个数据库进行读写操作的话,只需要使用自己数据库的事务功能即可; 但当需要对不同数据库进行操作时,便需要有事务管理器来协调和管理事务。
- XA提供接口来支持事务管理器与数据库之间的交互。
- 商用数据库基本都实现了XA接口。
TCC模式 适合分布式事务中有不提供事务机制的系统参与的情况(例如微服务、文件系统、Redis等)。 TCC也是强一致性,大概原理是,当某个系统执行失败,需要回滚时,这个回滚的实现由开发者通过代码来完成。
原理如下。
T for Try(尝试机制): 增加一种预提交的状态,先将资源冻结,或将状态设置为“进行中”;
这阶段就是开启事务,并执行sql,但不提交;文件系统则是新值内容。
C for Confirm(提交): 当所有Try操作成功后,则TCC框架会将所有Try操作冻结的资源释放,或将状态设置为“已成功”;
这阶段就是直接在数据库commit事务,文件系统则是保存文件。
C for Cancel(取消): 若其中一个Try操作失败,则TCC框架会取消所有冻结的资源,或将状态设置为“已失效”
做一些逆操作。数据库:将原来的值置回去;文件系统:把原来的内容贴回去。
消息队列(MQ)+本地事件表模式 假设有以下情况: 有一个业务需要按以下步骤进行:A->B->C。 其中服务器a负责A,b负责B,c负责C; 这些服务器都会维护一个事件表(同一数据库里的一张表),还有一个定时任务处理器(例如quartz); 步骤A、B、C就是根据事件表和MQ中的事件驱动,一步一步把流程走完。
一开始请求进来,调用服务器a完成A步骤; 【服务器a】 成功:
- 服务器a完成并提交A步骤到数据库,并将“A步骤已完成”这个事件写到本地事件表中(有一个事件ID、时间、事件类型);
- 定时任务器会将该事件发送到MQ上,并修改本地事件表中事件的状态。 至此服务器a工作完成。
失败:则直接返回失败。
【服务器b】 成功:
- 服务器b的定时任务器会监听MQ上的“A步骤已完成”事件;发现有,则写到自己的事件表中;
- 定时任务器还会监听自己事件表,如果有“A步骤已完成”事件,则开始调用B步骤的逻辑;
- 服务器b完成并提交B步骤到数据库,并将“B步骤已完成”这个事件写到本地事件表中(有一个事件ID、时间、事件类型);
- 定时任务器会将该事件发送到MQ上,并修改本地事件表中事件的状态。
至此服务器b工作完成。
失败:
- 服务器b若完成不了步骤B,也需要将“B步骤完成失败”这个事件写到本地事件表中(有一个事件ID、时间、事件类型);
- 定时任务器会将该事件发送到MQ上,由服务器a来监听该事件并回滚。
【服务器c】 成功:参考b
失败:参考b。也会发送“C步骤完成失败”的事件到MQ;B监听到后回滚并发送“B步骤完成失败”的事件到MQ,如此类推...
- 该模式适合无需同步完成的任务,所有步骤都是异步进行的,因此不能达到强一致性,但理论上能达到最终一致性。
- 为了避免重复消费,保证幂等性,应该为每个分布式事务都分配一个ID。这样一旦出现重复消费,在插入自己的本地事件表的时候就会报错。
- 每个服务器的本地事件表跟自己其他的业务表均在同一数据库中,因此若步骤完成失败了,就可以直接回滚两个表的操作。
可靠消息服务模式 与“本地事件表模式”相似,只不过将所有服务器的事件表都通过一个“可靠消息服务”来维护。
2. Java、Spring中,事务、分布式事务的运用?
Java事务框架:
- JDBC事务(仅为单个数据库提供事务支持)
- JTA事务(可理解为Java版XA规范;支持多个数据源的回滚:JDBC连接、JDO、JMS、EJB等)
JTA只是一套接口,其实现有:atomikos、JOTM
- JDO事务
- JPA事务
- JMS事务
- Hibernate事务
- Mybatis事务
- Spring事务
- LCN
- ByteTCC
- SeaTa
常见分布式事务框架:
| 框架 | TCC模式 | XA模式 | AT模式(2PC的改良版) | saga模式 | MQ | 原理 |
|---|---|---|---|---|---|---|
| Atomikos | --- | ✔ | --- | --- | --- | 两阶段提交(2pc) |
| LCN | ✔ | --- | --- | --- | --- | |
| ByteTCC | ✔ | --- | --- | --- | --- | |
| Seata | ✔ | ✔ | ✔ | ✔ | --- |
3. 整合Seata
因为Seata框架比较成熟,且支持多种模式,所以我们可以选择。
各模式的优缺点:
| 项目 | AT模式 | TCC模式 | XA模式 |
|---|---|---|---|
| 描述 | 会代理Datasource进行数据库操作,2PC模式的改良版 | 不会代理应用的数据库操作 | X/Open组织定义了分布式事务接口,Oracle、mysql等厂商进行具体的实现 |
| 一致性 | 最终一致性 | 强一致性 | |
| 整合方法 | @GlobalTransactional + undo_log(事务回滚日志表) | @LocalTCC、@TwoPhaseBusinessAction、定义3个接口对应3个阶段 | @GlobalTransactional |
| 优点1 | 无侵入性,很少分布式事务相关代码 | 性能高,框架层面解决了空回滚、幂等性、防悬挂的问题 | 无侵入性,极少分布式事务相关代码 |
| 优点2 | 弥补了 XA 模式中资源锁定周期过长的缺点,增加了undo_log 机制,性能更好 | 应用自主程度高,事务参与者不局限于数据库,可延伸至其它组件如:Redis | 支持任何实现了 XA 协议的数据源 |
| 缺点1 | 只能回滚数据库这种资源 | 侵入性高,应用需要以 TCC 规范去编写业务逻辑 | 只能回滚数据库这种资源 |
| 缺点2 | 不适合用于单个SQL修改大量数据的场景 | 改造成本大,对于存量业务来说,需要将原本的业务逻辑一分为三 | 性能差 |
| 适用场景 | 适用于依靠关系型数据库的保存数据的业务场景 | 对性能有较高要求的业务场景 | 适用于基于 XA 协议设计且无法进行改造的业务场景 |
AT模式是如何弥补XA模式资源锁定周期过长这个缺点的?
AT模式不锁定资源,而是先生成执行前的数据快照,然后直接执行并提交;
然后记录执行后的数据快照;
一旦需要回滚了,便直接用旧的数据快照覆盖新的即可;
什么是空回滚、幂等性、防悬挂?
简单讲讲AT、tcc、XA 3种模式分别的原理?
参考:https://wu55555.blog.csdn.net/article/details/124510770
- AT模式
AT模式中,假设有服务A,服务B,服务C
服务A,标记了@GlobalTransactional,因此服务A属于TM;
服务B、C,被服务A调用,因此是RM;
还有一个Seata-Server,负责全局事务的协调,是TC。
1.1 服务A向TC申请开启全局事务,获得一个全局事务ID(XID); 1.2 调用服务B并带上XID;
1.3 服务B向TC注册,并带上XID,好将服务B与服务A分到同一组;同时获得本地事务ID(BID)
1.4 服务B在提交前,服务B会:1. 申请全局行锁 2. 生成事务前数据快照(before image) 1.5 服务B提交后,还会生成事务后数据快照(after image)
1.6 然后,服务B提交事务执行结果 1.6.1 成功。根据before image,生成回滚SQL保存到undo_log里; 1.6.2 失败。根据after image,判断数据是否被脏读。有则报错,没有则事务回滚,并通知TC让其他本地事务回滚 1.7 服务C同理,按照1.3、1.4、1.5、1.6进行 1.8 服务A提交整体执行结果:(1)成功。TC通知服务A删除undo_log及其他事务管理数据;(2)失败:TM通知服务A所有本地事务回退
- TCC模式
TCC模式中,有Try、Confirm、Cancel三个阶段。
假设现在有一个提交订单的场景。提交完订单后需要扣减库存。
2.1 Try阶段
- 创建订单,并标注为未提交
- 业务检查(是否有足够的库存扣减)
- 资源预留(冻结库存,冻结的库存其他业务不能使用)
2.2 Confirm阶段
- 将订单状态改为已提交
- 冻结的库存取消冻结,正式扣减
2.3 Cancel阶段
- 删除未提交的订单
- 取消冻结的库存
在锁使用的方面,整个实现TCC的过程,实际上是没有用到全局锁的,这是和AT模式另外一个大的区别。
TCC模式更多的是利用本地行锁或者乐观锁、状态区分的形式来实现资源隔离。
因为没有全局锁的限制,所以其速度飞快提升。因此呢TCC模式也就适用于对性能有较高的分布式事务场景。
2.4 建立TCC模式的重试机制
confirm或者cancel方法也出现报错时,为了保证事务的最终一致性,我们应当做好重试机制处理:
比如将请求发送MQ,然后再接收处理。
2.5 幂等性问题
如果扣减库存时多次重试,仍然能保证只扣了1次库存,这种就称为保证了幂等性。
换句话说,操作多次和操作一次的效果一样,就是所谓的幂等。
如何保证幂等性:
- 添加状态字段,判断是否执行过
2.6 悬挂问题(又叫空回滚)
所谓悬挂问题,就是二阶段模式中,cancel比try先执行。
拿上面的例子,cancel是解冻库存,try是将订单状态改为未提交。
2.6.1 因为网络问题,导致商品服务的try一直卡住,然后报错
2.6.2 TM通知库存服务回滚,执行了cancel方法,解冻了库存;
2.6.3 此时商品服务又好了,执行了try方法,将订单状态改为未提交;
2.6.4 然而事务已经被cancel过了,就不会再执行confirm,也就没有谁再来将资源状态从预处理更新为已处理了。
针对悬挂问题进行防悬挂处理,方案呢就是限制如果二阶段执行完成,一阶段就不能再执行。
当然这些处理呢,seata已经帮我们实现了,这也是使用现成的分布式事务框架的好处。省心!但是我们自己要知道这些问题和原理。
seata中的解决方案是增加一个事务记录表,在cancel阶段最后往事务记录表中插入一条记录(xid-status)标记cancel阶段已经执行过。
此时try阶段进入时发现已经执行过回滚操作,则放弃try阶段的执行