1. 程式人生 > >【系統架構】億級Web 系統的容錯性實踐【中】

【系統架構】億級Web 系統的容錯性實踐【中】

設定超時時間

呼叫任何一個服務或者儲存,一個合理的超時時間(超時時間,就是我們請求一個服務時,等待的最長時間),是非常重要的,而這一點往往比較容易被忽視。通常Web系統和後端服務的通訊方式,是同步等待的模式。這種模式,它會帶來的問題比較多。

對於服務端,影響比較大的一個問題,就是它會嚴重影響系統吞吐率。假設,我們一個服務的機器上,啟用了100個處理請求的worker,worker的超時時間設定為5秒,1個worker處理1個任務的平均處理耗時是100ms。那麼1個work在5秒鐘的時間裡,能夠處理50個使用者請求,然而,一旦網路或者服務偶爾異常,響應超時,那麼在本次處理的後續整整5秒裡,它僅僅處理了1個等待超時的失敗任務。一旦比較大概率出現這型別的超時異常,系統的吞吐率就會大面積下降,有可能耗盡所有的worker(資源被佔據,全部在等待狀態,直到5s超時才釋放),最終導致新的請求無worker可用,只能陷入異常狀態。


算上網路通訊和其他環節的耗時,使用者就等待了超過5s時間,最後卻獲得一個異常的結果,使用者的心情通常是崩潰的。

解決這個問題的方式,就是設定一個合理的超時時間。例如,回到上面的的例子,平均處理耗時是100ms,那麼我們不如將超時時間從5s下調到500ms。從直觀上看,它就解決了吞吐率下降和使用者等待過長的問題。然而,這樣做本身又比較容易帶來新的問題,就是會引起服務的成功率下降。因為平均耗時是100ms,但是,部分業務請求本身耗時比較長,耗時超過500ms也比較多。例如,某個請求服務端耗時600ms才處理完畢,然後這個時候,客戶端認為等待超過500ms,已經斷開了連線。處理耗時比較長的這型別業務請求會受到比較明顯的影響。

解決超時時間過短帶來成功率下降問題

超時時間設定過短,會將很多本來處理成功的請求,當做服務超時處理掉,進而引起服務成功率下降。將全部業務服務,以一刀切的方式設定一個超時時間,是比較不可取的。優化的方法,我們分為兩個方向。

(1)快慢分離

根據實際的業務維度,區分對待地給各個業務服務配置不同的超時時間,同時,最好也將它們的部署服務也分離出來。例如,天天酷跑的查詢服務耗時通常為100ms,那麼超時時間我們就設定為1s,某新手遊的查詢服務通常耗時為700ms,那麼我們就設定為5s。這樣的話,整體系統的成功率,就不會受到比較大的影響。

(2)解決同步阻塞的

“快慢分離”可以改善系統的同步等待問題但是,對於某些耗時本來就比較長的服務而言,系統的程序/執行緒資源仍然在同步等待過程中,無法響應其他新的請求,只能阻塞等待,它的資源仍然是被佔據,系統的整體吞吐率仍然被大幅度拉低。

解決的思路,當然是利用I/O多路複用,通過非同步回撥的方式,解決同步等待過程中的資源浪費。AMS的一些核心服務,採用的就是“協程”(又叫“微執行緒”,簡單的說,常規非同步程式程式碼裡巢狀比較多層的函式回撥,編寫複雜。而協程則提供了一種類似寫同步程式碼的方式,來寫非同步回撥程式),以解決同步等待的問題。非同步處理的簡單描述,就是當程序遇到I/O網路阻塞時,就保留現場,立刻切換去處理下一個業務請求,程序不會因為某個網路等待而停止處理業務,進而,系統吞吐率即使遇到網路等待時間過長的場景,通常都能保持在比較高的水平。

值得補充一點的是,非同步處理只是解決系統的吞吐率問題,對於使用者的體驗問題,並不會有改善,使用者需要等待的時間並不會減少。

防重入,防止重複發貨

前面我們提到,我們設定了一個比較“合理的超時時間”,簡而言之,就是一個比較短的超時時間。而在資料寫入的場景,會引起新的問題,就我們的AMS系統而言,就是發貨場景。如果是發貨請求超時,這個時候,我們需要思考的問題就比較多了。

1、發貨等待超時,發貨服務執行發貨失敗。這種場景,問題不大,後續使用者重新點選領取按鈕,就可以觸發下一次重新發貨。

