前情提要: (Part 1)

補上欠很久的第二篇~

上一篇聊了進入微服務架構前,架構師本身的心理建設、為何要用微服務的原因,以及導入這些美好的架構背後的代價。 這篇開始拿我過去負責過的實際案例,來聊聊我如何把典型的商用大型軟體 (單體式架構),逐步調整為微服務架構的過程跟決策。

先寫在前面,如果你想看的是現在大型的雲端服務,如何用微服務架構落實的 (例如 Netflex 等等),那這篇可能不是你有興趣的。 這篇講的案例,比較是台灣大部分軟體公司會面臨的狀況,過去發展很久的傳統企業應用程式 (通常是 ASP.NET + MS-SQL),架構 較講究的會有三層式架構,有的甚至只有兩層 (WEB + DB) 而已。隨著時間的發展,功能不斷的增加,軟體越來越複雜,但是架構上 卻沒有太大的變化。

這樣發展了幾年下來,大概都已經面臨到維護的成本跟難度已經到達瓶頸的階段了吧! 這時會採用微服務架構的動機,就跟大型雲端 服務採用微服務架構的理由不同了。主要動機都會再改善既有問題為主。這篇講的就是我過去處理過的這類案例。所以也請各位先有 心理準備,這篇的內容並非完全按照典型的微服務的路線發展,我自己為這套系統的狀況做了些調整與取捨,寫這篇單純分享經驗而已~

看到這邊,準備好了就請繼續往下捲,本文開始~ :D

前言: 微服務架構 系列文章導讀


Microservices, 一個很龐大的主題,我分成四大部分陸續寫下去.. 預計至少會有10篇文章以上吧~ 目前擬定的大綱如下,再前面標示 (計畫) ,就代表這篇的內容還沒生出來… 請大家耐心等待的意思:

  1. 微服務架構(概念說明)
  2. 實做基礎技術: API & SDK Design
  3. API First Workshop: 設計概念與實做案例
    • API First #1 架構師觀點 - API First 的開發策略 - 觀念篇; 2022/10/26
    • API First #2 架構師觀點 - API First 的開發策略 - 設計實做篇; 2023/01/01
    • (計畫) API First # 微服務架構 - API 的安全機制;
  4. 架構師觀點 - 轉移到微服務架構的經驗分享
    • Part #1 改變架構的動機; 2017/05/09
    • Part #2 實際改變的架構案例; 2017/05/20
    • Part #3 實際部署的考量: 微服務基礎建設; 2017/07/11
  5. 基礎建設 - 建立微服務的執行環境
    • Part #1 微服務基礎建設 - Service Discovery; 2017/12/31
    • Part #2 微服務基礎建設 - 服務負載的控制; 2018/06/10
    • Part #3 微服務基礎建設 - 排隊機制設計; 2018/12/12
    • Part #4 可靠的微服務通訊 - Message Queue Based RPC; 2019/01/01
    • Part #5 非同步任務的處理機制 - Process Pool; 2020/02/15
    • (計畫) 微服務基礎建設 - 版控, CI/CD, 容器化部署; 大型團隊 CICD 的挑戰
  6. 案例實作 - IP 查詢服務的開發與設計
  7. 建構微服務開發團隊
  8. 分散式系統的基礎知識
    • 分散式系統 #1 如何保證 API 呼叫成功? 談 Idempotency Key 的原理與實作

實際轉移案例

上一篇 講到: 轉移到微服務架構是有條件的,基本門檻還達不到的話就貿然轉移到微服務 架構是很冒險的。微服務架構先天就較複雜,在開發、部署及維運這三個部份的要求都比單體式架構的 APP 來的高。因此我過去的經驗中, 我是採取比較保守的策略在做這件事。什麼叫 “保守”? 意思是我會先花很多時間研究及了解微服務架構的優缺點,也會花時間逐一進行 POC 驗證每個改變的環節是否都能順利進行,包含開發的技術,還有團隊的流程與系統執行的環境等等,而不是冒然的就把既有的服務整個打掉重寫。

