微服務架構下常見的四個技術難題

實務上時常與之對決的痛點

Fred Chien(錢逢祥)
Brobridge - 寬橋微服務
9 min readApr 6, 2020

--

Photo by David Pisnoy on Unsplash

引入微服務架構後,服務顆粒度縮小了許多,一般開發者要面對更多細節和以前很少出現的問題。原本習慣的開發方法和程式架構,也被迫要改變,甚至要思考更多未曾思考過的議題。

尤其是過去很輕易的程式內部呼叫、方法、模組化機制,在微服務架構下,被暴露在整體伺服器叢集之中,不再只是包裹在單一 CPU、記憶體等硬體之上,各種呼叫的安全性、延遲性或是額外的成本,都變成必須要考量的課題,不再容許開發者可以選擇忽略。

實務上有一些常見的技術難題,開發者和架構師在設計自己的微服務應用程式時,時常會與之對決。無奈的是,有些時候遭遇了也只能選擇臨時的 Workaround,除了埋下技術債之外,也免不了為未來埋下了炸彈。因此,很多時候,提前避開才是比較好的作法。

有鑑於此,本文將試著整理出四個比較常見的技術難題:

  • 來回網路通訊問題(Round-trips Problem)
  • 跨服務呼叫導致的可用性降低
  • 跨服務的資料維護問題
  • 資料一致性問題

來回網路通訊問題(Round-trips Problem)

當我們試圖切割服務並設計好 API 後,常會發現在單一個任務需求下,服務和服務之間會產生大量的呼叫。例如有個服務要對原始資料做豐富化(enrichment)的工作,所以大量呼叫另一個服務的 API,進行各種查詢作業。

你可以這樣想像,每當一個外部需求觸發,你的其中一個服務就開始對另一個服務進行阻斷服務攻擊(DDoS),大量的來回網路通訊,除了對網路環境有壓力之外,對於被呼叫的服務來說,也是相當重的負擔,甚至會嚴重影響服務的反應速度。

範例:當 A 服務為查詢每個座標的相關資訊,而大量呼叫 B 服務 API

若要解決這個問題,比較簡單的做法,是考慮將 API 設計成批次(Batch)處理的形式,避免大量單獨的 API 呼叫。但是要特別注意的是,如果整批資料處理需要花比較久的時間,就要考慮是否要將 API 設計成非同步(Asynchronous)機制,避免服務間通訊期的意外發生。

如果服務的關聯性很高,又沒有其他服務共用的需求,則可以考慮合併兩個服務,來減少跨服務通訊。設計上,甚至可以試圖將需求轉成「生產者—消費者」的模式,對需要長時間的業務處理工作進行比較妥當的管理。

此外,就算有與其他服務共用的需求,還是可以考慮合併服務。至於在資料的同步上,則可以引入 CQRS 模式,實現資料庫私有化(Database Per Service)。關於 CQRS 的說明和實現方式,可以參考另外兩篇舊文「淺談 CQRS 的實現方法」和「應用層 CQRS 的實現與困難」。

當碰到此問題,有時候也要檢查並思考一下,是否不小心過度以「重用性」拆分服務,過分的模組化和共享性會為網路通訊帶來許多壓力。

跨服務呼叫導致的可用性降低

微服務架構下的服務間溝通機制都依賴著 API 進行,而且往往一環扣一環,一個服務後面還緊接著下一個服務。一旦其中一個呼叫失敗,整個任務就等於失敗。這意味著,API 的穩定性代表了整體系統的穩定性。

跨服務呼叫,關乎整個任務執行成敗

你或許會說,服務出錯代表有臭蟲(Bug),只要將問題解決就可以。但現實是,有時候不完全是應用程式的問題,而是部署平台導致。現代的容器部署平台,都提供了良好的高可用(HA, High Availability)機制,但問題是平台在轉移、管理我們的容器時,免不了短暫停止容器中的服務實例。這就導致了服務的極短暫中斷,甚至是上面做一半的工作,都可能被強制中止。

為了解決這樣的問題,重試和容錯機制就必須要實現,但這容錯機制的壓力會落在呼叫方身上,相當麻煩。不過無須擔心,大多數建立連線的問題,平台上的 Load Balancer 和 Proxy 會幫我們避免。

只是但意外還是在所難免,尤其是每個呼叫處理如果花的時間比較長,導致保持連線的狀態比較久,意外中斷的機率比較高。又或是每次 API 被呼叫後執行的工作量很大,每次中斷重連後重新處理很耗系統效能。

非同步的 API 設計

為了減少 API 的連線狀態時間,以及資源佔用問題。這時,採用非同步傳遞模式的 API 會是比較妥當的作法。而非同步傳遞模式的做法有兩種,例如 Asynchronous API 的形式,或是採用訊息佇列(Message Queue)傳遞資料。

