微服務架構的責任困境

引言

創世記第11章1-9句記錄了「巴別城」的故事。當時地上的人們都說同一種語言,當人們離開東方以後,他們來到了示拿之地。在那裏,人們千方百計燒磚好讓他們可以造出一座城和一座高聳入雲的塔來傳播本身的名聲,以避免他們分散到世界各地。上帝來到人間後看到了這座城和這座塔,說一羣只說一種語言的人之後便沒有他們作不成的事了;因而上帝將他們的語言打亂,這樣他們就不能聽懂對方說什麼了,還把他們分散到了世界各地,這座城市也中止了修建。這座城市就被稱爲「巴別城」。《欽定版聖經》是這樣描寫的:java

4 他們說,「來吧,咱們要建造一座城和一座塔,塔頂通天,爲了揚咱們的名,省得咱們被分散到世界各地。」node

5 可是耶和華降臨看到了世人所建造的城和塔。python

6 耶和華說,「看哪,他們都是同樣的人,說着同一種語言,現在他們既然能作起這事,之後他們想要作的事就沒有不成功的了。」mysql

7 讓咱們下去,在那裏打亂他們的語言,讓他們不能知曉別人的意思。linux

8 因而耶和華使他們分散到了世界各地,他們也就中止建造那座城。程序員

9 由於耶和華在那裏打亂了天下人的言語,使衆人分散到了世界各地,因此那座城名叫巴別。——Genesis 11:4–9
[10]

願景

每人均可以隨時獲取一個開發環境。在其中作開發。而且隨時能夠驗證本身寫的代碼在整個系統裏集成起來是否工做正常。我能夠當即獲得有效反饋,從而提升工做效率。redis

嘗試一:書同文

最理想的狀況是全部的開發者使用徹底相同的技術。他們使用一樣的編程語言,他們使用一樣的開發框架。他們使用一樣的操做系統。他們使用一樣的IDE。他們使用一樣的風格管理 GIT 倉庫。他們使用同一種語言來寫構建腳本。若是這一些都是真的,想要得到一個開發環境,那會是很是簡單的事情。sql

曾經嘗試過各類方式把本身認爲「最優」的開發環境兜售給同僚,給下級,甚至是給個人僱員。可是都失敗了。讓這幫 geek 們使用一樣的方式工做,這個難題比咱們這裏要解決的問題更難。上帝來到人間後看到了這座城和這座塔,說一羣只說一種語言的程序員之後便沒有他們作不成的事了。因而神創造了emacs和vim。docker

打着提升開發效率的方式去強姦別人的開發環境是行不通的。不管你說你寫的庫再好,都不如個人庫寫得好。不管我今天寫的庫在好,也不如我明天想造的下一個輪子好。這個道理普世於各類IDE工具的執念,各類編程語言的執念。數據庫

不要只看到始皇帝作了書同文的偉業。還要看到人家焚書坑儒的本事。

嘗試二:自動化腳本

退而求其次的辦法是,不要求全部的組件都是一樣的方式開發出來的。咱們只要求大家各個模塊的owner都提供一個共同的自動化腳本。當咱們把這些腳本拼接到一塊兒以後,整個系統就實現了自動化的部署。

這條道路演進的盡頭就是一堆bash/python/msbuild腳本,加上一個cmdb數據庫組成的怪獸。我已經在 閒談集羣管理模式 - taowen - SegmentFault 一文裏描述了這個模式的問題

  • 沒法審查的腳本:給定一個 f(),你沒法知道這個 f() 到底作了什麼。這個是全部基於腳本組合的方式(函數套函數)作自動化的根本問題。最近此次 S3 的故障,就是由於誤執行一個 saltstack 腳本致使的。而任何運行這個腳本的程序,是沒法在加載腳本以後作任何事情來驗證其權限和危害的。對於任何runner來講,f() 就是一個黑盒的 f()。
  • 狀態漂移:開發環境腳本在一個新的開發機上可能運行,可是在一個已經安裝模塊A的機器上就可能執行失敗。或者在安裝了A,B的,可是沒有裝C的機器上會失敗。腳本執行的起始狀態可能有千千萬萬,不可能測試齊全。腳本執行完以後留下的系統的狀態也千千萬萬。

這兩點技術上的硬傷,致使了基於一堆腳本堆砌出來的自動化彷彿浮沙之上築高臺通常。

嘗試三:Docker + 組網技術