因此,我把既有的服務架構作了規劃,安排如何一步一步地進行微服務化。許多微服務化的架構建議我並沒有全面採用,因為我們的服務規模還不夠大,投入在這些環節上,短時間只會造成困擾,而不會有具體的改善,這類狀況我只要確保現在的架構不會成為將來進一步改進的阻礙就可以了。

在開始之前,先來看看我面臨的舊架構系統概況跟規模吧! 我試著盡量具體的量化 “規模” :

這服務是人資領域相關的系統,用於數位學習及人才發展的管理。這類系統的特色就是表單特多,流程複雜,每一頁都是滿滿的表單跟欄位要填寫 或是顯示。這系統的特性造成程式碼之間的耦合度過高,維護的成本居高不下,因此降低維護的複雜度是主要目標。傳統的作法,例如模組化等等其實 都有用上了,不過仍然在系統運行時碰到一些挑戰,例如更新升級不易等等大型單體式架構的常見問題。另外,這系統需要同時提供單機版本的 建置 (private cloud), 同時也提供雲端服務及代管模式 (SaaS, public cloud)。讓軟體架構同時適應這兩種 hosting 環境也是改版 的目標之一。

這是改變之前的系統架構圖。基本上就是很傳統的設計,主要是由 WEB + DB 兩種角色組成的典型架構。其中商業邏輯的部分雖然有適當的切割 (用 library 與 component 的型態),但是在執行期間仍然是被放在同一個 process,部署的架構上並沒有明確的將之獨立出來。除此之外, 因為系統包含數位學習,需要管理大量的教材內容,因此多了 file server 與 content server 的延伸架構。

各位可以想像一下,這狀況其實完全命中了微服務架構想解決的問題點啊! 這麼大規模的 code base, 改了任何一行就需要整個服務重新部署, 我們在這樣的基礎上導入了 CI / CD, 但是冗長的編譯時間也導致 CI / CD 的作用大打折扣。另外,商業邏輯也包含在 WEB 內,任何一個 模組失效 (如 out of memory exception 等等) ,都會導致整個系統掛掉…。

在當時,我歸納了整個系統架構上有四項主要的問題等著被解決:

即使如此,微服務架構仍然不是萬靈丹,因為前面有提到,微服務架構是有進入門檻與代價的啊! 經過評估之後,我們決定按照實際問題的影響程度, 逐步進行改善。對我們當時的狀況而言,最優先處理的是 維護困難、還有 雲端化困難 這兩項。而 部署困難 則是緊接著雲端化 問題解決後緊接著會面臨的問題,因此也一併考慮在內。因此我們著手進行微服務化架構的改進,切割的策略及進行順序都按照這重要性來安排。

回顧這決策的過程,我覺得 “這篇文章: 一窩蜂驅動開發” 最能描述我當時的心境了! 這是在事後才讀到的文章,不過這篇文章講的內容完全就是我當初思考的啊! 不分享不行,我特地花了點篇幅來介紹這篇文章要講的內容…

一窩蜂驅動開發!!

原文: 一窩蜂驅動開發

我只能說,這篇文章講的實在太傳神,太到位了! 甚至文內舉了幾個常見的例子,就包含了微服務架構 XD,根本完全命中我擔心的狀況啊…

其實多看看別人的解決方案,多看看新的技術,並不是壞事。糟糕的地方在於對自己過度自信,聽到很棒的新技術就相信自己能 100% 駕馭, 不顧後果的就往前衝,往往會讓自己 (及團隊) 陷入進退兩難的困境。通常會陷入這種 “一窩蜂” 的困境的人,都有幾種特質:

  1. 好奇心強,喜歡新的技術。
  2. 高度自信,認為自己無所不能,或是高估團隊的能力。
  3. 過度樂觀,缺乏替代計畫。
  4. 過於躁進,未做仔細評估就決定執行。
  5. 過於熱血,追求新技術的衝動,遠高於解決問題的企圖。

