代码改变世界

从银行ATM机取款失败说起

2005-02-05 23:49 FantasySoft 阅读(...) 评论(...) 编辑 收藏
        昨天晚上,我到中国银行的一台ATM机上取钱。插卡,输入密码,接着可供选择的操作出现:人民币提款,查询余额以及退卡。因为人民币提款选项的出现,所以我可以得知该ATM机是有现金可以提取的。于是很自然的,我先查询了余额,然后根据余额取相应的钱。当我选择了人民币提款,输入需要提取的金额之后,ATM机似乎开始罢工了,因为半晌都没有什么反应。大约两分钟左右,屏幕赫然显示出“通讯故障,操作无法执行”等字样,接着是一张单据被打印出来和退卡。我开始抱怨这糟糕网络,但是问题远没有那么简单。
         由于单据上打印出了我的提款金额,于是我想是不是我需要提取的金额超过了ATM机内现金的总数呢?但是,我很快就否定了这个推断,因为提取金额和ATM机内现金总额的比较是不需要去跟Server进行交互的,何来通讯故障呢?当时并没有继续往下想,就将卡再次插入ATM机,输入密码,接着可供选择的操作出现。这时我很惊奇的发现,人民币提款这一项已经消失了!让我更惊讶的是,我无法查询到自己帐户的余额!小屏幕显示着是ATM机的故障!然后,这个提示似乎是骗人的,因为当我的卡退出之后,Welcome的界面再次显示出来了。但是整台ATM机这时就是一台废物,因为其他人也无法从这里进行任何的操作,无法查询余额,更无法提款了。随后,我去其他银行,包括农业银行以及工商银行去查询卡上余额,但是都提示说发卡行无法进行操作。这时,我已经完全没有了办法,我根本无法知道钱是不是真的被扣掉了。到了今天,我再次到中国银行的ATM机上查询余额,发现钱已经少了!接着,我平生第一次投诉银行——填写了一份申诉表格。当询问柜台营业员需要多久才能将钱返还到我的帐户上,得到的答复是,需要3个以上工作日,由于是春节长假,最快也要到节后了。实在是太郁闷了!
        郁闷过后,我开始想,这样严重的错误是由于什么造成的呢?是否可以避免呢?以下是我自己的一些想法,受心情影响,可能会比较混乱,还请多多指教了。
        第一、这到底是一个怎样的错误呢?个人认为这是事务完整性的错误。用户取钱可以细分为几个步骤:用户输入取款金额 -> 检查输入是否合法,即客户端的校验。如是否为100的倍数以及是否超过一次取款的限度等 -> 检查用户帐户余额是否大于取款金额 -> ATM机出钱并且更新用户帐户余额 -> 用户将钱取走。在用户输入取款金额到用户将钱取走之间,能称得上事务的就是ATM机出钱并且更新用户帐户余额了。这两个操作本身是异步的,却是一个整体,也就是说其中一个操作失败了,另外一个操作就应该回退。而我的遭遇则是,ATM机没有出钱,但是余额却被更新了。
         第二、这个错误固然可以看作事务完整性发生了问题,但是这个事务却跟一般的事务有所不同。通常,我们将对数据库多张表的连续且有关联的更新看作一个事务,在这一连续的更新当中,只要有一个失败,则所有都失败。这样的事务还是比较容易实现的,毕竟所操作的是同一个数据库中的不同的表而已。但是在取款这个所谓的事务中,操作的对象则是不同的,一个是ATM机里装着的钱,一个则是用户的数据。根据事务的完整性,也就是说ATM机出了钱,但是用户帐户余额没有更新成功,就应该将钱从用户手上抢回来,但是,这样可能吗?因此,这个事务由于其操作对象的不同,其完整性的实现就存在很大的困难了。从保护银行本身利益方面去看,可以猜测银行的服务器应该是先更新用户帐户余额,接着向ATM机发送出钱的请求,然后ATM机再向服务器发送一个操作码,服务器再才真正更新数据表。
         第三、从第二点最后的猜测来看,ATM机没有出钱,就应该发送一个失败的操作码给服务器,然后服务器根据这个失败的操作码将用户帐户的更新回退,这样就不应该发生我所遭遇到的情况了。那么到底是什么导致了帐户的数据最终被修改了呢?我想所有的责任都应该是由这台ATM机来承担了。如果真的仅仅是ATM机的问题,或许问题就好解决了。但是,当我换了另外一台ATM机的时候,我无法查到自己的帐户余额。这意味着什么呢?我能够想到的,是数据表上了锁。我的帐户余额这个记录上了读跟写的锁。但是为什么要加锁呢?是不是连服务器本身也不清楚此时用户的余额到底是取款前还是取款后的呢?
         第四、最后我得出了一个结论,正是由于ATM机本身承载了太多的责任,不是一个真正的瘦客户端,直接导致了错误的发生。同时,我也觉得中国银行本身的程序如我现在的思路一样混乱。由混乱引发的程序的正确性与健壮性问题,着实让人汗颜啊!
        先说到这里了,希望这样的一个案例可以给大家一些别样的思考。