在前面我們說了微服務(wù)的兩個(gè)痛點(diǎn):微服務(wù)的職責(zé)劃分和微服務(wù)的粒度拆分痛點(diǎn),這里接著聊剩下的痛點(diǎn):
一、沒人知道系統(tǒng)整體整體架構(gòu)的全貌
不知道大家有沒有碰到過這種情況:每隔幾個(gè)月或半年,大領(lǐng)導(dǎo)就會(huì)發(fā)話讓我們匯報(bào)下每個(gè)部門的微服務(wù)數(shù)量、公司微服務(wù)總數(shù)量、每個(gè)微服務(wù)都用來做什么等情況。因?yàn)槠髽I(yè)微服務(wù)數(shù)較多,所以每次給大領(lǐng)導(dǎo)匯報(bào)時(shí),都是長(zhǎng)長(zhǎng)的一條清單。
然后大領(lǐng)導(dǎo)開始抱怨:“幾百個(gè)微服務(wù)?系統(tǒng)這么復(fù)雜了嗎?誰(shuí)能知道所有系統(tǒng)的全貌啊,如果出現(xiàn)問題,我們?nèi)绾慰焖俣ㄎ粏栴}點(diǎn)呢?”此時(shí)負(fù)責(zé)人們只好乖乖地低著頭,其中一個(gè)同事偷偷嘀咕:“我連自己部門的微服務(wù)列表都沒搞清楚呢。”
在以前,我們首先會(huì)把公司的整個(gè)架構(gòu)系統(tǒng)全貌搞清楚,之后一旦出現(xiàn)問題,也就容易定位故障點(diǎn)了??墒亲詮膩淼竭@家使用微服務(wù)的公司后,便再也沒有這樣的沖動(dòng)了,只要求搞懂自己的一畝三分地就行,如果出現(xiàn)問題臨時(shí)學(xué)習(xí)一下相關(guān)系統(tǒng)就好了。
因此,在實(shí)際工作中,很難找到這么一個(gè)人,他能知道系統(tǒng)整體架構(gòu)的全貌,這就是微服務(wù)的一個(gè)痛點(diǎn)。
二、重復(fù)代碼多
在以前的公司,我們把所有的代碼放在了同一個(gè)工程中,如果發(fā)現(xiàn)某些代碼可以重復(fù)使用,把這些代碼抽取出來存放在 Common 包中就行。但是這種代碼設(shè)計(jì)在微服務(wù)中經(jīng)常會(huì)出現(xiàn)問題,這里我還是舉個(gè)例子說明下。
比如某個(gè)團(tuán)隊(duì)做了一個(gè)日志自動(dòng)埋點(diǎn)的功能,它能自動(dòng)記錄一些特定方法的調(diào)用。其他團(tuán)隊(duì)知道這個(gè)功能后,感覺很不錯(cuò),想直接拿來用,于是埋點(diǎn)團(tuán)隊(duì)開心地給出了 Maven 的聲明。但是第一個(gè)吃螃蟹的團(tuán)隊(duì)使用后,立馬報(bào)出了一個(gè) JAR 版本沖突問題,這時(shí)如果他們將沖突的 JAR 進(jìn)行升級(jí),原始代碼就不能使用了。為節(jié)省人力成本,他們只好詢問埋點(diǎn)團(tuán)隊(duì)如何實(shí)現(xiàn)版本兼容。
為了兼容這個(gè)螃蟹團(tuán)隊(duì)的 JAR 版本,自動(dòng)埋點(diǎn)團(tuán)隊(duì)又重新設(shè)計(jì)了一版埋點(diǎn)的 JAR,并去掉了一些特定 API 的使用,最終 2 個(gè)團(tuán)隊(duì)終于可以正常使用了。
不過呢,第三個(gè)使用埋點(diǎn)的 JAR 的團(tuán)隊(duì)又匯報(bào)了一個(gè) JAR 版本沖突問題,此時(shí)自動(dòng)埋點(diǎn)團(tuán)隊(duì)從投入產(chǎn)出比角度考慮,不得不放棄維護(hù)這個(gè)公用的 JAR 了,并直接告知其他團(tuán)隊(duì):代碼就在 Git 上,你們自己直接 fork 修改吧。因此,這個(gè)代碼在不同團(tuán)隊(duì)的微服務(wù)中最終存在了多個(gè)不同版本。
后來我們復(fù)盤了下,得出結(jié)論:重用 JAR 本身沒有錯(cuò),錯(cuò)就錯(cuò)在我們使用的 JAR 版本太多了,必須改變這個(gè)局面。
于是我們將所有 JAR 版本進(jìn)行統(tǒng)一的項(xiàng)目正式立項(xiàng)了。第二天,因緊急業(yè)務(wù)需求下來了,大家都忘掉了這回事。又過了一段時(shí)間,有人提起了這個(gè)中心級(jí)項(xiàng)目,結(jié)果又被緊急的業(yè)務(wù)需求 PK 下去了。后來大家逐漸明白,這個(gè)項(xiàng)目沒法做,因?yàn)橥度氘a(chǎn)出比不高。
其實(shí)微服務(wù)之間存在重復(fù)的代碼也沒事,因?yàn)椴块T之間的重復(fù)代碼比比皆是,而且技術(shù)中心每個(gè)部門都有自己的 framework/Common/shared/arc 的 GitLab subgroups,它們可以實(shí)現(xiàn)對(duì)部門內(nèi)部的通用代碼進(jìn)行重用。
不過,維護(hù)這些小小的重復(fù)代碼總比統(tǒng)一排期做重構(gòu)、統(tǒng)一評(píng)審 JAR 版本的成本低得多。
三、耗費(fèi)更多服務(wù)器資源
曾經(jīng)了解到一個(gè)小公司。他們?cè)瓉硎褂玫氖菃误w式架構(gòu),一共部署了 5 臺(tái)服務(wù)器,后面他們一直抱怨系統(tǒng)耦合性太強(qiáng),代碼之間經(jīng)常互相影響,并且強(qiáng)烈要求將架構(gòu)進(jìn)行遷移。
于是,根據(jù)業(yè)務(wù)模塊,把原來的單體式架構(gòu)拆分為了 6 個(gè)微服務(wù)??紤]到高可用,每個(gè)服務(wù)至少需要部署在 2 個(gè)節(jié)點(diǎn)上,再加上網(wǎng)關(guān)層需要 2 臺(tái)服務(wù)器,最終,一共部署了 15 臺(tái)服務(wù)器。(因?yàn)槠渲幸粋€(gè)服務(wù)比較耗資源,為了保險(xiǎn)起見,多加了一個(gè)節(jié)點(diǎn)。)
在這個(gè)拆分過程中,業(yè)務(wù)沒有變,流量沒有變,代碼邏輯改動(dòng)也不大,卻無緣無故多出了 9 臺(tái)服務(wù)器,為此事還發(fā)生過爭(zhēng)執(zhí),當(dāng)時(shí)的爭(zhēng)議點(diǎn)是如果是這種情形,就不應(yīng)該一臺(tái)服務(wù)器只部署一種服務(wù),比如我們可以把服務(wù) AB 部署在 1 個(gè)節(jié)點(diǎn),BC 服務(wù)部署在 1 個(gè)節(jié)點(diǎn),AC 再部署在一個(gè)節(jié)點(diǎn),如下圖所示:

