從小白到架構師(2): 走向微服務

「從小白到架構師」系列努力以淺顯易懂、圖文並茂的方式向各位讀者朋友介紹 WEB 服務端從單體架構到今天的大型分佈式系統、微服務架構的演進歷程。在「從小白到架構師」系列的第一篇《應對高併發》中,我們介紹了通過緩存、橫向擴容、消息隊列、分佈式數據庫等基礎設施來提高系統併發量的方法。在實際開發中業務邏輯比基礎設施更加靈活多變且更容易出故障,架構設計不僅需要考慮基礎設施的建設,同樣需要關注業務開發的便利以及應對業務系統的故障。

上期: 從小白到架構師(1): 應對高併發

還是從博客開始

還是從我們熟悉的博客網站開始,小明是個喜歡寫博客的程序員,他覺得市面上的博客網站都太醜了,就想自己搞一個。說幹就幹,小明抄起LAMP(Linux-Apache-MySQL-PHP)一把梭三下五除二就把網站搞了起來,網站的名字就暫定爲「淘金網」😏

由於淘金網界面美觀方便好用, 越來越多的網友開始入駐淘金網來耕耘自己的一小片天地。漸漸地用戶們覺得只能寫博客功能太單一了,有些用戶想要把系列文章編輯成電子書、有些用戶想要做直播分享,有些用戶想要在這裏髮狀態,小明也想要賣點會員補貼一下服務器的支出…… 沒關係都可以有,繼續一把梭加邏輯就是了:

日子也就這麼一天一天的過下去,淘金網逐漸從個人小站發展成了一家小有規模的創業公司。

  1. 博客、電子書、微博每個模塊都向用戶、訂單這些表裏塞了一堆字段,一動線上就出 BUG, 誰都不敢動。
  2. 即使上線一個小功能也要發佈整個網站,有時候會不小心帶上了一些未經測試的代碼,有時候會在意想不到的地方出了錯誤。
  3. 一個業務容量不足需要加機器, 就相當於給所有模塊做了擴容,白白支出了其它模塊的固定開銷(無負載的服務所消耗的資源,比如 Spring 容器消耗的大量內存,後臺線程消耗的 CPU)。

這些都還可以忍,直到那個陽光明媚的早晨小明美滋滋打開後臺想要看一眼今天的入賬時,卻發現網站打不開了。。。

截屏2022-10-03 16.28.23.png

檢查日誌發現,在編寫創作者中心的邏輯時不小心寫了一個不停向數組中插入元素的死循環,過不了多久服務器就會 OOM 崩潰掉,只有 supervisord 還在徒勞的嘗試重啓服務器。。。

明總是個未雨綢繆的人, 他覺得BUG 是無法杜絕的,這樣的故障早晚會再次發生。 小明靈機一動決定把博客、電子書、會員、直播這些業務拆分成獨立的服務端程序,一個模塊拉起一個進程,這樣任你 OOM 還是 panic 都不會影響其它業務,萬一出了什麼故障損失也就小得多了。

小明發現把服務拆開後一些老問題也解決了:未測試代碼被誤上線的情況幾乎沒有了;各個模塊可以按照需求各自規劃服務器資源了;而且還有個意外之喜,每個模塊可以用不同的技術棧, 前端可以用 node.js 做 BFF, 數據分析可以用 Hadoop, 推薦系統可以用 Python...

還有個問題沒有解決,不同模塊依舊依賴同一個數據庫,表結構牽一髮而動全身,每次修改都小心翼翼如履薄冰。小明決定一鼓作氣,將數據庫也按照業務板塊進行拆分,每個模塊只允許讀寫自己的數據庫,需要其它模塊數據時一律調接口,禁止直接訪問數據庫。

ok, 現在一切都是那麼的完美,歲月如此靜好。。。

服務治理的難題

話說自從服務拆分之後再也沒有出現過全站崩潰的事故,小明美滋滋的準備開個年會,大家拿了年終獎回家過年。就在年會上,小明聽到程序員們在抱怨:

  • “支付系統一到有活動就擴容,活動結束就把臨時加的機器下掉,其它人也得跟着改配置文件才能找到服務地址”
  • “一個請求經過了好幾個模塊,出了 BUG 找半天都查不清是哪個模塊的故障”
  • “一上促銷商城那邊的調用就特別多,差點把我們的支付壓垮,直播的人就來抱怨說沒法刷禮物”
  • “會員那邊不靠譜,付款失敗還照樣發會員,損失好多錢”

