日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
Java多線程并發(fā)數(shù)據(jù)錯(cuò)亂了,接口冪等性如何設(shè)計(jì)?

業(yè)務(wù)背景

首先給大家說(shuō)說(shuō),假如說(shuō)要是我們線上系統(tǒng)的核心接口要是沒(méi)有冪等性保障機(jī)制的話,可能會(huì)出現(xiàn)什么情況?

成都創(chuàng)新互聯(lián)公司是專業(yè)的烏拉特后網(wǎng)站建設(shè)公司,烏拉特后接單;提供網(wǎng)站制作、成都網(wǎng)站建設(shè),網(wǎng)頁(yè)設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行烏拉特后網(wǎng)站開發(fā)網(wǎng)頁(yè)制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛(ài)的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來(lái)合作!

其實(shí)非常簡(jiǎn)單,假設(shè)你有一個(gè)系統(tǒng),他有一個(gè)接口,這個(gè)接口接受請(qǐng)求的時(shí)候假設(shè)會(huì)在數(shù)據(jù)庫(kù)里插入一條數(shù)據(jù),正常情況下一個(gè)用戶對(duì)這個(gè)接口發(fā)起一次請(qǐng)求應(yīng)該就只有一條數(shù)據(jù),結(jié)果可能某一天你會(huì)發(fā)現(xiàn)這個(gè)用戶通過(guò)這個(gè)接口插入了多條數(shù)據(jù)。

如下圖 1 所示:

圖 1

初版防重代碼

那么為什么會(huì)這樣呢?其實(shí)我們一般這類系統(tǒng)接口,但凡是寫的稍微好一點(diǎn)的,都會(huì)在接口里加入防重代碼。

就是會(huì)有代碼判斷一下,當(dāng)前你要寫入的這條數(shù)據(jù)是否存在,如果他要是不存在的話,就會(huì)進(jìn)行插入,但是如果他存在的話就不會(huì)允許你重復(fù)插入的。

這種防重代碼如下所示:

public void business(Request request) {   
// 1、先根據(jù)請(qǐng)求參數(shù)在db里查詢一下這條數(shù)據(jù)
Data data = findData(request);

// 2、如果這條數(shù)據(jù)在db里已經(jīng)存在了,此時(shí)就直接返回了
if(data != null) {
return;
}

// 3、如果要是這條數(shù)據(jù)在db里不存在,此時(shí)就會(huì)執(zhí)行數(shù)據(jù)插入邏輯了
insertData(request);
}

結(jié)合上面這段代碼的防重邏輯,我們可以看下圖 2 的運(yùn)行邏輯展示:

圖 2

在插入數(shù)據(jù)之前一定會(huì)先根據(jù)請(qǐng)求參數(shù)查詢這條數(shù)據(jù),如果查詢到了,則此時(shí)直接返回不會(huì)重復(fù)插入,但是如果沒(méi)有查詢到這條數(shù)據(jù),則此時(shí)會(huì)插入這條數(shù)據(jù)。

那么大家可能問(wèn)題來(lái)了,那既然都已經(jīng)有這個(gè)防重邏輯了,即使你用相同的請(qǐng)求參數(shù)重復(fù)多次調(diào)用這個(gè)接口插入數(shù)據(jù),也不應(yīng)該重復(fù)插入數(shù)據(jù)啊!

按說(shuō)確實(shí)是這樣子的,但是凡事總有例外,那就是大名鼎鼎的瞬時(shí)重試+多線程并發(fā)問(wèn)題。

瞬時(shí)重試+多線程并發(fā)問(wèn)題分析

下面我們給大家解釋一下,在上述的代碼防重邏輯下,如果要是短時(shí)間內(nèi)用戶用相同的請(qǐng)求參數(shù)重復(fù)的發(fā)起了兩次請(qǐng)求,為什么會(huì)穿透防重邏輯,在數(shù)據(jù)庫(kù)里插入兩條一樣的數(shù)據(jù)。

大家要打起且精神來(lái),仔細(xì)來(lái)看這個(gè)過(guò)程了, 首先用戶可能會(huì)因?yàn)檫^(guò)于激動(dòng)、手抖或者是網(wǎng)絡(luò)抽風(fēng)等各種原因,在一瞬間發(fā)起兩次請(qǐng)求參數(shù)完全相同的請(qǐng)求。