可是這個(gè)方案很快就被大家否定了,因?yàn)槿绻總€(gè)服務(wù)器只部署一種服務(wù),服務(wù)器的名字直接以服務(wù)的名字命名就行,之后運(yùn)維排查問題時(shí)也比較方便??墒侨绻覀儼巡煌奈⒎?wù)混合部署,服務(wù)器又該怎么命名呢?
于是,有人提議:“要不這樣吧,反正服務(wù)器比較便宜,多幾臺(tái)也無所謂。”大家紛紛附和贊同。公司的錢就被這幫程序員浪費(fèi)了,不過你別以為只有小公司這樣做,大公司同樣如此。
過了幾天后,CTO 召集所有研發(fā)人員正式開會(huì):“這個(gè)季度,我們的服務(wù)器預(yù)算太多了,財(cái)務(wù)部門審核不通過,你們需要想辦法縮減一下服務(wù)器數(shù)量,把不用的服務(wù)器都下掉?!?/p>
會(huì)議結(jié)束后,大家各自回到工位,開始對(duì)每個(gè)服務(wù)進(jìn)行檢查,于是就有了下面這段對(duì)話。
A:“這個(gè)服務(wù)怎么使用了這么多臺(tái)服務(wù)器?很耗資源嗎?”
B:“不是,主要是公司強(qiáng)制要求我們實(shí)現(xiàn)多數(shù)據(jù)中心部署?!?br> A:“這個(gè)服務(wù)很重要嗎??jī)?nèi)部使用的嗎?”
B:“是,這個(gè)目前只是開發(fā)人員在使用?!?br> A:“那干嗎做負(fù)載均衡,下了下了,只留一臺(tái)?!?br> B:“好吧?!?br> A:“現(xiàn)在我們縮減了多少臺(tái)服務(wù)器了?”
B:“……”
在大部分公司中,這種情形很常見,因此不得不說微服務(wù)真的很耗服務(wù)器。
四、分布式事務(wù)
分布式事務(wù)這個(gè)痛點(diǎn)對(duì)于微服務(wù)來說,簡(jiǎn)直就是地獄。為了深刻理解這個(gè)痛點(diǎn),我們先以曾經(jīng)經(jīng)歷的下單流程為例。
原本的下單流程是這樣的:插入訂單——>修改庫(kù)存——>插入交易單——>插入財(cái)務(wù)應(yīng)收款單——>返回結(jié)果給用戶,讓用戶跳轉(zhuǎn)。
在單體式架構(gòu)中,我們只需要把上面的下單流程包在一個(gè)事務(wù)里就行了,如果某個(gè)流程出錯(cuò)了,直接回滾數(shù)據(jù),并通過業(yè)務(wù)代碼告知用戶出錯(cuò)了就行,讓用戶重試就好了。
可是遷移到微服務(wù)后,因?yàn)檫@幾個(gè)流程分別存放在不同的服務(wù)中,所以我們需要更新不同的數(shù)據(jù)庫(kù),也就需要糾結(jié)以下邏輯。
某個(gè)流程出錯(cuò)是否需要將數(shù)據(jù)全部回滾?如果需要的話,那么我們需要在每個(gè)流程中寫上回滾代碼。那萬(wàn)一回滾失敗了呢?我們是不是還需要寫回滾的回滾代碼,回滾的回滾代碼算回滾嗎?
要不就某些流程回滾,某些流程不回滾?那哪些流程回滾哪些流程不回滾呢?
要不就統(tǒng)一不回滾,失敗就重試?這樣豈不是需要做成異步?如果做成異步,會(huì)不會(huì)出現(xiàn)時(shí)間超時(shí)?如果超時(shí)了,用戶怎么辦?需要回滾嗎?(怎么又要回滾了?)
如果我們只是糾結(jié)某些特定的流程也就罷了,問題是這種分布式服務(wù)更新數(shù)據(jù)的場(chǎng)景實(shí)在是太多了,如果每個(gè)場(chǎng)景都要糾結(jié)這些邏輯,我們得瘋了。本來業(yè)務(wù)部門就嫌我們交付太慢,我們還要花時(shí)間扯這些邏輯,干脆整個(gè)部門解散得了。
因此,針對(duì)這種情況,在大部分的場(chǎng)景下我們不考慮回滾和重試,只考慮寫 Happy Path,如果報(bào)錯(cuò)就記個(gè)異常日志,再線下手工處理,So easy!
結(jié)果你們也知道,機(jī)房網(wǎng)絡(luò)抖動(dòng)是常有的事情(運(yùn)維經(jīng)常拿這個(gè)當(dāng)理由),以至于三天兩頭數(shù)據(jù)更新出現(xiàn)異常,比如上游數(shù)據(jù)更新了,下游數(shù)據(jù)沒有更新,這時(shí)數(shù)據(jù)就對(duì)不上了,特別尷尬。
以至于業(yè)務(wù)部門經(jīng)常抱怨:“咦,這個(gè)訂單怎么找不到收款單了?咦,這個(gè)交易單怎么沒有交易流水?”然后我們只能回懟過去:“不是跟你解釋過了嗎?提個(gè)工單就好了,嘀嘀咕咕啥,生怕老板不知道嗎?”
使用微服務(wù)時(shí),分布式事務(wù)一直是痛點(diǎn)也是難點(diǎn),因此我們痛定思痛,決定好好解決這個(gè)問題,關(guān)于此問題的解決方案我們將在后面的內(nèi)容中進(jìn)行說明。
感興趣的朋友歡迎關(guān)注微信公眾號(hào):服務(wù)端技術(shù)精選
個(gè)人博客:http://jiangyi.cool