小明把這些問題一一記錄下來,開始尋找答案。

服務發現

支付系統一到有活動就擴容,活動結束就把臨時加的機器下掉,其它人也得跟着改配置文件才能找到服務地址

我們服務部署在不同的服務器上,而且會隨着負載情況不時的增刪機器,調用方如何及時準確的獲得服務的地址?實例之間如何均衡負載?我們將這個問題稱爲服務發現。

DNS 系統也可以算是一種服務發現,服務提供方的節點在啓動後向 DNS 註冊自己的地址,節點下線前將自己從 DNS 的節點列表中刪除。服務調用方通過域名向DNS查詢服務提供方的實際地址,DNS 會在節點列表中按預定策略挑選一個節點的 ip 地址返回給調用方。

在實際使用中更多的還是採用 Zookeeper、Consul、Etcd 等高一致性的 KV 組件做服務發現:

服務發現系統會通過心跳包等機制檢查節點健康狀態,並屏蔽不健康的節點。這樣即使節點在崩潰前沒有向配置中心報告故障,服務發現也能避免請求繼續到達異常節點:

因爲服務發現可以方便的控制調用方訪問的節點,所以也常常用來實現灰度發佈,A/B測試等功能:

限流、熔斷、降級

一上促銷商城那邊的調用就特別多,差點把我們的支付壓垮,直播的人就來抱怨說沒法刷禮物

雖然服務拆分之後單個進程崩潰不會波及其它進程,但是若下層的服務的實際負載超出了最大吞吐量出現響應過慢或超時的情況仍然可能波及其它上層服務。

因此,有必要在服務之間設置保護機制,防止小故障的影響不斷擴大,最終造成大面積的雪崩。常用的保護機制有三種:

熔斷:當某個服務或節點的調用失敗數或調用耗時超過閾值時,調用方應停止繼續調用此節點並快速返回失敗。防止自身請求大量堆積,整條鏈路浪費大量資源等待下游響應。

降級:當下遊服務停止工作後,如果該服務並非核心業務,則上游服務應該降級,以保證核心業務不中斷。

限流:當服務提供方的負載接近最大處理能力時可以丟棄請求並立即返回失敗,防止大量堆積的請求將自身壓垮。或者當某個上層服務的調用量過大時丟棄它的(部分)請求,避免自身崩潰影響其他上層服務。

鏈路追蹤

一個請求經過了好幾個模塊,出了 BUG 找半天都查不清是哪個模塊的故障

要想查清故障原因就需要記錄一個請求在系統中經過了哪些模塊以及模塊之間的調用關係,進而找到故障模塊的相關日誌,這種在服務系統中追蹤調用關係的技術稱爲鏈路追蹤。

鏈路追蹤的原理是在請求進入系統時爲它分配一個唯一的 traceID (通常使用雪花算法生成), 這個請求調用過程中產生的所有日誌數據都要帶上這個 traceID 並上報到統一的日誌數據庫。事後分析時只要使用 traceID 進行查詢就可以找到相關日誌了。

比較出名的鏈路系統是 Google 的 Dapper,有興趣的朋友可以點鏈接看一下詳細的資料,這裏就不展開討論了。

分佈式事務

會員那邊不靠譜,付款失敗還照樣發會員,損失好多錢

付款和變更訂單狀態兩個操作應該是原子的,要麼都執行要麼都不執行,不應該出現一個執行另一個不執行的情況。這是典型的數據庫事務問題,在我們將數據庫拆分之前這個問題可以交給數據庫的事務機制解決,但是數據庫拆分之後一個事務涉及到多個數據庫實例甚至是異構的數據庫,這就需要一些分佈式事務協調組件來處理了。