其實,這幾個特色並不算是缺點,甚至很多成功的人都會有這些特質。有往前進的動力是很好的,危險的是過於衝動及躁進的態度,沒有搭配 良好的執行計畫,才是致命傷啊! 過程我就不多說,文章裡面其實已經講得很精采了 XDD,我針對主題 (導入微服務架構) 拉回來討論:

前面才說到,微服務架構的進入門檻是很高的,這點會讓過度樂觀的人更容易掉入陷阱。我如果早個五年十年來經歷這些事情,我可能會毅然決然的 決定把整個架構砍掉重練 (Orz, 馬上中槍)。不過現在的經驗告訴我,沒事千萬別丟掉現有的 source code 從頭改寫。之前分享過一篇文章就是 在講這件事: 程序员,为什么千万不要重写代码?。舊的 code 再怎麼不堪,至少它的邏輯是符合使用者期待的,只是可能會伴隨著很糟的效能、很差勁的可靠度… 等。用新的架構改寫,的確可以一勞永逸的 解決這些問題,不過所有的系統開發案都一樣,你終究需要一段時間,不斷的經歷測試->修正->測試… 的循環,bugs 才會逐漸下降,系統的品質 才會逐步提高到可接受的水準。砍掉重練的話這部分是很難速成的啊! 這時不論解決了多少其他問題,你的新系統終究無法立即上線,專案終究必須 度過很長的一段黑暗期…

因此我採納了其他微服務的大師及成功案例的作法,改採 “重構” 的策略,逐步的把系統調整成微服務架構。系統太大,就把他重構切割為幾個小系統。 不好切割? 那就先重構 source code, 將 code 調整為較好切割的架構 (例如透過 interface 來使用 library 或是 component)。並且在那之前, 我花了不少時間,針對我有興趣的技術,做了各種 POC 及驗證,先用最短的時間,確認這些技術真的能夠解決我們面臨的主要問題。

這正好符合這篇文章講到的幾點避免陷入一窩蜂的做法:

快速測試 (Spikes) -
我做了非常多的小型 POC,每個都是針對我最在意的問題,寫一段驗證的 code, 確保過程跟我想像的是一致的,真的 能解決問題才結束。以我的例子來說,對我們的系統及團隊來說,微服務化我最在意的是 API 的相容性怎麼維持? 拆解成大量服務的話怎麼 解決部署的問題? 熱門的容器化技術,是否能對我們這種 pure .net develop team 發揮功效? 過程中我甚至花了一個禮拜,把其中一個小模組 親自改寫成微服務架構 + 容器化部署,包含 web api, sdk, 以及 schedule job 等部分,都改成用 windows container + asp.net mvc webapi 來實作。這部分的過程,我會在之後另外寫一篇文章介紹過程跟心得。

在投資報酬率很高的時候下手 -
書上講的 guidelines 有很多條,但是不是每條都對我的現況有幫助。我花了不少時間了解每條指引準則, 是為了解決甚麼問題? 同時檢視看看這問題是否是最困擾我們的問題? 重新思考及排序之後,我優先挑選對我們幫助最大的幾點做準備及改善。 而非清一色無腦的照著書上講的方式來進行。

經驗 + 有札實技術背景的人 -
這項其實講的就是,團隊裡要有個稱職的架構師啦!

這… 容我自誇一下… 哈哈,整個過程我最自豪的地方,就是過去學了那麼多 OOP,Design Patterns, 還有一堆軟體工程的知識及技巧, 終於派上用場了! 在單純專案開發,或是功能開發的階段,這些流程或是軟工領域的技能常常會被忽略,但是在碰到這種架構層面的改變時, 我過去累積的這些能力就幫了我很大的忙。