跨服務的資料維護

資料庫私有化(Database Per Service)是微服務架構下會出現的資料管理模式,而外部系統存取服務的資料庫時,只能透過該服務所提供的 API。這樣的架構意味著,資料被分散存放在不同的服務和資料庫中,無論資料有沒有關聯性,導致資料管理維護上麻煩了許多。

服務各自擁有獨立的資料庫系統

關於資料維護涉及到兩個層面的議題:

  1. 交易機制(Transaction)
  2. 資料一致性

關於資料一致性,會在本文的最後一點提及,因此我們在這一小節只討論交易機制(Transaction)的議題。此外,因為分散式交易機制是一個重要且相對複雜的議題,本文只單純提及概念,更進階的作法和細節,甚至涉及了程式的實作及 API 的定義,比較難三言兩語交代清楚,日後另撰文說明。

過去實現交易機制(Transaction)的方法,多半依賴著資料庫系統本身支援的機制,在單一資料庫中實現。但在微服務架構下,因為資料庫不再是分享共用的形式,若我們要再多個資料庫間實現交易(Transaction)機制,傳統在單一資料庫內部的兩階段提交(Two-Phase Commit)便不再適用,而要轉向尋求分散式交易的機制。

要實現跨服務的分散交易機制,比較常見的會引入 SAGA 模式,而 SAGA 又分為兩種實現方式:

  1. 中心化設計的 Orchestration (編排式、協作式)
  2. 去中化設計的 Choreography(編舞式)

由於其複雜程度是開發者的一大痛點,我們除了提供顧問諮詢外,也為此提供客戶一個開箱即用的分散式交易解決方案「Brobridge Twist」,簡化客戶在實現交易機制上的工作。

中心化設計的 Orchestration

Orchestration 實現上比較能受到開發人員的掌握,因為所有的交易邏輯和控制,掌握在一支程式手上,由該支程式去發號施令,無論是要進行資料變更,還是要回滾、退回,都在同支程式可以完成。

去中心化設計的 Choreography

而 Choreography 在實現上,雖然概念上並不困難,對開發人員來說也只需要看著事件觸發而做事情,相對來說比較彈性。但缺點是比較難以看見整個系統對該交易任務的全貌,中間的事件處理分支,會隨著系統長大而跟著變複雜,需要在維護工作的配套措施裡下一些功夫。

在導入微服務架構之後,交易機制的意義,不再只限於資料層面,而提升到了應用層面。許多工作和任務之間有相依性,保持其行為上的同步,也是我們需要面對的議題。

資料一致性

服務各自的資料庫其狀態可能不一致

延續前一節所敘,資料一致性是另一個在資料分散管理維護後,會產生的問題。交易任務產生的資料變更和同步問題,可以利用交易機制的實現來解決,但非交易任務下所產生的資料狀態不一致的問題,則要尋求 CQRS 這類方法進行資料庫和服務狀態的同步。

另外,縱使我們已經採用了各種資料同步的機制,但仍然在資料一致性上仍有些狀況會發生,一般來說問題發生的成因如下:

  1. 服務間訊息傳遞、處理的時間差
  2. 系統短暫異常

首先要暸解,分散式系統下的延遲問題肯定會存在,因為任務被分散在不同的系統之上運行,其中的網路連線延遲、運算時機以及結果聚合的時間消耗,都是造成延遲的主因。只不過,這些延遲非常的少,尤其當服務處於同一個內部網路、甚至同台機器上,其中的延遲時間幾乎可以忽略不計。

雖然說大部分應用上,這些延遲可以忽略不計,但在一些特定的應用上,卻不能完全容忍這些延遲,針對這些應用,系統需要特別做評估和優化。所以想要解決服務間訊息傳遞的時間差,請先確定任務是否有針對延遲的強烈要求,如果沒有,就沒有解決的必要。

至於系統短暫異常(例如網路、系統環境等問題),導致原本資料的同步機制失效,使得資料不一致的狀況發生。如果是自己實作同步機制,則可以利用 Event Sourcing 來重放事件,達成資料最終一致的要求。

如果是採用現成的 CQRS 方案,則不用擔心,這通常都是在相關解決方案的支援範圍內。

後記

微服務架構的各種設計模式(Design Pattern),其實同時都涉及了不同的實務問題,所以你會在不同問題中看到同一種設計模式的不同面向,例如 CQRS 就會大量出現在資料一致性、合併服務、領域邊界等場景下。所以,熟悉各種設計模式,是微服務架構的重要功課。

最後,如果您對微服務架構及其生態有任何疑問,或是需要技術上的評估與建議,歡迎與我們 Brobridge 聯絡,取得專業的教育訓練和顧問服務。

--

--