鉴于一个boy 发生支付宝提现事件,引发思考。支付宝提现成功,银行卡没查到到账。数据库不一致性问题··········

事务分为本地事务和分布式事务

本地事务

数据库事务四大特征:原子性(A),一致性(C),隔离性(I)和持久性(D),而在这四大特性中,一致性是最基本的特性,其它的三个特性都为了保证一致性而存在的

A账户给B账户转账100元(A、B处于同一个库中),如果A的账户发生扣款,B的账户却没有到账,这就出现了数据的不一致!为了保证数据的一致性,数据库的事务机制会让A账户扣款和B在账户到账的两个操作要么同时成功,如果有一个操作失败,则多个操作同时回滚,这就是事务的原子性,为了保证事务操作的原子性,就必须实现基于日志的REDO/UNDO机制。
但是,仅有原子性还不够,因为我们的系统是运行在多线程环境下,如果多个事务并行,即使保证了每一个事务的原子性,仍然会出现数据不一致的情况。
例如A账户原来有200元的余额, A账户给B账户转账100元,先读取A账户的余额,然后在这个值上减去100元,但是在这两个操作之间,A账户又给C账户转账100元,那么最后的结果应该是A减去了200元。但事实上,A账户给B账户最终完成转账后,A账户只减掉了100元,因为A账户向C账户转账减掉的100元被覆盖了!所以为了保证并发情况下的一致性,又引入的隔离性,即多个事务并发执行后的状态,和它们串行执行后的状态是等价的!隔离性又有多种隔离级别,为了实现隔离性(最终都是为了保证一致性)数据库又引入了悲观锁、乐观锁等等……本文的主题是分布式事务,所以本地事务就只是简单回顾一下,需要记住的一点是,事务是为了保证数据的一致性

事务隔离性理解:H1:当A转给B 100 之后,H2:A又转给C 100,但是在H2未提交之前,A查询到的余额还是H1,所以A此时余额只减少了100元,与实际应该减少200元,并发情况下数据不一致。
如下图是我自己的理解

分布式理论

学习一下别人的经历,警惕自己。
该经历来源 https://www.cnblogs.com/sujing/p/11006424.html
领导给我的第一个任务就是在列表上增加一个修改数据的功能。这能难倒我?我分分钟给你搞出来!不就是在列表上增加了一个“修改”按钮,点击按钮弹出框修改后保存就好了么。然而一切不像我想象的那么顺利,点击保存并刷新列表后,页面上的数据还是显示的修改之前的内容,像没有修改成功一样!过一会儿再刷新列表,数据就能正常显示了!测试多次之后都是这样!没见过什么大场面的我开始有点慌了,是我哪里写得不对么?最终,我不得不求助组内经验比较丰富的前辈!他深吸了一口气告诉我说:“毕竟是刚毕业的小伙子啊!我来跟你讲讲原因吧!我们的数据库是做了读写分离的,部分读库与写库在不同的网络分区。你的数据更新到了写库,而读数据的时候是从读库读取的。更新到写库的数据同步到读库是有一定的延迟的,也就是说读库与写库会有短暂的数据不一致”! “这样不会体验不好么?为什么不能做到写入的数据立马能读出来?那我这个功能该怎么实现呢?” 面对我的一堆问题,同事有些不耐烦的说:“听说过CAP理论吗?你先自己去了解一下吧”!

看了别人的经历,接下来属于我自己的探索了

1.CAP理论

CAP理论指的是一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)

好了,那如何理解CAP理论?
声明:链接:https://www.zhihu.com/question/54105974/answer/139037688
一个分布式系统里面,节点组成的网络本来应该是连通的。然而可能因为一些故障,使得有些节点之间不连通了,整个网络就分成了几块区域。数据就散布在了这些不连通的区域中。这就叫分区。当你一个数据项只在一个节点中保存,那么分区出现后,和这个节点不连通的部分就访问不到这个数据了。这时分区就是无法容忍的。提高分区容忍性的办法就是一个数据项复制到多个节点上,那么出现分区之后,这一数据项就可能分布到各个区里。容忍性就提高了。然而,要把数据复制到多个节点,就会带来一致性的问题,就是多个节点上面的数据可能是不一致的。要保证一致,每次写操作就都要等待全部节点写成功,而这等待又会带来可用性的问题。总的来说就是,数据存在的节点越多,分区容忍性越高,但要复制更新的数据就越多,一致性就越难保证。为了保证一致性,更新所有节点数据所需要的时间就越长,可用性就会降低。

如何理解P?
假设有三个节点ABC,还有一个你的客户端节点D,原先D和ABC都在一个网络中,你的D可以访问ABC,某时刻C节点因为网线断了还是啥原因反正和ABD的网络断开了,ABD是一个网络分区,C自己独立为一个网络分区,此时你的D客户端就访问不到C节点的数据了,这就是分区容忍性差,如果C节点有一个复制节点C1,他没有和大部队ABD断开网络连接,那么此时分区容忍性就相较之前较好。

分布式事务

