日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

面向失敗編程是編程中最難的事情。

話說程序員小林的某一天:起床->吃飯->坐地鐵->到公司->敲代碼->回家->玩游戲->睡覺。

這一天的另一個版本:起床->吃飯->坐地鐵->到公司->突然要 24 小時健康碼->進不了公司->坐地鐵回去->地鐵停運了->上廁所->踩到屎滑倒->摔成腦震蕩。

第二個版本充滿意外,貌似有些極端,但你我天天在新聞上看到類似的事情,說明它其實每天都在發生。

程序也是如此。

程序員小林給公司開發的某個系統,用戶量暴漲;三年后公司上市了,小林喜迎白富美。

另一個版本:上線后第二天被 SQL 注入刪庫了,造成大量投訴;小林被老板痛罵一頓后,卷鋪蓋走人了。

程序的世界充滿意外,你我的每一行代碼幾乎都是 bug。

寫出可用的系統很容易,但寫出健壯的系統很難。


 

一個”簡單“的例子

我們通過儲值卡消費這個例子來看看如此”簡單“的案例到底存在多少讓人眼花繚亂的失敗場景。

假設我們給某個加油站開發個儲值卡系統,用戶可以往里面充錢,可以用儲值卡加油消費,類似你在理發店、洗腳店開的那種充值卡。

我們看看車主加油消費的場景——而且只看這個場景中的”儲值卡扣款“這一個結點。

正常流程(簡化版)大致是這樣的:

編程為什么那么難:從儲值卡扣款說起

 

流程很簡單,加油員加完油后,用戶掏出手機掃碼進入付款頁面,輸入油槍、金額,選儲值卡支付,輸完密碼后點提交;后端創建訂單后調卡服務的扣款接口執行扣款(傳入卡號、訂單號、金額);卡服務扣款成功后返回告知用戶付款成功。

”這個需求大概要幾天開發?“產品經理問小林。

”五天。“小林覺得五天綽綽有余。

”三天吧,這周我們就要上線。“

”那就三天。“小林覺得其實三天足夠——不就一兩個接口調用嘛,卡服務是現成的。

于是小林擼起袖子開始敲代碼。進展比預想得要順利,兩天就敲完了(多少加了點班),一天測試完成,第四天就上線了!

某天夜里,小林正在擼貓時,運營同學打來電話:某車主的卡被莫名其妙扣款了!

事情是這樣的:車主魯某加了 3000 元的油,選擇用儲值卡支付,結果系統提示扣款失敗,于是魯某換微信付款成功,開車走人了。

蹊蹺的是:魯某十分鐘后收到消息說卡扣掉了 3000 元!

明明說支付失敗,怎么扣了 3000?于是魯某打電話找油站鬧。

小林趕緊排查日志,發現上圖中地第 3 步(調卡服務的扣款接口)超時了,于是業務系統告知前端扣款失敗

調卡服務扣款接口超時,業務系統能直接返回失敗給前端嗎?

不能!

因為接口超時并不能說明卡服務那邊實際上到底有沒有扣成功(有可能卡服務處理成功了,但返回的時候網絡出問題;也有可能卡系統負載高,業務系統等待超時從而斷開連接)。

我們看看上面的異常是怎么發生的:

編程為什么那么難:從儲值卡扣款說起

 

第四步超時后,業務后臺直接告知車主支付失敗,但實際上卡系統仍然在扣款!

那怎么辦?告訴車主”請您稍后查看支付結果?“

怎么可能!

一個想法是超時后業務系統調卡服務的查詢接口,看看這筆訂單實際是否支付成功。

問題是,如果查詢接口調用也超時呢(卡系統負載高的情況下這個概率很大)?

另外,查詢接口返回沒有扣款成功就能直接告訴用戶扣款失敗嗎?

不能!

因為查詢接口查數據庫的時候,數據庫里面沒有記錄,但有可能前面發起的那個扣款邏輯仍然在執行,稍后仍然會發生扣款。