如下圖 3 所示:

圖 3

接著呢,這兩個(gè)請(qǐng)求到了我們的系統(tǒng)后,其實(shí)是分別由一個(gè)線程來(lái)處理的,不管你是用 tomcat 部署提供的 controller 接口,還是基于 dubbo 提供的 rpc 接口,其實(shí)每個(gè)請(qǐng)求過(guò)來(lái)都是由一個(gè)獨(dú)立的線程來(lái)處理的。

如下圖 4 所示:

圖 4

接著呢,這兩個(gè)線程會(huì)并發(fā)的運(yùn)行相同的一段代碼邏輯,就是先根據(jù)請(qǐng)求參數(shù)查詢這條數(shù)據(jù)是否存在,存在就返回,不存在就進(jìn)行插入。

這個(gè)時(shí)候可能會(huì)出現(xiàn)一個(gè)問(wèn)題,因?yàn)槭嵌嗑€程并發(fā),所以很可能這兩個(gè)線程會(huì)同時(shí)執(zhí)行數(shù)據(jù)查詢邏輯,但是他們倆同時(shí)執(zhí)行數(shù)據(jù)查詢邏輯的時(shí)候,有一個(gè)問(wèn)題,那就是此時(shí)數(shù)據(jù)庫(kù)里沒(méi)數(shù)據(jù)啊!

所以說(shuō),這兩個(gè)線程并發(fā)運(yùn)行,完全可能會(huì)同時(shí)發(fā)現(xiàn)從數(shù)據(jù)庫(kù)里查詢出來(lái)的數(shù)據(jù)是空的。

如下圖 5 所示:

圖 5

然后這個(gè)時(shí)候,兩個(gè)線程既然發(fā)現(xiàn)自己查詢到的數(shù)據(jù)都是空的,那當(dāng)然都可以去插入數(shù)據(jù)了。

所以此時(shí)這兩個(gè)線程會(huì)基于這個(gè)請(qǐng)求參數(shù)分別插入一條數(shù)據(jù),而這條數(shù)據(jù)其實(shí)對(duì)于業(yè)務(wù)來(lái)說(shuō)是完全重復(fù)的,因?yàn)檎?qǐng)求參數(shù)是完全相同的。

如下圖 6 所示:

圖 6

這個(gè)時(shí)候就會(huì)導(dǎo)致本次數(shù)據(jù)重復(fù)問(wèn)題了,針對(duì)這種情況,我們一般把這種接口稱之為沒(méi)有冪等性。

因?yàn)槿绻粋€(gè)接口是有冪等性的,其實(shí)對(duì)于這個(gè)接口如果說(shuō)用相同的參數(shù)發(fā)起請(qǐng)求,那肯定是只會(huì)有一條數(shù)據(jù),不可能會(huì)有重復(fù)數(shù)據(jù)的,這才叫做冪等性。

而現(xiàn)在的問(wèn)題是,這個(gè)接口用相同的請(qǐng)求參數(shù)發(fā)起多次,結(jié)果數(shù)據(jù)有重復(fù)了,此時(shí)接口就沒(méi)有冪等性。

數(shù)據(jù)庫(kù)唯一索引實(shí)現(xiàn)冪等

針對(duì)上述這種接口冪等問(wèn)題,其實(shí)比較簡(jiǎn)單的一種解決方案,就是基于我們依賴的數(shù)據(jù)庫(kù)去實(shí)現(xiàn)冪等。

也就是說(shuō),用數(shù)據(jù)庫(kù)的唯一索引來(lái)實(shí)現(xiàn)即可,如果我們要是基于請(qǐng)求中的某一個(gè)或者多個(gè)業(yè)務(wù)字段組成一個(gè)唯一索引,那么其實(shí)你要往數(shù)據(jù)庫(kù)中用相同參數(shù)插入重復(fù)數(shù)據(jù),那就是不可能的。

因?yàn)閿?shù)據(jù)庫(kù)層面就會(huì)阻止你插入的,唯一索引會(huì)確保這一點(diǎn),你要重復(fù)插入,他就會(huì)拋異常。

如下 7 所示:

圖 7

