【转】真实案例引起的对系统健壮性的思考(张逸,2012-02-07)

大年初四(2012年1月26日)上午,我在重庆移动某营业厅的自助客户端使用招商银行信用卡为我妻子充话费(我妻子的手机已经停机)。在插入信用卡并输入密码后,系统提示正在交易。大约几秒后,我的手机收到招行的短信,提示消费100元,但自助客户端仍然显示正在交易。此时的我已经有了不详的预感。果然,在等待大约一分钟,系统提示操作失败,之后系统崩溃,弹出了一个Windows命令窗口。因为我妻子的手机停机了,所以立刻可以确认上一次充值确实是失败的。而我的手机能收到信用卡的消费信息,则可以确认银行确实已经支付了100元(我之后查询了我的信用卡账单,确实存在这一笔消费记录)。

之后,我又用现金充值话费,此次操作成功了,但却显示赠送的3元话费失败,提示相同Id的赠送记录冲突。这是因为系统规定了约束,要求当月同一个号码只能享受一次优惠。但问题是,我之前用信用卡的充值并未成功,也未收到赠送的话费。

我没有机会能够看到该自助充值系统的设计与代码,但以我的开发经验,可以直观感受到这是事务出现了问题。这个简单的充值操作,实际上完成了四个职责:
1)调用第三方的银行支付服务,付费;
2)获取优惠策略,并计算优惠金额;
3)保存优惠记录,便于满足优惠的约束条件;
4)充值(包括付费金额+优惠金额);

显然,这四个操作必须放在一个事务范围内,并遵循ACID原则中的一致性原则。由于在该操作中,至少对第三方银行支付服务的调用是跨系统跨资源的,因此,事务必须是分布式事务。目前看到的系统问题,显然是在充值时,系统出现了故障,却未能将前面的两个操作回滚,导致执行结果不一致。结果,我悲催了:银行扣了款,优惠没落着,费用没充上。

从直观表现看,我甚至有理由怀疑,对于充值的整个操作,系统是否使用了事务??因为,倘若将这四个不同的操作作为一个服务放在事务中时,应该不会在系统提示正在交易时,银行就扣款成功。不过,考虑到对于这样的业务需求,使用事务基本上已经成为了常识,这个系统的设计者或者实现者应该不会犯如此低级的错误,那我只能善良地认为,该系统没有能够很好或正确地使用分布式事务。

我不知道,该系统是基于什么平台开发,是使用了.NET的DTC,还是Java的JTA。然而基于分布式事务的基本原理来看,这是一种将对多个资源和服务的访问放在同一个事务中的情况。此外,系统调用的第三方银行支付服务必然也是使用了事务的,它会作为整个分布式事务的事务提交树中的子节点,而支付服务的事务则为根事务,是整个事务的总体协调者。由于访问的资源并不相同,即使各个操作放在了自己的事务中,也无法保证满足ACID,因此,这里应该使用两端式提交(two-phase
commit)。

重庆移动在提出需求时,必然首先考虑自身的利益,因而系统充值服务包含的四个操作,其执行顺序必然是:首先调用第三方银行支付服务,如果成功,再获取优惠策略,并获得优惠金额;然后充值(从故障表现看,似乎记录优惠信息的操作却在充值操作之前)。考虑简单的情况,假设后三个操作访问的是同一个资源(主要应该是数据库)。那么支付服务事务作为根节点,应该协调银行付费服务事务和充值事务的投票结果,然后再决定是否提交。当所有的参与者表示Prepare,才会提交。而在提交过程中如果出现问题,就必须回滚事务。从故障表现来看,似乎该事务并未采用两段式提交,因为它没有协调投票结果的过程(因为我的手机首先收到了银行的消费信息,优惠记录也保存了,否则不会出现优惠记录ID冲突)。此外,故障出现时,系统一直显示“正在处理……”字样,并在约1分钟左右提示故障。这说明系统可能考虑了Timeout值的设置。如果采用了两段式提交,在各个参与者准备就绪后,如果出现了问题,就应该存在未决(In-Doubt)事务,在规定时间内没有解决,分布式事务会中止整个事务,并回滚。

