網(wǎng)站首頁 編程語言 正文
前言:接口冪等性
問題,對于開發(fā)人員來說,是一個跟語言無關的公共問題。對于一些用戶請求,在某些情況下是可能重復發(fā)送的,如果是查詢類操作并無大礙,但其中有些是涉及寫入操作的,一旦重復了,可能會導致很嚴重的后果,例如交易的接口如果重復請求可能會重復下單。接口冪等性是指用戶對于同一操作發(fā)起的一次請求或者多次請求的結果是一致的,不會因為多次點擊而產(chǎn)生了副作用。
一、接口冪等性
1.1、什么是接口冪等性
在HTTP/1.1中,對冪等性進行了定義。它描述了一次和多次請求某一個資源對于資源本身應該具有同樣的結果,即第一次請求的時候?qū)Y源產(chǎn)生了副作用,但是以后的多次請求都不會再對資源產(chǎn)生副作用。這里的副作用是不會對結果產(chǎn)生破壞或者產(chǎn)生不可預料的結果。也就是說,其任意多次執(zhí)行對資源本身所產(chǎn)生的影響均與一次執(zhí)行的影響相同。
這類問題多發(fā)于接口的:
-
insert
操作,這種情況下多次請求,可能會產(chǎn)生重復數(shù)據(jù)。 -
update
操作,如果只是單純的更新數(shù)據(jù),比如:update user set status=1 where id=1
,是沒有問題的。如果還有計算,比如:update user set status=status+1 where id=1
,這種情況下多次請求,可能會導致數(shù)據(jù)錯誤。
1.2、為什么需要實現(xiàn)冪等性
在接口調(diào)用時一般情況下都能正常返回信息不會重復提交,不過在遇見以下情況時可以就會出現(xiàn)問題,如:
- 前端重復提交表單:?在填寫一些表格時候,用戶填寫完成提交,很多時候會因網(wǎng)絡波動沒有及時對用戶做出提交成功響應,致使用戶認為沒有成功提交,然后一直點提交按鈕,這時就會發(fā)生重復提交表單請求。
- 用戶惡意進行刷單:?例如在實現(xiàn)用戶投票這種功能時,如果用戶針對一個用戶進行重復提交投票,這樣會導致接口接收到用戶重復提交的投票信息,這樣會使投票結果與事實嚴重不符。
- 接口超時重復提交:?很多時候 HTTP 客戶端工具都默認開啟超時重試的機制,尤其是第三方調(diào)用接口時候,為了防止網(wǎng)絡波動超時等造成的請求失敗,都會添加重試機制,導致一個請求提交多次。
- 消息進行重復消費:?當使用 MQ 消息中間件時候,如果發(fā)生消息中間件出現(xiàn)錯誤未及時提交消費信息,導致發(fā)生重復消費。
本文討論的是如何在服務端優(yōu)雅地統(tǒng)一處理這種接口冪等性情況,如何禁止用戶重復點擊等客戶端操作不在此次討論范圍。
1.3、引入冪等性后對系統(tǒng)的影響
冪等性是為了簡化客戶端邏輯處理,能放置重復提交等操作,但卻增加了服務端的邏輯復雜性和成本,其主要是:
- 把并行執(zhí)行的功能改為串行執(zhí)行,降低了執(zhí)行效率。
- 增加了額外控制冪等的業(yè)務邏輯,復雜化了業(yè)務功能;
所以在使用時候需要考慮是否引入冪等性的必要性,根據(jù)實際業(yè)務場景具體分析,除了業(yè)務上的特殊要求外,一般情況下不需要引入的接口冪等性。
二、如何設計冪等
冪等意味著一條請求的唯一性。不管是你哪個方案去設計冪等,都需要一個全局唯一的ID?,去標記這個請求是獨一無二的。
- 如果你是利用唯一索引控制冪等,那唯一索引是唯一的
- 如果你是利用數(shù)據(jù)庫主鍵控制冪等,那主鍵是唯一的
- 如果你是悲觀鎖的方式,底層標記還是全局唯一的ID
2.1、全局的唯一性ID
全局唯一性ID,我們怎么去生成呢?你可以回想下,數(shù)據(jù)庫主鍵Id怎么生成的呢?
是的,我們可以使用UUID
,但是UUID的缺點比較明顯,它字符串占用的空間比較大,生成的ID過于隨機,可讀性差,而且沒有遞增。
我們還可以使用雪花算法(Snowflake)
?生成唯一性ID。
雪花算法是一種生成分布式全局唯一ID的算法,生成的ID稱為Snowflake IDs
。這種算法由Twitter創(chuàng)建,并用于推文的ID。
一個Snowflake ID有64位。
- 第1位:Java中l(wèi)ong的最高位是符號位代表正負,正數(shù)是0,負數(shù)是1,一般生成ID都為正數(shù),所以默認為0。
- 接下來前41位是時間戳,表示了自選定的時期以來的毫秒數(shù)。
- 接下來的10位代表計算機ID,防止沖突。
- 其余12位代表每臺機器上生成ID的序列號,這允許在同一毫秒內(nèi)創(chuàng)建多個Snowflake ID。
?當然,全局唯一性的ID,還可以使用百度的Uidgenerator
,或者美團的Leaf
。
2.2、冪等設計的基本流程
冪等處理的過程,說到底其實就是過濾一下已經(jīng)收到的請求,當然,請求一定要有一個全局唯一的ID標記
哈。然后,怎么判斷請求是否之前收到過呢?把請求儲存起來,收到請求時,先查下存儲記錄,記錄存在就返回上次的結果,不存在就處理請求。
一般的冪等處理就是這樣,如下:
三、接口冪等性常見解決方案
3.1、下游傳遞唯一請求編號
可能會想到的是,只要請求有唯一的請求編號,那么就能借用Redis做這個去重——只要這個唯一請求編號在Redis存在,證明處理過,那么就認為是重復的。
方案描述:
所謂唯一請求序列號,其實就是每次向服務端請求時候附帶一個短時間內(nèi)唯一不重復的序列號,該序列號可以是一個有序 ID,也可以是一個訂單號,一般由下游生成,在調(diào)用上游服務端接口時附加該序列號和用于認證的 ID。
當上游服務器收到請求信息后拿取該 序列號 和下游 認證ID 進行組合,形成用于操作 Redis 的 Key,然后到 Redis 中查詢是否存在對應的 Key 的鍵值對,根據(jù)其結果:
- 如果存在,就說明已經(jīng)對該下游的該序列號的請求進行了業(yè)務處理,這時可以直接響應重復請求的錯誤信息。
- 如果不存在,就以該 Key 作為 Redis 的鍵,以下游關鍵信息作為存儲的值(例如下游商傳遞的一些業(yè)務邏輯信息),將該鍵值對存儲到 Redis 中 ,然后再正常執(zhí)行對應的業(yè)務邏輯即可。
適用操作:
- 插入操作
- 更新操作
- 刪除操作
使用限制:
- 要求第三方傳遞唯一序列號;
- 需要使用第三方組件 Redis 進行數(shù)據(jù)效驗;
主要流程:
?主要步驟:
- ① 下游服務生成分布式 ID 作為序列號,然后執(zhí)行請求調(diào)用上游接口,并附帶“唯一序列號”與請求的“認證憑據(jù)ID”。
- ② 上游服務進行安全效驗,檢測下游傳遞的參數(shù)中是否存在“序列號”和“憑據(jù)ID”。
- ③ 上游服務到 Redis 中檢測是否存在對應的“序列號”與“認證ID”組成的 Key,如果存在就拋出重復執(zhí)行的異常信息,然后響應下游對應的錯誤信息。如果不存在就以該“序列號”和“認證ID”組合作為 Key,以下游關鍵信息作為 Value,進而存儲到 Redis 中,然后正常執(zhí)行接來來的業(yè)務邏輯。
上面步驟中插入數(shù)據(jù)到 Redis 一定要設置過期時間。這樣能保證在這個時間范圍內(nèi),如果重復調(diào)用接口,則能夠進行判斷識別。如果不設置過期時間,很可能導致數(shù)據(jù)無限量的存入 Redis,致使 Redis 不能正常工作。
3.2、防重 Token 令牌
方案描述:
針對客戶端連續(xù)點擊或者調(diào)用方的超時重試等情況,例如提交訂單,此種操作就可以用 Token 的機制實現(xiàn)防止重復提交。簡單的說就是調(diào)用方在調(diào)用接口的時候先向后端請求一個全局 ID(Token),請求的時候攜帶這個全局 ID 一起請求(Token 最好將其放到 Headers 中),后端需要對這個 Token 作為 Key,用戶信息作為 Value 到 Redis 中進行鍵值內(nèi)容校驗,如果 Key 存在且 Value 匹配就執(zhí)行刪除命令,然后正常執(zhí)行后面的業(yè)務邏輯。如果不存在對應的 Key 或 Value 不匹配就返回重復執(zhí)行的錯誤信息,這樣來保證冪等操作。
使用限制:
- 需要生成全局唯一 Token 串;
- 需要使用第三方組件 Redis 進行數(shù)據(jù)效驗;
主要流程:
① 服務端提供獲取 Token 的接口,該 Token 可以是一個序列號,也可以是一個分布式 ID 或者 UUID 串。
② 客戶端調(diào)用接口獲取 Token,這時候服務端會生成一個 Token 串。
③ 然后將該串存入 Redis 數(shù)據(jù)庫中,以該 Token 作為 Redis 的鍵(注意設置過期時間)。
④ 將 Token 返回到客戶端,客戶端拿到后應存到表單隱藏域中。
⑤ 客戶端在執(zhí)行提交表單時,把 Token 存入到 Headers 中,執(zhí)行業(yè)務請求帶上該 Headers。
⑥ 服務端接收到請求后從 Headers 中拿到 Token,然后根據(jù) Token 到 Redis 中查找該 key 是否存在。
⑦ 服務端根據(jù) Redis 中是否存該 key 進行判斷,如果存在就將該 key 刪除,然后正常執(zhí)行業(yè)務邏輯。如果不存在就拋異常,返回重復提交的錯誤信息。
注意,在并發(fā)情況下,執(zhí)行 Redis 查找數(shù)據(jù)與刪除需要保證原子性,否則很可能在并發(fā)下無法保證冪等性。其實現(xiàn)方法可以使用分布式鎖或者使用 Lua 表達式來注銷查詢與刪除操作。
參考鏈接:
阿里面試官:接口的冪等性怎么設計?
優(yōu)雅地處理重復請求(并發(fā)請求)
SpringBoot 接口冪等性實現(xiàn)的 4 種方案!這個我真的服氣了!
原文鏈接:https://blog.csdn.net/CSDN2497242041/article/details/124397655
相關推薦
- 2022-06-21 Android?Studio實現(xiàn)彈窗設置_Android
- 2022-03-08 基于Redis實現(xiàn)阻塞隊列的方式_Redis
- 2023-01-18 Python函數(shù)的參數(shù)列表解析_python
- 2021-12-12 七大經(jīng)典排序算法圖解_C 語言
- 2023-01-02 Flutter狀態(tài)管理Provider示例解析_Android
- 2023-03-17 學習win32com操作word之Range精講_python
- 2022-06-13 ASP.NET?Core中的靜態(tài)文件介紹_實用技巧
- 2022-03-22 C++實現(xiàn)簡易選課系統(tǒng)代碼分享_C 語言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結構-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支