不說別的,光是重構的技巧 + 單元測試的觀念,就讓我閃開不少地雷,切割微服務的過程中順利許多。表面上我好像浪費了很多時間在做改 code 以外的事情 (如 POC,花時間先整理 Code,寫測試,同時比對新舊架構執行結果等等),但是換來的好處是整個過程都很可靠很平穩地進行。 雖然不見得是最快速度,但是是風險最低的做法,同時過程中隨時都能維持可出貨可使用的狀態。這些都要歸功過去累積的知識及經驗。

奉勸各位 (尤其是技術決策者),看到我寫的這系列微服務架構的文章,千萬別太興奮就想要你的團隊一頭衝進來啊! 做這種事情,最關鍵的 角色就是團隊裡要有個稱職的架構師。架構師最重要的職責,就是負責平衡整個系統的各個環節。微服務架構,其實是過去十幾年來,分散式系統 與雲端服務等等技術的縮影,架構上的複雜度是很高的,因此要適度借重各種既有的服務、元件、基礎建設來降低使用微服務架構的門檻。但是 要能熟練地運用這麼多東西,如何挑選及組合,就是最重要的事情了。

經驗不足的 tech leader, 往往會高估團隊對技術的掌握能力, 貿然採用無法掌握的架構時,就會發生這篇文章描述的狀況 - 一窩蜂 XD… 這時你能做的,就是先做好各種事前驗證 (POC),盡早掌握這技術架構的可行性,還有盡早掌握可能面臨的風險在哪裡。如果要失敗,那就在 POC 先 失敗吧! 很多軟體工程的方法論鼓吹 Fast Fail 的道理就在這裡。

稱職的架構師,除了挑選及規劃架構的能力之外,還要具備良好的 coding 的能力,要好到什麼程度? 要能把你腦袋中想像的解決方案,實際 用 code 把核心部分寫出來,同時盡可能簡化或是略過非必要的部分。這能力至關重要,因為這是架構師如何能跟團隊溝通的重要工具。對外, 你可以用這樣的技能做出 POC,跟老闆與客戶證明你的構想與架構是可行的,對內,你則能給團隊信心,同時取得團隊對你的信任,眼見為憑, 工程師們眼裡只有 code, 只有親眼看到你能寫出 code 證明你的概念可行,才會相信你後面的判斷及規劃。

架構師要有足夠的 coding 能力,原因就在這裡。這一切對團隊的幫助,都不是其他外面請來的技術顧問能夠取代的,因為這是要完全融入團隊 例行的開發流程。架構師擁有夠好的 coding 能力,就能用工程師共通的語言 - source code, 跟團隊其他技術人員溝通,也能透過 source code 指導團隊其他成員。這種事只要你經歷過就知道,再多的文件,都不如一段真的可執行的 source code 來的實際, 可以讓你底下的工程師了解你 到底要表達什麼。

想了解架構師到底要做什麼,可以參考這幾篇,講得很到位:

因此,如果你們家的架構師是不寫 code 的 (不管是不會寫,還是不想寫都一樣),那麼上面講的事情,他應該很難做到… 你就別指望他能做好 這件事了。很多管理的文章也都講到,主管不肯親自動手做,不肯把手弄髒,是無法做好事情的。這句話套用到架構師身上也適用。

套句鄉民的句型: 好的架構師是很重要的,沒有的話快點去找一個 XD

補個我常被問到的問題:

有人問我,若團隊內沒有架構師,能否聘請技術顧問來取代?

我的答案是: 能找到跟架構師 (上面描述的) 有同等能力的技術顧問,當然是件好事。不過我判斷的標準也一樣,外部顧問如果沒辦法 從過程中引導團隊進行,而是只出一堆文件與規劃,但是一行 code 都寫不出來 (或是不寫),那對團隊一樣是沒有幫助的。如果你不確定 顧問是否符合你的期望,也可以試試在正式合作前,先從小的案子,或是企業內訓等等方式開始合作,從中觀察就可以得知。

