成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

redis中如何解決分布式冪等問題

這篇文章給大家分享的是有關(guān)redis中如何解決分布式冪等問題的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

創(chuàng)新互聯(lián)建站專業(yè)為企業(yè)提供泌陽網(wǎng)站建設(shè)、泌陽做網(wǎng)站、泌陽網(wǎng)站設(shè)計、泌陽網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、泌陽企業(yè)網(wǎng)站模板建站服務(wù),十載泌陽做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。

一 背景

分布式系統(tǒng)由眾多微服務(wù)組成,微服務(wù)之間必然存在大量的網(wǎng)絡(luò)調(diào)用。下圖是一個服務(wù)間調(diào)用異常的例子,用戶提交訂單之后,請求到A服務(wù),A服務(wù)落單之后,開始調(diào)用B服務(wù),但是在A調(diào)用B的過程中,存在很多不確定性,例如B服務(wù)執(zhí)行超時了,RPC直接返回A請求超時了,然后A返回給用戶一些錯誤提示,但實際情況是B有可能執(zhí)行是成功的,只是執(zhí)行時間過長而已。

redis中如何解決分布式冪等問題

用戶看到錯誤提示之后,往往會選擇在界面上重復(fù)點擊,導(dǎo)致重復(fù)調(diào)用,如果B是個支付服務(wù)的話,用戶重復(fù)點擊可能導(dǎo)致同一個訂單被扣多次錢。不僅僅是用戶可能觸發(fā)重復(fù)調(diào)用,定時任務(wù)、消息投遞和機器重新啟動都可能會出現(xiàn)重復(fù)執(zhí)行的情況。在分布式系統(tǒng)里,服務(wù)調(diào)用出現(xiàn)各種異常的情況是很常見的,這些異常情況往往會使得系統(tǒng)間的狀態(tài)不一致,所以需要容錯補償設(shè)計,最常見的方法就是調(diào)用方實現(xiàn)合理的重試策略,被調(diào)用方實現(xiàn)應(yīng)對重試的冪等策略。

二 什么是冪等

對于冪等,有一個很常見的描述是:對于相同的請求應(yīng)該返回相同的結(jié)果,所以查詢類接口是天然的冪等性接口。舉個例子:如果有一個查詢接口是查詢訂單的狀態(tài),狀態(tài)是會隨著時間發(fā)生變化的,那么在兩次不同時間的查詢請求中,可能返回不一樣的訂單狀態(tài),這個查詢接口還是冪等接口嗎?

冪等的定義直接決定了我們?nèi)绾稳ピO(shè)計冪等方案,如果冪等的含義是相同請求返回相同結(jié)果,那實際上只需要緩存第一次的返回結(jié)果,即可在后續(xù)重復(fù)請求時實現(xiàn)冪等了。但問題真的有這么簡單嗎?

筆者更贊同這種定義:冪等指的是相同請求(identical request)執(zhí)行一次或者多次所帶來的副作用(side-effects)是一樣的。

引自:https://developer.mozilla.org/en-US/docs/Glossary/Idempotent An HTTP method is idempotent if an identical request can be made once or several times in a row with the same effect while leaving the server in the same state. In other words, an idempotent method should not have any side-effects (except for keeping statistics).

這個定義有一定的抽象,概括性比較強,在設(shè)計冪等方案時,其實就是將抽象部分具化。例如:什么是相同的請求?哪些情況會有副作用?該如何避免副作用?且看三部曲。

三 解決方案三部曲

不少關(guān)于冪等的文章都稱自己的方案是通用解決方案,但筆者卻認為,不同的業(yè)務(wù)場景下,相同請求和副作用都是有差異性的,不同的副作用需要不同的方案來解決,不存在完全通用的解決方案。而三部曲旨在提煉出一種思考模式,并舉例說明,在該思考模式下,更容易設(shè)計出符合業(yè)務(wù)場景的冪等解決方案。

第一部曲:識別相同請求

冪等是為了解決重復(fù)執(zhí)行同一請求的問題,那如何識別一個請求有沒有和之前的請求重復(fù)呢?有的方案是通過請求中的某個流水號字段來識別的,同一個流水號表示同一個請求。也有的方案是通過請求中某幾個字段甚至全部字段進行比較,從而來識別是否為同一個請求。所以在方案設(shè)計時,明確定義具體業(yè)務(wù)場景下什么是相同請求,這是第一部曲。

