一、介紹
冪等性就是針對同一個請求,不管該請求被提交了多少次,該請求都將被視為同一個請求,服務端不應該將同一個請求進行多次處理,以確認處理邏輯的正確性,針對交易性系統冪等性的設計尤為重要,否則由於網絡或服務器處理超時等問題,就會造成交易混亂,最嚴重的后果就是亂扣用戶的錢,造成投訴満天飛。
二、客戶端設計原則
系統設計時,一定是要從最壞的角度上去考慮,如網絡問題、服務器問題,甚至於包括人為的攻擊行為,如果純粹從服務端的角度去做實現保證,一個是系統的復雜度會加大,二個也會對系統產生更大的並發壓力,因而客戶端的合理設計也非常重要,舉一個示例:
用戶在網頁端或APP端(統稱為客戶端)上購買一個商品,通常都是以觸發按鈕的行為提交該請求,如果客戶端沒有做請求提交控制,那么由於網絡原因或者系統繁忙等原因(這是非常常見的場景),沒有在用戶期望的時間之內響應,那么用戶就會再次點擊,那么這個請求又會發往服務端,那么服務端又會再次對這個請求進行處理。 |
針對這種場景,服務端就會接收處理到多個訂單請求,從而產生多條不合理的用戶訂單。此時在客戶端稍微優化一下,當用戶的請求提交以后,將訂單提交的請求按鈕置為不可用的狀態,待請求成功響應后跳轉到下一步處理邏輯,或者是提單請求超時后再將訂單提交按鈕置為可用,提示用戶上一次的提交可能已經失敗,並允許用戶再次提交。
當然客戶端這樣設計並不能完全做到冪等性原則,因為用戶同樣可以針對相同的訂單執行多次提交,服務端如果沒有做控制,還是會產生多個訂單,只是讓錯誤變得優雅了一點。這個時候需增加令牌策略,在下單之間,針對當前訂單從服務端獲取一個Token,每次提交的時候都從把該Token帶上,針對同一個訂單帶上相同的值,這樣服務端就可以根據該Token來判斷是不是一個已經處理的了訂單。
說了那么多,看文字太累,還是看圖方便,來一個:
三、服務端設計原則
根據不同的應用場景,服務端可以使用不同的解決方式,如:
其中數據庫的樂觀鎖和防重表的使用,都是涉及到數據的參與,在高並發的應用場景中,業務的判斷邏輯盡量不要使用數據庫參與,特別是RDBMS的參與,因為RDBMS天生具有不易擴展及事務處理屬性,吞吐量上都會有相應的瓶頸,因而用數據庫做業務邏輯的控制不是我的菜,這里就不會重點說這兩種方式。
1、Token令牌+分布式鎖的方式
Token是用於確定交易的唯一屬性,也是服務端用於檢驗當前交易是否合法交易的依據,但是在分布式的復雜環境中,如果沒有分布式鎖的控制,同一筆交易就可能會被處理多次,因而為了確認交易的冪等性,Token令牌和分布式鎖必須要一起使用。
實現邏輯步驟如下:
實現邏輯參見下圖:
其中分布式鎖的獲取,可以通過Redis和Zookeeper實現,請參看筆者的另外兩篇分別介紹通過Redis和Zookeeper實現分布式的文章:
2、異步處理
異步處理,通常的做法是將認為需要消費的交易,提交到消息隊列中,並注冊監聽事件,待交易被處理完后,再由處理交易的應用回調注冊的監聽事件反饋處理的結果。交易處理的調度應用,需要負責對交易的處理符合冪等性的原則,將重復請求的交易請求做去重處理。
實現邏輯見下圖:
從邏輯處理上可以看到,只要交易處理器足夠多,處理速度也不一定會受到多少的影響,交易生產者和交易接收者甚至可以同步返回結果,當交易接收者接收處理結果超時后,再提示用戶過一會兒查看交易的處理結果。