分布式下的區(qū)域問(wèn)題,讓我們大戰(zhàn)了300回合
本文轉(zhuǎn)載自微信公眾號(hào)「蘇三說(shuō)技術(shù)」,作者蘇三說(shuō)技術(shù)。轉(zhuǎn)載本文請(qǐng)聯(lián)系蘇三說(shuō)技術(shù)公眾號(hào)。
前言
我最近參與了公司的一個(gè)新項(xiàng)目,需要通過(guò)openapi接口把接入方的數(shù)據(jù),比如:企業(yè)、訂單、合同、物流等,同步到我們平臺(tái),然后我們平臺(tái)給他們提供金融能力。
由于我方跟對(duì)接方不在同一個(gè)城市,為了提高工作效率,雙方進(jìn)行了多次在線視頻溝通。剛開始比較順利,沒(méi)想到在溝通企業(yè)信息上傳接口時(shí),接口文檔中有個(gè)非常不起眼的企業(yè)注冊(cè)地id字段,讓我們一下子進(jìn)入了僵局。
到底是怎么回事呢?
1.地區(qū)問(wèn)題
在我們平臺(tái)的企業(yè)表中有一個(gè)企業(yè)注冊(cè)地id字段,是必填的,用戶在注冊(cè)企業(yè)的頁(yè)面需要選擇一個(gè)地區(qū),作為該企業(yè)的注冊(cè)地,實(shí)際上數(shù)據(jù)庫(kù)保存的是地區(qū)的id。
如果該企業(yè)注冊(cè)成功了,會(huì)在企業(yè)詳情頁(yè)面上展示該地區(qū)名稱。當(dāng)然我們系統(tǒng)的后臺(tái)邏輯是先通過(guò)地區(qū)id到地區(qū)表反查出地區(qū)名稱,然后在用戶界面中展示出來(lái)。
為了跟企業(yè)表保持一致,我方在定義接口文檔時(shí),企業(yè)注冊(cè)地id字段也做成必填了。
當(dāng)時(shí)的情況是這樣的:我方地區(qū)表中有id、地區(qū)名稱、國(guó)標(biāo)碼、等級(jí)等字段,但這里的id,是我方數(shù)據(jù)庫(kù)的主鍵,對(duì)接方系統(tǒng)中肯定是沒(méi)有的。對(duì)接方系統(tǒng)中也有一套地區(qū)表,不過(guò)id是他們的數(shù)據(jù)庫(kù)id,他們的表中也有地區(qū)名稱、國(guó)標(biāo)碼、等級(jí)等字段。
所以他們系統(tǒng)內(nèi)部需要經(jīng)過(guò)一番轉(zhuǎn)換,才能把我們所需的地區(qū)id傳給我們。
1.1 持久化本地地區(qū)表
其實(shí)這個(gè)項(xiàng)目我是中途才加入的,之前在處理別的事情,我加入的時(shí)候接口文檔已經(jīng)定義好了。
我方跟對(duì)接方進(jìn)行第二次在線溝通的時(shí)候,雙方一起過(guò)接口文檔的細(xì)節(jié),包括:接口的作用、每個(gè)參數(shù)的含義,以及他們是否有值傳過(guò)來(lái)等等。
其中過(guò)到企業(yè)信息上傳接口時(shí),接口文檔中有個(gè)企業(yè)注冊(cè)地id字段,對(duì)方?jīng)]法傳值過(guò)來(lái)。為了解決這個(gè)問(wèn)題,我方第一版的方案是:
- 對(duì)接方調(diào)用我方地區(qū)查詢接口,通過(guò)多次分頁(yè)查詢,最終能獲取我方所有地區(qū)數(shù)據(jù),落庫(kù)到他們本地的地區(qū)表。
- 他們?cè)谡{(diào)用我方企業(yè)信息上傳接口之前,先查詢本地的地區(qū)表,轉(zhuǎn)換成我方所需要的地區(qū)id。
在討論的過(guò)程中,對(duì)接方覺(jué)得他們也是平臺(tái),不應(yīng)該做這些額外的事情。所以在那次會(huì)議中,雙方針對(duì)這個(gè)問(wèn)題,誰(shuí)也沒(méi)有說(shuō)服誰(shuí),最終也沒(méi)能達(dá)成共識(shí)。
后來(lái),我思考了一下,確實(shí)這個(gè)方案太過(guò)理想化了,沒(méi)有真正站到用戶的角度思考問(wèn)題,忽略了很多細(xì)節(jié)??赡芨臋n設(shè)計(jì)者不對(duì)地區(qū)表不太熟悉有關(guān)系。
1.2 按名稱調(diào)用地區(qū)查詢接口
那次會(huì)議當(dāng)中,我們這邊的幾位同事,短暫的討論了一下。既然對(duì)接方不愿意接受在他們本地持久化地區(qū)表,我們就退而求其次,不要求他們持久化了。這時(shí)我們這邊有個(gè)同事提出,改成按名稱調(diào)用地區(qū)查詢接口,反查出地區(qū)id,具體方案如下:
這個(gè)方案表面上看起來(lái)沒(méi)有問(wèn)題,但我之前負(fù)責(zé)過(guò)區(qū)域相關(guān)功能,我知道,就怕出現(xiàn)如下情況:
- 如果對(duì)接方傳的地區(qū)名稱不完整,比如:本來(lái)是成都市,實(shí)際上傳的成都。這樣,我們地區(qū)查詢接口,需要做模糊匹配,如果并發(fā)調(diào)用接口可能影響接口性能。
- 如果輸入關(guān)鍵字北京市,在我們這邊的地區(qū)表中,可以找到兩條數(shù)據(jù),一條是跟省級(jí)別一樣的,另一條是跟市級(jí)別一樣的。到底對(duì)應(yīng)哪條數(shù)據(jù)呢?
所以我當(dāng)時(shí)把這兩個(gè)問(wèn)題拋出來(lái)了,不建議使用地區(qū)名稱查詢。
1.3 按國(guó)標(biāo)碼調(diào)用地區(qū)查詢接口
那個(gè)同事聽完之后,也覺(jué)得用地區(qū)名稱查詢有點(diǎn)不靠譜。他馬上修改方案,改成使用地區(qū)的國(guó)標(biāo)碼查詢地區(qū)id,具體方案如下:
由于當(dāng)時(shí)討論時(shí)間非常短,我們沒(méi)來(lái)得及考慮太多,暫且打算用這套方案。
1.4 企業(yè)上傳接口入?yún)鲊?guó)標(biāo)碼
過(guò)了一會(huì)兒,雙方繼續(xù)過(guò)接口文檔,重新討論企業(yè)信息上傳接口中企業(yè)注冊(cè)地id字段傳值問(wèn)題。
他們?cè)谡{(diào)企業(yè)信息上傳接口之前,先調(diào)一下我們地區(qū)查詢接口,查出地區(qū)id,入?yún)⑹菄?guó)標(biāo)碼。然后再將這個(gè)地區(qū)id,在企業(yè)信息上傳接口中傳過(guò)來(lái)。
對(duì)接方仔細(xì)聽了我們的方案,猶豫了一下,他們覺(jué)得沒(méi)有必要再調(diào)一次地區(qū)查詢接口,雙方都使用國(guó)標(biāo)碼不就行了?
他們的想法是:在企業(yè)信息上傳接口中,入?yún)⒂善髽I(yè)注冊(cè)地id改成企業(yè)注冊(cè)地國(guó)標(biāo)碼,由于國(guó)標(biāo)碼是國(guó)家統(tǒng)一的唯一編碼,雙方肯定是一樣,能保證數(shù)據(jù)的一致性。
2.想起了一個(gè)問(wèn)題
說(shuō)實(shí)話,如果你沒(méi)接觸過(guò)地區(qū)功能的話,大部分人可能會(huì)同意這套方案的。
但比較巧合的是我之前正好接觸過(guò)類似的功能,當(dāng)時(shí)我突然想起了一個(gè)問(wèn)題:雙方數(shù)據(jù)的一致性如何保證?
我們都知道,由于國(guó)家的發(fā)展,有些城市可能會(huì)改名,比如:襄樊改成了襄陽(yáng),另外有時(shí)候多個(gè)地級(jí)市合并成一個(gè)市,這樣國(guó)標(biāo)碼會(huì)變化,所以國(guó)家統(tǒng)計(jì)網(wǎng)每年都會(huì)調(diào)整地區(qū)名稱和國(guó)標(biāo)碼。
我方的地區(qū)表是兩年之前創(chuàng)建的,數(shù)據(jù)初始化好之后沒(méi)有就更新過(guò)。
而對(duì)接方不是跟我們?cè)谕粫r(shí)刻初始化的數(shù)據(jù),而且他們會(huì)定期更新地區(qū)數(shù)據(jù),這樣就導(dǎo)致了兩邊的數(shù)據(jù)不一致。如果對(duì)接方的業(yè)務(wù)表單中使用了新加的城市名和國(guó)標(biāo)碼,而這些信息在我方的地區(qū)表中沒(méi)有,就無(wú)法查詢出我方所需的地區(qū)id。
這種情況該怎么辦?
2.1 雙方同一時(shí)刻更新地區(qū)表
顯然上面的問(wèn)題是一個(gè)非常棘手的問(wèn)題,這時(shí)候有些小伙伴可能會(huì)說(shuō):雙方使用job同一時(shí)刻更新地區(qū)表,不就能解決問(wèn)題了?
我不太贊成這種方案,主要原因如下:
我方僅跟這個(gè)對(duì)接方有個(gè)同步執(zhí)行的job,沒(méi)問(wèn)題。但如果還有其他的對(duì)接方,也需要調(diào)用企業(yè)信息上傳接口,是不是也要整一個(gè)job,而且還要求大家都同一時(shí)刻執(zhí)行,耦合性太大了。
如果我方和對(duì)接方同時(shí)執(zhí)行job,但萬(wàn)一有任意一方執(zhí)行失敗了,也會(huì)導(dǎo)致數(shù)據(jù)不一致的情況。如果恰好這時(shí)候?qū)臃皆谡{(diào)用企業(yè)信息上傳接口,會(huì)不會(huì)出問(wèn)題?
2.2 以一方的地區(qū)數(shù)據(jù)為準(zhǔn)?
上面的雙方同一時(shí)刻更新地區(qū)表的方案確實(shí)有點(diǎn)不靠譜,但有些讀者可能會(huì)問(wèn),以一方的地區(qū)數(shù)據(jù)為準(zhǔn),另一方把數(shù)據(jù)同步過(guò)來(lái)不就行了。具體方案如下:
這個(gè)方案其實(shí)跟之前我方給出的第一個(gè)方案很相似,已經(jīng)被對(duì)接方拒絕了。站在他們的角度來(lái)說(shuō),確實(shí)沒(méi)有必要因?yàn)樯蟼髌髽I(yè)信息,而保存我們的地區(qū)數(shù)據(jù)。
說(shuō)實(shí)話,即使他們同意了,這種跨公司跨系統(tǒng)的數(shù)據(jù)一致性問(wèn)題,也不好保證,因?yàn)槿绻麑?duì)接方調(diào)用我們的地區(qū)接口失敗了,此時(shí),正好在上傳企業(yè)信息,是不是也有問(wèn)題?
3.其他解決方案
其實(shí),我們當(dāng)時(shí)為了解決問(wèn)題,還穿插著討論過(guò)這些方案。
3.1 上傳的數(shù)據(jù)存快照
我當(dāng)時(shí)提出既然是保存對(duì)接方的數(shù)據(jù),為啥不能存快照呢?我們可以把數(shù)據(jù)寫到mongodb,數(shù)據(jù)格式用json,簡(jiǎn)單又高效。我的方案是:
我們自己的業(yè)務(wù)數(shù)據(jù)存到mysql的業(yè)務(wù)表,而對(duì)接方的數(shù)據(jù)存在mongodb,互不干擾。
看起來(lái),沒(méi)有問(wèn)題。
但是,當(dāng)時(shí)產(chǎn)品說(shuō):銀行那邊規(guī)定,審查數(shù)據(jù)時(shí)只看我們mysql的業(yè)務(wù)表,其他的數(shù)據(jù)源不看。
好吧,不得不承認(rèn)銀行惹不起。
3.2 人工更新數(shù)據(jù)
另外一個(gè)同事的想法是,先讓他們調(diào)用企業(yè)信息上傳接口,如果發(fā)現(xiàn)有地區(qū)問(wèn)題,我們手動(dòng)幫他們調(diào)整地區(qū)表的數(shù)據(jù)。
具體方案如下:
如果調(diào)用企業(yè)信息上傳接口時(shí),出現(xiàn)地區(qū)不存在的情況,則發(fā)報(bào)警郵件給指定人員。然后,指定人員手動(dòng)新增或修改相關(guān)的地區(qū)數(shù)據(jù)。
這套方案看起來(lái)也可以,不過(guò)有個(gè)比較坑爹的地方就是,就怕在下班或者周末的時(shí)候出問(wèn)題,反正我是不愿意去做這個(gè)事情的,你愿意嗎?
3.3 提供更新接口
除此之外,我們還相關(guān)這套方案:對(duì)接方在調(diào)我們企業(yè)信息上傳接口之前,先調(diào)我們地區(qū)查詢接口查一下數(shù)據(jù)是否存在,如果不存在,則保存地區(qū)接口(保存包括:新增和修改),如果存在,則正常上傳數(shù)據(jù)。
具體方案如下:
這個(gè)方案還可以簡(jiǎn)化一下:
將查詢并保存地區(qū)的邏輯可以放到企業(yè)信息上傳接口中,這樣對(duì)接方肯定非常高興,對(duì)他們來(lái)說(shuō)是透明的,地區(qū)問(wèn)題不存在了。
但產(chǎn)品覺(jué)得地區(qū)是我們的基礎(chǔ)數(shù)據(jù),處于安全考慮,不能提供入口給他們修改,不然以后可能會(huì)亂套的。
這樣不行,那也不行。我們一下子進(jìn)入了困境,但為了不影響整體進(jìn)度,只能先記錄一下問(wèn)題,然后跳過(guò)這個(gè)問(wèn)題,繼續(xù)討論其他字段了。
4.如何解決這個(gè)問(wèn)題?
我當(dāng)天晚上思考了良久,第二天早上,發(fā)現(xiàn)跟我們老大的想法不謀而合。得出的結(jié)論是,既然存在差異化,沒(méi)辦法避免,我們就要從系統(tǒng)設(shè)計(jì)上接受差異化。在企業(yè)信息上傳接口中增加兩個(gè)字段:企業(yè)注冊(cè)地國(guó)標(biāo)碼 和 地區(qū)名稱,對(duì)接方改成傳入這兩個(gè)字段,具體方案如下:
- 在我方的企業(yè)表中增加地區(qū)名稱字段,是非必填的,同時(shí)把之前的地區(qū)id字段也改成非必填。
- 對(duì)接方在調(diào)用我方企業(yè)信息上傳接口時(shí),同時(shí)傳入地區(qū)國(guó)標(biāo)碼和地區(qū)名稱。
- 我方企業(yè)信息上傳接口中判斷,如果通過(guò)國(guó)標(biāo)碼能夠找到地區(qū)id,則將地區(qū)id寫入db,如果找不到,則將地區(qū)名稱寫入db。
我們?cè)u(píng)估了一下影響范圍,在企業(yè)表中的地區(qū)字段,只做展示用,沒(méi)有修改入口,所以上面的這套方案是可行的。
后來(lái),再次跟對(duì)接方在線溝通時(shí),把我們的這套方案告訴他們了,他們非常贊同。
5 總結(jié)
雖說(shuō)這個(gè)地區(qū)問(wèn)題,在眾多技術(shù)問(wèn)題中不值得一提。但是我仔細(xì)思考了一下,還是有一些寶貴的經(jīng)驗(yàn)值得總結(jié)一下的,給有需要的小伙伴一個(gè)參考。
5.1 要從用戶的角度設(shè)計(jì)接口
在設(shè)計(jì)接口文檔時(shí),要真正做到從用戶的角度出發(fā)。
尤其是像這種openapi接口,定義的參數(shù)應(yīng)該盡量選擇通用的,大家都認(rèn)可的參數(shù),避免出現(xiàn)我方定制化的參數(shù),比如:地區(qū)id。
盡量減少用戶的復(fù)雜度,讓他們調(diào)用接口時(shí)更簡(jiǎn)單一些。
5.2 技術(shù)方案要有包容性
技術(shù)方案要有包容性,不是非黑即白,需要有柔性的思想。在分布式環(huán)境中,如果去一味地追求數(shù)據(jù)的強(qiáng)一致性,不會(huì)有太好的結(jié)果。就像高并發(fā)下的商品秒殺系統(tǒng),如果非要用同步方案去實(shí)現(xiàn),系統(tǒng)最終可能會(huì)掛掉,更好的方案其實(shí)是改成異步隊(duì)列處理。
我方和對(duì)接方都有地區(qū)表,數(shù)據(jù)很難保證完全一致,我們不要為了一致性而一致性,這樣會(huì)適得其反。為了工作能夠順利進(jìn)行下去,必然有一方要妥協(xié),我的建議是openapi接口方做妥協(xié),這種技術(shù)方案才夠通用。
5.3 沒(méi)有最好的方案,只有最適合的
我方最后的那個(gè)方案,其實(shí)并沒(méi)有完全解決地區(qū)id找不到的問(wèn)題,但是從業(yè)務(wù)的角度來(lái)看,即使沒(méi)有地區(qū)id,有地區(qū)名稱也是一樣的。很顯然,最后的方案是非常適合我們實(shí)際業(yè)務(wù)場(chǎng)景的。
所以沒(méi)有最好的方案,只有最適合業(yè)務(wù)場(chǎng)景的。
5.4 進(jìn)行有效的溝通
在跟對(duì)接方在線溝通時(shí),不要因?yàn)槟硞€(gè)問(wèn)題卡殼了,而一直僵持下去。如果當(dāng)時(shí)沒(méi)有好的技術(shù)方案,可以先選擇暫時(shí)跳過(guò)這個(gè)問(wèn)題,而溝通其他的內(nèi)容。后面我們?cè)偎较聠为?dú)花時(shí)間,仔細(xì)思考當(dāng)時(shí)的問(wèn)題,從而能夠提出更合理的方案。
5.5 技術(shù)是為業(yè)務(wù)服務(wù)的
本文的這個(gè)地區(qū)問(wèn)題,咋一看比較簡(jiǎn)單。如果一細(xì)想,會(huì)發(fā)現(xiàn)里面有點(diǎn)東西。再加上各種外部因素的限制,你會(huì)發(fā)現(xiàn)分布式的環(huán)境中保證地區(qū)數(shù)據(jù)一致性,并不是那么好實(shí)現(xiàn)。
整個(gè)過(guò)程當(dāng)中,我們提出了很多種技術(shù)方案,有些方案看似可以完美解決問(wèn)題,但都被我們實(shí)際的業(yè)務(wù)場(chǎng)景給否定了。
技術(shù)是為業(yè)務(wù)服務(wù)的,技術(shù)雖說(shuō)非常重要,但是如果離開了業(yè)務(wù)都是紙上談兵。