方案舉例:token機制識別前端重復(fù)請求

在一條調(diào)用鏈路的后端系統(tǒng)中,一般都可以通過上游系統(tǒng)傳遞的reqNo+source來識別是否是為重復(fù)的請求。如下圖,B系統(tǒng)是依賴于A系統(tǒng)傳遞的reqNo+source來識別相同請求的,但是A系統(tǒng)是直接和前端頁面交互的系統(tǒng),如何識別用戶發(fā)起的請求是相同的呢?比如用戶在支付界面上點擊了多次,A系統(tǒng)怎么識別這是一次重復(fù)操作呢?

redis中如何解決分布式冪等問題

前端可以在第一次點擊完成時,將按鈕設(shè)置為disable,這樣用戶無法在界面上重復(fù)點擊第二次,但這只是提升體驗的前端解決方案,不是真正安全的解決方案。

常見的服務(wù)端解決方案是采用token機制來實現(xiàn)防重復(fù)提交。如下圖,

redis中如何解決分布式冪等問題

(1)當用戶進入到表單頁面的時候,前端會從服務(wù)端申請到一個token,并保存在前端。

(2)當用戶第一次點擊提交的時候,會將該token和表單數(shù)據(jù)一并提交到服務(wù)端,服務(wù)端判斷該token是否存在,如果存在則執(zhí)行業(yè)務(wù)邏輯。

(3)當用戶第二次點擊提交的時候,會將該token和表單數(shù)據(jù)一并提交到服務(wù)端,服務(wù)端判斷該token是否存在,如果不存在則返回錯誤,前端顯示提交失敗。

這個方案結(jié)合前后端,從前端視角,這是用于防止重復(fù)請求,從服務(wù)端視角,這個用于識別前端相同請求。服務(wù)端往往基于類似于redis之類的分布式緩存來實現(xiàn),保證生成token的唯一性和操作token時的原子性即可。核心邏輯如下。

// SETNX keyName value: 如果key存在,則返回0,如果不存在,則返回1

// step1. 申請token
String token = generateUniqueToken();

// step2. 校驗token是否存在
if(redis.setNx(token, 1) == 1){
  // do business
} else {
 // 冪等邏輯
}

第二部曲:列出并減少副作用的分析維度

相同的請求重復(fù)執(zhí)行業(yè)務(wù)邏輯,如果處理不當,會給系統(tǒng)帶來副作用。那什么是副作用?就是業(yè)務(wù)無法接受的非預(yù)期結(jié)果。最常見的有重復(fù)入庫、數(shù)據(jù)被錯誤變更等,大多數(shù)冪等方案就是圍繞解決這類問題來設(shè)計的。而系統(tǒng)往往可能在多個維度都存在副作用,例如:

(1)調(diào)用下游維度:重復(fù)調(diào)用下游會怎樣?如果下游沒有冪等,重復(fù)調(diào)用會帶來什么副作用?

(2)返回上游維度:例如第一次返回上游異常,第二次返回上游被冪等了?會給上游帶來什么副作用?

(3)并發(fā)執(zhí)行維度:并發(fā)重復(fù)執(zhí)行會怎樣?會有什么副作用?

(4)分布式鎖維度:引入分布式鎖來防止并發(fā)執(zhí)行?但是如果鎖出現(xiàn)不一致性,會有什么副作用?

(5)交互時序維度:有沒有異步交互,是否存在時序問題?會有什么副作用?

(6)客戶體驗維度:從數(shù)據(jù)不一致到最終一致,必須在多少時間內(nèi)完成?如果該時間內(nèi)沒有完成,會有什么副作用?例如大量客訴(秉承客戶第一的原則,在支付寶,客訴量太大會定級為生產(chǎn)環(huán)境故障)。

(7)業(yè)務(wù)核對維度:重復(fù)調(diào)用是否存在覆蓋核對標識的情況,帶來無法正常核對的副作用?在金融系統(tǒng)中,資金鏈路無法核對是無法接受的。

(8)數(shù)據(jù)質(zhì)量維度:是否存在重復(fù)記錄?如果存在會有什么副作用?

redis中如何解決分布式冪等問題