所以,我在这里有理由相信该系统即使使用了事务,也没有很好地用好事务,尤其是分布式事务。在这里,第三方银行服务是没有任何问题的,它自身的事务必然是完整的,但此时它作为整个事务的参与者,是事务提交树的子节点,却没有被很好地协调。

当故障出现后,系统在提示“操作失败”后的表现是崩溃,而不是回到主界面,这也说明了系统连基本的异常处理也可能没有做好。

这里,事实上还存在一个小插曲。那就是在我询问了营业厅的营业员后,该营业员打开机器,查询了日志文件夹下的交易日志,并没有查询到我的充值记录。后来,她才醒悟,说道自助客户端并不会记录银行卡充值的交易信息。这让我倍感纳闷。虽然现金充值和银行卡充值是两种不同的充值方式,但从抽象层面来看,它们的行为是完全是一致的,充值方式不过是充值策略的两种体现罢了。从设计的角度来看,这是一个典型的Template

Method模式。因为对于Charge操作来看,除了支付的实现不同之外,其余操作包括充值、获得优惠策略并计算优惠金额、事务处理、异常处理、资源管理以及日志记录,都应该是完全相同的,为何不能在系统中统一处理呢?显然,它应该是定义在应用服务层的一个统一服务接口(它可以是一个抽象类,也可以作为接口,并另外定义一个抽象类实现它,并实现共同的逻辑),并提供Cash和Card的两个实现类。系统会根据输入实例化不同的实现类。

而对于系统维护而言,日志本身就是必不可少的信息(也许系统内部有日志可供维护人员查询,但日志可以是分级的,以便于不同角色根据需要对日志进行查询)。对于这种涉及到金钱交易的业务,日志记录更显得重要,因为它能够减少很多消费纠纷。这样的设计真的让我很不解。不错,我没有看到系统的实现(我很有兴趣能够看看这个系统的设计与实现,可惜没有这个机会),但根据这些故障表现,确实可以分析得到,这样的系统没有很好地保障系统的健壮性。这个真实案例,很可以值得我们软件从业人员深思。

原文地址:https://www.cnblogs.com/stevens0102/p/10343045.html

时间: 2024-08-03 16:05:53

【转】真实案例引起的对系统健壮性的思考(张逸,2012-02-07)的相关文章

真实案例——EMOS邮件集成系统

---恢复内容开始--- 磁盘镜像技术:浪费磁盘空间 校验码机制:比如说有4块盘,留下一块盘作为校验盘,既提升了速度,又提升了冗余. 重新计算的过程是修复的过程. raid0条带 raid1镜像 校验码是系统的瓶颈那怎么办 轮换把盘作为校验码 以防止某个盘老是被读写 这一举措是Raid5与Raid4的区别 RAID简介 是一个控制器上有五个接口,对于计算机来说就相当于一个整体 廉价冗余磁盘阵列(Redunt Arrays of Inexpensive Disks) 通过硬件/软件技术,将较小/低

设计模式--组合模式真实案例

所有的例子均来源与实际开发项目 本节介绍组合模式的使用–商品结果排序评分系统 首先还是重复一下:设计模式是思路,而不是一味套用,如果业务场景和功能需求恰好吻合,那最好不过:如果有偏差,一定要具体情况具体分析,更具实际场景选择合适的模式类型(注意,是类型,并不特定指某种模式,有的时候一个场景多种模式都可以做) 本节所举得例子为商品结果排序评分系统,也就是很多项目中,在比较重要任何事物查询完毕后,会有一个排序过程,比如在淘宝上搜索完商品后那个商品列表的排序过程.而且其复杂度当然远远超过数据中SQL语