既然怕查詢的時候扣款邏輯仍然在執行,那我們能不能等一會(比如五分鐘)再查結果呢(等那個可能的扣款執行流跑完)?

也不能!

因為車主在那等著呢!難道手機上一直在那轉圈,跟車主說現在負載高,請先喝杯茶,讓子彈飛一會?

因為必須要立即告知用戶處理結果,所以這種情況下(扣款超時且未查到扣款記錄)只能告訴用戶扣款失敗。

只不過,在告知用戶之前,業務系統需要先撤銷本次扣款申請,告訴儲值卡系統本次扣款流程不能執行了(回滾本次事務)。

于是小林做了如下優化:

編程為什么那么難:從儲值卡扣款說起

 

現在系統健壯多了,很久沒出現上次的問題了,小林又跑去擼貓了。

某天深夜,小林又接到運營同學電話:上次的問題重現了!

尼瑪,見鬼了!

小林又跑去查日志,發現確實是扣款接口超時了,但撤銷接口調成功了(雖然調了幾次才成功)——那為毛還扣了錢啊?

想了半天,小林終于發現了問題:和前面提到的查詢問題一樣,撤銷的時候同樣無法保證那個該死的扣款流程已經跑完了啊!這次是因為撤銷邏輯確實執行了,但執行的時候扣款邏輯還在跑(還沒寫庫)!

所以撤銷接口必須考慮兩種情況:

  1. 撤銷的時候,扣款已經發生了——此時能正確撤銷;
  2. 撤銷的時候,扣款還沒發生,但扣款流程正在執行——此時撤銷會失敗,稍后錢仍然會被扣掉;

于是小林就想:既然扣款超時后立即調撤銷接口有可能因時序問題導致撤銷失敗,那我把撤銷操作做成異步調度不就行了嘛——在一段時間內(比如五分鐘)如果因未找到記錄而撤銷失敗,就稍后重試。

小林的撤銷邏輯是這樣的:

編程為什么那么難:從儲值卡扣款說起

 

原本由業務系統同步調撤銷接口,現在改成走調度系統異步撤銷,業務系統投遞撤銷任務完成后立馬返回結果給客戶端。

因為有異步重試機制,撤銷總是能成功(除了實際中幾乎不會發生的極端情況),因而這次一定能保證不會意外扣錢!

小林同學抱著如釋重負的心態繼續擼貓。

然而,安穩日子沒過幾天,一個雷電交加的夜晚,手機再次響起:車主儲值卡消費的錢莫名其妙給人家退回去了!油站打電話要我們賠償!

小林趕緊查日志,發現場景是這樣的:車主王某用儲值卡支付 1000 元油款,失敗了;十幾秒后車主再次用儲值卡發起支付,成功了。

支付最終成功了,莫非人工退錢了?沒看到任何退款記錄啊?

抓耳撓腮,百思不得其解。小林只能打電話給儲值卡系統負責人小李。

小李一頓查日志,最終發現這筆錢是被調度系統調撤銷接口給撤銷了!

小林如夢方醒,才知道之前自己自鳴得意地犯了個天大的錯誤。

本次消費,業務系統共向儲值卡系統發起了兩次扣款申請——雖然都是同一筆訂單的扣款,卻是兩個獨立的事務

小林(以及儲值卡系統)的錯誤在于,撤銷操作是作用在訂單上,而不是事務上。

在本次事故中,第一次扣款超時后,業務系統投遞了撤銷任務;而后車主又對該筆訂單(訂單號相同)發起了第二次扣款,成功了;與此同時,調度系統第一次撤銷失敗(卡系統未找到消費記錄,或者接口超時),一段時間后又發起第二次撤銷——而這個時候,車主已經完成了第二次扣款且成功了,于是這次的撤銷便作用在這個成功的扣款上(儲值卡系統的扣款和撤銷接口都是根據訂單號來的,它能保證同一筆訂單不會重復扣款,但撤銷的時候無法區分扣款是哪次發起的)。