上面是一些常見的分析維度,不同行業(yè)的系統(tǒng)中會存在不一樣的維度,盡可能地總結(jié)出這些維度,并列入系統(tǒng)分析時的checklist中,能夠更好地完善冪等解決方案。沒有副作用才算是完備的冪等解決方案,但是副作用的維度太多,會提高冪等方案的復(fù)雜度。所以在能夠達成業(yè)務(wù)的前提下,減少一些分析維度,能夠使得冪等方案實現(xiàn)起來更加經(jīng)濟有效。例如:如果有專門的冪等表存儲返回給上游的冪等結(jié)果,第(2)維度不用考慮了,如果用鎖來防止并發(fā),第(3)個維度不考慮了,如果用單機鎖代替分布式鎖,第(4)個維度不考慮了。

這是解決冪等問題的第二部曲:列出并減少副作用的分析維度。在這部曲中,涉及的解決方案往往是解決某一個維度的副作用問題,適合以通用組件的形式存在,作為團隊內(nèi)部的一個公共技術(shù)套路。

方案舉例:加鎖避免并發(fā)重復(fù)執(zhí)行

很多冪等解決方案都和防并發(fā)有關(guān),那么冪等和并發(fā)到底有什么關(guān)聯(lián)呢?兩者的聯(lián)系是:冪等解決的是重復(fù)執(zhí)行的問題,重復(fù)執(zhí)行既有串行重復(fù)執(zhí)行(例如定時任務(wù)),也有并發(fā)重復(fù)執(zhí)行。如果重復(fù)執(zhí)行的業(yè)務(wù)邏輯沒有共享變量和數(shù)據(jù)變更操作時,并發(fā)重復(fù)執(zhí)行是沒有副作用的,可以不考慮并發(fā)的問題。對于包含共享變量、涉及變更操作的服務(wù)(實際上這類服務(wù)居多),并發(fā)問題可能導(dǎo)致亂序讀寫共享變量,重復(fù)插入數(shù)據(jù)等問題。特別是并發(fā)讀寫共享變量,往往都是發(fā)生生產(chǎn)故障后才被感知到。

所以在并發(fā)執(zhí)行的維度,將并發(fā)重復(fù)執(zhí)行變成串行重復(fù)執(zhí)行是最好的冪等解決方案。支付寶最常見的方法就是:一鎖二判三更新,如下圖。當一個請求過來之后:一鎖,鎖住要操作的資源;二判,識別是否為重復(fù)請求(第一部曲要定義的問題)、判斷業(yè)務(wù)狀態(tài)是否正常;三更新:執(zhí)行業(yè)務(wù)邏輯。

redis中如何解決分布式冪等問題

小A:鎖可能造成性能影響,先判后鎖再執(zhí)行,可以提升效能。 大明:這樣可能會失去防并發(fā)的效果。還記得double check實現(xiàn)單例模式嗎?在加鎖前判斷了下,那加鎖后為啥還要判斷下?實際上第二次check才是必須的。想想看? 小A畫圖思考中... 小A:明白了,一鎖二判三更新,鎖和判的順序是不能變的,如果鎖沖突比較高,可以在鎖之前判斷下,提高效率,所以稱之為double check。 大明:是的,聰明。這兩個場景不一樣,但并發(fā)思路是一樣的。

private volatile static Girl theOnlyGirl;

// 實現(xiàn)單例時做了 double check
public static Girl getTheOnlyGirl() {

    if (theOnlyGirl == null) {   // 加鎖前check
        synchronized (Girl.class) {
            if (theOnlyGirl == null) {  // 加鎖后check
                theOnlyGirl = new Girl();    // 變更執(zhí)行
            }
        }
    }

    return theOnlyGirl;
}

鎖的實現(xiàn)可以是分布式鎖,也是可以是數(shù)據(jù)庫鎖。分布式鎖本身會帶來鎖的一致性問題,需要根據(jù)業(yè)務(wù)對系統(tǒng)穩(wěn)定性的要求來考量。支付寶的很多系統(tǒng)是通過在業(yè)務(wù)數(shù)據(jù)庫中新建一個鎖記錄表來實現(xiàn)業(yè)務(wù)鎖組件,其分表邏輯和業(yè)務(wù)表的分表邏輯一致,就可以實現(xiàn)單機數(shù)據(jù)庫鎖。如果沒有鎖組件,悲觀鎖鎖住業(yè)務(wù)單據(jù)也是可以滿足條件的,悲觀鎖要在事務(wù)中用select for update來實現(xiàn),要注意死鎖問題,且where條件中必須命中索引,否則會鎖表,不鎖記錄。

