聊聊微服務是什麼 – 服務探索

今年我 SITCON 有投一篇微服務的議程:「選課卡成狗?微服務架構帶你翻轉校園系統」。在 SITCON 之前,我打算每天在 Blog 上寫一篇和 Cloud Native 相關的短文,來當作議程的前導內容。當然針對每一篇短文的意見回饋(看不懂也是一種意見反饋 🥺),最終都有助於我產出更好的議程內容~ 這個 Tag 的第四個主題是「服務探索」。從這個主題開始,我們要開始探討前三章說的微服務架構會有什麼問題,以及可以怎麼克服了。 考慮到 Blog 本身不是一個很好的互動平台,我在每篇文章的底下都會留「💬 互動區塊」,連結到和這篇文章相關的社交媒體上。你可以在社交媒體上和這篇文章互動~ 同台機器上,服務如何互相連線? 前文的結尾我有留一個問題:「如何讓服務之間可以互相連線?」你或許會覺得這問題非常基本,因此我們不妨拿之前的微服務架構模擬看看。假如你在本機端而且不想配置網路的話,最直接的做法可能是每個服務都開不同的 port,讓 Gateway 直接用 port 連線。比如說你把使用者服務安排在 port 3001、把選課服務安排在 port 3002,然後 Gateway 用 localhost:3001 連線使用者服務、用 localhost:3002 連線選課服務;selection-service 也是用 localhost:3001 連線使用者服務、用 localhost:3002 連線選課服務。 事實上假如你只在一台機器上運作的話,這種方法真的沒有什麼問題,甚至可以說是行之有年了。 多台機器上,服務如何互相連線? 不過假如我們進入到分散式的世界:把這堆服務打散到各個機器上,這樣的做法會碰到什麼問題呢?我們在第一章有分析出選課相關的功能相對耗資源,所以我們把「選課系統」獨立到不同的機器(這裡叫他機器 2)上。首先不同機器肯定會有不同的 IP,所以「機器 1」要連線到「選課服務」,肯定就不能用 localhost 連線了,我們肯定是要用「機器 2」的 IP 搭配上之前設定的 port 進行連線,也就是下面這張圖。 問題來了:IP 是永恆不變的嗎?機器的 IP 可能會因為區域變動(比如從新北機房移到高雄機房)而變化,我們也不一定能保證「選課服務」一定只在機器 2 上面。甚至說假如其他服務也開始有壓力,我們想要把它分出機器 1,我們也同樣需要關心到 IP 地址的議題。既然 IP 不是永恆不變的,我們就要關心起「如果 IP 改動時,會發生什麼樣的事情。」試想一下,我們把「選課服務」的 IP 換掉,我們需要改掉多少個服務連線位址呢? ...

February 6, 2025 · pan93412

聊聊微服務是什麼 – 閘道和服務通訊基礎

今年我 SITCON 有投一篇微服務的議程:「選課卡成狗?微服務架構帶你翻轉校園系統」。在 SITCON 之前,我打算每天在 Blog 上寫一篇和 Cloud Native 相關的短文,來當作議程的前導內容。當然針對每一篇短文的意見回饋(看不懂也是一種意見反饋 🥺),最終都有助於我產出更好的議程內容~ 這個 Tag 的第三個主題,就來補充我們昨天微服務中一直沒有提到的部分:「Gateway」吧!我們昨天聊了服務的拆法(領域拆分),以及 RPC 分查詢和更動的目的(CQRS),但是你會發現到有個服務長得和其他服務不太一樣:Gateway。Gateway 沒有 RPC 方法,而且是唯一一個連接到前端的服務。為什麼需要 Gateway,以及 Gateway 是怎麼和其他服務通訊的呢?這篇文章就來詳細解釋這個問題。最後,我們也會簡單說明微服務之間的通訊流程。 考慮到 Blog 本身不是一個很好的互動平台,我在每篇文章的底下都會留「💬 互動區塊」,連結到和這篇文章相關的社交媒體上。你可以在社交媒體上和這篇文章互動~ 什麼是 Gateway? 「Gateway」(閘道), 基本上就是你那些微服務的前台,負責給前端一組好用、封裝自 RPC 的 REST 或 GraphQL API,同時在轉送給微服務前進行正確的權限檢查(比如說「加入課程」只有管理員能執行)。這樣一來,前端就不需要知道所有微服務的位置、了解服務切分的細節,只需要和 Gateway 進行互動就好。 拆出 Gateway 的意義 你或許會疑惑「為什麼我們不要一步到位,讓前端直接連線這些微服務呢?」首先我們先看看讓前端連線微服務會發生什麼事情: 現在前端會直接連線到這些微服務上。假如沒有 Gateway,如你所見,「認證」和「授權」的重責大任就落在每個微服務身上了。身分認證的目的是判斷請求者持有的 token 是否有效,確定是對應使用者登入的;授權的目的是檢查對應使用者是否可以取用這個方法,防止使用者進行越權的操作。 因為每個微服務都需要做一次認證和授權,單個微服務會需要處理自身領域外的事情,增加複雜度1:首先,每個微服務都需要有自己一套進行認證和授權的程式碼,除了拖慢呼叫一個 RPC 方法的速度(現代微服務一個 Endpoint 可能會需要呼叫好幾十個 RPC 方法),微服務本身也會因為這些驗證而變得臃腫。接著,認證和授權都需要依賴「使用者服務」,如果「使用者服務」故障了,幾乎所有微服務都會停擺。最後,服務之間的通訊該怎麼認證呢?如果你的認證方式是 OAuth 的話,你很需要給所有服務組合產生一組 M2M token⋯⋯ 除了後端一團糟外,前端也會一團亂。你的前端需要先知道「我需要的方法在哪個服務上」,然後還需要知道這個服務的連線網址。你的 API 呼叫時需要指向各種不同的網址,當你要撤換服務時,需要一一檢查前端有沒有地方引用到這個服務,增加你撤換和更新這些服務時的困難度。同時你的這些服務因為都跟使用者的前端(也就是用戶端)扯上關係了,所以設計上又需要考慮用戶端會怎麼搞壞你的服務,並且需要花上更多時間把你的 RPC 方法封裝得更適合讓任意用戶端呼叫(比如因此捨棄簡單的 gRPC,轉為使用 REST API,去提升各個用戶端的相容性)。 ...

