<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          微服務(wù)的災(zāi)難

          共 11039字,需瀏覽 23分鐘

           ·

          2021-05-14 02:43

          《微服務(wù)的災(zāi)難》是我于 2019 年五一期間寫的系列文章,當(dāng)時其實寫了很多:


          只發(fā)了一部分(另外一部分出于言辭激烈或影射太容易對號入座等原因沒有發(fā)),最近又看到有老外寫類似的東西,閱讀后主要是說基礎(chǔ)設(shè)施和穩(wěn)定性的,和我當(dāng)時發(fā)出來的六篇正好算是互補吧。

          這里將當(dāng)時的內(nèi)容匯集一下,僅供參考,雖然都是故事,但也都是真實(這讓人想到了最終幻想里的 This is a fantasy based on reality,哈哈)。

          通用語言的災(zāi)難

          在架構(gòu)師們很喜歡的 Domain Driven Design,即 DDD 中,第一課就是教導(dǎo)團隊形成自己獨有的通用語言(Ubiquitous Language),作為業(yè)務(wù)概念沉淀下來。

          作為非英語母語的國家,我們在日常交流中使用的是中文,在公司業(yè)務(wù)戰(zhàn)略描述上使用的是中文,在高層進行任務(wù)拆分的時候使用的是中文,在領(lǐng)導(dǎo)安排工作的時候使用的是中文。唯獨到了具體實現(xiàn),即代碼這一環(huán)節(jié)便變成了英文。當(dāng)然這里我們不考慮有些公司會有漢語拼音這種尷尬的情況。

          兩種語言天生便有難以填平的鴻溝,在業(yè)務(wù)人員編寫代碼時,從中文到英文的轉(zhuǎn)換,往往丟失一部分業(yè)務(wù)信息,產(chǎn)生一部分信息噪音,或者發(fā)生概念上的偏移。

          英文語系的人對業(yè)務(wù)進行建模時,與業(yè)務(wù)方(領(lǐng)域?qū)<?交流時,產(chǎn)生的概念和反饋可以直接落實到代碼上,他們所使用的詞匯不會發(fā)生變化。而其它語系的人就會在編寫代碼的時候發(fā)生概念偏移,比如我司是做打車業(yè)務(wù),快車在不同的系統(tǒng)中會存在不同的翻譯,有人稱之為 fastcar,有人稱之為 quickcar,有人甚至就直接是 kuaiche。甚至同一個系統(tǒng)中,對于同一個概念也會存在不同形式的自創(chuàng)翻譯。即使以文檔的形式記錄了業(yè)務(wù)的標準翻譯,但顯然以國內(nèi)業(yè)務(wù)疊代的速度,這種詞匯上的統(tǒng)一是做不到的。即使在一個只有 7~8 個人的組中都做不到。并不是所有人的英文都可以達到可用的程度,有些代碼中的詞匯可能根本就是詞不達意,是某些搜索引擎中給出的直譯結(jié)果,與真實的含義相差十萬八千里。

          這樣的代碼會給后來人帶來理解上的困惑。一旦在同一個系統(tǒng)中,針對同一個業(yè)務(wù)概念存在三種以上的不同詞匯,就需要閱讀者在這些“錯誤”的詞匯上不停地進行上下文切換,以正確地理解錯誤詞匯的涵義。

          可能有些人會提出反駁意見,碰上這種情況我們只要對代碼進行重構(gòu)就可以了,并不需要被這種弱智的事情折磨啊。重構(gòu)雖好,在很多情況下,詞匯的重構(gòu)是不可能的。打個比方,上面提到的 fastcar 出現(xiàn)在我們系統(tǒng)提供給別人所用的 api 的關(guān)鍵字段中,quickcar 出現(xiàn)在我們內(nèi)部數(shù)據(jù)庫的字段名中,kuaiche 出現(xiàn)在異步發(fā)送的消息中。這種時候修改任何一個單詞,對于我們來說都是不可能的事情。api 和事件中的字段名是我們對于外部系統(tǒng)的承諾,這種承諾也是編程契約中的一部分,不能隨便修改。即使我們想要修改,在當(dāng)今大多數(shù)互聯(lián)網(wǎng)公司的架構(gòu)下,根本就沒法知道到底是誰在使用你的哪一個字段。也就是說,我們沒有辦法獲得粒度細到“字段”級別的外部使用信息,所以我們沒有辦法對契約本身進行重構(gòu)。如果未來的微服務(wù)管理能對服務(wù)間的依賴進行標準化,并且能夠?qū)Ψ?wù)之間字段的依賴進行顯式管理,那么契約就是可以進行變更的了(就像單模塊的重構(gòu)那樣),不過這也就是個設(shè)想,顯然不太可能。而數(shù)據(jù)庫中的字段雖然有重命名方法,并且在 《Refactoring Databases》這本書中也給出了各種數(shù)據(jù)庫重構(gòu)的完善方案。但同樣的,上了體量的互聯(lián)網(wǎng)公司,想要動動數(shù)據(jù)庫結(jié)構(gòu),是比登天還難的(等五年后應(yīng)該好一些)。

          所以當(dāng)你接手到這樣的系統(tǒng)時,讀代碼的時候肯定是會罵娘的,但是讀完之后也確實沒有什么辦法。只要你負責(zé)維護,就持續(xù)地接受這種痛苦吧。

          通用語言的問題不只是單模塊中存在,跨模塊時也存在。在微服務(wù)的架構(gòu)下,很多需求是必然會跨越模塊的。別說不可能,那些鼓吹中臺的公司跨模塊的需求更普遍。一個需求改 20 個模塊都不奇怪。

          模塊間負責(zé)人探討新功能的實現(xiàn)時,混亂的命名和詞匯也很可能讓兩邊的溝通變得驢頭不對馬嘴。在服務(wù)之間是接力棒式運作,沒有中心服務(wù)時,這種情況特別普遍。相信你也遇得到。

          遺憾的是,目前推崇的微服務(wù)架構(gòu)是沒有辦法解決這樣的問題的。在肉眼可見的將來,程序員依然會因為概念產(chǎn)生的歧義而不斷地受苦。

          這些苦痛最終都會體現(xiàn)到業(yè)務(wù)開發(fā)迭代的速度上。

          技術(shù)棧的災(zāi)難

          微服務(wù)的布道師們特別喜歡鼓吹一個觀點:拆分微服務(wù)之后,我們可以隨意地對小模塊進行重構(gòu),選擇最合適的技術(shù)棧,并且如果寫失敗了隨時對這個模塊拿其它語言進行重寫。這一點被大多數(shù)布道師當(dāng)作微服務(wù)的重點優(yōu)勢。

          但是布道師們有意地把這樣做所帶來的問題忽略了,或者更惡意的是,他們明知道有問題,但是不說?啊哈哈。

          一個公司業(yè)務(wù)上有多種語言的話,理論上可以吸引到各種“語言的人才”。這確實不假,并且可以提供給各種語言大佬一個互相掐架的優(yōu)秀競技場,只要干掉對手(其它語言的大佬)了,我就可以擴張團隊,讓團隊把所有其它語言的模塊用我們擅長的語言重寫一遍,善哉善哉。小伙子們一年的事情就都安排上了。

          但是顯然這種說辭是有問題的。在現(xiàn)行的微服務(wù)架構(gòu)下,除了業(yè)務(wù)本身的研發(fā)人力投入之外,在業(yè)務(wù)之外的支持系統(tǒng)的研發(fā)工作也有很大的工作量,比如典型的,服務(wù)發(fā)現(xiàn),熔斷,優(yōu)雅重啟,存儲系統(tǒng) client,消息隊列 client,緩存 client,配置系統(tǒng) client 等等。。各種周邊系統(tǒng)版本依次疊代下來,那可能也是幾百上千人一兩年的工作。為什么會帶來這么多的工作量?其中很大一部分就是因為語言和技術(shù)棧混亂造成的。比如一個公司的技術(shù)棧能夠統(tǒng)一到 java 的話,那沒什么說的,大家都用 Spring Cloud 全家桶或者 Dubbo 全家桶就可以了。但是你們既有 java 又有 Go 又有 PHP 又有 C++ 又有 NodeJS 又有 Rust,這樣顯然就很難在眾多神仙中達成一致。比如你想要選用 java 的 Spring Cloud 生態(tài),但是這里面的服務(wù)發(fā)現(xiàn)或者配置系統(tǒng)并沒有打算對其它語言進行支持,即使支持可能也支持地不全面。一旦支持不全面,公司內(nèi)的輪子黨們一定會跳出來,強行給你找出幾十個缺點,一桿子打回去,最終得到一定要自己造這些輪子的結(jié)論。

          好家伙,五種語言八種框架,一個服務(wù)發(fā)現(xiàn)的 client 輪子造五遍都能算少的了。目前開源界的趨勢是將那些和業(yè)務(wù)無關(guān)的非功能性需求從模塊中剝離出來,比如 service mesh 就是很好的嘗試,只不過現(xiàn)階段用過的都說坑。說好的那都是不懷好意,拉人入坑。對于研發(fā)人員來說,一個輪子造五遍真的沒什么意思,可能也就是熟悉了五種語言的語法,并且寫出了五種風(fēng)格各異的 bug。只不過滿足了部分中層管理老板的人員擴張野心。

          語言和框架太多,對于公司來說顯然是災(zāi)難。比如常見的公司組織架構(gòu)調(diào)整,業(yè)務(wù)技術(shù)部門進行重組,不同部門的系統(tǒng)一般會進行暴力交接。這里說的“暴力”的意思是,不管你能不能接得下來,反正我是給你了。之后的維護也別來找我,甚至連簡單的問答可能原部門都不一定愿意做。雖然公司對程序員的要求是可以隨意地在不同語言技術(shù)棧之間切換,但程序員一般都有自己執(zhí)著的美學(xué)偏好。讓 java 程序員寫 Go,往往是會翻車的。大多數(shù) java 程序員在語言內(nèi)的生態(tài)足夠好,能滿足幾乎所有需求時,沒有任何意愿去學(xué)習(xí)一門新語言。這是天然的抗拒心理,可以理解。我們這里已經(jīng)有無數(shù)的案例表明,java 程序員轉(zhuǎn) Go,在寫不滿三個月的時間內(nèi)就會離職 2333。就算不說 java,國內(nèi)的 php 專家們也是不愿意寫 Go 的,那些 PHP 大佬們哪怕在 swoole 之類的框架中重新實現(xiàn)一套 goroutine,也不愿意直接去寫更原生的 Go 語言,因為用別人的東西體現(xiàn)不出輪子哥的價值啊。

          對于一個公司來說,不應(yīng)該聽信那些微服務(wù)布道師的胡言,任由公司內(nèi)的技術(shù)棧隨意分裂。最終在公司調(diào)整或者變化的時刻才發(fā)現(xiàn)積重難返。那些幾千甚至上萬研發(fā)的“大公司”,大多都沒有做到技術(shù)棧的統(tǒng)一,哪怕是在線業(yè)務(wù)技術(shù)棧也是如此。

          拆分與收斂的災(zāi)難

          在之前寫事故驅(qū)動開發(fā)的時候,提到過,在企業(yè)中的項目進行開發(fā)時,只要是自己方便,一個人可以用拆分和收斂同時作為自己的標準。所以大家都是雙標狗。

          目前業(yè)界的微服務(wù)方法論一般也沒有固定的套路,比如在 《Building Microservice》 一書中,作者也講到了服務(wù)之間協(xié)作的時候,可以選擇編排(orchestration)和協(xié)同(choreography)這兩種方式來對服務(wù)進行架構(gòu)。所以在拆分階段,就沒有什么硬性的標準了,每個公司可能風(fēng)格都有差別,并且都可以闡述出自己的條條以支持自己的架構(gòu)是“正確”的。

          顯然,這件事情沒有絕對正確的解法。無論哪種拆分方式,都會遇到業(yè)務(wù)邊界的問題。在大企業(yè)中,頂著“架構(gòu)師”頭銜的這些架構(gòu)師們根本就不會管任何實現(xiàn)上的細節(jié)。相對較大的業(yè)務(wù)需求,一般也是一線的 RD 商量怎么進行實現(xiàn)上的拆分。想要達到合適的職責(zé)劃分,需要多個合作方的所有人都靠譜才行。這個要求實在是有點強人所難。比如在目前的公司內(nèi)進行了三年多的開發(fā),就和各種類型的人都打過交道。

          兢兢業(yè)業(yè)的人比較多,但也不乏一些完全不負責(zé)任的人。有的素質(zhì)奇差,只會逢迎甩鍋,自己模塊的職責(zé)都搞不清楚。本來自己該做的兜底不做,讓所有下游系統(tǒng)給他擦屁股。從應(yīng)聘要求上來講,程序員應(yīng)該是一個注重邏輯能力,編碼能力的職業(yè)。然而你總發(fā)現(xiàn)有些人是沒有辦法講道理的(可能是早期的一些能力一般的員工?),在與這些人討論技術(shù)實現(xiàn)時,會陷入無窮無盡的無意義循環(huán)。而他能說(逼)服(迫)所有其它人就范的法寶,就是在兩個小時的會議中不斷地復(fù)讀自己的觀點,而完全不聽取任何別人的觀點。一旦這樣的人在你的某個系統(tǒng)邊界上待著,那你所面臨的也是持續(xù)的痛苦。并且不斷地在自己的系統(tǒng)中進行妥協(xié),做那些職責(zé)上跟你的系統(tǒng)完全沒什么關(guān)系的東西。

          除去人的問題,業(yè)務(wù)部門的大多數(shù)一線領(lǐng)導(dǎo)是需要有業(yè)務(wù)上的業(yè)績的。這種業(yè)績怎么來?一般都是攬各種各樣的活兒,能攬多少就攬多少。

          從設(shè)計原則上來講,邏輯上相同或者類似的代碼應(yīng)該放在一個地方來實現(xiàn)。這個稍微學(xué)過一點 SOLID 中的 SRP 原則就應(yīng)該知道。這樣可以避免邏輯本身過于分散,好處是:“一個類(模塊)只會因為一個理由而發(fā)生變化”,其實就是相同的需求,盡量能夠控制在單模塊內(nèi)完成。當(dāng)然了這是一種理想狀態(tài),確實有些情況下達不到這種完美的平衡狀態(tài)。微服務(wù)場景下這種業(yè)務(wù)邊界往往劃分都非常糟糕。即使 Domain Driven Design 的觀點講述了再多的上下文劃分技巧,你在實際工作中會發(fā)現(xiàn)沒有多少人把這些思想、原則當(dāng)回事,一線 leader 在乎的就是攬活兒而已。他們在劃分模塊的職責(zé)時只考慮這么幾點:

          這件事情有沒有業(yè)務(wù)收益,我能不能摻一腳 如果沒有收益,這件事能不能甩給別人,我就不去做了 把業(yè)務(wù)上的影響全刨去,才會考慮架構(gòu)上的事情。有些大佬會講,系統(tǒng)是演化出來,而不是設(shè)計出來的,而這些“演化論”的大佬也是不參與一線開發(fā)的。你再看看實際的情況,只靠演化,可能演化出合理的系統(tǒng)么?

          不可能的,對人的要求實在太高。而且是對所有開發(fā)人員要求都高。大多數(shù)企業(yè)的業(yè)務(wù)系統(tǒng),都缺乏較為頂層的設(shè)計,有人管這個叫“戰(zhàn)略設(shè)計”。相對復(fù)雜的業(yè)務(wù)邏輯,在企業(yè)的系統(tǒng)中根本沒法合理解耦,很多時候?qū)崿F(xiàn)一套業(yè)務(wù)流程的代碼會隨意地散落在多個模塊,在你把所有模塊的代碼都看過之前,你是沒法確定哪部分邏輯在哪個模塊里的。可以稱之為薛定諤的業(yè)務(wù)邏輯。

          這樣在模塊發(fā)生負責(zé)人離職或者工作交接時,所有 RD 都會進入非常痛苦的階段,我只看自己的模塊,根本沒法理清全局的業(yè)務(wù)邏輯!對于產(chǎn)品來說也一樣,所有邏輯的分布都具有不確定性,在哪里控制我需要去問研發(fā),而研發(fā)還要再問其他的研發(fā),其他的研發(fā)如果是剛接手,又要去看大量的代碼才能確定到底是怎么回事。如果代碼寫的爛(對于大多數(shù)業(yè)務(wù)系統(tǒng)來說,如果可以去掉),那可能連看都看不懂。就隨便胡謅應(yīng)付過去好了。現(xiàn)實就是如此荒誕。

          在所有服務(wù)都在單體的時代,我們可以在合適的時間,參考《重構(gòu)》書里的觀點對我們的模塊進行重構(gòu)。重構(gòu)對系統(tǒng)本身的要求其實也不多,只要測試覆蓋率足夠,然后是強類型語言,大多數(shù) IDE 對重構(gòu)支持都很不錯了。但演化到微服務(wù)的時代,原來很簡單的重構(gòu)就沒那么簡單了。在 http://xargin.com/disaster-of-microservice-ul/ 中我們提到,開放的 API、消息、數(shù)據(jù)庫中的字段名根本沒有辦法進行重構(gòu),為什么沒有辦法,因為我們的模塊都被切開了,原本在代碼中的強聯(lián)系變成了分布式系統(tǒng)中的弱聯(lián)系,薛定諤的聯(lián)系。如果我們想要實現(xiàn)和單體服務(wù)一樣的重構(gòu)功能要怎么辦?根本實現(xiàn)不了。你至少需要下面這些設(shè)施支持才能完成這樣的偉業(yè):

          所有其它模塊對你都有集成測試,并且有統(tǒng)一的 API 平臺管理所有你們之間的“聯(lián)系”。有全局所有模塊的“同時重構(gòu)”工具 上線時,能針對舊版和新版的流量自動進行識別,防止新流量訪問到舊系統(tǒng) 這個顯然不可能啊,目前業(yè)界提出的 API 版本管理,也只是緩解了這種情況,新功能我如果在新版 API 實現(xiàn),把舊版 API deprecated 掉,這樣就可以逼迫用戶放棄對我原來版本的依賴,平滑遷移到新版 API 上來。但顯然加上版本,也并沒有從本質(zhì)上來解決問題,API 的用戶在沒有迭代需求的前提下,因為依賴方進行了修改就不得不進行修改,這是額外的工作量。

          你也看到了,拆分給我們帶來的并不全是好事,當(dāng)前中大規(guī)模公司的開發(fā)日常流程,可能最終還是會把系統(tǒng)整體引向一片混沌。

          難以治理的依賴地獄

          微服務(wù)模式下,我們的系統(tǒng)中往往需要集成進各種各樣的 SDK,這些 SDK 部分來自于非功能性的業(yè)務(wù)需求,例如 bool 表達式解析,http router,日期時間解析;一部分來自于對公司內(nèi)基礎(chǔ)設(shè)施的綁定,如 MQ Client,配置下發(fā) Client,其它服務(wù)的調(diào)用 SDK 等等。

          一般的觀點會認為公司內(nèi)的 SDK 是較為可靠的,而開源庫的穩(wěn)定性不可控,所以人們在升級公司內(nèi)部庫時往往較為激進,開源庫版本升級較為保守。具體到 Go 語言,公司內(nèi)的庫,我們可能會直接指定依賴的版本為 master(glide 中每次構(gòu)建會使用 master 分支的代碼)。

          實際上真的如此么?業(yè)界有個名詞叫 dependency hell,指的是軟件系統(tǒng)因依賴過多,或依賴無法滿足時會導(dǎo)致軟件無法運行。導(dǎo)致依賴地獄的問題有:

          依賴過多 一個軟件包可能依賴于眾多的庫,因此安裝一個軟件包的同時要安裝幾個甚至幾十個庫包。多重依賴 指從所需軟件包到最底層軟件包之間的層級數(shù)過多。這會導(dǎo)致依賴性解析過于復(fù)雜,并且容易產(chǎn)生依賴沖突和環(huán)形依賴。依賴沖突 即兩個軟件包無法共存的情況。除兩個軟件包包含內(nèi)容直接沖突外,也可能因為其依賴的低層軟件包互相沖突。因此,兩個看似毫無關(guān)聯(lián)的軟件包也可能因為依賴性沖突而無法安裝。依賴循環(huán) 即依賴性關(guān)系形成一個閉合環(huán)路,最終導(dǎo)致:在安裝A軟件包之前,必須要安裝A、B、C、D軟件包,然而這是不可能的。我們編寫的服務(wù)也屬于軟件系統(tǒng)的范疇,所以也難以擺脫依賴地獄的問題。在微服務(wù)場景下,因為本文開頭所述的原因,我們必然會依賴一大堆外部 SDK。對于開發(fā)者來說,實際上真正有選擇權(quán)力的就只有我可以使用什么樣的開源庫。公司內(nèi)的 SDK 是沒有自己造輪子的價值的。畢竟自己造的司內(nèi) SDK 也沒有人會幫你修 bug,原生 SDK 至少有單獨的團隊維護。

          在開發(fā) lib 時,比較好的做法是盡量引入少的依賴,以避免上面提到的問題 1。實際上沒有幾個提供 SDK 的團隊能做得到,想想當(dāng)初 javascript 圈子的 leftpad 事件吧,即使是一行代碼的庫,被人刪除就引起了無數(shù)大公司系統(tǒng)無法 build 的悲劇。對于目前 Go 編寫的系統(tǒng),實際上存在同樣的風(fēng)險,我們的依賴大多來自于 github,如果你們沒有使用 vendor 方案把依賴緩存到自己的系統(tǒng)中,別人刪庫了你有轍么?

          一般 star 數(shù)比較高的開源庫作者還是比較有節(jié)操的,刪庫的人畢竟是少,所以我們先假裝這個問題不存在。公司內(nèi)的實際開發(fā)過程中,我們遇到的依賴地獄大多體現(xiàn)在依賴沖突上,這個比較好理解,比如:

          A --> B --> D.v1 A --> C --> D.v2 A 模塊依賴 B 和 C,而 B 和 C 分別依賴 D 的不同版本,如果 D.v1 和 D.v2 恰好進行了 API 不兼容的更新,且都是在 github.com/xxx/D 路徑下,通過 tag 來區(qū)分版本。那么就會對我們造成很大的麻煩,B 和 C 如果又恰好是公司內(nèi)的不同部門的不同團隊,要求他們因為這種原因進行兼容性更新就像是去求大爺一樣難。

          印象中之前 Go 社區(qū)是沒有辦法解決這種問題的,為了解決這個問題,我司還專門對 glide 進行了定制,以在依賴沖突的時候提醒用戶,阻止構(gòu)建,防止之后出現(xiàn)問題。Go 的社區(qū)在這方面也做得確實不太好,gomod 我還沒有試用,不清楚是否對依賴沖突有優(yōu)雅的解決方案。之前社區(qū)里大多數(shù)人對 Go 的依賴管理也一直頗有微詞,希望 gomod 能徹底解決這些問題吧。

          除了語言本身的問題,我發(fā)現(xiàn)公司內(nèi)的 library 研發(fā)們,根本沒有任何開源界的節(jié)操,版本升級時根本不考慮向前兼容或者向后兼容的問題,并且出現(xiàn)問題的時候也不會做任何提示,連日志都基本不打印。經(jīng)常會有配置管理 v1 和配置管理 v2 的 sdk 同時存在模塊中時,發(fā)生同一個全局變量初始化兩次,發(fā)生沖突,邏輯不能正常運行,結(jié)果啟動階段沒有任何 warning,直到執(zhí)行階段才出現(xiàn)詭異錯誤,導(dǎo)致用戶在線上埋下定時炸彈的問題。

          實在是不知道該說什么好。

          多模塊之間的循環(huán)依賴就更不用說了,如果循環(huán)依賴出現(xiàn)在單機系統(tǒng)中,至少在 Go 語言中是沒法編譯通過的,而因為微服務(wù)的關(guān)系,循環(huán)依賴往往會存在那些沒有合理劃分業(yè)務(wù)邊界的系統(tǒng)當(dāng)中。據(jù)我觀察,出現(xiàn)得還不少。

          這時候你能重新劃分職責(zé),讓循環(huán)依賴消失么?

          顯然是不行的。程序員在當(dāng)前的微服務(wù)架構(gòu)下,將持續(xù)地被外部的垃圾 SDK 和各種莫名其妙的依賴問題所困。

          嘴上最終一致,實則最終不一致

          現(xiàn)在的架構(gòu)師總喜歡把最終一致掛在嘴上,好像最終一致是解決分布式場景下數(shù)據(jù)一致問題的金科玉律。事實上又怎么樣呢?

          事實上的這些人嘴里的最終一致,往往都是最終不一致。在多個系統(tǒng)之間進行數(shù)據(jù)傳遞時,無非通過 RPC 或者異步消息。RPC 能保證一致性么?當(dāng) B 系統(tǒng)需要 A 系統(tǒng)提供數(shù)據(jù)時,想要達到一致的效果,那么在 A call B 時發(fā)生失敗,那么必須讓 A 中的邏輯終止。這樣才能夠使 B 中的狀態(tài)或數(shù)據(jù)與 A 中的完全一致。這樣實際上需要讓 A 和 B 成為生死共同體,B 掛了,那 A 也得掛。可能么?在中大型規(guī)模的互聯(lián)網(wǎng)公司的業(yè)務(wù)系統(tǒng)中,其下游系統(tǒng)往往有幾十個,因此實際的場景是 A call B -> A call C -> A call D .... -> A call Z。這種情況下,你想讓所有系統(tǒng)中的狀態(tài)都一致,那是不可能的。

          有的架構(gòu)師又拿出 saga pattern 來說事,我如果有寫數(shù)據(jù)的邏輯,那么我自然會有一套回滾邏輯,只要在中間發(fā)生錯誤,那么我就對之前的所有調(diào)用執(zhí)行回滾邏輯即可。然而回滾是需要開發(fā)量的。我所有下游系統(tǒng)那都得支持回滾才行啊,你覺得做得到么? saga pattern 的異常處理就更扯蛋了:回滾過程中發(fā)生失敗的話,那需要人工介入,人肉處理。顯然人肉是處理不過來的,機房網(wǎng)絡(luò)抖動實在太正常了,可能一天兩天的就會有一次,每次抖動都造成 bad case,研發(fā)人員不用干別的事情了,都去處理 bad case 好了。

          當(dāng)然上面這種情況比較極端,一般公司內(nèi)有靠譜的 MQ 方案的話,會選用 MQ 對這種數(shù)據(jù)同步的場景進行解耦。之前我做的一些總結(jié)也都提到過,只要往 MQ 發(fā)一條消息,在字段上盡量滿足下游系統(tǒng),那么我就不用挨個兒去調(diào)用他們了,可以很好地進行解耦。從設(shè)計的角度上來講,這確實是比較好的解耦形式。但是你要考慮,邏輯執(zhí)行和消息發(fā)送這兩步操作并不具備原子性,除非 MQ 支持事務(wù)消息,我才能完成兩個操作同時成功或者失敗,何況邏輯執(zhí)行內(nèi)部可能還有更多的子操作,這件事情遠沒有打打嘴炮那么簡單。

          也有的公司會將發(fā)送失敗的消息進行落盤,比如落進 MySQL 或者寫入到磁盤,在發(fā)送失敗之后,由后臺線程在合適的時間進行重發(fā),以讓消息能夠最終發(fā)出。一些簡單的場景,這樣確實算是解決了問題。如果下游對于消息本身有順序要求呢?比如訂單的狀態(tài)流轉(zhuǎn),如果順序錯了,那狀態(tài)機最終的狀態(tài)都錯亂了。又是一個麻煩的問題。

          在當(dāng)前的開發(fā)環(huán)境下,想要達到最終一致的效果需要上下游同時進行很多工作,例如上面說的異步消息的場景,上游至少需要做失敗落盤和后臺發(fā)送。而下游需要在狀態(tài)機的正常狀態(tài)流轉(zhuǎn)之外,處理各種麻煩的亂序問題。這種亂序處理基本和業(yè)務(wù)是強相關(guān)的,并沒有通用方案。即使是同一套狀態(tài)機,針對不同的業(yè)務(wù)場景可能還需要定制不相同的業(yè)務(wù)邏輯。

          除了網(wǎng)絡(luò)抖動,數(shù)據(jù)不一致的問題可能還會因為模塊上線導(dǎo)致。有些公司(比如我司)為了簡單 MQ 的消費邏輯,提供了一套由 MQ 平臺消費,然后通過 http post 來將消息發(fā)送給業(yè)務(wù)系統(tǒng)的邏輯,降低了業(yè)務(wù)系統(tǒng)的消息消費開發(fā)成本(這樣就不用使用 MQ 的 client)了。這種情況下如果模塊發(fā)生上線的話,即使在 MQ 平臺側(cè)有 post 重試,但在模塊上線時,還是有概率發(fā)生消息丟失。如果有一些狀態(tài)機流轉(zhuǎn)強依賴于這些消息,那也會造成一部分 bad case。而且這種 bad case 查起來真是沒什么意思。之后的數(shù)據(jù)修復(fù)也基本只能靠研發(fā)人員自行修復(fù)。

          這種惡劣的場景下,也有一些人想到了一種方法,我在業(yè)務(wù)模塊中插入多個樁,只要可以每過一段時間觸發(fā)狀態(tài)的全量更新,那么我就找一個其它模塊來持續(xù)地刷新我系統(tǒng)中的數(shù)據(jù)狀態(tài)。從而達到“最終一致”。只要這些最終一致的數(shù)據(jù)沒有暴露給用戶,沒人看得見,那就是最終一致。倒確實是個可用的方案。但架構(gòu)師們在吹牛逼的時候,對于這種惡心的邏輯一定是絕口不提的。

          大多數(shù)公司的架構(gòu)師嘴里的最終一致,依靠的都是人肉而非技術(shù)。

          合作的災(zāi)難

          架構(gòu)師們常講的設(shè)計定律之中最為重要的是康威定律,康威定律的定義:

          Conway's law is an adage named after computer programmer Melvin Conway, who introduced the idea in 1967. It states that. organizations which design systems ... are constrained to produce designs which are copies of the communication structures of these organizations.

          這里的 'are constrained to' 即是該定律的精髓所在。如果一個公司的組織架構(gòu)已經(jīng)基本成型了,那么基本上設(shè)計出的系統(tǒng)架構(gòu)和其人員組織架構(gòu)必然是一致的。

          在微服務(wù)場景下,團隊會按照其所負責(zé)的模塊被依次切開成為一個 5-10 人小團隊,然后再由更為頂層的架構(gòu)少來按照組織架構(gòu)設(shè)計相應(yīng)的系統(tǒng)。但是這里面有一個先后關(guān)系,是先設(shè)計系統(tǒng),再根據(jù)系統(tǒng)來形成對應(yīng)的團隊。但很多時候也并不一定是如此,因為某些公司招聘速度過快(笑),可能團隊先形成了,然后才有系統(tǒng)設(shè)計,這時候,系統(tǒng)設(shè)計可能甚至?xí)粓F隊架構(gòu)所反作用(大概)。還是比較荒唐的。

          即使是正常的設(shè)計流程,業(yè)務(wù)需求總是難以預(yù)測的。架構(gòu)師們一般在設(shè)計完最初版本的系統(tǒng)架構(gòu)之后,便會抽身到新的系統(tǒng)中繼續(xù)挖坑。新的需求卻在后續(xù)的實現(xiàn)過程中漸漸發(fā)現(xiàn)無法與最初的架構(gòu)設(shè)計相匹配,具體體現(xiàn)在很難在當(dāng)前架構(gòu)上實現(xiàn),或?qū)崿F(xiàn)成本過于高昂,單模塊幾人天的事情,在當(dāng)前架構(gòu)上需要以月計的工時,這顯然是不可接受的。

          這時候該怎么辦呢?沒什么好辦法。一套系統(tǒng)的架構(gòu)一旦形成了,如果不是發(fā)生重大事件(例如迭代龜速導(dǎo)致公司在響應(yīng)速度上跟不上競爭對手的步調(diào);或者發(fā)生輿論事件,導(dǎo)致公司陷入風(fēng)口浪尖,高層承諾短時間無法兌現(xiàn)),一般系統(tǒng)本身并不會有架構(gòu)上的變動。一線的開發(fā)人員最能體會這時候的痛苦,但是痛苦也沒有什么卵用,因為這時候沒有人有動力去推進架構(gòu)上的變動。試想,在風(fēng)平浪靜的平日,沒有任何一個一線 RD 能有能力去推動一堆比他們高兩三級的“專家”做事。而一線的 leader 也沒有動力去做這種于自己于自己組完全無益的變動,哪怕明知道現(xiàn)行架構(gòu)已完全無法滿足業(yè)務(wù)需求,多一鍋不如少一鍋。Manager 們就更不用說了,多一事不如少一事,能堆人解決的問題就盡量不用技術(shù)去解決。

          所以你也看到了,這種問題是無法解決的。曾經(jīng)在和同事討論的時候,同事提出,按照這種說法來看的話,小公司的架構(gòu)可能比大公司還要靠譜?

          這當(dāng)然也不一定,小公司一般開不出優(yōu)秀人才的價格,所以優(yōu)秀的架構(gòu)師基本上是不會去小公司的,這就意味著大多數(shù)小公司的架構(gòu),肯定更加不靠譜。除非他們能持續(xù)發(fā)展壯大,公司財務(wù)健康,在不進行服務(wù)治理沒有辦法繼續(xù)做業(yè)務(wù)的困境時,招入了合適的架構(gòu)師來做全局把控,完成一次大的整體重構(gòu),徹底償還歷史技術(shù)棧,才會慢慢有所好轉(zhuǎn)。當(dāng)然這也只是個空想,業(yè)務(wù)驅(qū)動的公司不可能把業(yè)務(wù)完全停下來支持這種技術(shù)上的整體重構(gòu),記得阿里的人說在 09 年的時候進行公司的服務(wù)化,讓整個公司的業(yè)務(wù)停了半年?這種項目如果最后效果不好,那負責(zé)人肯定是要離職謝罪的。大多數(shù)技術(shù)老板也是一定沒有這個魄力讓業(yè)務(wù)半年沒有進展的,這樣搞不好直接就被 CEO 干掉了好嗎。

          從技術(shù)上來講有解決方案的問題,如果把政治也考慮在內(nèi),可能就變成了無解的問題。大多數(shù)公司內(nèi)的業(yè)務(wù)系統(tǒng)所要承受的這個痛苦的過程從公司發(fā)展的歷程上來講,是必然的。所以各位技術(shù)同學(xué),就不要抱怨業(yè)務(wù)代碼寫得亂了。

          技術(shù)人員所能發(fā)揮作用的范圍被限制于自己的模塊內(nèi),或者那些愿意接自己需求的其它支持系統(tǒng)間。除了前面說的組織架構(gòu)的問題,還需要考慮 KPI 的問題。

          之前和同事一起得到了一個在大公司內(nèi)推進事情的靠譜結(jié)論,如果一件事情在一個部門內(nèi)就可以解決,那可以開開心心地推動它解決。如果一件事情需要跨部門,那還需要本部門的大領(lǐng)導(dǎo)出面才能解決,哪怕這事情再小。如果一件事情需要跨兩個部門,那就沒治了,誰出面都不行。這種事情做不了的。而如果一件事情和你要跨的部門 KPI 有沖突,那就更別想了,把部門重組了才能解決,這是 CTO 才能干的事情。

          如果想要在大公司得到較好的績效,遵循 KPI 規(guī)則是必然的。沒有辦法。


          瀏覽 60
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91禁在线观看 | 很很鲁很很综合在线 | 狠狠操狠狠爽 | 婷婷影音| 国产白丝精品91 |