与本地事务不同的是,分布式事务需要保证的是分布式环境下,不同数据库表中的数据的一致性问题。分布式事务的解决方案有多种,如XA协议、TCC三阶段提交、基于消息队列等等,本文只会涉及基于消息队列的解决方案。

本地事务讲到了一致性,分布式事务不可避免的面临着一致性的问题!回到最开始跨行转账的例子,如果A银行用户向B银行用户转账,正常流程应该是

1、A银行对转出账户执行检查校验,进行金额扣减。
2、A银行同步调用B银行转账接口。
3、B银行对转入账户进行检查校验,进行金额增加。
4、B银行返回处理结果给A银行。

在正常情况对一致性要求不高的场景,这样的设计是可以满足需求的。但是像银行这样的系统,如果这样实现大概早就破产了吧。我们先看看这样的设计最主要的问题:

1、同步调用远程接口,如果接口比较耗时,会导致主线程阻塞时间较长。
2、流量不能很好控制,A银行系统的流量高峰可能压垮B银行系统(当然B银行肯定会有自己的限流机制)。
3、如果“第1步”刚执行完,系统由于某种原因宕机了,那会导致A银行账户扣款了,但是B银行没有收到接口的调用,这就出现了两个系统数据的不一致。
4、如果在执行“第3步”后,B银行由于某种原因宕机了而无法正确回应请求(实际上转账操作在B银行系统已经执行且入库),这时候A银行等待接口响应会异常,误以为转账失败而回滚“第1步”操作,这也会出现了两个系统数据的不一致。
   对于问题的1、2都很好解决,如果对消息队列熟悉的朋友应该很快能想到可以引入消息中间件进行异步和削峰处理,于是又重新设计了一个方案,流程如下:
   >1、A银行对账户进行检查校验,进行金额扣减。
2、将对B银行的请求异步写入队列,主线程返回。
3、启动后台程序从队列获取待处理数据。
4、后台程序对B银行接口进行远程调用。
5、B银行对转入账户进行检查校验,进行金额增加。
6、B银行处理完成回调A银行接口通知处理结果。


  通过上面的图我们能看到,引入消息队列后,系统的复杂性瞬间提升了,虽然弥补了我们第一种方案的几个不足点,但也带来了更多的问题,比如消息队列系统本身的可用性、消息队列的延迟等等!并且,这样的设计依然没有解决我们面临的核心问题-数据的一致性!

1、如果“第1步”刚执行完,系统由于某种原因宕机了,那会导致A银行账户扣款了,但是写入消息队列失败,无法进行B银行接口调用,从而导致数据不一致。
2、如果B银行在执行“第5步”时由于校验失败而未能成功转账,在回调A银行接口通知回滚时网络异常或者宕机,会导致A银行转账无法完成回滚,从而导致数据不一致。

面对上述问题,我们不得不对系统再次进行升级改造。为了解决“A银行账户扣款了,但是写入消息队列失败”的问题,我们需要借助一个转账日志表,或者叫转账流水表,该表简单的设计如下:


  这个流水表需要怎么用呢?我们在“第1步”进行扣款时,同时往流水表写入一条操作流水,状态为“待处理”,并且这两个操作必须是原子的,也就是说必须通过本地事务保证这两个操作要么同时成功,要么同时失败!这就保证了只要转账扣款成功,必定会记录一条状态为“待处理”的转账流水。如果在这一步失败了,那自然就是转账失败,没有后续操作了。如果这步操作后系统宕机了导致没有将消息成功写入消息队列(也就是“第2步”)也没关系,因为我们的流水数据已经持久化了!这时候我们只需要加入一个后台线程进行补偿,定期的从转账流水表中读取状态为“待处理”且最后更新的时间距当前时间大于某个阈值的数据,重新放入消息队列进行补偿。这样,就保证了消息即使丢失,也会有补偿机制!B银行在处理完转账请求后会回调A银行的接口通知转账的状态,从而更新A银行流水表中的状态字段!这样就完美解决了上一个方案中的两个不足点。系统设计图如下:

  到目前为止,我们很好的解决了消息丢失的问题,保证了只要A银行转账操作成功,转账的请求就一定能发送到B银行!但是该方案又引入了一个问题,通过后台线程轮询将消息放入消息队列处理,同一次转账请求可能会出现多次放入消息队列而多次消费的情况,这样B银行会对同一转账多次处理导致数据出现不一致!那怎么保证B银行转账接口的幂等性呢?

同样的,我们可以在B银行系统中需要增加一个转账日志表,或者叫转账流水表,B银行每次接收到转账请求,在对账户进行操作的时候同时往转账日志表中插入一条转账日志记录,同样这两个操作也必须是原子的!在接收到转账请求后,首先根据唯一转账流水Id在日志表中查找判断该转账是否已经处理过,如果未处理过则进行处理,否则直接回调返回! 最终的架构图如下:
  
所以,我们这里最核心的就是A银行通过本地事务保证日志记录+后台线程轮询保证消息不丢失。B银行通过本地事务保证日志记录从而保证消息不重复消费!B银行在回调A银行的接口时会通知处理结果,如果转账失败,A银行会根据处理结果进行回滚。