February 5, 2025 · pan93412

聊聊微服務是什麼 – 入門微服務

今年我 SITCON 有投一篇微服務的議程:「選課卡成狗?微服務架構帶你翻轉校園系統」。在 SITCON 之前,我打算每天在 Blog 上寫一篇和 Cloud Native 相關的短文,來當作議程的前導內容。當然針對每一篇短文的意見回饋(看不懂也是一種意見反饋 🥺),最終都有助於我產出更好的議程內容~ 這個系列的第二個主題,還是延續我們上次的選課系統設計。昨天我們發現到單體的方式開始造成資源的浪費以及資料庫的瓶頸,所以我們或許可以看看怎麼把選課系統拆成雲端原生的微服務架構。 考慮到 Blog 本身不是一個很好的互動平台,我在每篇文章的底下都會留「💬 互動區塊」,連結到和這篇文章相關的社交媒體上。你可以在社交媒體上和這篇文章互動~ 怎麼將單體服務拆分成微服務? 或許你會疑惑「微服務」是什麼。首先就「服務」這個詞來說,後端是個服務,前端也是個服務,基本上任何能提供 API 進行呼叫的都是服務。「微服務」本質上就是把我們的後端拆得更細緻:比如說我們可以把使用者相關的東西變成一個服務、把課程相關的東西變成一個服務、把志願序變成一個服務等等。 我們通常會把每個服務管轄的部分稱之為「領域」,只有這個區域內的服務可以直接操縱資料庫的資料,而不在這個區域的只能透過 RPC(遠端程序呼叫,比面向用戶端的 REST API 相對隨便,主要供內部使用的 API)撈資料。你可能會想「領域是不是就是資料表的名稱?」,但實際上其實不止於此,粒度通常會大得多,通常會追求「服務(的領域)可以獨立運作」。我們把以「領域」進行思考的一套模式叫做 DDD(領域驅動開發),實際展開的篇幅會相當長,這裡就不對拆服務和 DDD 多做說明(實際上你也不一定要學會 DDD 才能用微服務,靠經驗拆也是可以的!) 就以上面的例子來說,我自己認為使用者服務(user-service)、課程資訊服務(course-service)和選課服務(selection-service)是三個領域上可以獨立運作的服務——換言之,假如使用者服務掛掉,我預期我還能看課程資訊以及改動志願序;假如選課服務掛掉,我一樣可以更新我的選修學分。不過你應該也有發現到「選課服務」相對複雜一點,因為插入志願和最終志願序需要做很多判斷邏輯,需要連線到使用者服務看權重,以及連線到課程服務查詢限修規則,這時我們可能需要處理選課服務在其他兩個服務掛掉時的處理邏輯。但撇除掉這兩個 RPC 方法,服務大體上還是可以獨立運作的。 【🤔 想想看!】你覺得上面的微服務架構,是不是一個好的拆法? 微服務的水平擴縮 (scaling) 接著回答昨天的問題:我們這樣開發出的服務,可以怎麼降低資料庫的壓力,以及怎麼針對性的對服務進行擴充來分散壓力?首先,從上圖其實就能很明顯看到「我們把每個領域對應的資料庫都拆出去了,」換句話說,每個資料庫只需要負責自己領域內的事情(使用者資訊、課程資訊、選課資訊),壓力自然就比單體的「包山包海」小上很多。接著,其實每個微服務都應該可以水平擴縮 (scaling),所以你可以規劃「志願序服務的壓力比較大,所以我們可以開多一點服務來平衡」;「使用者服務幾乎不怎麼需要讀取,所以我們可以就開少少的機器就好」: 接下來我們也會講到很多微服務上的設計技巧,來發揮微服務更大的作用——比如快取。 【🤔 想想看!】我們要怎麼把請求分散到上面說的這三個微服務呢?是不是所有微服務都必須共用一套狀態(資料庫)?共用的缺點是什麼,以及快取區需要共用嗎? 查詢與更動分離 (CQRS) 你接著還會疑惑「查詢動作跟更動動作」差在哪裡。舉個例子,為什麼我們沒有選擇加一個「插入志願之後列出學生志願序」的查詢 + 更動 RPC,而是選擇分開變成「插入志願 RPC」+「志願序 RPC」呢?其實這種叫 CQRS,把「查詢」跟「更動(命令)」分開的一種設計模式。 為什麼我們要分開查詢(讀取)跟更動(寫入)呢?通常讀取動作不用擔心狀態問題,也就是說「就算我今天讀取 1000 次同一筆資料,只要沒有改動,得到的結果應該都要是一樣的」;寫入操作的狀態問題就複雜得多,執行順序、重複插入就足以造成大影響。所以: 讀取動作我們可以快取、可以在唯讀的資料庫 replica 實例讀取(對,很多資料庫系統是可以建立出很多個跟隨主要資料庫的唯讀 replica 實例的,通常我們叫這種功能為複寫 – replication) 寫入操作只能在主要資料庫進行操作,並且通常是不能快取的,相對來講效能改進的彈性會小一些。 在我們知道讀取和寫入兩個的複雜度不同後,CQRS 的設計就變得合理多了。就以「課程資料庫」來說,我們可以這樣規劃查詢和寫入: ...