我們畫下流程:

編程為什么那么難:從儲值卡扣款說起

 

如圖,第二次的扣款被調度系統撤銷了。

小林和小李這才發現需要給扣款和撤銷接口增加事務編號

之前扣款接口主要參數是 card_no、order_code、amount,現在變成 card_no、order_code、trans_id、amount。

之前撤銷接口參數是 order_code,現在變成 order_code、trans_id。

通過 trans_id 將扣款和撤銷綁定到同一個操作事務上,只會撤銷相應 trans_id 的扣款操作。

trans_id 由客戶端根據當前時間毫秒數生成(后面會說為啥取毫秒時間戳),它不一定需要全局唯一,只需要針對同一個訂單是唯一的即可。

加了事務的概念后,小林和小李發現壓根不需要通過調度系統不斷嘗試,只要保證撤銷接口調成功就能保證對應的扣款事務一定能夠被撤銷(或者阻止執行)。

現在撤銷接口做兩件事:

  1. 寫入一條撤銷記錄;
  2. 試圖撤銷掉已經產生的扣款;

撤銷邏輯如下:

編程為什么那么難:從儲值卡扣款說起

 

再看看扣款的邏輯。

扣款記錄表大致長這樣子:

編程為什么那么難:從儲值卡扣款說起

 

扣款邏輯如下:

  1. 先檢查是否存在該訂單的扣款記錄;
  2. 如果不存在,則走正常扣款流程;如果存在記錄,則要比較事務編號:如果已存在的那條事務編號小于當前的,則用當前的事務編號覆蓋,否則不做任何處理(后面會解釋這么做的原因);

流程圖如下:

編程為什么那么難:從儲值卡扣款說起

 

現在我們看看當撤銷流程執行時,被撤銷的扣款事務處于不同狀態下的情況:

  1. 扣款事務執行失敗。此時壓根不會產生扣款;
  2. 扣款事務已經執行完畢,產生了實際扣款。此時撤銷流程會撤銷掉這筆扣款;
  3. 扣款事務正在執行中,還沒有寫庫,但稍后會寫庫。扣款事務實際寫庫之前,會先檢查是否存在對該事務的撤銷記錄,因為先前撤銷流程已經寫入了一條對該事務的撤銷記錄,扣款事務此時會查到撤銷記錄,從而阻止本次扣款事務寫庫(本次事務主動回滾)。

由于撤銷的時候是按事務編號來的,所以不會撤銷別的事務的扣款。

現在我們解釋下為何要用當前時間的毫秒時間戳作為事務編號。

回到上面車主王某的場景。王某第一次用卡支付超時,于是他決定重試。該場景中,卡系統接收到同一筆訂單的兩次扣款事務以及一次撤銷事務。假如兩次事務都嘗試寫庫,那么當后面的事務(不一定是第二次扣款的那個)嘗試寫庫時,肯定已經存在一條扣款記錄,此時后面這個事務要如何做?

  1. 用后者的事務編號替換掉前者的。
  2. 不做任何處理。

兩次事務的執行邏輯完全相同,產生的扣款記錄數據也是完全相同的——除了事務編號和扣款時間。

這里的關鍵是,我們無法確定第一次扣款、第二次扣款、對第一次扣款的撤銷這三個請求寫庫的先后順序

所以,如果采用方案 1,替換事務編號,那么當第二次的提交先寫庫時,后面事務(第一次提交的扣款請求)的替換會導致事務編號變成了待撤銷的那個,因而很可能會被撤銷掉,這就會導致用戶付的錢莫名其妙被退回了。

如果采用方案 2,不做任何處理,那么當第一次的提交先寫庫時,事務編號就一直是待撤銷的那個,也會被撤銷掉,導致用戶付的錢莫名其妙被退回。

也就是說,無腦替換或不替換都是有問題的。

編程為什么那么難:從儲值卡扣款說起

 

第一次扣款事務先寫庫的情況

