微服務架構是近幾年很流行的主題,過去有非常多研討會在談論如何部署、管理和實作微服務,但最近這個趨勢減緩了。取而代之的是,大家都開始討論微服務的複雜度。肇因於微服務的高複雜度,人們已經不把微服務當成仙丹妙藥,而僅是當作一個可行方案來對待。
但在開始介紹微服務前,讓我們先來看看理想的微服務應該長什麼樣子。
理想上的微服務應該具備以下這些特性:
用架構圖來描繪大概會是這個樣子。
當客戶端與後端系統溝通時,後端通常透過API閘道器根據某個規則將請求路由到特定為服務上,而該微服務可以獨力處理請求而不需要透過別人參與。
現實世界往往不像我們想像的這麼美好。
你知道這是什麼動物嗎?
據我所知,至少有三種可能,可能是馬,也可能是驢,甚至是騾。
為什麼畫成這樣?
一開始我們都嘗試根據理想或教科書上的架構描繪出來,這時一切看起來都很完美。但隨著時間流逝,越來越多需求被加入,我們能使用的時間被壓縮,甚至我們還必須解決已經發生的錯誤。
最終,這匹像馬的動物會越來越潦草。
如果一開始至少有個好的設計和計畫,我們還可以從形狀判斷這可能曾經是一匹馬。若是一開始根本沒設計,那連他最後長什麼樣,我們都無法想像。
用架構圖來表示就會像下面這樣。歡迎來到現實世界。
A、B和C都有自己的職責來處理領域邊界內的任務,但隨著需求成長,A發現需要B的資料,B需要C的功能,甚至C要回頭來呼叫A。最終,架構變成一個大泥球(big ball of mud)。
有許多原因:
在了解了軟體開發的現實面,接下來讓我們來看看設計微服務我們會碰到問題。首先要了解,微服務系統本質上就是高度分散的分散式架構,因此在分散式架構上碰到的謬誤同樣也會出現在微服務上。
人們總是假設兩個端點間的網路是穩定可靠的。但事實是,在網路上的封包有可能遺失,甚至連線有可能中斷。
因此,分散式系統必須要有重試的機制才能盡可能提升可靠度。
在設計分散式架構時,常常有一個潛意識的認知是兩個端點間沒有延遲,造成設計時將一些功能分散到多個端點上執行。
不幸的是,端點間的延遲永遠不可能是零,就算是內部網路也一樣。
因此在做遠端呼叫時,設定一個超時閥值是很重要的,即使資料庫連線也是。此外,短時間頻繁的呼叫也應該盡量包裝成單次,以Redis為例,要善用pipeline
或multi
來減少端點間的來回次數(RTT)。
人們總是感覺端點間的網路頻寬沒有限制,所以肆無忌憚的傳送資料。
事實上,端點間的頻寬小到超出你的想像。
特別是一些資料庫的查詢,例如MySQL的SELECT *
,往往會把頻寬耗盡而不自知。當需求成長,資料表也會隨之變大,可能有許多大型的資料例如BLOB
或TEXT
會被寫入表中。此時,原有沒問題的SELECT *
會超乎你想像的耗用頻寬。
這是常見的陷阱,事實上人們總是認為內網很安全。
但其實一點也不,這也就是為什麼「零信任(zero trust)」這幾年被廣為提倡的原因。
這個謬誤很有趣。人們覺得網路拓墣不會改變,所以兩個端點始終能夠透過預定義的IP或FQDN找到對方。
但現實是,拓墣會因為很多不同的原因改變,包含VPC分割、公有雲遷移或者系統演化等。
我們總是期待會有至少一個系統管理者來管理服務。
通常在每個組織中,都會有一個維運的角色來維護系統。因此,維運部門扮演系統管理著的角色,一但有任何問題發生,維運部門都可以第一個發現。
大錯特錯。
任何系統都必須具備可觀測性(observability)且系統開發者必須要有能力從遙測中找出潛在的問題。
有四種常見的遙測指標:
這其實是很嚴重的謬誤。
我們必須了解任何遠端呼叫都有成本,特別是在公有雲上,這成本更加可觀。即使只是存取資料庫或對外呼叫HTTP請求,這些行為都有成本。
所以不管是什麼遠端呼叫,都要優化,無論是呼叫次數或者資料大小。
這是很容易忽略的陷阱。
網路絕對是異質性的,所以當一個端點呼叫另外一個端點,我們無法保證這些呼叫的順序跟預期相同。換句話說,不能對遠端呼叫的順序有錯誤的期待。
經過這些討論,我相信我們都同意分散式架構真的很複雜。
微服務是由無數端點組成的大型分散式架構,每個端點的設計都必須考量剛提到的各種因素,更有甚者,微服務也有自己要面對的議題,例如資料一致性、系統擴充性等諸如此類。
因此下一篇文章我們會來聊聊微服務的專屬議題。