2019-03-14 21:52:37

前面 谈谈分布式架构 中我们知道了啥是分布式架构。

分布式架构就是原有的单体应用无法支撑服务了,需要根据业务、功能等将单一应用拆分成多个应用,然后分别进行部署,降低业务复杂,同时提高系统的负载。

不过将应用拆分之后我们不可避免的会出现分布式事务的问题。

# 啥是事务?

事务其实就是数据库事务。

一个数据库事务可以当成是记录成一系列读写操作的一个序列码。 执行一次事务操作,就是读取该序列码中记录的读写操作,根据这些读写操作进行相关的数据操作。 在数据库中,根据 ACID 定义,数据库支持单一事务的强一致性。就是说这个事务操作不是成功就是失败。

# 那啥是分布式事务?

将分布在多个数据库中的单一事务组合成一个事务,这就是分布式事务。

分布式事务图解

如图:在这一次请求中,由“支付服务事务” + “用户服务事务” 组成了一个事务操作。传统的数据库只能解决单一的事务一致性,也就是说,支付服务 只能保证自己的事务一致性,用户服务只能保证自己的事务一致性,但是我们需要的是针对这两个单一事务组合而成的事务进行一致性操作。换句话说就是,只有当支付事务和用户事务同时成功,这一次事务才算做成功。

# 针对分布式事务有什么解决办法?

有,既然两个人无法解决纠纷,那就再找一个人来做调解。

# 二阶段提交

二阶段提交图解

# 第一阶段:准备提交阶段

协调者 问 数据库1 : “你准备好提交了吗?” 数据库1回答: “准备好了!”

协调者 问 数据库2 : “你准备好提交了吗?” 数据库2回答: “准备好了!”

# 第二阶段:执行提交阶段

协调者 通知 数据库1 : “你可以提交(回滚)了” 协调者 通知 数据库2 : “你可以提交(回滚)了”

下面我们根据不同场景来分析一下

  • 协调者挂了,数据库正常

    在事务进行中,如果协调者挂了,重新起一个新的协调者。 然后通过询问所有参与者的状态是否都是 commit,如果都commit,则继续,如果没有 则abort(回滚)

  • 协调者挂了,数据库也挂了

    如果都挂了,就没办法知道状态,只能由数据库管理员人为介入,进行数据的恢复保证数据的一致性。

  • 协调者正常,参与者挂了

    在事务进行中,如果参与者某个参与者挂了,或者全挂了,在重新启动之后可以询问其他参与者或者协调者,从而知道事务状态,然后进行未完成的操作,比如提交或者回滚。

总结 由此我们看到,在出现协调者 和 所有数据库 都挂掉的情况下,就无法保证数据的一致性了。 同时二阶段存在阻塞问题,在第二阶段,数据库需要等待协调者发送 commit/abort 指令进行下一步操作,这时数据库资源被占用,如果刚好有新的请求来获取该资源,就得阻塞等待上一个请求释放资源。

# 三阶段提交

三阶段提交图解

根据上图可以知道,三阶段提交对比二阶段提交增加了 prepare commit 阶段。

其实就是把 二阶段提交中的第二阶段执行阶段拆分成预提交阶段和确认阶段。这样做解决了二阶段提交中,由于协调者和数据库同时挂掉时只能通过人为介入恢复数据一致性的问题

prepare commit 阶段目的是让数据库保存了当前的一个处理状态,当遇到协调者和数据库同时挂掉的情况,在重启数据库服务之后,会自己根据已经保存的状态进行事务的判定,进行下一步操作执行,无需人为介入恢复数据一致性。同时由于记录了操作状态,该事务也无需处于阻塞状态。只等协调者发送通知然后执行 commit/fallback 操作。

总结 三阶段提交的提出就是为了解决 二阶段提交的缺陷,但是三阶段提交需要更多的通讯次数,实现比较复杂。

# TCC

TCC 也是二阶段提交的一种,对比二阶段提交跨DB层面的操作,TCC可以说是应用层面的二阶段提交。

三阶段提交图解

从图中我们可以看到,TCC 事务分为三个操作步骤

  • Try:尝试进行相关操作
  • Confirm:执行操作
  • Cancel:出现异常时回滚状态。

我们可以将TCC事务看做 “先上车后买票”。

Try:我先看看这辆车能不能上,哎呦,可以, Confirm:直接上车。可以到目的地。买票。 Cancel :这辆车没法到达目的地,不买票,下车。

# 最终一致性

上面的那么多分布式事务解决方案,其实都是为了解决数据的一致性。但是即使是上面的TCC方案最多也就只能解决大约 99.9% 的数据一致性问题。 但是在金融系统等对一致性要求非常高的应用中,即使是 0.1% 的数据不一致都是不能容忍的。

那么针对这种情况有什么解决方案吗?

解决方案就是我们上一篇文章 “谈谈分布式架构 ”中谈到的 BASE 理论。 BASE 提轮提出的最终一致性。通过最终一致性,来弥补那 0.1% 的数据不一致。

下面我们来谈谈一些解决最终一致性的解决方案。

# 查询模式

场景:例如微信支付提供的查询接口。

在业务场景中,我们不可避免的需要对接微信支付,在支付成功之后,我们需要根据微信给到我们的回调来确认支付结果,以完成后续的业务操作。 但是我方的回调处理存在网络问题,或者处理代码异常等各种因素导致的数据不一致。 微信针对可能出现这些问题,提供了查询接口。 我们能够根据对应的接口查询到相关状态,确保我方数据与微信方的数据一致。

# 补偿模式

场景:A 服务调用 B 服务时,出现超时。

当我们访问 B 服务出现超时时我们可以有如下应对策略: 第一:重试本次操作,如果 B 返回了结果进行处理 第二:取消本次操作,进行数据回滚,然后重新向 B 发起操作

# 异步确保模式

场景:用户任务操作之后进行消息推送。

异步确保模式

在某些场景中,如用户任务操作,在任务操作中,当任务完成了,需要通知用户。 这时通知用户并非一个主流程操作,但是通知操作的失败,如异常报错等,可能会导致主流程任务操作一起失败。 通过如消息队列等将用户任务操作和消息通知进行剥离,当任务正确完成之后,将发送消息的任务写入队列中,然后由辅助的通知程序进行发送消息的通知。

这样的操作能够确保,辅助的消息通知程序异常导致的非正常任务失败。且辅助程序在未发送成功消息通知前会重试,保证消息通知能够顺利发送成功。

# 定期校对模式

定期校对模式,就是根据业务需求制定特定的校对平台,根据校对规则,针对定期时间内的所有数据进行汇总校对,保证数据最终一致性。

最终一致性的解决方案根据应用场景不懂,根据业务不同,解决的方式各不相同,这里就不一一列举,我们列举的这四种最终一致性的解决方案是在日常开发中最常用的4中解决方式。

精彩内容推送,请关注公众号!
最近更新时间: 3/24/2020, 9:44:42 PM