真实案例引起的对系统健壮性的思考
大年初四(2012年1月26日)上午,我在重慶移動某營業(yè)廳的自助客戶端使用招商銀行信用卡為我妻子充話費(fèi)(我妻子的手機(jī)已經(jīng)停機(jī))。在插入信用卡并輸入密碼后,系統(tǒng)提示正在交易。大約幾秒后,我的手機(jī)收到招行的短信,提示消費(fèi)100元,但自助客戶端仍然顯示正在交易。此時的我已經(jīng)有了不詳?shù)念A(yù)感。果然,在等待大約一分鐘,系統(tǒng)提示操作失敗,之后系統(tǒng)崩潰,彈出了一個Windows命令窗口。因?yàn)槲移拮拥氖謾C(jī)停機(jī)了,所以立刻可以確認(rèn)上一次充值確實(shí)是失敗的。而我的手機(jī)能收到信用卡的消費(fèi)信息,則可以確認(rèn)銀行確實(shí)已經(jīng)支付了100元(我之后查詢了我的信用卡賬單,確實(shí)存在這一筆消費(fèi)記錄)。
之后,我又用現(xiàn)金充值話費(fèi),此次操作成功了,但卻顯示贈送的3元話費(fèi)失敗,提示相同Id的贈送記錄沖突。這是因?yàn)橄到y(tǒng)規(guī)定了約束,要求當(dāng)月同一個號碼只能享受一次優(yōu)惠。但問題是,我之前用信用卡的充值并未成功,也未收到贈送的話費(fèi)。
我沒有機(jī)會能夠看到該自助充值系統(tǒng)的設(shè)計(jì)與代碼,但以我的開發(fā)經(jīng)驗(yàn),可以直觀感受到這是事務(wù)出現(xiàn)了問題。這個簡單的充值操作,實(shí)際上完成了四個職責(zé):
1)調(diào)用第三方的銀行支付服務(wù),付費(fèi);
2)獲取優(yōu)惠策略,并計(jì)算優(yōu)惠金額;
3)保存優(yōu)惠記錄,便于滿足優(yōu)惠的約束條件;
4)充值(包括付費(fèi)金額+優(yōu)惠金額);
顯然,這四個操作必須放在一個事務(wù)范圍內(nèi),并遵循ACID原則中的一致性原則。由于在該操作中,至少對第三方銀行支付服務(wù)的調(diào)用是跨系統(tǒng)跨資源的,因此,事務(wù)必須是分布式事務(wù)。目前看到的系統(tǒng)問題,顯然是在充值時,系統(tǒng)出現(xiàn)了故障,卻未能將前面的兩個操作回滾,導(dǎo)致執(zhí)行結(jié)果不一致。結(jié)果,我悲催了:銀行扣了款,優(yōu)惠沒落著,費(fèi)用沒充上。
從直觀表現(xiàn)看,我甚至有理由懷疑,對于充值的整個操作,系統(tǒng)是否使用了事務(wù)??因?yàn)?#xff0c;倘若將這四個不同的操作作為一個服務(wù)放在事務(wù)中時,應(yīng)該不會在系統(tǒng)提示正在交易時,銀行就扣款成功。不過,考慮到對于這樣的業(yè)務(wù)需求,使用事務(wù)基本上已經(jīng)成為了常識,這個系統(tǒng)的設(shè)計(jì)者或者實(shí)現(xiàn)者應(yīng)該不會犯如此低級的錯誤,那我只能善良地認(rèn)為,該系統(tǒng)沒有能夠很好或正確地使用分布式事務(wù)。
我不知道,該系統(tǒng)是基于什么平臺開發(fā),是使用了.NET的DTC,還是Java的JTA。然而基于分布式事務(wù)的基本原理來看,這是一種將對多個資源和服務(wù)的訪問放在同一個事務(wù)中的情況。此外,系統(tǒng)調(diào)用的第三方銀行支付服務(wù)必然也是使用了事務(wù)的,它會作為整個分布式事務(wù)的事務(wù)提交樹中的子節(jié)點(diǎn),而支付服務(wù)的事務(wù)則為根事務(wù),是整個事務(wù)的總體協(xié)調(diào)者。由于訪問的資源并不相同,即使各個操作放在了自己的事務(wù)中,也無法保證滿足ACID,因此,這里應(yīng)該使用兩端式提交(two-phase commit)。
重慶移動在提出需求時,必然首先考慮自身的利益,因而系統(tǒng)充值服務(wù)包含的四個操作,其執(zhí)行順序必然是:首先調(diào)用第三方銀行支付服務(wù),如果成功,再獲取優(yōu)惠策略,并獲得優(yōu)惠金額;然后充值(從故障表現(xiàn)看,似乎記錄優(yōu)惠信息的操作卻在充值操作之前)。考慮簡單的情況,假設(shè)后三個操作訪問的是同一個資源(主要應(yīng)該是數(shù)據(jù)庫)。那么支付服務(wù)事務(wù)作為根節(jié)點(diǎn),應(yīng)該協(xié)調(diào)銀行付費(fèi)服務(wù)事務(wù)和充值事務(wù)的投票結(jié)果,然后再決定是否提交。當(dāng)所有的參與者表示Prepare,才會提交。而在提交過程中如果出現(xiàn)問題,就必須回滾事務(wù)。從故障表現(xiàn)來看,似乎該事務(wù)并未采用兩段式提交,因?yàn)樗鼪]有協(xié)調(diào)投票結(jié)果的過程(因?yàn)槲业氖謾C(jī)首先收到了銀行的消費(fèi)信息,優(yōu)惠記錄也保存了,否則不會出現(xiàn)優(yōu)惠記錄ID沖突)。此外,故障出現(xiàn)時,系統(tǒng)一直顯示“正在處理……”字樣,并在約1分鐘左右提示故障。這說明系統(tǒng)可能考慮了Timeout值的設(shè)置。如果采用了兩段式提交,在各個參與者準(zhǔn)備就緒后,如果出現(xiàn)了問題,就應(yīng)該存在未決(In-Doubt)事務(wù),在規(guī)定時間內(nèi)沒有解決,分布式事務(wù)會中止整個事務(wù),并回滾。
所以,我在這里有理由相信該系統(tǒng)即使使用了事務(wù),也沒有很好地用好事務(wù),尤其是分布式事務(wù)。在這里,第三方銀行服務(wù)是沒有任何問題的,它自身的事務(wù)必然是完整的,但此時它作為整個事務(wù)的參與者,是事務(wù)提交樹的子節(jié)點(diǎn),卻沒有被很好地協(xié)調(diào)。
當(dāng)故障出現(xiàn)后,系統(tǒng)在提示“操作失敗”后的表現(xiàn)是崩潰,而不是回到主界面,這也說明了系統(tǒng)連基本的異常處理也可能沒有做好。
這里,事實(shí)上還存在一個小插曲。那就是在我詢問了營業(yè)廳的營業(yè)員后,該營業(yè)員打開機(jī)器,查詢了日志文件夾下的交易日志,并沒有查詢到我的充值記錄。后來,她才醒悟,說道自助客戶端并不會記錄銀行卡充值的交易信息。這讓我倍感納悶。雖然現(xiàn)金充值和銀行卡充值是兩種不同的充值方式,但從抽象層面來看,它們的行為是完全是一致的,充值方式不過是充值策略的兩種體現(xiàn)罷了。從設(shè)計(jì)的角度來看,這是一個典型的Template Method模式。因?yàn)閷τ贑harge操作來看,除了支付的實(shí)現(xiàn)不同之外,其余操作包括充值、獲得優(yōu)惠策略并計(jì)算優(yōu)惠金額、事務(wù)處理、異常處理、資源管理以及日志記錄,都應(yīng)該是完全相同的,為何不能在系統(tǒng)中統(tǒng)一處理呢?顯然,它應(yīng)該是定義在應(yīng)用服務(wù)層的一個統(tǒng)一服務(wù)接口(它可以是一個抽象類,也可以作為接口,并另外定義一個抽象類實(shí)現(xiàn)它,并實(shí)現(xiàn)共同的邏輯),并提供Cash和Card的兩個實(shí)現(xiàn)類。系統(tǒng)會根據(jù)輸入實(shí)例化不同的實(shí)現(xiàn)類。
而對于系統(tǒng)維護(hù)而言,日志本身就是必不可少的信息(也許系統(tǒng)內(nèi)部有日志可供維護(hù)人員查詢,但日志可以是分級的,以便于不同角色根據(jù)需要對日志進(jìn)行查詢)。對于這種涉及到金錢交易的業(yè)務(wù),日志記錄更顯得重要,因?yàn)樗軌驕p少很多消費(fèi)糾紛。這樣的設(shè)計(jì)真的讓我很不解。不錯,我沒有看到系統(tǒng)的實(shí)現(xiàn)(我很有興趣能夠看看這個系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn),可惜沒有這個機(jī)會),但根據(jù)這些故障表現(xiàn),確實(shí)可以分析得到,這樣的系統(tǒng)沒有很好地保障系統(tǒng)的健壯性。這個真實(shí)案例,很可以值得我們軟件從業(yè)人員深思。
轉(zhuǎn)載于:https://www.cnblogs.com/wayfarer/archive/2012/02/07/2340781.html
總結(jié)
以上是生活随笔為你收集整理的真实案例引起的对系统健壮性的思考的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 百度搜索结果URL参数含义解析
- 下一篇: 定义根目录, window格式 转化为l