常見的分佈式事務實現方案有 TCC(try-confirm-catch)事務、MQ 事務消息、Saga 事務等。分佈式事務主要有兩種實現思路,第一種的典型代表是 TCC 事務,TCC 事務分爲三個階段:

  1. Try 階段: 事務協調器要求參與方預留並鎖定事務所需資源;
  2. Confirm 階段: 若所有參與方都表示資源充足可以提交,事務協調器會向所有參與方發出 Confirm 指令,要求實際執行事務。
  3. Cancel 階段: 若 Try 或 Confirm 階段任一參與者表示無法繼續事務協調器會向所有參與方發出 Cancel 指令解鎖預留資源並回滾事務。

第二種實現思路的典型代表是 Saga 事務,Saga 事務將一個大事務拆分成多個有序的子事務並且每個子事務都準備了撤銷操作,事務協調器會順序的執行子事務,如果某個步驟失敗,則根據相反順序一次執行一次撤銷操作。

上面我們只簡單介紹了分佈式事務保證原子性的機制,在實際實現中還要考慮分佈式事務的一致性(強一致還是最終一致)、隔離性(Saga 事務會暴露事務執行到一半時的狀態)、對業務的侵入性、併發量等各種問題,簡言之分佈式事務是一種非常複雜、成本很高的技術。

由於分佈式事務的高成本,在在實際開發中經常使用「對賬」的方式來保證多模塊事務的最終一致性,即用離線任務定時掃描數據庫找出未正確處理的事務,然後按照預定策略進行補償(比如撤銷未成功付款用戶的會員身份)或者要求人工介入修復。

微服務時代的基礎設施

容器化和 Kubernetes

淘金網從最開始便是直接部署在雲服務器上的,到了後來做了服務拆分也依舊沒有改變。每次擴容都要等待新的雲服務器慢慢啓動、跑腳本裝環境最後拉起服務進程,一等就是半天。需要升級 JRE 的時候還要寫腳本一臺一臺連上去進行升級,有時候還會升級失敗需要人工介入進行處理。 還有些時候爲了充分利用資源會在一臺雲服務器上部署好幾個服務,這些混部的機器管理起來也是各種麻煩。

這一切都讓程序員們苦不堪言,於是小明又開始了調研,這時一種叫「容器化」的新技術吸引他的視線。

我們都知道計算機可以分爲三層:硬件、操作系統和應用程序。所謂的雲服務器本質上是虛擬機,虛擬機可以模擬硬件的接口,這樣做最大的好處是可以在虛擬機上運行與宿主機不同的操作系統程序,比如我們可以 Windows 系統上運行 Linux 虛擬機。但是,操作系統內核的計算量十分龐大,在軟件模擬出的硬件上運行其性能可想而知。

對於雲服務器而言並不需要再運行一個操作系統內核,雲服務器只是需要獨立的目錄樹、進程空間、協議棧就可以了,就是說即使雲服務器 A 和 B 運行在同一臺的宿主機上 A 的根目錄和 B 根目錄是獨立的。

容器化技術的實質是模擬操作系統內核,實際上運行的只有宿主機一個操作系統內核,但是宿主機上的每個容器都認爲自己擁有一個獨立的操作系統內核。

Docker 是目前容器技術的事實標準,它使用使用 Linux Namespaces 技術隔離目錄樹、進程空間、協議棧等,使容器之間互不影響;使用 cgroups 機制分隔宿主機的 CPU、內存等資源。

Docker 的另一個重要貢獻是定義了容器鏡像的標準。 Docker 鏡像使用分層文件系統 AUFS。每層數據一旦提交便不可改變,只能添加一個新層將其覆蓋。Docker 鏡像的不可變性保證了運行環境的一致,免除了登錄雲服務器裝環境的痛苦,一致的運行環境也減少了「測試環境是好的,怎麼一上正式環境就出問題了」的發生。 分層文件系統使得每次打包 Docker 鏡像只需要更新業務二進制,比虛擬機鏡像小很多。Docker 允許將任何現有容器作爲基礎鏡像來使用,極大的方便了重用。

Docker 只提供了單機上的容器化支持,而我們的生產環境是由很多服務器組成的,有些模塊負載不足需要橫向擴容多加幾個容器,有些宿主機會宕機需要將上面運行的容器換臺宿主機重啓。解決這個問題的是大名鼎鼎的 Kubernetes, Kubernetes 不僅可以完成服務編排的工作,而且提供了描述集羣架構的規範。我們通過編寫 yml 定義集羣的最終狀態,Kubernetes 可以將系統自動達到並維持在這個狀態。這種能力將人力從繁重的運維工作中解脫出來,實現了方便可靠的部署和擴縮容。