雖然在執行這改版計畫時,我的身分是 CTO,屬管理職,不過在這過程中我同時身兼架構師 (當然是很愛寫 code 的那一類), 才順利的 跨過微服務的門檻 (不然也就不會有這麼多篇文章了)

回到微服務化,我們最後做了那些改變?

一窩蜂完畢,回到主題: 微服務化。回頭來看看我面臨到的問題與當時的系統架構,還有我對新架構的期望。原本的架構我再重貼一次:

典型的 WEB + DB 架構,WEB 可以 scale out, 配合數位學習有大量內容管理需要,後端搭配 file server,與類似 CDN 的分散式部署 方式 (file sync based) 來解決流量問題。

這樣的架構其實蠻落伍了,最大的問題在於,這架構的思維完全是早期企業內部的網路架構,完全是 intranet based 的思維設計出來的架構。 其實這也沒什麼不對,因為這是 2000 年就規劃出來的架構啊 XD (是,也是我設計的… Orz)。這樣的架構最大的缺點就是 CP 值很低,所有 關鍵的環節都是自己來,建置與維護成本很高。雲端時代的優勢完全沒有用到啊,廣大的 open source 資源也沒用到啊,更別提微服務架構了..

過程中其實我想過很多符合 “熱門” 技術下的架構規劃,不過都被我自己否決了,因為我發現我自己陷入了一窩蜂的坑了啊… 後來我重新思考 熱門技術到底能帶給我甚麼優勢? 我才逐漸掌握到我需要的架構是什麼。我先歸納這幾點,後面再逐一說明:

  1. 服務的細粒度要夠高 - 慎選既有系統的切割方式
  2. 部署方式要夠靈活 - 能同時適應企業內部環境,也能適應混合雲 (public cloud + private cloud) 的環境
  3. 要能充分運用 cloud service 的特性,包含 public cloud 無窮盡的運算資源,也要考慮 cloud service 的商業及營運模式

這裡每一項拆開都可以再寫個一兩篇文章 @@,我先跳到結果,這張圖是調整後第一步要進行的架構:

這是我初步規劃,調整後的架構。主要的改變有幾個地方:

1. Content Service 從單純的 CDN 升級為 Courseware Service:

原本的 content service 很陽春,就是個處理靜態檔案的 web app, 做個簡單的 url routing, 自動選擇正確版本目錄的 asp.net mvc web application。 目錄同步的功能就用現成的工具替代。重新調整之後,這服務沒有被切割成更細微的服務 (這樣還能再切什麼出來?),反而把部分跟教材相關 的功能,從主系統那邊合併過來,甚至替他配置一個專屬的 SQL database,整個運作的規模變得較大較完整了。這邊主要考量是,微服務應該是個能獨立自主運作,能夠把某一件事完整地做好,同時要能夠集中部署在公有雲為主要考量 (我們都放在 Windows Azure, 架構也都配合 Azure 做過最佳化調整)

微服務的概念中提到一個很重要的觀念,就是每個服務該要有 獨立自主性,能夠獨立部署運作,其他服務都要透過 API 來跟她溝通;服務本身 同時應該要 小巧,專注做好一件事情。我從這角度思考,content server 要做好的那一件事情到底是什麼? 單純的分流? 還是做 edge server (CDN)? 這些應該不是他主要的目的。我的構想中 content service 應該要做好的,是對客戶要做好教材代管服務才對。主系統應該要能有效率地 把教材內容委託給 content server 運用,使用者若能透過類似 oauth2 那樣的認證機制,不論來自哪個系統,content server 若都能服務 好這個使用者,讓他順利的完成教材閱讀,同時做好學習紀錄與考試,之後有統一的 API 能夠讓其他系統來取回學習歷程與紀錄回去做後續的 管理,這才是我心目中理想的微服務啊。

