如何保證分布式情況下的冪等性
關(guān)于這個(gè)分布式服務(wù)的冪等性,這是在使用分布式服務(wù)的時(shí)候會(huì)經(jīng)常遇到的問題,比如,重復(fù)提交的問題。而冪等性,就是為了解決問題存在的一個(gè)概念了。
什么是冪等
冪等(idempotent、idempotence)是?個(gè)數(shù)學(xué)與計(jì)算機(jī)學(xué)概念,常?于抽象代數(shù)中。
在編程中?個(gè)冪等操作的特點(diǎn)是其任意多次執(zhí)?所產(chǎn)?的影響均與?次執(zhí)?的影響相同。冪等函數(shù),或 冪等?法,是指可以使?相同參數(shù)重復(fù)執(zhí)?,并能獲得相同結(jié)果的函數(shù)。這些函數(shù)不會(huì)影響系統(tǒng)狀態(tài), 也不?擔(dān)?重復(fù)執(zhí)?會(huì)對(duì)系統(tǒng)造成改變。例如,“setTrue()”函數(shù)就是?個(gè)冪等函數(shù),?論多次執(zhí)?,其結(jié) 果都是?樣的,更復(fù)雜的操作冪等保證是利?唯?交易號(hào)(流?號(hào))實(shí)現(xiàn).
接?冪等性就是?戶對(duì)于同?操作發(fā)起的?次請(qǐng)求或者多次請(qǐng)求的結(jié)果是?致的,不會(huì)因?yàn)槎啻吸c(diǎn)擊? 產(chǎn)?了副作?。
什么是接口的冪等性
在HTTP/1.1中,對(duì)冪等性進(jìn)行了定義。它描述了一次和多次請(qǐng)求某一個(gè)資源對(duì)于資源本身應(yīng)該具有同樣的結(jié)果(網(wǎng)絡(luò)超時(shí)等問題除外),即第一次請(qǐng)求的時(shí)候?qū)Y源產(chǎn)生了副作用,但是以后的多次請(qǐng)求都不會(huì)再對(duì)資源產(chǎn)生副作用。這里的副作用是不會(huì)對(duì)結(jié)果產(chǎn)生破壞或者產(chǎn)生不可預(yù)料的結(jié)果。也就是說,其任意多次執(zhí)行對(duì)資源本身所產(chǎn)生的影響均與一次執(zhí)行的影響相同。
不能保證冪等性的操作
- 前端重復(fù)提交表單:在填寫一些表格時(shí)候,用戶填寫完成提交,很多時(shí)候會(huì)因網(wǎng)絡(luò)波動(dòng)沒有及時(shí)對(duì)用戶做出提交成功響應(yīng),致使用戶認(rèn)為沒有成功提交,然后一直點(diǎn)提交按鈕,這時(shí)就會(huì)發(fā)生重復(fù)提交表單請(qǐng)求。
- 用戶惡意進(jìn)行刷單:例如在實(shí)現(xiàn)用戶投票這種功能時(shí),如果用戶針對(duì)一個(gè)用戶進(jìn)行重復(fù)提交投票,這樣會(huì)導(dǎo)致接口接收到用戶重復(fù)提交的投票信息,這樣會(huì)使投票結(jié)果與事實(shí)嚴(yán)重不符。
- 接口超時(shí)重復(fù)提交:很多時(shí)候 HTTP 客戶端工具都默認(rèn)開啟超時(shí)重試的機(jī)制,尤其是第三方調(diào)用接口時(shí)候,為了防止網(wǎng)絡(luò)波動(dòng)超時(shí)等造成的請(qǐng)求失敗,都會(huì)添加重試機(jī)制,導(dǎo)致一個(gè)請(qǐng)求提交多次。
- 消息進(jìn)行重復(fù)消費(fèi):當(dāng)使用 MQ 消息中間件時(shí)候,如果發(fā)生消息中間件出現(xiàn)錯(cuò)誤未及時(shí)提交消費(fèi)信息,導(dǎo)致發(fā)生重復(fù)消費(fèi)。
如果放到數(shù)據(jù)庫(kù)的操作層面,那么就有很多操作需要去保證冪等性了。
- A: 查詢操作
查詢對(duì)于結(jié)果是不會(huì)有改變的,查詢?次和查詢多次,在數(shù)據(jù)不變的情況下,查詢結(jié)果是?樣的。 select是天然的冪等操作
- B: 刪除操作
刪除?次和多次刪除都是把數(shù)據(jù)刪除。(注意可能返回結(jié)果不?樣,刪除的數(shù)據(jù)不存在,返回0,刪除 的數(shù)據(jù)多條,返回結(jié)果多個(gè),在不考慮返回結(jié)果的情況下,刪除操作也是具有冪等性的)
- C: 更新操作
修改在?多場(chǎng)景下結(jié)果?樣,但是如果是增量修改是需要保證冪等性的,如下例?:
把表中id為XXX的記錄的A字段值設(shè)置為1,這種操作不管執(zhí)?多少次都是冪等的
把表中id為XXX的記錄的A字段值增加1,這種操作就不是冪等的
- D: 新增操作
增加在重復(fù)提交的場(chǎng)景下會(huì)出現(xiàn)冪等性問題,如以上的?付問題
如何實(shí)現(xiàn)冪等性
其實(shí)實(shí)現(xiàn)冪等性的方案有不少,但是呢,這就得需要你根據(jù)不同的業(yè)務(wù)場(chǎng)景去選擇合適的方式了。
實(shí)現(xiàn)方式一
數(shù)據(jù)庫(kù)唯一主鍵
數(shù)據(jù)庫(kù)唯一主鍵的實(shí)現(xiàn)主要是利用數(shù)據(jù)庫(kù)中主鍵唯一約束的特性,一般來說唯一主鍵比較適用于“插入”時(shí)的冪等性,其能保證一張表中只能存在一條帶該唯一主鍵的記錄。
使用數(shù)據(jù)庫(kù)唯一主鍵完成冪等性時(shí)需要注意的是,該主鍵一般來說并不是使用數(shù)據(jù)庫(kù)中自增主鍵,而是使用分布式 ID 充當(dāng)主鍵(可以參考 Java 中分布式 ID 的設(shè)計(jì)方案 這篇文章),這樣才能能保證在分布式環(huán)境下 ID 的全局唯一性。
而實(shí)際上生成這個(gè)主鍵的方式就是在當(dāng)請(qǐng)求的時(shí)候后,生成分布式唯一ID,然后當(dāng)做主鍵插入數(shù)據(jù)庫(kù),來保證唯一即可。
實(shí)現(xiàn)方式二
Token機(jī)制
Token機(jī)制,實(shí)際上也可以稱為 Token 令牌
- 服務(wù)端提供了發(fā)送token的接?。我們?cè)诜治鰳I(yè)務(wù)的時(shí)候,哪些業(yè)務(wù)是存在冪等問題的,就必須在 執(zhí)?業(yè)務(wù)前,先去獲取token,服務(wù)器會(huì)把token保存到redis中。(微服務(wù)肯定是分布式了,如果 單機(jī)就適?jvm緩存)。
- 然后調(diào)?業(yè)務(wù)接?請(qǐng)求時(shí),把token攜帶過去,?般放在請(qǐng)求頭部。
- 服務(wù)器判斷token是否存在redis中,存在表示第?次請(qǐng)求,這時(shí)把redis中的token刪除,繼續(xù)執(zhí)?業(yè)務(wù)。
- 如果判斷token不存在redis中,就表示是重復(fù)操作,直接返回重復(fù)標(biāo)記給client,這樣就保證了業(yè)務(wù)代碼,不被重復(fù)執(zhí)?。
實(shí)現(xiàn)方式三
數(shù)據(jù)庫(kù)樂觀鎖
數(shù)據(jù)庫(kù)樂觀鎖方案一般只能適用于執(zhí)行“更新操作”的過程,我們可以提前在對(duì)應(yīng)的數(shù)據(jù)表中多添加一個(gè)字段,充當(dāng)當(dāng)前數(shù)據(jù)的版本標(biāo)識(shí)。這樣每次對(duì)該數(shù)據(jù)庫(kù)該表的這條數(shù)據(jù)執(zhí)行更新時(shí),都會(huì)將該版本標(biāo)識(shí)作為一個(gè)條件,值為上次待更新數(shù)據(jù)中的版本標(biāo)識(shí)的值。
為了每次執(zhí)行更新時(shí)防止重復(fù)更新,確定更新的一定是要更新的內(nèi)容,我們通常都會(huì)添加一個(gè) version 字段記錄當(dāng)前的記錄版本,這樣在更新時(shí)候?qū)⒃撝祹?,那么只要?zhí)行更新操作就能確定一定更新的是某個(gè)對(duì)應(yīng)版本下的信息。
這樣的話,有了 version 的存在,這樣就能保住更新的冪等,多次更新對(duì)結(jié)果不會(huì)產(chǎn)生影響。
你還了解有哪些實(shí)現(xiàn)冪等性操作的方式呢?