文档章节

一分钱引发的系统设计“踩坑”案例

2019在路上
 2019在路上
发布于 2018/01/16 22:43
字数 2120
阅读 52
收藏 1

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

摘要:阿里妹导读:阿里巴巴的电商业务十分复杂,一方面是市场多样化,业务多样化,另外是消费者,商家的影响面非常广,任何一个小故障都可能引发一些社会问题,所以阿里对产品的质量,对服务的连续性有严格的要求。阿里技术人员在日常的研发运维过程中,积累了丰富的实战经验。

阿里妹导读:阿里巴巴的电商业务十分复杂,一方面是市场多样化,业务多样化,另外是消费者,商家的影响面非常广,任何一个小故障都可能引发一些社会问题,所以阿里对产品的质量,对服务的连续性有严格的要求。阿里技术人员在日常的研发运维过程中,积累了丰富的实战经验。今天,阿里妹将为大家分享一个关于故障,排查,分析和改进的真实案例。他山之石可以攻玉,希望对广大开发和运维工程师带来帮助。

背景说明

某日,做产品X的开发接到客户公司电话,说是对账出了1分钱的差错,无法处理。本着“客户第一”的宗旨,开发立马上线查看情况。查完发现,按照产品X当日的年化收益率,正常情况下用户在转入57元后一共收益3分钱,合计是57.03元。但是该客户当日却有一笔消费57.04元,导致客户公司系统对多出的1分钱处理不了。再进一步分析,发现用户收益结转时多了1分钱的收益,并且已消费……

也就是说,本来用户只有3分钱收益,结果多发了1分钱给他,也就给公司造成1分钱的损失!用户在产品X里当天收益本应该是0.03元,怎么会变成0.04元呢?多出的1分钱收益从哪里来的呢?

数据库记录分析

带着上面的一系列疑问,开发人员首先排查了产品X收益的数据库记录。通过查询数据库发现,该用户收益结转在同一天内存在2笔交易记录。交易记录1创建时间为8:00:23,记录2创建时间为8:00:29,交易记录1和2的最后修改时间均为8:00:29,如图4-1所示。

正常情况下产品X收益每天只会结转一次,而这个用户当日有两笔收益结转记录。开发人员怀疑,很可能是出现了并发问题。

继续跟踪第一笔“TXID a”的记录,开发确认线上日志存在超时情况,失败原因是数据库链接数已满,线程等待提交。

分布式锁超时时间是5s,第一笔记录从创建到修改提交经历了6s,由此可见是在分布式锁失效之后,获得了数据库链接,进行提交成功。

有了以上三个排查思路后,我们可以开始逆推整个过程。

过程逆推

根据数据库记录逆推当时的运行情况,如图4-2所示。

(1)由于数据库连接数被占满,流水1创建的事务处于等待提交状态。

(2)系统A发现交易失败,重试次数不满8次的,立即发起重试,触发生成流水2的请求。

(3)5s以内数据均被分布式锁拦截,无法提交。

(4)经过5s后,系统B的分布式锁失效,此时事务仍在等待未提交。

(5)6s时,流水2成功越过数据库查询幂等校验发起事务,此时流水1拿到数据库连接,流水1和2两个事务同时提交。

(6)由于数据库未做唯一索引,且支付受理模块打穿下层幂等原则,生成2个TXID,导致两事务同时提交成功。

(7)收益结转重复记账,用户多了一笔收入。

深入分析

完成了整个问题的过程逆推后,开发人员进一步分析,发现问题真正的原因还是在系统设计上。如图4-3所示,系统A的事务允许一定时间的等待,而上层业务的重试时间又比这个等待的时间要短。这就存在一个问题:系统A的事务还在等待中,业务就又发起了重试。如果是在这个应用场景下(可能业务上对重试要求更高一些),那么对幂等控制的要求就更高了。而仅仅通过一个分布式锁来控制,如果分布式锁的超时时间设置的比事务允许等待的时间短,那么在锁失效之后就一定会同时提交两笔请求。

图4-3 分布式锁超时并发控制时间轴

继续对整个过程抽象化,开发人员得出一个结论:分布式锁在以下条件同时满足的情况下并发控制会被打穿。

(1)上层业务系统层面有重试机制。

(2)业务请求存在一定时间之后提交成功的情况,例如本例中第一次请求在事务等待6s后获得了数据库链接,提交数据库成功。

(3)下游系统缺乏其他有效的幂等控制手段。

思考

了解了问题的来龙去脉后,接下来要怎么解决这类问题呢?我们想了以下几个方案。

(1)调整B系统上的tr和分布式锁超时时间,tr超时调整为10s,分布式锁超时调整为30s。

(2)防止做收益结转产生并发控制幂等,调整了收益结转流水号的生成规则:前8位取X收益结转传入的交易号的前8位,第10位系统版本设置为“9”,最后8位seq取交易号的最后8位,降低问题出现几率。