并發(fā)維度幾乎是一個分布式冪等的通用分析維度,所以一個通用的鎖組件是很有必要的。但這也只是解決了并發(fā)這一個維度的副作用。雖然沒有了并發(fā)重復(fù)執(zhí)行的情況,但串行重復(fù)執(zhí)行的情況依舊存在,重復(fù)執(zhí)行才是冪等核心要解決的問題,重復(fù)執(zhí)行如果還存在其它副作用,冪等問題就是沒有解決掉。

加鎖后業(yè)務(wù)的性能會降低,這個怎么解決?筆者認為,大多數(shù)情況下架構(gòu)的穩(wěn)定性比系統(tǒng)性能的優(yōu)先級更高,況且對于性能的優(yōu)化有太多地方可以去實現(xiàn),減少壞代碼、去除慢SQL、優(yōu)化業(yè)務(wù)架構(gòu)、水平擴展數(shù)據(jù)庫資源等方式。通過系統(tǒng)壓測來實現(xiàn)一個滿足SLA的服務(wù)才是評估全鏈路性能的正確方法。

第三部:識別細粒度副作用,針對性設(shè)計解決方案

在解決了部分維度的副作用之后,就需要針對單個粒度的副作用進行逐一識別并解決了。在數(shù)據(jù)質(zhì)量維度上,最大的一個副作用是重復(fù)數(shù)據(jù)。在交互維度上,最大的一個副作用是業(yè)務(wù)亂序執(zhí)行。一般這類問題不設(shè)計成通用組件,可以開發(fā)人員自由發(fā)揮。本節(jié)用兩個常見方案做為例子。

方案舉例1:唯一性約束避免重復(fù)落庫

在數(shù)據(jù)表設(shè)計時,設(shè)計兩個字段:source、reqNo,source表示調(diào)用方,seqNo表示調(diào)用方發(fā)送過來的請求號。source和reqNo設(shè)置為組合唯一索引,保證單據(jù)不會重復(fù)落兩次。如果調(diào)用方?jīng)]有source和reqNo這兩個字段,可以根據(jù)業(yè)務(wù)實際情況將請求中的某幾個業(yè)務(wù)參數(shù)生成一個md5作為唯一性字段落到唯一性字段中來避免重復(fù)落庫。

redis中如何解決分布式冪等問題

核心邏輯如下:

try {
    dao.insert(entity);    
    // do business
} catch (DuplicateKeyException e) {
    dao.select(param);
    // 冪等返回
}

這里直接insert單據(jù),若果成功則表示沒請求過,舉行執(zhí)行業(yè)務(wù)邏輯,如果拋出DuplicateKeyException異常,則表示已經(jīng)執(zhí)行過,做冪等返回,簡單的服務(wù)通過這種方式也可以識別是否為重復(fù)請求(第一部曲)。

利用數(shù)據(jù)庫唯一索引來避免重復(fù)記錄,需要注意以下幾個問題:

(1)因為存在讀寫分離的設(shè)計,有可能insert操作的是主庫,但select查詢的卻是從庫,如果主備同步不及時,有可能select查出來也是空的。

(2)在數(shù)據(jù)庫有Failover機制的情況下,如果一個城市出現(xiàn)自然災(zāi)害,很可能切換到另外一個城市的備用庫,那么唯一性約束可能就會出現(xiàn)失效的情況,比如并發(fā)場景下第一次insert是在杭州的庫,然后此時failover將庫切到上海了,再一次同樣的請求insert也是成功的。

(3)數(shù)據(jù)庫擴容場景下,因為分庫規(guī)則發(fā)生變化,有可能第一次insert操作是在A庫,第二次insert操作是在B庫,唯一索引同樣不起作用。

(4)有的系統(tǒng)catch的是SQLIntegrityConstraintViolationException,這個是完整性約束,包含了唯一性約束,如果未給一個必填字段設(shè)值,也會拋這個異常,所以應(yīng)該catch鍵重復(fù)異常DuplicateKeyException。

