本文首發於 Gitchat,原文連結:微服務演進中的經驗和反思
轉載請標明出處。
大部分微服務的案例,我們往往都只能看到一個結果,很難看到其過程,特別是實踐中的彎路。讓人有一種“採用就會成功的錯覺”。經過前三篇的探討,我們通過一個成功案例的三方面分析對微服務成功度量、技術演進和組織演進有了一個基本的認識。本文試著把我在客戶身上看到微服務落地中那些經驗和反思分享給大家。
軟體開發中的“灰犀牛事件”
“灰犀牛”是與“黑天鵝”相互補足的概念,“灰犀牛事件”是太過於常見以至於人們習以為常的風險,“黑天鵝事件”則是極其罕見的、出乎人們意料的風險。
在產品研發的早期,特別是產品開始投入市場的時候,為了取得短期的高速增長所採用的臨時方案。然而,雖然會有資深架構師或者程式設計師告訴你產品這樣做不行。但作為決策層,它並未感到技術債帶來的成本和風險(風險和成本的積累是需要時間反應的)。於是技術債就變成了一個“狼來了”的故事,而架構本身就變成了一個灰犀牛事件:
我們從未切實的感到過應用架構崩潰所帶來的成本,所以對技術風險選擇性失明。
然而,隨著資本週轉的速度越來越快,這些技術債務的利息會慢慢到期,變成一個又一個定時炸彈。於是應用的交接就變成了一個擊鼓傳花的遊戲。越早構建的應用越能體會這樣的痛:
競爭對手的變更越來越頻繁,如果不這樣很難保持領先優勢,因此你也需要更快的交付;
應用質量使得應用交付沒有辦法快起來;
為了避免質量問題,增加需要採用嚴格的流程和中間環節審查才能確認變更沒有問題;
為了採用嚴格的流程和中間環節審查,於是應用交付的流程越來越長,導致交付速度進一步變慢;
由於應用交付的流程越來越長,限於交付截止日期。每個人都只關注自己所處的流程,而無法把控整體質量,導致質量進一步變差。
於是,這就變成了一個悖論:你想讓軟體交付變快的手段只會導致它越來越慢。
對於以上的問題,DevOps 給出瞭解決方案:通過精益(Lean)縮短流程,通過自動化(Automation)提高效率,通過度量(Measure)看到問題,通過分享/分擔(Share)避免只見樹木不見森林,通過文化(Culture)一系列的自律自治而非頂層設計產生的原則注入到組織裡的每個人身上。這就是 DevOps 的 CLAMS 原則。
然而,DevOps 並沒有解決“規模”的問題,它所適用的場景對於“兩個披薩”的團隊來說如魚得水。但那些超過“一百個披薩的團隊”又應該怎麼辦?
慶幸的是,在“規模化 DevOps” 出現之前,就有人意識到了 “DevOps 規模化”面對的問題,也避免了那些對“規模化 DevOps ” 避而不談的尷尬。畢竟,“規模化敏捷”也正處在騎虎難下的境地之中。直到“微服務”吸引了大家的注意力。
我們並沒有看到那些技術債,因為工程師們正在承擔著技術債的利息。我們也沒有看到那些崩潰的應用,因為新的應用會取而代之。那些負責人呢?別擔心,也總會有下一個。畢竟所有人都在閉著眼睛扶梯子,而且會有人對你說“你又沒站在梯子上,何必認真呢?”
直面風險:關注彈性而非確定性
風險管理的本質:不是讓所有的風險都消失,而是確保風險發生時有相應的應對措施。 ——《人件》
在打造穩定的應用系統上,人們往往傾向於提升應用系統預期結果的確定性,避免異常情況的出現,這就是讓“風險都消失”。這實際上是灰犀牛問題的一種表現:我們選擇的不去面對那些一定會發生的風險,而是一廂情願的避免真實的問題發生。
在這種觀念下打造的應用系統會因為僵化而變得更加脆弱,使黑天鵝事件造成的影響更大。然而,如果我們把所有的風險都窮盡,解決這些問題則會花費過多的成本。
我們可以通過事件發生的頻率高低和影響大小,構造一個開發-運維事件矩陣。並且監控每個事件對系統造成的影響,如下圖所示:
根據上圖,通過不斷的度量,我們可以看到在微服務的過程中帶來的變化。然後,我們可以根據各種事情的變化,構建出一個動態的、可自動恢復的彈性應用系統。
Chaos Engineering——"混沌工程"就是一種方法論,能夠通過模擬真實發生的風險來驗證你的自動化應對措施是否有效。
組織結構上也存在同樣的缺乏彈性問題,一個常見的風險就是人員的離職和流動,這是一個常常被忽視的且影響很大的風險。而一個錯誤的做法是極力挽留一個“重要的人”。
如果一個人離開了造成的很大的影響,凸顯出這個人重要性的同時也說明一個組織制度的不成熟。所以,我們要構建一個職責輪換的機制,提升這些事情低頻率的發生,並通過組織自發的改進機制來降低它帶來的影響。這是我所認為 Design For Failure 的意思:直面風險,而不是選擇性失明。
所以你得先看到那些高頻率的影響大的事情。去製造它的發生,然後在不斷的適應中讓他不再那麼痛苦。
保持團隊資訊的極度透明
微服務架構實施中一個常見的反模式就是組織和應用的“碎片化”:很多組織在拆分微服務之後,會安排獨立的團隊負責微服務,並以責任邊界隔離程式碼和團隊。
這樣會使團隊之間從技術到組織流程進入了另一個“深井”。為了解決這個問題,就需要增加了更多的管理人員來解決這些問題。於是一個微服務後的組織被不斷的“墊高”。
按照《我們如何衡量一個微服務實施的成功》一文中的度量方式。如果在微服務改進中管理的長期成本提升,往往說明我們走錯了路。微服務的實施不能帶來資訊的壟斷和碎片化,反而要提升透明度和統一化。以下兩點十分重要:
打破資訊的壟斷,讓所有團隊的所有狀態和資訊——產品路線圖、交付進度、運營狀態等——對所有團隊開放,而不是隻存在幾個人的手裡。
程式碼全民所有制:團隊和微服務不應該是強繫結的關係,用任務型別取代角色。任何人都可以修改任何微服務的程式碼,每個人對自己的修改負責。
按需拆分微服務
很多企業已經在拐點到來之前開始進行微服務改造:引入 Docker、Kubernetes、Kafka……而對於真正的架構問題,大家避而不談,三緘其口,只把一些時興的工具祭奠成了玩具。但是,
如果用技術去解決一個管理問題,要麼你高估了技術帶來的轉變,要麼你低估了管理問題的難度。
微服務帶來的第一個好處就是對風險的隔離:把穩定的部分和不穩定的部分隔離開來。
這種隔離是兩個方面的,一方面是一個應用系統運維部分和開發部分的隔離,把經常變動的業務邏輯和不經常變動的業務邏輯執行環境隔離。另一方面就是在應用內部隔離,把穩定的業務和不穩定的業務隔離。
因為微服務本身就是把內部的複雜度,也就是應用內部的依賴。通過分散式工具轉化為外部複雜度,也就是應用之間的依賴。所以,需要通過額外的基礎設施來管理這些應用之間的依賴。 然而,這樣的轉換所帶來的就是運維成本的上升。
此外,從業務邏輯上“重新拆分”:為了便於理解,進行了更高層的抽象。劃分成了“幾大中心”,“幾大模組”,“各司其職,分別治理”。這滿足了決策層“重新畫蛋糕”的變革訴求,也很想當然的降低的管理的難度。而習慣於“整體規劃”的人往往會從高層設計一個“全域性微服務架構”,然而這些設計難以落地,因為高層不理解底層實現。接下來的微服務就會不計成本的落地。一方面為了維護“架構師的尊嚴”,一方面為了“縮短時間成本”。然而,這些都美好的願望、經常碰到幾個問題:
理想的架構和現實的架構差異很大,因為很多實現的問題在設計的時候並沒有考慮;
服務拆分後每人只關注自己的服務,缺失的服務間業務設計則需要增加管理層來彌補;
自動化不足,需要更多的人員來解決工作量;
急功近利面對太多的未知引發風險和成本的規模性增長;
由於以上問題缺乏度量,微服務的改造進度處於“停滯”的狀態。
如果不對微服務採取度量,你的微服務改造往往是失控的。
此外,並不是所有的應用都適合微服務架構。首先得明白微服務架構所解決的問題。而微服務面對的問題有一個常見的誤解是遺留系統不斷膨脹,使其內部複雜度達到一個不可維護規模,即:
應用規模會增長到規模的增加邊際收益為零為止。
這可以說是經濟學邊際回報遞減規律的一種應用。也就是說,無論你增加功能還是增加人手。當這種增長並不會帶來收益的時候,它就已經到達極限了。然而,我們對這些指標並不度量,所以很多情況下這種成本是未知的。
所以這種事情並不一定會發生,因為我們應用的增長規模並不顯著。我們有足夠長的交付週期和人員流動可以讓這些問題變得不顯著。
只有系統整合的時候,特別是採用 ESB 進行系統整合的時候,這種突然陡增的規模才會引發規模性痛點。整合到一起的系統,同時也加倍整合了複雜度和風險。在這種整合下,交付週期和人員規模一下就變得嚴重短缺。我們並沒有從深層次的角度去化簡這種複雜性,而是通過增加人手來彌補這種短缺。直到無法繼續招聘到合適的人為止。
我們可以看到,我們從一種均衡(各自緩慢演進的獨立系統),經過了規模化動盪(通過系統整合匯聚了風險和成本),通過增加維護人員,達到了另外一種均衡(更大規模的系統和組織)完成了增長。
然而這種增長仍然會面臨規模上限的挑戰,直到而對資本回報率的要求會帶來連鎖性質的崩潰——不光財務債務到期,技術債務也到期了。縮小規模化成本最簡單粗暴的形式——裁員以降低維護成本,而剩下的工程師工作量更大了。
人們是不會砍掉系統功能來縮減維護成本的。畢竟,應用系統是對客戶的承諾,承諾砍了,就意味收入砍了。所以這就帶來了微服務拆分的第二件事:微服務是架構的重構,重構意味著不改變應用的行為,而對應用的內部組織進行優化。而這種優化所帶來的好處是無法度量的。這就意味著如果投入人力,它的成本可能無限大。
對於業務/IT 分離組織來說,沒有業務價值的技術改造是沒有預算的。這也就意味著我們不能用“休克療法”——停止開發,開始改造。所以,微服務的拆分往往就是在開車的時候更換輪胎,伴隨著新需求開發的同時進行應用的重構。
而這時,我們要對新需求成本和微服務變更成本進行約束。根據新需求的預算限定新團隊的大小,並通過團隊的大小約束微服務的大小和規模。在新團隊中採用新的程式碼庫和環境以隔離對遺留系統的影響。採用“絞殺者”策略隔離新引入系統的風險。通過“修繕者”策略隔離遺留系統內部的風險。通過“拆遷者”策略替換掉那些不需要維護的程式碼以降低維護成本。
在我介紹的案例裡,我的客戶用了 5 年把系統的微服務化推進到了 60% 到 70% 左右就不再繼續了。因為微服務化的目的是降低開發運維產品的成本,而不是引入微服務。因此,我們在做新需求的同時兼顧了架構的演進。在不同的階段採用了不同的策略,同時度量這些活動所帶來的成本。如果不進行估算和度量,你的微服務改造一定會因為失控而面對更多的麻煩。
所以,就像之前的文章提到的,做微服務的時候先不要急於你“是”一個微服務的架構。而應該在你的系統裡先有一個微服務。然後看看這個微服務帶來什麼樣的效果,做好各方面的度量,它的投入是什麼?收益是什麼?你需要花更多的成本管理中間的問題?而且你要面對一些不確定的風險?
所以,如果你一開始做微服務的話,你可以考慮一下先成立一個小的微服務團隊,然後感受一下。如果你已經做到深水區,你可以適當減緩速度,縮小規模。或者通過自動化提升效率和規範性。但第一要先做的,是做好度量。
微服務的合併
如果你看到上文發現自己的微服務改造下手太早了,或者已經停滯不前了,往往說明你進入了深水區。在這個階段你開始度量,你會發現微服務的拆分並沒有帶來收益,反而增加了成本。具體表現為以下幾個方面:
基礎設施沒有規範化、自動化管理;
強依賴被拆分了,需要額外的手段保證;
拆分後質量更加難以把控,測試的難度增加了;
微服務網路效能低下;
API 閘道器成為了新的單點故障。
當你出現了這幾種情況,你所面對的可能是微服務的一個反模式——“毫服務”(Nano Service),這個反模式往往是因為在缺乏度量和風險約束的情況下進行拆分導致的。因此,過早拆分微服務是萬惡之源。
所以,當你開始度量,你會發現有些應用是不能拆分的。如果你已經拆分了,就要根據實際的度量,進行合併。我把“微服務的合併”看做是企業微服務化的一個成熟度標誌,這標誌著微服務拆分的度量體系形成。你開始考慮什麼時候該拆,什麼時候該合,也會理解影響拆合的因素。
這些因素主要包括:組織責任邊界、一致性、效能。
我們曾經有一個子系統,它因為組織變動的關係,會在兩三次的變動中劃給不同的部門。劃完之後我們微服務就是拆了合,合了又拆,拆到最後一次拆,前後拆合了 3 次。
在做微服務拆合的時候心裡要牢記康威定理:你的系統結構跟你的組織結構是一一對應的。這樣,你就可以從巨集觀的角度發現問題,也知道哪些是在加速過程中難以逾越的“音障”。
對於效能或者保證事務完整性做的合併,這往往是由於在設計的過程中沒有考慮部署架構。你得理解,網路間的遠端呼叫並不如一個程式內的程式上下文呼叫那麼可靠和效能高。微服務拆分到分散式的環境中需要額外考慮網路的問題。
因此,為了避免這些問題,提前瞭解並規劃好部署結構是十分重要的。
六邊形架構
我們習慣於採用分層的方式來描述架構:展現層、領域層、基礎設施層、資料訪問層,然後我們根據業務,採用垂直的方式進行切分。如下圖所示:
然而採用這種方式進行微服務架構的拆分都會落入一個陷阱:業務中的依賴會被忽視,直到程式碼拆分和服務間通訊的時候才發現架構的錯亂。那是因為分層架構展示的是技術層面的關係,通過分層架構我們是很難描述出業務層面的依賴性的。而業務的依賴性則是微服務拆分的核心:業務中決定了哪些部分是強依賴,哪些部分是弱依賴。
然而,在大規模系統中,特別是整合後的大規模系統中,這些依賴的發現則更加困難。
而採用六邊形架構的方式,我們則更容易的看到業務之間的關係。
“六邊形架構”又被稱之為“埠與介面卡”模式。 六邊形架構將系統分為內部(內部六邊形)和外部,內部代表了應用的業務邏輯,外部代表應用的驅動邏輯、基礎設施或其他應用。內部通過埠和外部系統通訊,埠代表了一定協議,以 API 呈現。一個埠可能對應多個外部系統,不同的外部系統需要使用不同的介面卡,介面卡負責對協議進行轉換。這樣就使得應用程式能夠以一致的方式被使用者、程式、自動化測試、批處理指令碼所驅動,並且,可以在與實際執行的裝置和資料庫相隔離的情況下開發和測試。
在六邊形架構中,業務是固定和穩定的。而實現則是可以替代和替換的。不同的六邊形代表了不同的領域物件,各自可以成為一個獨立的微服務。如果你可以用如果你能用六邊形架構去畫你的架構圖,那麼你離微服務已經不遠了。它的耦合和技術實現相對清晰和穩定。
對齊“統一語言(UBIQUITOUS LANGUAGE)”
實際上,微服務的架構需要考慮以下幾個架構的相互匹配:
組織架構:人員的管理層級架構;
業務架構:根據人員管理層級帶來的業務流轉架構;
應用架構:根據業務流轉進行的應用程式邏輯劃分架構;
部署架構:將應用程式邏輯組織在分散式計算平臺上的架構。
在這四類架構中,1 和 2 都是非技術因素,但是決定了 3 和 4 的技術因素。這就是上文提到的“康威定律”的另一種表現形式。而工程師看待系統的角度和使用者看待系統的角度是不同的。這很大一方面的原因是沒有采用同一種方便兩者相互理解和互動的語言進行對話。
此外,不同的系統在整合時也需要對齊介面中所採用的語言和單詞,否則同一個詞語在不同的系統中有不同的解釋,導致深層次的麻煩。
在我之前提到的客戶身上,發生了這樣一件事情。他們系統包含了一個報價系統和一個核算系統。一個對內部使用,一個給外部使用。在國外,對於外部使用的系統來說,價格是含稅的。而對於內部計算統計來說,報價是不含稅的淨收入。這個比率大概是在 10% 左右。然而,由於兩個不同的系統都採用統一的單詞 Price 來作為系統之間交換資料的欄位。因此,系統就把內部的不含稅價格暴露給了客戶。當我們發現這個問題的時候,這個系統已經執行了三年。也就是說,這個系統在這三年裡每年都損失了 10% 的收入幫客戶繳稅。
整個過程是因為我們在幫這些系統增添自動化整合測試案例的時候,發現這個自動化測試失敗了,然後就發現了兩個系統之間價格的不一致性,雖然都是價格(Price),但一個含稅,一個不含稅。
當你去和不同的系統進行整合的時候,你會發現對齊統一語言非常重要。這也是我們在做微服務架構的過程中會發現的一個問題。由於程式設計師不懂得業務,就會導致這樣的問題。所以你需要有一個領域專家在不同的系統整合時對齊領域語言。
後來,我們把“價格”這個單詞在不同系統裡用兩個不同的單詞來表示。這樣我們就知道在進行價格計算的時候需要重新考慮它是否含稅。然後,我們編寫了一些自動化測試來保證計算正確。一旦稅率修改了或者規則修改了,我們就知道在哪裡去修改相應的業務邏輯。
不要過早的開發出統一化的工具
微服務本質上仍然是一個分散式系統,在拆分微服務的過程中,是用運維的代價去交換開發的代價。由於運維的相關內容比較穩定,因此進一步隔離了系統中的風險。
如果我們把這一類運維問題看做一個獨立的問題,我們也可以採用軟體的方式來解決這個問題。很多組織,包括我所介紹案例中的客戶,也開發了一套統一的工具來解決微服務的問題。
但是,統一化的工具是一把雙刃劍。作為程式設計師總是喜歡不斷重複造輪子來自動化,然後就會做一些工具加快自己的工作效率。但是當你去做統一化的工具的時候,你會要求每一個團隊都使用這個工具,就會造成新的依賴。
而且,這個工具未必沒有開源的成熟解決方案。如果你要開發一個工具,你需要投入一個人,甚至一個團隊來維護它的時候,很可能就會進入“黃金錘”反模式。
而在微服務化的初期,通用場景識別的很少的情況下,它的場景是有限的。一旦場景出現變化,統一化工具就會變成一個阻礙。到後面場景越來越複雜的時候你需要不斷修改工具,保障向前相容的同時還要向後相容。你可能會發現你開發微服務的時間要遠遠小於你處理統一化工具問題的時間,你就要額外花費成本。
我們的客戶就開發了一個結合 AWS 釋出 Docker 應用的生命週期管理工具以及對應的微服務模板。但是隨著場景的增加,這個工具變得越來越複雜。很小的部署訴求都要增添很多無用的元件和配置。於是我把這個工具去掉,採用最簡單的 AWS 原生方式來部署應用。
如果你做了一個統一化工具,那你就把它開源推廣。如果你做一個工具不去推廣,你的工具會被其它成熟的開源工具取代,你的研發投入就浪費了。如果你開源效果好,說不定成為了業界的某一標準,就會有很多人幫助你來維護這些工具。
我聽說過某網際網路電商大廠的一個故事:由於 Docker 化的需要,就自己研發出了一套很先進的容器管理平臺,但是就憋在自己手裡,不開源。後來的故事就是,它們把自己辛苦研製的容器管理平臺換成了 Kubernetes。所以當你一開始做這種工具,就要考慮它的研發成本和替換成本,減少這個成本的一個方式就是開源。在全球範圍內,我們去編寫軟體構造技術壁壘是沒有意義的,因為總會有一個人把好的方案開源出來。然後成為大家競相模仿的物件,而後形成事實上的標準。
在這種情況下,如果你的團隊有想開發統一工具的一點點動向,請首先考慮業界有沒有成熟的開源方案。造輪子、卸輪子和換輪子的成本都很高。軟體行業發展這麼多年,天底下並不會有特別新鮮的事情。
越來越厚的微服務平臺
統一化工具的另一個方面就是微服務平臺。統一化工具給了我們一套開發的規範,而微服務平臺給我們了一個透明的基礎設施。
我們的客戶成立了“熊貓團隊”(PandA,Platform AND Architecture 平臺和架構團隊)來降低微服務的部署和釋出的門檻。這往往是由於 DevOps 的“二次分裂”產生的。當 DevOps 的應用越來越廣泛之後,開發和運維的邊界從模糊再一次變得清晰。使得開發應用越來越快,越來越標準化。另一方面,運維部門會把所有的服務都 SaaS 化並提供統一的規範來降低管理的成本。
隨之而來的是另外一個問題:當我們使用流程、工具,應用更多平臺的概念的時候。我們會發現,整個工作流程可能變得不再敏捷了。在敏捷裡,我們說個體和互動高於流程和工具。在這個情況下,我們會增加越來越多的流程和工具,從而減少個體和互動。
我們在之前強調敏捷的時候,認為敏捷宣言的的左項和右項是對立的。但是現在我們回顧敏捷宣言,我們可不可以兩者同時有?既提升個體和互動,又有流程和工具。從這個角度想的時候,我們就會創造新的工具、新的方法論解決它的矛盾。
這就是我所說的後 DevOps 時代,我們運維團隊變成內部的平臺產品團隊,它在給開發團隊提供一個基礎設施產品。而開發團隊變成了一個外部的應用產品團隊,它在給使用者提供一個滿足業務需求的產品。彼此獨立但又保持 DevOps 生命週期的完整性。
最後
以上是我對這個客戶 5 年來微服務改造過程中的部分觀察,作為微服務改造的親身經歷和見證者,我有幸觀察到了一個組織是如何通過微服務從內而外發生變化的。在我的經歷裡,微服務是 DevOps 深入的必然結果。當我們的部署和釋出遇到了瓶頸,就需要採用低成本且可靠的方式分離關注點和風險點,調整應用的架構以進一步提升 DevOps 的反饋。
當我們開始實踐微服務的時候,首先得先統一微服務的認識,讓大家對微服務有統一的理解。其次是瞭解我們為什麼要做微服務?微服務解決了你的什麼問題,而不是陷入了盲目的技術崇拜中。