方案一:调整超时时间

调整超时时间后,业务重试时间与分布式锁有效时间的分布时间轴如图4-4所示,即在事务允许等待后提交成功的时间之外,再进行重试,另外分布式锁在整个阶段均有效,防止提交。

图4-4 分布式锁超时并发控制时间轴

方案一验证有效。

方案二:增加幂等控制(推荐)

如图4-5所示,单纯靠分布式锁不是控制并发幂等的方式,最稳妥的方式还是在提交记录的时候通过数据库严格控制幂等。确保不论如何设置超时时间,都不会出现幂等控制的问题。

图4-5 分布式锁超时并发控制时间轴

方案二验证有效。

小结

资金安全无小事,而幂等控制又是资金安全中的重中之重。回顾本文案例,从问题分析定位,到整个逻辑的梳理清洗,其中涉及了三个时间轴的相互作用,再加上事务、分布式锁、重试等,整个问题发生的逻辑还是比较复杂的。因此,在系统并发幂等控制设计中,单纯的分布式锁并不具备严格控制并发幂等的作用,建议在系统设计时,将第三方唯一性的幂等控制作为幂等控制的兜底方案,控制好这道幂等防线,这样不论业务如何设计,就万变不离其宗了。



作者:阿里云云栖社区
链接:https://www.jianshu.com/p/ae8a69185a5f
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

 

本文转载自:https://www.jianshu.com/p/ae8a69185a5f

2019在路上
粉丝 44
博文 147
码字总数 77205
作品 0
杭州
高级程序员
私信 提问
如何高效排查系统故障?一分钱引发的系统设计“踩坑”案例

摘要:阿里妹导读:阿里巴巴的电商业务十分复杂,一方面是市场多样化,业务多样化,另外是消费者,商家的影响面非常广,任何一个小故障都可能引发一些社会问题,所以阿里对产品的质量,对服务...

阿里云云栖社区
2017/11/28
0
0
Bug是一种财富-------研发同学的错题集、测试同学的遗漏用例集

此文已由作者王晓明授权网易云社区发布。 欢迎访问网易云社区,了解更多网易技术产品运营经验。 各位看官,可能看到标题的你一定认为这是一篇涉嫌“炒作”的文章,亦或是为了吸引眼球而起的标...

网易云
2018/10/18
0
0
再现一分钱中标,中国电信拿下海南政务云项目

继今年4月以0.01元中标原预算893万元的政务项目之后,中国电信又一次以一分钱中标政务项目。 6月7日,中国海南政府采购网发布中标公告,中国电信集团系统集成有限责任公司以一分钱中标海南政...

玄学酱
2018/03/27
0
0
Python 踩坑之旅进程篇其五打不开的文件

代码示例支持 平台: Centos 6.3 Python: 2.7.14 代码示例: 菜单 - Python踩坑指南代码示例 1.1 踩坑案例 长期运行的daemon进程或者socket测试类进程, 经常遇到的坑是: IOError: [Errno 24] T...

急速奔跑中的蜗牛
07/03
0
0
程序员的老毛病,到底什么时候才能改?

程序员经常会被说成是“不善与人沟通”的群体,与程序以外的事务打交道,稍不留神就踩了“情商低”的坑……这不,前两天,#如何与程序员沟通#居然成了微博热搜话题,引发了许多跟程序员打交道...

助你双赢的
2018/11/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

JVM性能调优的6大步骤,及关键调优参数详解

JVM内存调优 对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数。 1.Full GC 会对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比较慢,...

一只会编程的狼
17分钟前
6
0
并发和并行性有什么区别?

并发和并行性有什么区别? 示例被赞赏。 #1楼 并发性:具有共享资源潜力的多个执行流 例如:两个线程争用一个I / O端口。 平行主义:将问题分成多个相似的块。 例如:通过在文件的每半部分上...

javail
19分钟前
4
0
(推荐使用)提高开发效率工具集合

提高开发效率工具集合(推荐使用) 一、Hutool工具类 官网地址:https://www.hutool.cn/ Github地址:https://github.com/looly/hutool/ Gitee 地址:https://gitee.com/loolly/hutool/ 文档参...

明德先生
23分钟前
4
0
java并发-缓存一致性协议和内存屏障的思考和理解

################这是之前的思考 内存屏障只是保证清空流水线,如何保证高速缓存的内容更新到最新或刷新到主存呢?这个问题突然想到了,不知道这个需要怎回答。 内存屏障保证的CPU执行执行序...

萧默
26分钟前
5
0
类型名称后面的括号是否与new有所不同?

如果“测试”是普通类,则之间是否有任何区别: Test* test = new Test; 和 Test* test = new Test(); #1楼 new Thing(); 很明显,您想要一个构造函数,而new Thing; 表示您不介意是否未调...

技术盛宴
50分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部