微服務那麼好,這家公司為何要退回單體架構?
作者 | Mark Little
譯者 | 王強
近年來,我們發布了很多文章介紹企業向微服務遷移的成敗經驗。最近,Segment 的 Alexandra Noonan 寫了一篇文章,講述了他們從單體架構遷移到微服務,之後又退回單體應用的經歷。文中 Alexandra 具體介紹了他們從原來的簡單架構遷移到微服務的過程:
我們原來有個 API 負責攔截事件並將它們轉發到一個分散式的消息隊列中。這裡的一個事件指的是由網頁或移動應用生成的,包含用戶與用戶動作信息的 JSON 對象。當隊列中的事件被消費後,系統會檢查用戶設置來決定接收事件的目標。(……)之後事件被逐個發送到每個目標的 API。這樣的流程很合理,因為開發者只需要將事件發送到 Segment 的 API 這一個目標即可,無需創建幾十個集成。
如果事件交付失敗,就會被系統重新加入隊列,也就是說有時工作者進程要一邊發送新的事件,一邊嘗試重新發送之前失敗的事件。這樣會導致所有目標都出現延遲,Alexandra 解釋說:
為解決最緊迫的阻塞問題,團隊為每個目標各創建了一個獨立的服務和隊列。這一新架構包括一個新的路由進程,它會接收入站事件並向每個選定的目標分發一份該事件的拷貝。現在如果某個目標出現問題,只有它自己的隊列會回溯,不會影響其它目標。這一微服務化的架構將各個目標獨立開來,這樣當某個目標又出現常遇到的問題時就會非常有用。
文章之後寫到,Segment 的開發團隊一開始將所有代碼存放在一起,但這引發了許多問題:
最大的麻煩在於,只要一個測試崩潰,那麼所有目標的測試都會失敗。當我們試圖部署一項改動時,我們必須先費勁修復崩潰的測試,就算改動與一開始的變動毫無關聯也得這樣做。為了解決這個問題,我們決定將代碼拆分到每個目標各自的存儲庫里。
這樣一來開發團隊的靈活性的確改善了許多。然而隨著目標數量的增加,存儲庫的數量也在同步增長。為了讓開發者免受維護這麼多代碼庫的麻煩,Segment 團隊創建了許多共享庫,存放所有目標通用的變換和功能。這組共享庫大大減輕了他們維護工作的壓力。但這個措施也有不太容易發現的負面影響:向共享庫更新並測試改動會花費大量時間,還會增加破壞無關目標的風險。最後這些庫開始分裂為不同的版本,各自不統一,引發了之前沒有想到的一個問題:每個目標的代碼庫都會依賴不同版本的共享庫。Alexandra 承認,他們當時可以開發工具來自動將改動更新到這些庫中,但那時他們又遇到了這個微服務架構產生的一些新問題。
新出現的問題是,每個服務都有自己的載入模式。有些服務一天處理幾個事件,有的服務每秒就能處理幾千個。如果目標只處理很少的事件,工作者線程就得在出現載入問題時手動擴展服務以滿足需求。
他們的系統集成了自動擴展能力,但因為每個服務都需要指定 CPU 和內存資源分配,調整自動擴展的設置「更像玄學而非科學」。如前所述,每次存儲庫的數量增長時他們都要增加目標,最後團隊平均每月要增加三個目標,當然還要加上更多的隊列和服務。
2017 年初,Segment 的一個產品的核心部分使我們達到了峰值負載。當時的情況下我們好像在從微服務的大樹上摔下來,一路撞上了所有的樹枝。我們這個小團隊非但沒有提升效率,反而陷入了愈加複雜的泥潭。這個架構的核心優勢都成了負擔。我們的速度暴降,故障率卻在暴增。(……)於是,我們決定回退一步,重新考慮整個流程。
文章最後 Noonan 回顧了他們如何擺脫這個微服務架構,其中他們還開發了 Centrifuge 來替換所有獨立的隊列,將所有事件都發送到一個單體服務上。他們還將所有目標的代碼都遷移到一個存儲庫里,不過這一次新增了一些代碼管理的規則:所有目標都要使用同一個版本,每次更新時同步更替到新版本。他們再也不用操心各個獨立版本之間的差異了,因為所有的目標都在使用一個版本,以後也是如此。對於開發者來說,管理越來越多的目標所花費的時間減少了,風險也降低了。
Noonan 的文章還寫了很多內容,都是關於他們退回單體服務的經歷。感興趣的讀者應該去仔細讀一下,文章裡面有很多架構細節、關於存儲庫架構的思考和建立彈性測試集的方法。最後,團隊將回退的好處總結如下:
2016 年時我們還在使用微服務架構,我們為共享庫帶來了 32 項改進。而僅僅今年到現在我們就做出了 46 項改進。過去半年來我們為庫帶來的改進比 2016 年全年都多。因為所有的目標都處於同一服務內,我們可以很好地搭配 CPU 密集型服務和內存密集型服務,所以擴展服務以滿足性能需求變得非常容易。更大的工作者池能載入更多內容,所以我們不再需要將處理少量載入的目標掛起到頁面了。
不過這個架構回退過程也有一些負面影響,包括:隔離錯誤變得更困難(一個目標的錯誤導致目標崩潰,結果會傳染到所有目標);升級一個目標的版本可能會破壞其它一些目標,於是後者也需要升級。Noonan 在文章最後寫下了誠懇的總結:
在微服務和單體架構之間做選擇時,要注意它們各自都有自己需要考慮的因素。我們的架構中有些部分是微服務表現更出色,但服務端的目標遷移到微服務後的一系列麻煩是一個很好的教訓,證明這一流行趨勢在某些情況下能對生產力和性能有多大負面影響。結果對於我們來說,單體架構才是最終解決方案。
其實他們關於微服務的某些看法是很眼熟的。今年早些時候我們報道說,ThoughtWorks 根據觀察認為微服務尚未進入普及周期。當時的報道寫到:「主要原因之一是很多組織並沒有為微服務做好準備,他們缺少一些關於運營和自動化的基礎實踐」。此外,Jan 在另一篇文章中總結了多年來微服務遷移的失敗案例。Berico 科技的首席軟體工程師 Richard Clayton 提到了他們當時遇到的一個問題:
在不同服務之間共享通用功能代碼,以消滅各個服務中的重複功能的努力卻帶來了巨大的負面影響,最終導致了大規模回退。
回到原文,有很多關於這個話題的討論,比如 Hacker News 和 Reddit 上的這些;有些討論者認為與微服務無關的一些因素可能導致了這些問題。比如,有些評論指出 Noonan 的文章並沒有引用 CI,只有 CD,起碼這是一個奇怪的組合。還有評論認為不止微服務會引發這些問題,所有的分散式系統都是一個樣。關於這一點我們之前也提到過,有人使用 SOA 時有過類似的經驗:
我曾在一個類似的代碼庫中工作過,那時他們管它叫 SOA,雲還沒開始流行。對服務的每次調用都會啟動一個完整的服務實例。我想我們應該強制將網路延遲規定為架構設計的要素之一。
有趣的是很多討論串談到了微服務中數據上下文的問題。這個話題我們探討過很多次,這也是微服務反對者的主要論據之一。HackerNews 的一條評論舉例說:
比這還糟呢。據我觀察多數微服務架構根本就沒考慮一致性(「我們才不要亂七八糟的事務!」),盲目地隨大流還樂在其中。我搞不懂為啥子人們會覺得,把軟體模塊拆分開來然後用緩慢不可靠的網路和弱爆的手動連接 REST 處理串起來,就能神奇地讓架構面目一新哩?我覺得人們產生這種生產力幻覺的原因是:」我把這些都搞定啦,現在我也有一套』管它是什麼即服務『的先進玩意兒嘍!看看那酷斃的數據面板上閃爍的小綠燈吧,我們可是為了它幹了好幾個月呢!「
另外,為微服務定義域是多年來我們一直強調的微服務部署關鍵環節。有一篇 PPT 介紹了如何使用 DDD 解構單體應用,Reddit 的一個討論串也談到了這一點:
建立一個出色的微服務架構是很難的。我現在覺得關鍵在於恰當地分隔你的域,當系統進化時持續關注這一層面。微服務並不像它的名字那樣,它不必非得那麼小,但是要搭配適合這個架構的元素。很多人的失敗正是因為忽視了這一點。
其他人怎麼看?比如說,Segment 的微服務架構出現的問題能否用其它方式解決,無需退回單體應用?或者一開始的單體架構是否有辦法進化得更好,解決原來的問題,而無需切換到微服務?
英文原文
https://www.infoq.com/news/2018/07/segment-microservices


※洗鍊你的架構認知,看看這100個技術團隊怎麼做架構
※這款限量款T恤讓你1秒趕上科技圈大勢
TAG:InfoQ |