編程為什么那么難:從儲值卡扣款說起

 

第二次扣款事務先寫庫的情況

實際的業務場景是,對于同一筆訂單,無論發出多少次扣款請求,只允許一次成功,而且這次成功的扣款不能被誤撤銷。有很多方案可以實現這一點,不過有些方案需要增加額外表,有些則需要為同一筆訂單維護多條扣款記錄,這些都會帶來額外的復雜性。

我們采取事務序列號(毫秒時間戳)的方式來保證扣款事務的時序性,只允許后面覆蓋前面的,不允許反過來覆蓋。其基于這樣的事實:用戶如果對同一筆訂單發出多次扣款請求,那一定是前面扣款失敗了,因而業務系統會為前面那些失敗的扣款發出撤銷請求,所以只要保證僅允許后面覆蓋前面的事務,就不會造成誤撤銷(因為唯有最后那個扣款事務不會存在撤銷請求。感興趣的可參照上面的圖示推演一下)。

這里說的事務是指一次扣款處理流,不是指數據庫事務。


 

所以呢?

我不想編程了,說真的,這么個簡單的扣款場景就扯出這么多幺蛾子,太難了!

現實中比這復雜的場景多得是。

程序員到底是怎么活下來的?

答案是,他們的一生是在沒完沒了的 bug 中度過的。

90% 以上的 bug 都是因為對失敗場景考慮不周導致。

如果把現實看成事件流,那么事件流中的絕大多數節點都有不止一個出口分支(典型的是”正常“和”異常“)。2022 年 4 月 30 日晚,小林同學可能躺在床上玩游戲,也可能躺在 ICU。

系統(特別是業務系統)是對現實世界業務的反映,每個節點同樣存在多種可能。

典型的業務流分析步驟是這樣的:

編程為什么那么難:從儲值卡扣款說起

 

幾乎所有的結點都要考慮失敗場景,而對于一些失敗場景的補償措施仍然可能失敗,如此遞歸,最終由自動補償系統(如漏單檢測/補償系統)或人工處理來兜底。

失敗的一大重要根源是分布式。

不要提什么單體架構,做 web 開發的,自入行第一天起就面對分布式系統。

典型的分布式是前后端交互。自 ajax 出世以來,前后端接口交互成為常態,接口失敗也是每個程序員都會遇到的問題。很大部分的前后端交互失敗的場景沒有得到很好地處理(特別是超時),比如沒有去重,導致重復寫入數據。

自從微服務橫行以來,后端開發人員無不被分布式事務搞得焦頭爛額。業界也總結了些解決方案,比如兩階段提交、SAGA、TCC 等,但真正實現起來都不簡單,一個看似簡單的業務都會搞得很復雜。所以業界又搞了些現成的開源方案如 seata、DTM。


 

還有救嗎?

好消息是,不是所有的系統都需要那么高的可靠性保證,也不是所有的失敗場景都要做補償處理。

你可能是在一家初創公司,別說系統一分鐘不可用了,就是庫被刪了估計也沒事。

你做的系統可能只是給內部人員用用,凡是遇到失敗就拋異常,大不了人工去修復數據也是可以的。

這些情況下,很可能你并不需要去開發高可用系統,他們更講究效率,把正常流程碼出來基本就完事了。

講究點可用性的,稍微把代碼寫好點,服務器配置堆高點,業務流程設計上注意點,基本也能規避大部分祭天性的問題。

等你公司真的發展成 BAT 那種了,是真正拼刀工的時候,萬分之一概率的異常場景可能就會讓系統天天宕機,賬戶天天少錢。那時候各種方案、架構、分析都要拿到桌面上來了。

所以,面向失敗編程誠然很難,但不代表你必須得天天面對著失敗抓耳撓腮,你需要評估你所負責的系統在成本、效率、健壯性上應做怎樣的取舍

文章來自
https://www.cnblogs.com/linvanda/p/16172767.html

分享到:
標簽:儲值卡
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定