分布式鎖實(shí)現(xiàn)冪等

但是很多時(shí)候我們會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題,那就是我們可能不一定說(shuō)每次都可以依賴數(shù)據(jù)庫(kù)的 唯一索引實(shí)現(xiàn)這種冪等性。

因?yàn)橛锌赡苣阍跇I(yè)務(wù)邏輯里,除了依賴數(shù)據(jù)庫(kù)以外,還依賴了別的服務(wù)接口,或者是 elasticsearch、redis 等多種數(shù)據(jù)存儲(chǔ),也可能是依賴了數(shù)據(jù)庫(kù)中的多張表里的數(shù)據(jù),你不可能每張表都做一個(gè)唯一索引來(lái)確保冪等性。

所以對(duì)于有復(fù)雜業(yè)務(wù)邏輯的接口來(lái)說(shuō),要確保冪等性,往往需要引入一個(gè)關(guān)鍵組件,那就是分布式鎖。

所謂的分布式鎖,意思就是依賴外部的某個(gè)系統(tǒng)來(lái)加一把鎖,鎖加了以后后續(xù)還可以釋放這把鎖,現(xiàn)在比較常見(jiàn)的分布式鎖實(shí)現(xiàn)主要是依賴 redis 和 zookeeper 這兩個(gè)來(lái)實(shí)現(xiàn)的,我們這里就以 redis 分布式鎖來(lái)舉例說(shuō)明。

先往簡(jiǎn)單了說(shuō),我們可以在接口的入口代碼處,基于 redis 加一把分布式的鎖,這個(gè)時(shí)候只有一個(gè)線程可以成功加鎖。

加鎖之后,就這一個(gè)線程就可以去查詢這條數(shù)據(jù)是否存在,如果不存在,就可以插入一條數(shù)據(jù)進(jìn)去,然后再釋放鎖,在這個(gè)過(guò)程中,另外一個(gè)線程因?yàn)楂@取不到 redis 分布式鎖,所以只能干等著。

如下圖 8 所示:

圖 8

等第一個(gè)線程加鎖,然后查詢數(shù)據(jù),發(fā)現(xiàn)數(shù)據(jù)不存在,接著插入一條數(shù)據(jù),最后釋放鎖之后,接著第二個(gè)線程就才能得到機(jī)會(huì)再次加鎖,接著第二個(gè)線程加鎖后查詢數(shù)據(jù),發(fā)現(xiàn)數(shù)據(jù)已經(jīng)存在了,此時(shí)他就會(huì)直接返回,不會(huì)重復(fù)插入數(shù)據(jù)了。

如下圖 9 所示:

圖 9

如上圖,大家可以發(fā)現(xiàn),只要在核心接口的入口處加一把分布式鎖,就可以實(shí)現(xiàn)多線程并發(fā)下,復(fù)雜業(yè)務(wù)邏輯不會(huì)被重復(fù)執(zhí)行了,而且不依賴數(shù)據(jù)庫(kù)某個(gè)表的唯一索引,只要基于 redis 實(shí)現(xiàn)加鎖和釋放鎖就可以了。

而至于 redis 分布式鎖是如何實(shí)現(xiàn)的,就不在本文的討論中了,我們這次主要是給大家先分析一下線上系統(tǒng)接口的冪等問(wèn)題,當(dāng)沒(méi)有冪等性的時(shí)候,接口是如何在多線程并發(fā)場(chǎng)景下出現(xiàn)數(shù)據(jù)重復(fù)問(wèn)題的。

總結(jié)

然后我們分析了,如果要是基于數(shù)據(jù)庫(kù)表加一個(gè)唯一索引,就可以實(shí)現(xiàn)接口冪等了 ,可是如果業(yè)務(wù)邏輯過(guò)于復(fù)雜,有很多數(shù)據(jù)存儲(chǔ),或者涉及很多表,此時(shí)就不能單單依賴一個(gè)唯一索引了,需要依靠在接口入口處加分布式鎖,然后才可以解決復(fù)雜接口的冪等性。


分享名稱:Java多線程并發(fā)數(shù)據(jù)錯(cuò)亂了,接口冪等性如何設(shè)計(jì)?
分享網(wǎng)址:http://m.5511xx.com/article/coshoee.html