這個 Tag 的第五個主題是水平擴縮跟負載平衡。還記得我們昨天提到的「服務探索」嗎?到目前來講,我們只「探索」了一個服務,但是微服務的其中一個目標,就是在不擴展機器的情況下,盡可能把請求負載分擔到各個服務上。我們要怎麼做到這個「分擔」呢?
這篇是之前 SITCON 2025 的草稿,不是後面寫的,也還沒有做過 peer reviewing!如果有任何問題的話,也歡迎到 Threads 或 X 上標我指正。
什麼是擴縮和負載平衡?
答案意外得簡單:我們不就多開幾個服務,然後按一套規則平均把請求分擔到這些服務就好了嗎?恭喜你發明了水平擴縮 (horizontal scaling) 跟負載平衡 (load balancing) 了!這兩件事情的邏輯很簡單:「我們要在不動單一個節點的情況下,盡量把服務『擴縮』到各個地方,並且平均地『分配』請求到這些服務上?」
只是在實務上會更複雜,舉例來說,你會有 N 個 Gateway,連線到 M 個選課服務,這 M 個選課服務還會分別連線到 X 個課程服務,以及 Y 個使用者服務。我們接下來要解決的議題,就是如何在更複雜的場景,可以將連線分配到服務上、如何在單一服務下線時讓其他服務繼續工作,以及 HTTP 的世界,負載平衡通常該怎麼做。
要怎麼擴縮服務?
首先,我們先從比較簡單的問題開始:「怎麼擴縮?」它本質上就是多開幾個服務,你在好幾個終端機視窗,重複啟動你的服務,其實就是一種「擴展」(scaling up);你把過剩的服務用 Ctrl-C 殺掉,其實就是一種「縮減」(scaling down)。不過你假如真的這樣做,會發現到「重複用相同的參數啟動服務會遇到 port 衝突的問題」(比如說不能有兩個服務開在 3000 port 上),所以你就需要將另一個服務開在不同的 port 上(比如說,3001 port),這樣一來,你就起了有兩個 replicas 的服務了!
📚 「Replicas」是什麼?其實就是「重複的實例」,比如說上圖中的 course-service(課程服務),我們可以稱他有 6 個 Replicas。
要怎麼分配請求(負載平衡)?
下一個問題,就是「怎麼分配請求?」這個問題跟「服務探索」一樣,可以從用戶端和伺服器端下手。就以上面的圖來說:
- 用戶端解法:Gateway 在知道 6 個課程服務的位址之後,用分配演算法(隨機挑選、循環調度 (round robin),或者是比較複雜的根據負載分配)來決定要連線到哪台機器。
- 伺服器端解法:我們可以在前面掛一個代理伺服器(用 NGINX、Caddy),由代理伺服器透過分配演算法來決定要代理到哪台機器,而 Gateway 直接連線到這個代理伺服器就好。
其中,「伺服器端解法」中那個「代理伺服器」的別稱就是「負載分配器」(Load Balancer)。你應該在 AWS、GCP、Cloudflare 等 IaaS 上看過這個名詞,實際上他的原理就是透過一套演算法,將請求分配、代理到指定的服務。
這兩種方式的優劣其實也挺直覺的:「用戶端解法」就是把分配和維護服務位址的複雜性放在用戶端上;「伺服器端解法」就是得多開一個服務來做分配,負載也會壓在這個伺服器上面。我自己比較常用「伺服器端解法」來處理這個問題,不過假如你用了內建「用戶端解法」的框架(據我所知 Spring Boot 就有),其實用戶端解法也是挺不錯的。
🤔 服務探索跟負載平衡有什麼關係?怎麼把這兩個整合在一起?
📚 延伸閱讀:Kubernetes 的 Replicas 和負載平衡器
在 Kubernetes、Docker Swarm 或者是其他的容器管理工具上,我們可以使用 replicas
參數來指定要啟動多少個容器(Kubernetes 的說法是 Pod)。在容器中你通常不用關心「port 衝突」的問題,所以你也不需要擔心 port 的問題,直接用同個容器樣板複製好幾個容器就好了。接著,Kubernetes 的 ClusterIP 可以用循環調度 (round robin) 的方式,將 DNS name 分配到還可以正常運作 Pod 的 IP,基本上就是個很簡單的 Load Balancer。
怎麼和其他服務溝通?
我們終於搞定了服務連線和擴縮的問題了,現在你知道各個服務的 IP 和 port,甚至有能力將連線分配到好幾個服務上了,但連線之後呢?舉例來說,我連線到了「課程服務」,但我要用什麼方法呼叫,才有辦法取回「課程資訊?」我們下一個主題就會是服務溝通 (Service Communication),聊聊怎麼呼叫對方服務的方法 (method)。