對于第(1)個問題,將insert 和select放在同一個事務(wù)中即可解決,對于(2)和(3),支付寶內(nèi)部為了應(yīng)對容量暴漲和FO,設(shè)計了一套基于數(shù)據(jù)復(fù)制技術(shù)的分布式數(shù)據(jù)平臺,這個case筆者了解不深,后續(xù)有機會再討論。

小A:如果我用唯一性約束來保證不會落重復(fù)數(shù)據(jù),是不是可以不加鎖防并發(fā)了? 大明:兩者沒有直接關(guān)系,加鎖防并發(fā)解決的是并發(fā)維度的副作用問題,唯一性約束只是解決重復(fù)數(shù)據(jù)這單個副作用的問題。如果沒有唯一性約束,串行重復(fù)執(zhí)行也會導(dǎo)致insert重復(fù)落數(shù)據(jù)的問題,唯一性約束本質(zhì)上解決的是重復(fù)數(shù)據(jù)問題,不是并發(fā)問題。

方案舉例2:狀態(tài)機約束解決亂序問題

一個業(yè)務(wù)的生命周期往往存在不同的狀態(tài),用狀態(tài)機來控制業(yè)務(wù)流程中的狀態(tài)轉(zhuǎn)換是不二之選。在實際業(yè)務(wù)中單向的狀態(tài)機是比較常用的,當狀態(tài)機處于下一個狀態(tài)時,是不能回到前面的狀態(tài)的。以下場景經(jīng)常會用到狀態(tài)機做校驗:

(1)調(diào)用方調(diào)用超時重試。

(2)消息投遞超時重試。

(3)業(yè)務(wù)系統(tǒng)發(fā)起多個任務(wù),但是期待按照發(fā)起順序有序返回。

對于這種類問題,一般是在處理前先判斷狀態(tài)是否符合預(yù)期,如果符合預(yù)期再執(zhí)行業(yè)務(wù)。當業(yè)務(wù)執(zhí)行完成后,變更狀態(tài)時還會采取類似于于樂觀鎖的方式兜底校驗,例如,M狀態(tài)只能從N狀態(tài)轉(zhuǎn)換而來,那么更新單據(jù)時,會在sql中做狀態(tài)校驗。

update apply set status = 'M' where status = 'N'

如果狀態(tài)被設(shè)計成可逆的,就有可能產(chǎn)生ABA問題。即在update之前,狀態(tài)有可能做過這樣的變更:N -> M -> N。所以狀態(tài)機設(shè)成單向流轉(zhuǎn)是比較合理的。

四 總結(jié)

本文首先引出了冪等的定義:相同請求無副作用,然后提出了設(shè)計冪等方案的三部曲,并舉例說明。設(shè)計者要能夠清晰地定義相同請求,并且采用通用組件減少一些副作用的分析維度,再針對具體的副作用設(shè)計相應(yīng)的解決方案,直至沒有任何副作用,才是真正完備的冪等解決方案。在實際業(yè)務(wù)中,實現(xiàn)三部曲不一定是嚴格的先后順序,但只要按照這三部曲來構(gòu)思方案,必能開拓思路,化繁為簡。 redis中如何解決分布式冪等問題

感謝各位的閱讀!關(guān)于“redis中如何解決分布式冪等問題”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

網(wǎng)站標題:redis中如何解決分布式冪等問題
本文來源:http://jinyejixie.com/article10/jjhego.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營銷推廣網(wǎng)站收錄、微信公眾號、網(wǎng)站導(dǎo)航靜態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

微信小程序開發(fā)
东乌珠穆沁旗| 皮山县| 铜鼓县| 仁化县| 扎兰屯市| 巴林右旗| 饶阳县| 姜堰市| 靖州| 汨罗市| 天长市| 健康| 龙州县| 宜丰县| 岗巴县| 临邑县| 广南县| 陇南市| 二手房| 灵石县| 拜泉县| 荆州市| 宜兴市| 桂林市| 郴州市| 若尔盖县| 宁武县| 台北县| 曲沃县| 哈尔滨市| 团风县| 潮安县| 资源县| 信丰县| 南城县| 古交市| 黑水县| 宁津县| 健康| 剑河县| 屯留县|