February 4, 2025 · pan93412

聊聊微服務是什麼 – 從單體服務進行擴縮

今年我 SITCON 有投一篇微服務的議程:「選課卡成狗?微服務架構帶你翻轉校園系統」。在 SITCON 之前,我打算每天在 Blog 上寫一篇和 Cloud Native 相關的短文,來當作議程的前導內容。當然針對每一篇短文的意見回饋(看不懂也是一種意見反饋 🥺),最終都有助於我產出更好的議程內容~ 這個系列的第一個主題,就先從微服務這個主軸開始吧!但在這之前我們或許可以先聊聊「單體架構可能會遇到什麼樣的瓶頸」,以及我們要怎麼使用一些技術來稍微改善你的單體系統。 考慮到 Blog 本身不是一個很好的互動平台,我在每篇文章的底下都會留「💬 互動區塊」,連結到和這篇文章相關的社交媒體上。你可以在社交媒體上和這篇文章互動~ 單體服務的瓶頸 假設你今天要開發一套用比序來選課的系統,你或許會像下圖這樣來設計你的系統。其中後端就是一個很大的應用程式,前端去呼叫後端來選課。 乍看之下是個挺合理的設計吧?但我們設想一種情況:假如現在已經到了選課的尾聲,大家都會想要看看「自己選的課是否可以上得了」,所以「選課人數」Endpoint 的請求量也會隨時增加,然後系統的 CPU 資源就被「選課人數 Endpoint」吃光了。因為登入、選課、課程名單、志願序等等的 Endpoint 也在同個系統上,所以你的選課系統除了前端的部分會全部掛掉,學生們準備在 Dcard 上罵爆你的系統了。 擴縮單體服務 此時你會想到:那我們多開好幾套單體的後端 (replicas),然後前端隨機選擇 API(也就是所謂的「負載平衡」)呢?其實確實是個可行的方案,不過你首先要讓你的後端變成無狀態的 (Stateless) 的。「無狀態」是什麼概念呢?就以下圖來說,我們無論選到哪個 instance 的 Endpoint,呼叫結果都應該要是一致的。換言之,你的後端不可以儲存只有這個 instance 知道的東西,也就是所謂的「狀態」。當然取決於你的設計,你可能多少會不小心存一些狀態在後端裡面(比如登入的 session 以及 lock 檔案⋯⋯),所以你可能會需要花點時間重構這些邏輯,讓這堆狀態不要跟後端放在一起(或甚至變成不用儲存狀態也能判斷的東西,比如 JWT)。 【🤔 想想看!】哪些東西可能會導致一個服務變成有狀態的(Stateful)? 單體服務的擴縮問題 在完成相關的重構後,就算其中一個 instance 有著很大的負載,其他 instance 也能有效的分散掉請求,讓系統不至於完全停擺。不過這種方法粒度或許還是太大了——我們只有 1 個 endpoint 遇到瓶頸,但卻需要因此開出 5 個(甚至更多)完整的後端 instances,資源用量上會不會變得太多;而且所有 instances 最終還是連到一台資料庫上,遇到大量讀取、寫入的場景可能還是會 lag。如果我們用微服務、分散式系統的邏輯重新規劃後端,我們有沒有機會解決掉這個問題? ...

February 4, 2025 · pan93412