分布式系统的数据一致性

举例:订单系统-支付系统-仓库系统,数据一致性问题
  • 一个简单的购物流程
  • 正常流程:用户在京东上下了一个订单,发现自己在京东的账户里面有余额,然后使用余额支付,支付成功之后,订单状态修改为支付成功,然后通知仓库发货。假设订单系统,支付系统,仓库系统是三个独立的应用,是独立部署的,系统之间通过远程服务调用。
  • 订单的有三个状态:I:初始 P:已支付 W:已出库,订单金额100, 会员帐户余额200。
  • 如果整个流程比较顺利,正常情况下,订单的状态会变为I->P->W,会员帐户余额100,订单出库。
问题1
  • 订单系统调用支付系统支付订单,支付成功,但是返回给订单系统数据超时,订单还是I(初始状态),但是此时会员帐户余额100。

    即支付系统正常扣了钱,但是支付成功的通知没有及时发送给订单系统,订单系统就不会通知仓库系统发货。用户扣了钱却不会发货。

  • 假设方法一:假设调用支付系统支付订单的时候先不扣钱,订单状态更新完成之后,在通知支付系统你扣钱。

    不行。
    如果采用这种设计方案,那么在同一时刻,这个用户,又支付了另外一笔订单,订单价格200,顺利完成了整个订单支付流程,由于当前订单的状态已经变成了支付成功,但是实际用户已经没有钱支付了,这笔订单的状态就不一致了。即使用户在同一个时刻没有进行另外的订单支付行为,通知支付系统扣钱这个动作也有可能完不成,因为也有可能失败,反而增加了系统的复杂性。

  • 假设方法二:订单系统自动发起重试(重复下单),多重试几次,例如三次,直到扣款成功为止。

    不行。
    这个看起来也是不错的考虑,但是和解决方案一样,解决不了问题,还会带来新的问题,假设订单系统第一次调用支付系统成功,但是没有办法收到应答,订单系统又发起调用,完了,重复支付,一次订单支付了200。

  • 假设方法三:在方法二的基础上,在支付系统上对订单号进行控制,一笔订单如果已经支付成功,不能再进行支付。返回重复支付标识。那么订单系统根据返回的标识,更新订单状态。假设应用上重试三次,如果三次都失败,先返回给用户提示支付结果未知。假设这个时候用户重新发起支付,订单系统调用支付系统,发现订单已经支付,那么继续下面的流程。如果会员没有发起支付,系统定时(一分钟一次)去核对订单状态,如果发现已经被支付,则继续后续的流程。

    能够解决订单和支付数据的一致性问题,但是用户体验非常差。
    用户体验非常差,告诉用户支付结果未知,用户一定会骂你,你丫咋回事情,我明明支付了,你告诉我未知。假设告诉用户支付失败,万一实际是成功的咋办。你告诉用户支付成功,万一支付失败咋办。

  • 假设方法四:回到第一种方案,我们先不扣钱,但是可以先把这一部分钱冻结起来。订单系统先调用支付系统成功的时候,支付系统先不扣钱,而是先把钱冻结起来,不让用户给其他订单支付,然后等订单系统把订单状态更新为支付成功的时候,再通知支付系统,你扣钱吧,这个时候支付系统扣钱,完成后续的操作。

    假设订单系统在调用支付系统冻结的时候,支付系统冻结成功,但是订单系统超时,这个时候返回给用户,告知用户支付失败,如果用户再次支付这笔订单,那么由于支付系统进行控制,告诉订单系统冻结成功,订单系统更新状态,然后通知支付系统,扣钱吧。如果这个时候通知失败,没有问题,反正钱都已经是冻结的了,用户不能用,只需要定时扫描订单和支付状态,进行扣钱而已。
    如果用户重新拍下来一笔订单,100块钱,对新的订单进行支付,这个时候由于先前那一笔订单的钱被冻结了,这个时候用户余额剩余100,冻结100,发现可用的余额足够,那就直接在对用户扣钱。这个时候余额剩余0,冻结100。先前那一笔怎么办,一个办法就是定时扫描,发现订单状态是初始的话,就对用户的支付余额进行解冻处理。这个时候用户的余额变成100,订单数据和支付数据又一致了。
    假设原先用户余额只有100,被冻结了,用户重新下单,支付的时候就失败了啊,的确会发生这一种情况,所以要尽可能的保证在第一次订单结果不明确的情况,尽早解冻用户余额,比如10秒之内。但是不管如何快速,总有数据不一致的时刻,这个是没有办法避免的。

  • 分析了问题1以及相应的方案,发现在数据分布的环境下,很难绝对的保证数据一致性(任何一段区间),但是有办法通过一种补偿机制,最终保证数据的一致性。

问题2
  • 订单系统调用支付系统成功,状态也已经更新成功,但是通知仓库发货失败,这个时候订单是P(已支付)状态,此时会员帐户余额是100,但是仓库不会发货。
  • 解决方法:采取重试机制,如果发现通知仓库发货失败,就一直重试。有两种方式:

    异步方式:通过类似MQ(消息通知)的机制,这个是异步的通知。
    同步调用:类似于远程过程调用。
    对于同步的调用的方式,比较简单,我们能够及时获取结果;对于异步的通知,就必须采用请求,应答的方式进行。

问题3
  • 订单系统调用支付系统成功,状态也已经更新成功,然后通知仓库发货,仓库告诉订单系统,没有货了。

  • 方法一:在会员下单的时刻,就告诉仓库把货物留下来。(下单时检查仓库,如果有货,正常下单,未支付,仓库数量减一)

    如果用户恶意拍下来,没有去支付,就影响到了其他用户的购买。京东可以设置一个订单超时时间,如果这段时间内没有支付,就自动取消订单。

  • 方法二:在会员支付订单时候,支付之前检查仓库有没有货,如果没有货,就告知会员没有货物了。

    对于那些已经下了订单,但没有及时付钱的用户,可能买不到货。对于用户体验不好。

  • 方法三:如果会员支付成功,这个时候没有货了,就会退款给用户或者等待有货的时候再发货。

    体验更不好,而且用户感觉受到京东欺诈,但是对于京东来说,比第二种方案更有益,毕竟我还可以多卖出一点东西。

  • 铁道部在这个问题上,采用的是第一种方案,就是因为用户体验,而且铁道部不担心票卖不出去,第一种方案对他影响没有什么。

总结
  • 分布式环境下(数据分布)要任何时刻保证数据一致性是不可能的,只能采取妥协的方案来保证数据最终一致性。这个也就是著名的CAP定理。
  • CAP是在分布式的环境下设计和部署系统时的3个核心的需求:

    Consistency:一致性,这个和数据库ACID的一致性类似,但这里关注的所有数据节点上的数据一致性和正确性,而数据库的ACID关注的是在在一个事务内,对数据的一些约束。
    Availability:可用性,关注的在某个结点的数据是否可用,可以认为某一个节点的系统是否可用,通信故障除外。
    Partition Tolerance:分区容忍性,是否可以对数据进行分区。这是考虑到性能和可伸缩性。

待续。。。

【参考文档】
https://blog.csdn.net/g1607058603/article/details/81462162

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值