在發現了腳本自動化的缺陷以後,我陷入了對 Docker 技術的癡迷。Docker 的技術上的好處顯而易見。它完美地把業務代碼封裝到了一個容器裏,不管你是什麼語言寫的,什麼框架開發的,最終都是一個 docker 執行命令。放佛巴別塔問題解決了,只要有了 docker,什麼東西都是同樣部署的。docker 鏡像由模塊owner提供,他們怎麼弄出來是他們本身的事情。你想用 node.js,仍是 haskell,隨便。並且 Docker 完美解決了基於腳本技術的狀態累積漂移問題。所謂immutable infrastructure。

Docker 還稍微有一些問題,那就是還須要把多個服務經過網絡組裝到一塊兒。這個也不是什麼大問題。雖然經過服務發現這樣的方式推廣起來有難度,可是咱們還有網絡代理的大招。好比指定100.64.0.1 表明 mysql,100.64.0.2 表明 redis。經過網絡層攔截這些 ip 的請求,咱們能夠不修改業務代碼的狀況下,把這些 docker 容器給組裝起來。

docker 基於了 linux ABI,代理版本的服務發現基於了 tcp/ip。這兩個東西在巴別塔的時代就是一個bug級別的存在,可是本質上仍是書同文。上帝留了一個口子,給咱們鑽了漏洞。因而咱們又能夠把這些亂七八糟的東西攢一塊兒了。

Docker 也沒法解決書同文的問題

這種方式完美了嗎?咱們再次發現了巴別塔問題。實際上的軟件是這樣工做的

某種程度上來講,咱們經過Docker,經過組網,構建出來的系統只是一個空殼而已。它只是一門開發語言,它經過各類配置界面提供了本身的開發工具。PM經過產品配置,配置出了產品,而運營再基於產品,配置出了真正運行起來能夠賺錢的系統。

Docker 能夠利用 linux ABI 這個「書同文」的接口,整合了後臺服務。網絡代理利用 TCP/IP 這個「書同文」的接口把這些服務串聯了起來。可是對於各個模塊本身提供的產品配置和運營配置,咱們就沒有這麼幸運了。有的模塊使用了csv,有的使用了json,有的使用了數據庫。當你要重現一個「環境」的時候。它不只僅是意味着進程的啓動,意味着網絡的鏈接。它還須要把各類配置數據灌入到系統。而搞清楚有哪些配置,讓這些模塊使用徹底相同的方式來定義和使用配置,咱們就又回到了第一步,書同文的路數裏了。書同文的成功實施,來自於強大的中央權威。若是擁有強大的中央權威,徹底能夠從一開始就強推書同文的開發模式(好比都是java,好比都是finagle)。這就造成悖論了。

微服務架構的責任困境

過去的開發模式是這樣的。我負責一個服務,它從DB往上都是個人。產品經理的需求,我所有負責。中間會有少許的調用外部接口進行支付,郵件羣發之類的事情。可是這些調用通常處於流程的末端,不參與主要的業務邏輯而且接口清晰。

這個年代的測試很是清楚,mock掉外部依賴(支付,短信網關,ftp接口),啓動真實的數據庫,從用戶的接口測試個人服務。我直接向用戶負責。這種工做模式,我至今認爲是效率最高的方式。

如今的微服務的模式是疊羅漢式的。我負責了B,上面有A,下面還有C。A依賴B和C才能跑起來,B依賴A和C才能和客戶端完整交互。

在這種拆分下,咱們強調了每一個團隊的自主性。並且你們共同承擔了最終的業務敏捷性和穩定性的要求。對於生產環境,確實是這樣的。若是C掛了,C會當即去修,由於影響了生產環境。若是B掛了,B也會當即去修。

可是把這個問題改爲線下和線下分開呢?若是是在服務B的開發環境裏,服務C出問題了,有一個設置沒有配置對。或者這個設置改了,B沒有更新代碼。你認爲C會積極地幫B解決問題麼?你認爲B會進入到C的代碼目錄下,看他們打的日誌麼,而後本身就知道怎麼修復問題了?即使能夠解決,B也會遇到巨大的相應延遲的問題。C可能在開會,C可能在忙別的事情。

那麼解法是什麼?線下環境直接利用生產環境的部署腳本?一套腳本,可隨時複製環境?前面已經討論過這些自動化部署腳本,以及docker等模式的技術缺陷了。事實上,部署兩套如出一轍的環境是至關困難的。並且引入複雜的部署工具,以及專職的團隊還有一個更加有趣的現象,它把兩方的關係,變成了三方的互動