Service Mesh

由於微服務需要提供服務發現、熔斷限流等服務自治能力,所以微服務框架所要提供的功能比傳統的 Web 框架多很多。看到現在仍不少見的 Centos7、Java 5、Struts2 等各種老舊的基礎設施就可以想象升級基礎框架是一件多麼痛苦的事情。

爲了解決基礎架構組和業務組之間爲了升級框架帶來的瘋狂扯皮,小明找到了一個新的思路。這種方式稱爲 Service Mesh,它將服務發現、認證授權、調用追蹤等服務治理所需的能力放到一個被稱爲 SideCar 的代理組件中,所有出站入站的流量都通過 SideCar 進行處理和轉發, 業務方只需要和 SideCar 進行通信即可。

Service Mesh 中還有個被稱爲控制面(Control Panel) 的組件來統一管理所有 Sidecar 的配置,SideCar 和業務組成的部分稱爲數據面(Data Panel), 控制面和數據面共同組成了 Service Mesh 架構。

因爲 Side Car 和業務只通過 RPC 進行通信,兩者可以獨立升級,免去了升級基礎框架時需要改動業務代碼的種種麻煩。由於 RPC 調用天生可以跨語言,所以只需要開發一次 SideCar 就可以對接多種語言開發的業務系統。

圖片來源: Pattern: Service Mesh

ServiceMesh 直譯是服務網格,大概是因爲架構圖比較像網格才起了這個名字吧~

總結

何謂微服務

又是一年年終季,小明看着自己搞的這麼多東西打算整個 PPT 去行業交流會上吹吹牛。他左翻右找,終於找到了一篇論文:Microservices: a-definition-of-this-new-architectural-term, 原來自己做的這套結構有一個好聽的名字:「微服務」:

微服務是一種通過多個小型服務組合來構建單個應用的架構風格,這些服務圍繞業務能力而非特定的技術標準來構建。各個服務可以採用不同的編程語言,不同的數據存儲技術,運行在不同的進程之中。服務採取輕量級的通信機制和自動化的部署機制實現通信與運維。

「什麼是微服務」這個問題是典型的一千個人眼裏有一千個哈姆雷特,不過回顧「淘金網」的歷程我們發現有一些理念已經是業界的共識:

  1. 按照業務板塊將單體大服務拆分爲多個獨立的小服務,通過分割解耦的方式控制代碼的複雜度。小服務的獨立性賦予了它更高的靈活性,比如採用異構技術和異構架構的自由。分散部署也有效的阻止了局部錯誤造成大範圍的故障。
  2. 數據去中心化: 各個服務獨立維護數據庫,降低模塊之間的耦合程度。
  3. 重視服務治理,但鼓勵各模塊自治。微服務不僅僅是將服務拆分,而且重視處理拆分後出現的一系列問題,比如控制流量路由的服務發現系統;避免連鎖故障的限流、熔斷、降級技術;用於調試和排查的鏈路追蹤系統;以及維護事務安全性的分佈式事務機制。服務治理能力不是由中心或者基礎架構強加給各模塊的,而是各模塊根據自己的需要靈活選擇治理能力和實現方式。
  4. 重視彈性:服務的部署容量不是固定的,而是根據業務需求量隨時增加或減少節點數,並且因此促進了 Kubernetes 等彈性平臺的廣泛使用。
  5. 重視彈性:服務系統應該可以按照業務需要靈活的進行擴縮容。

下集預告:揭開分佈式系統的面紗

很多同學一提到分佈式系統便想到 CAP 理論、Paxos 算法、Hadoop 等嚇人的名詞,甚至失去了繼續學習的勇氣。「從小白到架構師」 系列的前兩篇幾乎每句都與分佈式系統密切相關,第三篇文章「揭開分佈式系統的面紗」我們將一起探索分佈式系統的各種技術和問題:如何編寫在分佈式環境中運行的業務代碼?什麼是分佈式共識問題,又有哪些解決方案?CAP 定理是什麼,又有哪些例證?那些經典的分佈式數據庫又是如何工作的?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章