分布式事务 - raymond-zhao/cat-mall GitHub Wiki
分布式事务
下面的操作涉及多个微服务,属于分布式事务。
graph LR;
order(订单服务下订单)-->stock(库存服务锁定库存)-->credit(用户服务修改积分);
事务保证:
- 订单服务异常,库存锁定不运行,全部回滚, 撤销操作。
- 库存服务事务自治,锁定失败全部回滚,订单感受到,继续回滚。
- 库存服务锁定成功了,但是网络原因返回数据,途中出现问题?
- 库存服务锁定成功了,库存服务下面的逻辑发生故障,订单回滚了,怎么处理?
利用消息队列实现最终一致
库存服务锁定成功后发给消息队列消息(当前库存工作单),过段时间自动解锁,解锁时先查询订单的支付状态。解锁成功则修改库存工作单详情项为已解锁。
- 远程服务假失败:远程服务其实成功了,但是由于网络故障等原因没有返回成功信息,导致订单回滚,库存却扣减。
- 远程服务执行完成,下面的其他方法出现问题:但是已执行的远程请求,肯定是不能回滚的。
同一个对象内事务方法互相调用默认失败
原因:绕过了代理对象,而事务是通过代理控制的。
解决:使用 AOP:AspectJ 的代理功能,使用 AspectJ 的代理,而不使用 JDK 的代理。
- 引入
aop-starter:spring-boot-starter-aop
,引入 AspectJ @EnableAspectJProxy(exposeProxy=true)
;开启 AspectJ 动态代理功能,以后所有的动态代理都是 AspectJ 产生的代理。- 本类互相调用对象
OrderServiceImpl orderService = (OrderServiceImpl) AopContext.currentProxy();
orderService.b();
orderService.c();
CAP 定理
在理论计算机科学中,CAP 定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
- 一致性(Consistency): 等同于所有节点访问同一份最新的数据副本。
- 可用性(Availability): 每次请求都能获取到非错的响应—但是不保证获取的数据为最新数据。
- 分区容错性(Partition tolerance): 以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 C 和 A 之间做出选择。
理解 CAP 理论的最简单方式是想象两个节点分处分区两侧。允许至少一个节点更新状态会导致数据不一致,即丧失了 C 性质。如果为了保证数据一致性,将分区一侧的节点设置为不可用,那么又丧失了 A 性质。如果要同时保证 C 和 A,需要两个节点可以互相通信,但是这又会导致丧失 P 性质。
组件名 | 语言 | CAP | 服务健康检查 | 对外暴露接口 | SpringCloud集成 |
---|---|---|---|---|---|
Eureka | Java | AP | 可配支持 | HTTP | 已集成 |
Consul | Go | CP | 支持 | HTTP/DNS | 已集成 |
Zookeeper | Java | CP | 支持 | 客户端 | 已集成 |
Nacos | Java | CP+AP | 支持 | HTTP/DNS | 已集成 |
问题:各个组件具体是怎么保证 CAP 的?
Raft 一致性算法
- 一个结点可以有三种状态:Follower(随从)、Candidate(候选者)、Leader(领导)
- 开始时所有的结点都处于 Follower 状态
- 如果 Followers 没有收到 Leader 的信息,他们就可以成为 Candidate
- 接下来 Candidate 就会请求其他结点进行投票
- 其他的 Followers(结点) 就会用投票来回复
- 如果 Candidate 获取了大多数结点的支持,它就会成为 Leader 结点
上面的过程称为 Leader Election(领导选举),接下来对于这个系统的所有操作都将会经过 这个 Leader 。
- 每一次改变都会成为结点的 log 中的一条记录(Entry)
- 如果 log entry 没有被提交的话,自然也就不会更新 结点的值
- 如果要提交 log entry 的话,首先需要复制刚才的操作到 Follower 结点,然后 Leader 会等待大多数结点写入这条 entry,然后 Leader 会通知 Followers 说 entry 修改已被提交。
- 这个时候集群(Cluster) 就会就系统的状态达成共识,进入一致性状态。
上面的过程叫做 Log Replication。
Leader Election
- 在 Raft 中控制选举过程的有两种计时设置
- 一个叫做 election timeout,这个计时是指一个 Follower 等待成为 Candidate 所需要的时间。这个时间是 150ms~300ms 之间的随机值。
- 首先完成选举倒计时的结点(Follower)将会成为 Candidate,然后开始进行下一轮的选举。
- 成为 Candidate 的结点首先给自己投票,然后发送请求投票 (Request Vote) 信息给其他结点。
- 收到信息的结点在本轮投票中如果还未投票的话,就会投票给 Candidate,然后重新开始倒计时(election timeout)。
- 一旦 Candidate 结点拥有大部分结点的投票支持后它就会成为 Leader
- Leader 开始发送 Append Entries(添加条目) 消息给它的 Followers
- 这些消息以 heartbeat timeout(心跳时间) 指定的时间间隔发送
- Followers 然后会给每一条 Append Entries 的消息予以回应
- 这个过程将会持续到 Follower 停止收到 heartbeat 并且成为 Candidate 为止
需要多数结点的投票保证了在每轮选举中只会产生一个 Leader,如果两个结点都同时成为 Candidate,将会产生分裂投票(脑裂?)
举个例子,如果在 4 个结点中(A、B、C、D),两个结点(A、B)同时成为了 Candidate,并且同时发送了请求投票的信息,并且同时又各自得到了剩余的两个结点(C、D)中其中之一的支持。这样的话,在本轮投票中 A、B 加上自身的投票与其支持者的投票都各自有了两票,并且不能够再获得其他的投票。
出现这种情况的话将会开始新一轮的投票,在本轮选举中可能就会产生多数投票的结果,选举出新的 Leader。
Log Replication
- 一旦选举出 Leader 的话,我们就需要复制所有的修改(all changes) 给系统中的所有结点。
- 这是通过与 Heartbeats(心跳检测) 中所用到的 Append Entries 实现的(它们都是用的 Append Entries)。
- 首先,一个客户端(Client)发送修改(change)给 Leader,这条修改被写进 Leader's Log,然后这些改变将会在下一个 Heartbeat 时发送给它的 Followers。
- 一旦多数 Followers 确认了修改之后 entry 将会被提交,响应(Response) 也会被发送给 客户端(Client)。
Raft 甚至在 network partitions(不同网络中,可能出现了网络故障)时也能够保持一致性。
因为分区,现在有不同选举阶段的 Leader,当在网络故障阶段即使不同分区中的 Leader 和 Followers 都接收到了修改请求也能够保持一致性。
比如集群中有 5 个结点,因为网络分成了2、3两个区,它们的多数值是 3,具有 2 个结点的集群中即使收到了修改也不会提交 entry,因为得不到 3 个支持,会保持 uncommitted。
这是因为,在网络恢复之后,将会校验不同分区中 Leader 的选举次数(election term),具有较低选举次数的 Leader(并且没有) 将会甘拜下风,让 Term 值更大的 Leader 成为新的 Leader,但是之前与 Term 较小值同区的结点的 未提交的条目(uncommitted entries) 将会回滚(rollback),然后去同步新 Leader 的 log。然后系统会再次进入一致性状态。