這三方的關係是互相不信任的,由於代碼不是本身寫的。服務消費方遇到了困難,去找服務集成方。集成方會認爲多是提供方的代碼有問題。因而把提供方拉進來定位問題。提供方會以爲個人服務在線上是好好的, 爲何在線下就不行了呢?是否是你部署得有問題,gcc版本是否是不對。這種責任的鏈式傳導很快就會讓環境的使用方以爲,能用就用,不能用我也無法推進去定位問題。

從根本上來講,這個困境在於模塊的owner,只對生產環境負責,不對別人使用的開發環境裏本身的服務負責。花時間幫別人解決問題,提升團隊的總體效率,對於模塊的owner來講不是最優解。由於他花費了額外的時間去幫別人完成KPI,而不是專一於領導佈置的下一個任務。

而服務的集成方,既不知道服務是如何消費的,也不知道服務是如何提供的。他不可能有足夠的精力,時間與動機去深刻了解全部的模塊的工做細節。即使有意願把這個集成工做作好,也沒有能力在脫離模塊owner的狀況下把工做真正作好。

解法一:可複製的環境列入KPI

一種解法是把環境的可複製性變成每一個人的KPI。這樣每一個模塊不只僅負責一個環境(生產環境)的工做正常。他還要負責保持這種可複製性。若是你的業務模式剛好依賴於這一點,則能夠努力推進。好比你要去加拿大運營一套獨立的系統,而可以一鍵部署一套環境給加拿大的運營使用,則變成了一件有業務收益的事情。

這種作法就是要把全流程的持續集成列爲全部人的KPI。若是在線下環境集成失敗,亮紅燈,全部人負責來定位問題。就和生產環境出告警了同樣來對待。若是作不到這一點,所謂可複製的環境就是鏡花水月了。

解法二:生產環境自檢

若是你們都只認同我只須要爲生產環境負責,那就把生產環境變得更強大好了。複雜的機器都有「自檢」的功能。咱們要作的就是讓生產環境能夠跑測試的流量實現自檢,相似於 windows 「打印測試頁」 這樣的功能。四色建模裏的 party/place/thing/moment interval,大部分要測的行爲都是moment interval。經過把 party 這個主體給換掉,把 place/thing 這兩個維度的代碼加以改造(好比一些分城市統計邏輯),能夠實現 moment interval 的重放。

這種方式的實質是仍然是推進全流程的集成。由於全流程持續集成在線下推行失敗,而退而求其次,選擇在生產環境來作。

解法三:基於接口契約的開發

強化接口契約的做用。經過mock掉全部的外部依賴,單獨測試我本身負責的模塊。經過線上tcpdump的方式,方便構造這些mock的請求和響應,減小mock的工做量,以及讓mock真實可信。避免查了半天問題,結果發現是mock接口和線上實際行爲不一致,這樣的烏龍問題。

同時進行接口的形式化定義,而且經過 consumer driven contract 的方式把調用方的mock,變成提供方的測試。

這種作法就是認可微服務架構的實質是利用組織邊界來強化軟件架構的邊界。既然人爲構建了組織牆,與其忽視其存在,還不如對它好好對待。既然我調用對方,對方不肯意幫我解決環境問題,我也沒有能力獨立把對方的代碼跑起來。那不如咱們就劃界而治吧。你們把接口約定好,我用假的實現來替代你的真實實現來作開發。這種作法和咱們調用國企銀行的接口,雙方聯調的方式並沒有本質不一樣。

刻度尺

在中央威權強的場景,書同文的方式是最經濟的方式。不管是統一開發語言,仍是統一RPC框架,仍是統一服務發現。本質上來自於書同文帶來的收益大於威權推動的成本。

在越鬆散的組織下,越會趨向於面向接口開發。

後記

從統一的自動化腳本,puppet/chef/saltstack

到 docker + 網絡代理

到 線上 tcpdump 線下流量回放,基於接口契約的開發

技術難度逐步升級。經過技術上的技巧(好比 docker 利用了你們都是基於統一的操做系統接口)確實能夠解決一些問題。可是技術再怎麼升級,也沒法解決全部問題。畢竟,所謂環境問題,所謂測試問題,所謂個人代碼跑不起來的問題,都是人與人之間如何總體地高效協做的問題。技術的盡頭,是政治。

相關文章
相關標籤/搜索