首頁  >  文章  >  高並發下的介面如何確保冪等性

高並發下的介面如何確保冪等性

Java学习指南
Java学习指南轉載
2023-07-26 16:58:48818瀏覽

一、背景

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

例如:

  1. 前端重複提交選取的數據,應該後台只產生對應這個數據的一個反應結果。

  2. 我們發起一筆付款請求,應該只扣用戶帳戶一次錢,當遇到網路重發或系統bug重發,也應該只扣一次錢;

  3. 發送訊息,也應該只發一次,同樣的簡訊發給用戶,用戶會哭的;

  4. 建立業務訂單,一次業務請求只能創建一個,創建多個就會出大問題。

等等很多重要的情況,這些邏輯都需要冪等的特性來支援。

二、冪等性概念

冪等(idempotent、idempotence)是一個數學與電腦學概念,常見於抽象代數中。

在程式設計中.一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。冪等函數,或稱冪等方法,是指可以使用相同參數重複執行,並能得到相同結果的函數。

這些函數不會影響系統狀態,也不用擔心重複執行會對系統造成改變。例如,「getUsername()和setTrue()」函數就是一個冪等函數.

更複雜的操作冪等保證是利用唯一交易號(流水號)實現.

我的理解:冪等就是一個操作,不論執行多少次,產生的效果和返回的結果都是一樣的

三、技術方案

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 tablexxx set avaiamount=avaiamount-#subAmount# where avaiamount-#subAmount# >= 0

要求:quality-#subQuality# >= ,這個情境適合不用版本號,只更新是做資料安全校驗,適合庫存模型,扣份額和回滾份額,性能更高

注意:乐观锁的更新操作,最好用主键或者唯一索引来更新,这样是行锁,否则更新时会锁表,上面两个sql改成下面的两个更好

update tablexxx set name=#name#,version=version+1 where id=#id# and version=#version#update tablexxx set avaiamount=avaiamount-#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,這個兩個欄位在提供方系統裡面做聯合唯一索引

這樣當第三方呼叫時,先在本方系統裡面查詢一下,是否已經處理過,返回相應處理結果;沒有處理過,進行相應處理,並返回結果。

注意,為了冪等友好,一定要先查詢一下,是否處理過該筆業務,不查詢直接插入業務系統,會報錯,但實際上已經處理了。

總結

冪等性應該是合格程式設計師的一個基因,在設計系統時,是首要考慮的問題,尤其是在像支付寶,銀行,網路金融公司等涉及的都是錢的系統,既要高效,數據也要準確,所以不能出現多扣款,多打款等問題,這樣會很難處理,用戶體驗也不好。

以上是高並發下的介面如何確保冪等性的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:Java学习指南。如有侵權,請聯絡admin@php.cn刪除