ENode 2.0 第一个真实案例剖析-一个简易论坛(Forum)

前言 经过不断的坚持和努力,ENode 2.0的第一个真实案例终于出来了.这个案例是一个简易的论坛,开发这个论坛的初衷是为了验证用ENode框架来开发一个真实项目的可行性.关于ENode是什么,本文就不多介绍了,可以参考这篇文章的介绍.本文重点介绍一下ENode是如何帮助我们开发一个基于DDD+CQRS+Event Sourcing架构的应用程序的.这个论坛使用到了ENode, EQueue两个框架,EQueue是一个分布式的消息队列组件,该组件的主体思想是参考阿里的RocketMQ.当我们使用

一文教会你数据库性能调优(附某大型医院真实案例)

原文:一文教会你数据库性能调优(附某大型医院真实案例) 前言 微软工程师的一个工程师曾经对性能调优有一个非常形象的比喻:剥洋葱 .我也非常认可,让我们来一层一层拨开外面它神秘的面纱. 六大因素 下面祭出的是我们在给客户分析数据库性能问题最常用的图. 看完这个图,你是不是对性能调优有了个基本的概念了.通常来讲我们会依照下面的顺序来进行分析: 硬件能力 系统规模 数据库内部因素 软件环境 这4个的顺序可以有所调整或者交换,但是对于系统的性能优化一定要从全局出发.切勿一来就深入到某一个SQL语句的优化

RestTemplate真实案例

1. 场景描述 现在越来越的系统之间的交互采用http+json的交互方式,以前用的比较多的HttpClient,后来用的RestTemplate,感觉RestTemplate要比httpClent简洁的多,简单介绍下,项目中正在使用的get和post调用方式. 2. 解决方案 2.1 简要说明 RestTemplate是集成在spring-web中的,因为springboot的starter已经默认加载进来,所以可以直接使用不用再配置maven的gav了. 2.2 post调用方式 2.2.1

架构设计经典案例:X窗口系统

X Window在1984年由MIT研发,它的设计哲学之一是:提供机制,而非策略(类似面向对象思想中的"针对接口编程,而不是针对实现编程").机制(mechanism)是指需要提供哪些功能,策略(policy)是指如何实现这些功能.X Window将"提供机制,而非策略"这个哲学贯彻地非常彻底,以致于核心协议基本稳定,不需要特别大的改动.你可能会诧异,30年了,X Window的核心都没有特别大的变化,它能适应现代桌面的快速发展吗?这就要再次提到X Window的设

工厂方法和观察者模式的一个真实案例

1 import java.util.ArrayList; 2 3 class Manager 4 { 5 private String name; //经理的名字 6 public Manager(String string) { 7 name=string; 8 } 9 public void update(String id) 10 { 11 System.out.println("哈哈"+id+"房子已经被买了,好开心"); 12 } 13 14 15 }

性能测试 - 响应 vs 延迟 vs 吞吐量 vs 负载 vs 扩展性 vs 压力 vs 健壮性

本文译自Niraj Bhatt 所著 Performance Testing - Response vs. Latency vs. Throughput vs. Load vs. Scalability vs. Stress vs. Stress vs. Robustness. 原文地址:https://nirajrules.wordpress.com/2009/09/17/measuring-performance-response-vs-latency-vs-throughput-vs-lo

健壮性与可靠性

健壮性(鲁棒性)和可靠性是有区别的,两者对应的英文单词分别是 robustness 和 reliability.健壮性主要描述一个系统对于参数变化的不敏感性,而可靠性主要描述一个系统的正确性,也就是在你固定提供一个参数时,它应该是产生稳定的,能预测的输出.例如一个程序,它的设计目标是获取一个参数并输出一个值.假如它能正确完成这个设计目标,就说它是可靠的.但在这个程序执行完毕后,假如没有正确释放内存,或者说系统没有自动帮它释放占用的资源,就认为这个程序及其"运行时"不具备健壮性或者鲁棒性