天天看點

API接口幂等性設計概念技術方案4、token機制,防止頁面重複送出。四、總結

概念

我們實際系統中有很多操作,是不管做多少次,都應該産生一樣的效果或傳回一樣的結果。 

例如

1. 前端重複送出選中的資料,應該背景隻産生對應這個資料的一個反應結果;

2. 我們發起一筆付款請求,應該隻扣使用者賬戶一次錢,當遇到網絡重發或系統bug重發,也應該隻扣一次錢;

3. 發送消息,也應該隻發一次;

4. 建立業務訂單,一次業務請求隻能建立一個,建立多個就會出大問題等等很多重要的情況都需要幂等的特性來支援。 

技術方案

1、查詢操作

查詢一次和查詢多次,在資料不變的情況下,查詢結果是一樣的。select是天然的幂等操作;

2、删除操作

删除操作也是幂等的,删除一次和多次删除都是把資料删除。(注意可能傳回結果不一樣,删除的資料不存在,傳回0,删除的資料多條,傳回結果多個) ;

3、唯一索引,防止新增髒資料。

比如:支付寶的資金賬戶,支付寶也有使用者賬戶,每個使用者隻能有一個資金賬戶,怎麼防止給使用者建立資金賬戶多個,那麼給資金賬戶表中的使用者ID加唯一索引,是以一個使用者新增成功一個資金賬戶記錄。要點:唯一索引或唯一組合索引來防止新增資料存在髒資料(當表存在唯一索引,并發時新增報錯時,再查詢一次就可以了,資料應該已經存在了,傳回結果即可);

4、token機制,防止頁面重複送出。

業務要求: 頁面的資料隻能被點選送出一次;發生原因: 由于重複點選或者網絡重發,或者nginx重發等情況會導緻資料被重複送出;解決辦法: 叢集環境采用token加redis(redis單線程的,處理需要排隊);單JVM環境:采用token加redis或token加jvm記憶體。處理流程:1. 資料送出前要向服務的申請token,token放到redis或jvm記憶體,token有效時間;2. 送出後背景校驗token,同時删除token,生成新的token傳回。token特點:要申請,一次有效性,可以限流。注意:redis要用删除操作來判斷token,删除成功代表token校驗通過,如果用select+delete來校驗token,存在并發問題,不建議使用;

5、悲觀鎖——擷取資料的時候加鎖擷取。

select * from table_xxx where id='xxx' for update; 注意:id字段一定是主鍵或者唯一索引,不然是鎖表,會死人的悲觀鎖使用時一般伴随事務一起使用,資料鎖定時間可能會很長,根據實際情況選用; 

6、樂觀鎖

樂觀鎖隻是在更新資料那一刻鎖表,其他時間不鎖表,是以相對于悲觀鎖,效率更高。樂觀鎖的實作方式多種多樣可以通過version或者其他狀态條件:1. 通過版本号實作update table_xxx set name=#name#,version=version+1 where version=#version#如下圖(來自網上);2. 通過條件限制 update table_xxx set avai_amount=avai_amount-#subAmount# where avai_amount-#subAmount# >= 0要求:quality-#subQuality# >= ,這個情景适合不用版本号,隻更新是做資料安全校驗,适合庫存模型,扣份額和復原份額,性能更高;

      注意:樂觀鎖的更新操作,最好用主鍵或者唯一索引來更新,這樣是行鎖,否則更新時會鎖表,上面兩個sql改成下面的兩個更好 

update table_xxx set name=#name#,version=version+1 where id=#id# and version=#version#;

update table_xxx set avai_amount=avai_amount-#subAmount# where id=#id# and avai_amount-#subAmount# >= 0;

 7.分布式鎖

還是拿插入資料的例子,如果是分布是系統,建構全局唯一索引比較困難,例如唯一性的字段沒法确定,這時候可以引入分布式鎖,通過第三方的系統(redis或zookeeper),在業務系統插入資料或者更新資料,擷取分布式鎖,然後做操作,之後釋放鎖,這樣其實是把多線程并發的鎖的思路,引入多多個系統,也就是分布式系統中得解決思路。要點:某個長流程處理過程要求不能并發執行,可以在流程執行之前根據某個标志(使用者ID+字尾等)擷取分布式鎖,其他流程執行時擷取鎖就會失敗,也就是同一時間該流程隻能有一個能執行成功,執行完成後,釋放分布式鎖(分布式鎖要第三方系統提供);

8. select + insert

并發不高的背景系統,或者一些任務JOB,為了支援幂等,支援重複執行,簡單的處理方法是,先查詢下一些關鍵資料,判斷是否已經執行過,在進行業務處理,就可以了。注意:核心高并發流程不要用這種方法;

9. 狀态機幂等

在設計單據相關的業務,或者是任務相關的業務,肯定會涉及到狀态機(狀态變更圖),就是業務單據上面有個狀态,狀态在不同的情況下會發生變更,一般情況下存在有限狀态機,這時候,如果狀态機已經處于下一個狀态,這時候來了一個上一個狀态的變更,理論上是不能夠變更的,這樣的話,保證了有限狀态機的幂等。注意:訂單等單據類業務,存在很長的狀态流轉,一定要深刻了解狀态機,對業務系統設計能力提高有很大幫助 

10. 對外提供接口的api如何保證幂等。

如銀聯提供的付款接口:需要接入商戶提傳遞款請求時附帶:source來源,seq序列号 

source+seq在資料庫裡面做唯一索引,防止多次付款(并發時,隻能處理一個請求) 。重點:對外提供接口為了支援幂等調用,接口有兩個字段必須傳,一個是來源source,一個是來源方序列号seq,這個兩個字段在提供方系統裡面做聯合唯一索引,這樣當第三方調用時,先在本方系統裡面查詢一下,是否已經處理過,傳回相應處理結果;沒有處理過,進行相應處理,傳回結果。注意,為了幂等友好,一定要先查詢一下,是否處理過該筆業務,不查詢直接插入業務系統,會報錯,但實際已經處理了。 

四、總結

      幂等與你是不是分布式高并發還有JavaEE都沒有關系。關鍵是你的操作是不是幂等的。一個幂等的操作典型如:把編号為5的記錄的A字段設定為0這種操作不管執行多少次都是幂等的。一個非幂等的操作典型如:把編号為5的記錄的A字段增加1這種操作顯然就不是幂等的。要做到幂等性,從接口設計上來說不設計任何非幂等的操作即可。譬如說需求是:當使用者點選贊同時,将答案的贊同數量+1。改為:當使用者點選贊同時,確定答案贊同表中存在一條記錄,使用者、答案。贊同數量由答案贊同表統計出來。總之幂等性應該是合格程式員的一個基因,在設計系統時,是首要考慮的問題,尤其是在像支付寶,銀行,網際網路金融公司等涉及的都是錢的系統,既要高效,資料也要準确,是以不能出現多扣款,多打款等問題,這樣會很難處理,使用者體驗也不好。 

繼續閱讀