2、發貨等待超時,發貨服務實際在更晚的時候執行發貨成功,我們稱之為“超時成功”。比較麻煩的場景,則是每次都是發貨超時,而實際上都發貨成功,如果系統設計不當,有可能導致使用者可以無限領取禮包,最終造成活動運營事故。

第二種場景,給我們帶來了比較麻煩的問題,如果處理不當,使用者再次點選,就觸發第多次“額外”發貨。

例如,我們假設某個發貨服務超時時間設定為6s,使用者點選按鈕,我們的AMS收到請求後,請求發貨服務發貨,等待6s後,無響應,我們給使用者提示“領取失敗”,而實際上發貨服務卻在第8秒執行發貨成功,禮包到了使用者的賬戶上。而使用者看見“領取失敗”,則又再次點選按鈕,最終導致“額外”多發一個禮包給到這個使用者。

例子的時序和流程圖大致如下:

這裡就提到了防重入,簡單的說,就是如何確認不管使用者點選多少次這個領取按鈕,我們都確保結果只有一種預期結果,就是隻會給使用者發一次禮包,而不引起重複發貨。我們的AMS活動運營平臺一年上線的活動超過4000個,涉及數以萬計的各種型別、不同業務系統的禮包發貨,業務通訊場景比較複雜。針對不同的業務場景,我們做了不同的解決方案:

1、業務層面限制,設定禮包單使用者限量。在發貨伺服器的源頭,設定好一個使用者僅能最多獲得1個禮包,直接避免重複發放。但是,這種業務限制,並非每個業務場景都通用的,只限於內部具備該限制能力的業務發貨系統,並且,有一些禮包本身就可以多次領取的,就不適用了。

2、訂單號機制。使用者的每一次符合資格的發貨請求,都生成一個訂單號與之對應,通過它來確保1個訂單號,只發貨1次。這個方案雖然比較完善,但是,它是依賴於發貨服務方配合做“訂單號發貨狀態更新“的,而我們的發貨業務方眾多,並非每一個都能支援”訂單號更新“的場景。

3、 自動重試的非同步發貨模式。使用者點選領取禮包按鈕後,Web端直接返回成功,並且提示禮包在30分鐘內到賬。對於後臺,則將該發貨錄入到發貨佇列或者儲存中,等待發貨服務非同步發貨。因為是非同步處理,可以多次執行發貨重試操作,直到發貨成功為止。同時,非同步發貨是可以設定一個比較長的超時等待時間,通常不會出現“超時成功”的場景,並且對於前端響應來說,不需要等待後臺發貨狀態的返回。但是,這種模式,會給使用者帶來比較不好的體驗,就是沒有實時反饋,無法立刻告訴使用者,禮包是否到賬。


非訂單號的特殊防刷機制

某些特殊的合作場景,我們無法使用雙方約定訂單號方式,例如一個完全隔離獨立的外部發貨介面,不能和我們做訂單號的約定。基於這種場景,我們AMS專門做了一種防刷的機制,就是通過限制read超時的次數。但是,這種方案並非完美解決重複發貨問題,只是能起到夠儘可能減少避免被刷的作用。一次網路通訊,通常包含:建立連線(connect),寫入資料發包(write),等待並且讀取回包(read),斷開連線(close)。

通常一個發貨服務如果出現異常,大多數情況,在connect步驟就是失敗或者超時,而如果一個請求走到等待回包(read)時超時,那麼發貨服務另外一邊就有可能發生了“超時但發貨成功”的場景。這個時候,我們將read超時的發生次數記錄起來,然後提供了一個配置限制次數的能力。假如設定為2次,那麼當一個使用者第一次領取禮包,遇到read超時,我們就允許它重試,當還遇到第二次read超時,就達到我們之前設定的閥值2,我們就認為它可能發貨成功,拒絕使用者的第三次領取請求。


這種做法,假設發貨服務真的出現很多超時成功,那麼使用者也最多隻能刷到2次禮包(次數可配置),而避免發生禮包無限制被刷的場景。但是,這種方案並不完全可靠,謹慎使用。

在發貨場景,還會涉及分散式場景下的CAP(一致性、可用性、分割槽容錯性)問題,不過,我們的系統並非是一個電商服務,大部分的發貨並沒有強烈的一致性要求。因此,總體而言,我們是弱化了一致性問題(核心服務,通過非同步重試的方式,達到最終一致性),以追求可用性和分割槽容錯性的保證。

對於web系統容錯機制,還有服務降級、服務解耦等一系列方法將在下篇文章總結。