後端--防重複提交策略方法

蘑菇街的一個方案,下面的文章轉自蘑菇街的技術方案前端

http://mogu.io/prevent-duplicate-requests-4redis

redis官方給出的方案及模式數據庫

http://redis.io/commands/incr後端

 

#防重複處理總結
##背景
在業務開發中,咱們常會面對防止重複請求的問題。當服務端對於請求的響應涉及數據的修改,或狀態的變動時,可能會形成極大的危害。重複請求的後果在交易系統、售後維權,以及支付系統中尤爲嚴重。緩存

前臺操做的抖動,快速操做,網絡通訊或者後端響應慢,都會增長後端重複處理的機率。網絡

前臺操做去抖動和防快速操做的措施,咱們首先會想到在前端作一層控制。當前端觸發操做時,或彈出確認界面,或disable入口並倒計時等等,此處不細表。併發

但前端的限制僅能解決少部分問題,且不夠完全,後端自有的防重複處理措施必不可少,責無旁貸。前端優化

在接口實現中,咱們常要求接口要知足冪等性,來保證屢次重複請求時只有一次有效。分佈式

查詢類的接口幾乎老是冪等的,但在包含諸如數據插入,多模塊數據更新時,達到冪等性會比較難,尤爲是高併發時的冪等性要求。好比第三方支付前臺回調和後臺回調,第三方支付批量回調,慢性能業務邏輯(如用戶提交退款申請,商家贊成退貨/退款等)或慢網絡環境時,是重複處理的高發場景。高併發

##嘗試

這裏針對「用戶提交退款申請」的例子,說明一下嘗試過的防重複處理方法的效果。

後端防重複處理的方式,咱們前後嘗試了三種:

####1)基於DB中退款訂單狀態的驗證

這種方式簡單直觀,從DB查詢出來的退款詳情(包括狀態)每每還能夠用在後續邏輯中,沒有花額外的工做專門應對重複請求的問題。

這種查詢狀態後進行驗證的邏輯,從代碼上線後就一直存在於全部含狀態的業務邏輯處理中,必不可少。但對於防重複處理效果並很差:在前端添加防重複提交前,每週平均在25筆;前端優化後,每週降到7筆。這個數量佔總退款申請數的3%%,一個仍然沒法接受的比例。

理論上,任意次請求只要在數據狀態更新以前都完成了查詢操做,則業務邏輯的重複處理就會發生。以下圖所示。優化的方向是減小查詢到更新之間業務處理時間,可下降空檔期的併發影響。極致狀況下若是查詢和更新變成了原子操做,則就不存在咱們當前的問題。

image


####2)基於緩存數據狀態的驗證
Redis存儲查詢輕量快速。在request進來的時候,能夠先記錄在緩存中。後續進來的request每次進行驗證。整個流程處理完成,清除緩存。以退款爲例子:

I.  每次退款發起申請,讀取緩存中是否有以orderId爲key的值
    II. 沒有,則往緩存中寫入以orderId爲key的value
    III.有,則說明有該訂單的退款正在進行。
    IV. 操做完清緩存,或者緩存存值的時候設置生命週期

與1)的發放相比,數據庫換成響應更快的緩存。可是仍然不是原子操做。插入和讀取緩存仍是有時間間隔。在極致的狀況下仍是存在重複操做的狀況。
此方法優化後,每週1筆重複操做。

image


####3)利用惟一索引機制的驗證

須要原子性操做,想到了數據庫的惟一索引。
新建一個TradeLock表:

CREATE TABLE `TradeLock` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`type` int(11) NOT NULL COMMENT '鎖類型',
`lockId` int(11) NOT NULL DEFAULT '0' COMMENT '業務ID',
`status` int(11) NOT NULL DEFAULT '0' COMMENT '鎖狀態',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='Trade鎖機制';

每次request進來則往表裏面插入數據:

——成功,則能夠繼續操做(至關於獲取鎖);
——失敗,則說明有操做在進行。

操做完成後,刪除此條記錄。(至關於釋放鎖)
目前已經上線,等待下週的數據統計。

image


####4)基於緩存的計數器驗證:

因爲數據庫的操做比較消耗性能,瞭解到redis的計數器也是原子性操做。果斷採用計數器。既能夠提升性能,還不用存儲,並且能提高qps的峯值。

仍是以訂單退款爲例子:

每次request進來則新建一個以orderId爲key的計數器,而後+1。

若是>1(不能得到鎖): 說明有操做在進行,刪除。 若是=1(得到鎖): 能夠操做。

操做結束(刪除鎖):刪除這個計數器。
要了解計數器,能夠參考:

link

image


##總結:

PHP語言自身沒有提供進程互斥和鎖定機制。所以纔有了咱們上面的嘗試。

網上也有文件鎖機制,可是考慮到咱們的分佈式部署,建議仍是用緩存。

在大併發的狀況下,程序各類狀況的發生。特別是涉及到金額操做,不能有一分一毫的差距。因此在大併發要互斥的狀況下能夠考慮三、4兩種方案。

愛迪生嘗試了1600多種材料選擇了鎢絲髮明瞭燈泡,實踐出真知。遇到問題,和問題鬥爭,最後解決問題是一個最大提高自個人過程,不但加寬本身的知識廣度,更加深了本身的技能深度。達到目標以後的成就感更是不言而喻。

    • 做者:蘑菇街商家平臺資金團隊工程師 @木照
相關文章
相關標籤/搜索