因此,原本單純的 content service, 就從靜態網站升級為具備下列功能的完整服務了:

  • content publish api: 提供主系統遠端做教材管理及傳遞的 api
  • 與主系統之間的使用者認證資訊傳遞,還有 session 的管理
  • 內容的使用 (閱讀,追蹤紀錄,計時 or 計次等所有計費所需紀錄)
  • 處理 HLS + AES 的加密與金鑰的管理
  • 與主系統之間的授權管理 (參考這篇: API & SDK Design #5, 如何強化微服務的安全性? API Token / JWT 的應用, 2016/12/01)

當這些都完成後,就商業的角度來看,content service 就不再只是個處理頻寬問題的解決方案了,而是提升為能負責服務用戶所有教材相關 需求的獨立服務,可以是我們整個平台中的一個重要服務,也是邁向 PaaS (platform as a service) 的關鍵之一。跟前面講的一樣,這其實 也算不上是新觀念了。古早以前我在學物件導向設計時就看過這原則: Single-Responsibility Principle,觀念上是相通的,只是過去 OOP 的範圍主要是應用在物件與類別的設計規劃,而套用到微服務,則是組成整套系統裡的每個個別服務該如何設計規劃而已。 順手貼個 2011 的文章: 亂談軟體設計(3):Single-Responsibility Principle

2. 採用 SVN (subversion) 來取代原有的 file server。

現在把視線移回主系統本身。要有教材傳遞給 content service 之前,主系統本身必須先做好教材的儲存與管理。在這領域裡, 其實有很多實作上的難題,包括:

教材的版本控制 -
過去就是在目錄結構裡,切一層出來當作版本號碼,每個版本都存有一份該版本完整教材檔案,管理不易,且儲存空間使用效率很低。 理想的狀況是儲存時能夠做到壓縮及差異儲存。一份教材動輒 500mb, 但是兩個版本之間可能只是修正幾個錯字,或是替換幾段文字。 儲存方式是否最佳化,會極大的影響佔用空間。若是搬移到雲端空間 (例如: Azure storage), 費用是按照每個月每GB付費的,影響更大。 儲存方式必須能同時符合這幾項要求。

教材的同步與傳輸 -
過去採用 IT 的各種檔案同步的工具,不外乎是 robocopy, DFS… 等等,大都需要透過 SMB 的通訊協定。這類協定開放到 internet 上面會面臨到資安的問題。另外單純的目錄同步服務也很難配合系統自動化,只同步某份教材或是某個版本。整個機制在自動化管理 並沒有很簡潔的做法。若在考慮傳輸上的安全問題,需要配合用 HTTP 才能突破防火牆,有時則需要採用加密的連線 (HTTPS or SSH), 甚至是極度簡化的單機版本,可能要跳過網路層,直接 access local file system 都有可能…。

教材庫的維護 -
為了改善前面幾點問題,通常都要設計專屬的儲存結構,以及索引的建立與維護方式。這些設計做下去,將來在系統運作一段時間後,就會 面臨儲存庫的結構檢查,一致性檢查與修復,資料備份,還原,異地備援等等問題都得自己來。一般軟體開發團隊很難將自己設計的儲存庫 實做到這麼完整的地步。

因為有這些需求,我本來想找些其他分散式檔案系統來解決這問題,或是其他現成的內容管理系統。不過看來看去都有點殺雞用牛刀的感覺… 直到某天,我才想到,為何不直接用 developer 最熟悉的版控系統? 經過一連串的 POC 與研究,證實 SVN 能同時解決我這些需求之後, 這時 subversion 變成我的首選。不採用當紅的 git, 是因為 subversion 集中式的架構更適合我的需求啊,svn 在運作模式上也更貼近 我的需求,同時他也發展得夠久夠可靠了。只是單純的挑當紅的 git, 其實對我並沒有甚麼幫助。

於是這部分也被我切割成獨立的服務了。只是這次這個服務並不是我們自己開發,而是直接找現成且成熟的系統: subversion 來使用。不但有 全球上億開發人員的使用經驗背書,穩定可靠的 API,完整的文件 (Deployment Guide / Maintainess Guide …) 等等工具樣樣不缺, 連 .NET 版本的 API 都有很完整的實作 (SharpSVN) 可以使用。對我而言,SVN 就像是專門儲存教材的 database,專門針對檔案的版本管理,同步,儲存等等操作做最佳化的服務。我的團隊只要熟悉基本的 svn 與 svnadmin 指令,就能完成絕大部分的 儲存庫維護的動作,上面列舉的功能每樣都能完美達成…,實在沒甚麼好挑剔了 (即使她已經是個古老的版控系統了)。我想這樣的功能, 自己的團隊,砸下幾百萬的經費,投入一年的人力也做不到這樣吧?

3. 系統之間的通訊方式,採用 Message Queue

過去系統架構單純的時候 (複雜度都在單一一套 web application 內部),沒甚麼這種跨系統之間的溝通問題。大概就 WEB + DB + JOB 就可以解決完所有的事情了。不過當你切割成越來越多獨立的服務時,跨越系統之間的通訊問題就來了。最常見的就是用 HTTP + REST API, 不然就是 share file / share database, 並且不斷地用 pooling 的方式偵測異動來進行通訊。不過缺點實在不少啊,隨便舉幾個都是坑啊:

被動的呼叫/回應模式 - 缺乏推送通知的能力
服務如果只實作 REST,那就只能被動的被呼叫。沒有主動 call back 的能力。甚至某些動作暫時失敗,你想要實作 wait 30 sec 後 重新執行一次也沒辦法,因為你總不能等 30 sec 時,叫 user 的瀏覽器在那邊轉轉轉吧? 若你想實作一些簡單的 background job 也沒辦法。 通常 HTTP Request 在 server 端執行太久,就會被 web server 強制中斷執行了。例如: IIS 預設就有 90 秒的 Script TimeOut 限制。

這問題可以靠排程解決,不過排程的時間精確度大概只能到分鐘的等級,而且密集的執行,其實就是 pooling 的模式,會對效能有很大的影響。 你要越高的時間精確度,就要付出越高的效能代價。現在的使用者都要求要 “即時” 回應,如果驗證簡訊要等個一分鐘才寄出,應該會收到一堆 客訴吧~

只能同步 (sync) 通訊
這點尤其在牽扯到交易時最明顯。交易系統常有這種模式,下單後續的處理及檢查很冗長,要花很多時間。這時 只能同步通訊的話就很頭痛,呼叫端 (caller) 必須等到動作完成後才能繼續下一步的動作。若通訊機制能支援非同步通訊的話, 呼叫端就可以不必一直在那邊等待了。

不過請留意,也許有人會這樣想:

那我用 C# 的 async / await 不就可以了嗎?

請留意,C# 只是在語法層面上讓你實作非同步的操作,但是兩個服務之間的連線與通訊仍然是同步的,只是你的 code 不用一直在那邊等而已。 兩者有甚麼差別? 第一: 連線還是一直占用,直到呼叫結束為止。第二: 執行到一半時若碰到 IIS restart, 或是 App Pool 回收等等狀況, 這個連線仍然會被中斷,而且無法回復。我們需要的是在通訊機制層級的非同步方案。

以這個交易的案例來說,除了非同步通訊之外,若被呼叫方能先把訊息儲存起來,再按照順序取出執行,那整個系統的運作會更有效率,也可以 提高可靠度。因為這麼一來,取出執行失敗的話,就還能有機會重新嘗試一次。這也是我這次主要的改善方向: 改用 message queue。

不適合長時間的通訊
HTTP 很多情況下是不適合長時間通訊的。他的設計基礎就是建立在很多大量小型的 Request / Response 的通訊模式。長時間的連線,通常 會佔住 connection pool, 如果 web server connection 數量達到上限,那其他的 Request 就得被迫排隊,或是根本無法進來了。

另外長時間的通訊,也會導致通訊很不可靠。Web 的設計大都是無狀態的,這類長時間的連線,往往會阻礙負載平衡的進行,也會阻礙部分 Web Server 的暫時離線、重新啟動、或是回收等等動作。長時間的通訊,或是需要推送的機制時,都可以用前面的技巧替代。例如改成 非同步通訊,執行完畢再用推送的機制把執行結果傳回原呼叫端。

點對點通訊,不支援廣播或是發布訂閱模式
HTTP 是很典型的點對點的傳輸協定,架在上面的 REST API 就是 client - server 的呼叫。如果通訊系統的需求複雜一點,要用廣播 (client 送出訊息,多個服務都要共同去接收處理) 機制的話是很頭痛的。這類的應用可以建構出跨越服務的事件處理機制 (event handler)。 如果跨越整個系統的事件處理機制夠可靠的話,這甚至能處理跨越不同資料庫或是儲存系統之間的交易。

跨服務的通訊其實考量很多,這些在後面的微服務基礎建設會統一介紹。我這次的微服務化野心還沒那麼大,只要先處理可靠度 及非同步通訊而已。因此這次在關鍵的任務部分,底層直接採用 Message Queue (MSMQ) + Worker 建構起來的 RPC 機制,來取代 直接呼叫 REST API。

小結 - 微服務架構規劃的要點

以上這些調整,最終的成果就是這張系統架構圖了。你會發現,很多微服務架構指南提到的準則,我並沒有每一條都照表操課。我只挑選真正對我們 團隊有足夠的效益,我才採納。其他價值不是那麼高的設計,我就放在心裡,隨時優化系統,只要做好準備,將來需要時可以用最快速度重構就可以了。 真的要嚴格的檢視,我其實還沒做到微服務的架構,頂多只是做到 “準” 微服務架構而已,有人稱作 microservice ready, 或是稱作 miniservice ..

雖然主題一直圍繞在 “微服務” 身上,不過這個案例其實還不夠到位,只是朝向微服務在調整架構的過程而已。但是我想在台灣,這才是大部分 團隊面臨的狀況吧! 因此我特地拿實際的案例,讓大家了解我做這件事的思考方向與過程。其中最關鍵的部分永遠在如何讓服務的架構配合你的 商業流程或是組織流程。兩者之間能夠有效的對應,你的系統才能順利地跟著你的組織一起成長改善。

從這逐步演化的角度來看,我認為這階段最重要的架構考量有這幾項,想清楚就可以繼續往下個階段進行了。分別是:

現有系統如何切割成獨立服務?
(例如: 我舉的案例裡的 content service)

整個微服務化的藍圖,有哪些地方能改採用成熟的第三方服務替代?
(例如: 我用 subversion 取代原本的 file server)

整個微服務化的藍圖,有哪些必要的基礎建設?
(例如: reverse proxy, message queue 等等)

這些考量不需要一次到位,但是架構上的規畫以及執行順序要先想清楚,才不會做到一半又得打掉重練。所有架構師要考量的要點,永遠都包括 這一條 “Think Big, Start Small.” 先想好你期望你的系統發展成甚麼樣子,在過程中你才知道有哪些環節是需要改善的,需要改成甚麼樣子。 你可以帶領你的團隊逐步向目標靠近,而不會總是修修改改搖擺不定,過了幾年系統仍然在修修補補毫無進展。

這篇文章的內容,其實我在 2017/02/22 DevOps Meetup #4 那個場次有現場分享過這篇的內容,我補充一下相關的連結,方便大家參考:

在這邊也感謝當天到現場參加的朋友們,給我平均 6.59 分的滿意度 (滿分 7.0 分), 有任何意見或看法, 也都歡迎在這邊討論!

原本想說這篇應該可以帶到為何要選擇容器化部署了,沒想到寫著寫著又寫太多了 @@ 下一篇會繼續聊聊微服務的基礎建設,還有容器化的部署考量,敬請期待 Part 3 XDD






Facebook Pages

Edit Post (Pull Request)

Post Directory