本文综合csdn文章、知乎、百度学习与理解,进行总结。欢迎小伙伴一起学习!

转账引发数据一致性思考相关推荐

  1. 腾讯「小借条」引发的思考:区块链+的商业模式让各企业争先恐后的奥秘

    区块链+又再一次被推到我们面前. 原文标题:<区块链+万物?腾讯"小借条"引发的思考>,作者陈丽姗 Key Takeaways: 腾讯区块链新品"小借条&qu ...

  2. JAVA-OPTS引发的思考

    JAVA-OPTS引发的思考 我们在性能测试过程中,经常要修改应用的JAVA-OPTS参数.修改这些参数,不单单是修改这些数字,本着知其所以然的态度,我们要知道这些参数背后的意义. 常见的JAVA-O ...

  3. 一个小程序引发的思考

    既然是一个小程序引发的思考,那么我们就先看看这个小程序,看看他有何神奇之处: namespace ConsoleApplication1 {class Program{static void Main ...

  4. 由“递归遍历二叉树”引发的思考

    由"递归遍历二叉树"引发的思考 递归的用法我感觉自己已经掌握了,可是今天在递归遍历二叉树时,它又迷一样令我费解.我本人在算法这一块很薄弱,所以想把这个问题发出来,希望有大神能够为我 ...

  5. 由一行文本输入框引发的思考

      文章是关于React组件之表单单行文本输入框的一些思考.可能大家第一反应都是,不就是一行<input/>嘛,没什么特别的吧?如果说到输入框的值的话,可能圈子里上大多数封装好的React ...

  6. 由熊猫烧香引发的思考

    由熊猫烧香引发的思考 早期的计算机病毒诞生,作者是以技术炫耀为主,编写这类病毒需要更深入的了解系统及网络技术,开发功底也要更深厚.现阶段,一个合格的程序员,制造出具备破坏性的代码,已经不怎么难.甚至, ...

  7. Spring之LoadTimeWeaver——一个需求引发的思考---转

    原文地址:http://www.myexception.cn/software-architecture-design/602651.html Spring之LoadTimeWeaver--一个需求引 ...

  8. 由SecureCRT引发的思考和学习

    由SecureCRT引发的思考和学习 http://mp.weixin.qq.com/s?__biz=MzAxOTAzMDEwMA==&mid=2652500597&idx=1& ...

  9. C语言中递归什么时候能够省略return引发的思考:通过内联汇编解读C语言函数return的本质...

    C语言中递归什么时候能够省略return引发的思考:通过内联汇编解读C语言函数return的本质 事情的经过是这种,博主在用C写一个简单的业务时使用递归,因为粗心而忘了写return.结果发现返回的结 ...

  10. 由「Metaspace容量不足触发CMS GC」从而引发的思考

    转载自  由「Metaspace容量不足触发CMS GC」从而引发的思考 某天早上,毛老师在群里问「cat 上怎么看 gc」. 好好的一个群 看到有 GC 的问题,立马做出小鸡搓手状. 之后毛老师发来 ...

最新文章

  1. 性能优化之Java(Android)代码优化
  2. 基于正交投影的点云局部特征描述详解
  3. button标签设置隐藏和显示_离职后我隐藏一张工作表,老板找了一天没找到
  4. Python机器学习---2.聚类算法理论部分
  5. NSTimer注意内存泄露(真该死)
  6. 软件测试方法的分类细谈
  7. Ubuntu 16.04 安装caffe(CPU)以及编译问题处理
  8. 【nodejs学习】0.nodejs学习第一天
  9. 破解wifi并实施中间人攻击
  10. BitTorrent协议规范(BitTorrent Protocol Specification)之Peer Wire协议(Peer Wire Protocol)-第四部分
  11. 使用Aspose在C#中将PLT转换为PDF或JPEG图像
  12. 10、Linux上常见软件的安装:安装JDK、安装Tomcat、安装Eclipse
  13. C语言中的a = b = c ? d : e;
  14. [书籍翻译]12周撰写期刊文章 学术出版成功指南——第 2 周:开始您的文章
  15. enq: HW - contention
  16. 通过数据库存储过程调用Web服务的办法
  17. Springboot之邮件发送(内附源码)
  18. 54家国内主要百货上市公司上半年营收排行榜
  19. linux进程调度策略和优先,linux进程调度之FIFO和RR调度策略
  20. 什么是互联网资产 如何保护好你的这些资产

热门文章

  1. DirectX SDK 2010 , DXSDK_Jun10.exe, 下载地址
  2. 【概率论与数理统计】目录
  3. winrar 注册码
  4. php如何获取手机序列号,Android应用获取设备序列号的方法
  5. 简洁大气的网站微信QQ防红跳转代码
  6. 谭浩强c语言程序设计作业,谭浩强《C语言程序设计》第7章习题解答(13、14题)...
  7. (自学笔记) 谭浩强 C语言程序设计 第五版 第二章:算法
  8. 复变函数 —— 4. 什么是调和函数
  9. 树莓派添加RTC时钟模块的方法
  10. linux中文变成日文,linux nkf 日文编码转换命令[转载]