跳至主要内容

系統設計系列文14:API Gateway 負責引導

· 閱讀時間約 5 分鐘
阿昇
Software Engineer

昨天把 Notification 從 PokeThreads 的 Monolith 拆成了獨立的 Notification Service,跟原本的 API Service 之間用 gRPC 溝通:

API Service ⇄ gRPC ⇄ Notification Service

拆完第一個 Service 之後,接下來這個趨勢大概不會停在這裡。假設之後 Search、Media Processing 也陸續被拆成獨立 Service,Client(瀏覽器、App)要面對的後端,就從「一個 Application」變成「一群 Service」:

  • User Service 在哪裡?
  • Feed Service 在哪裡?
  • Search Service 在哪裡?

如果讓 Client 直接記住每個 Service 的位址、各自打各自的 API,那還沒扯到功能,光是「知道要打誰」這件事,就已經讓前端變得很脆弱。

任何一個 Service 的位址一旦改變,所有 Client 都要跟著改。而且如果每個 Service 都要各自驗證使用者身份,Authentication 邏輯就會重複散落在每個 Service 裡。

比喻:百貨公司的服務台​

這個情境很像逛一間有幾十家專櫃的百貨公司,如果沒有服務台,顧客得自己記住「化妝品在 3 樓左邊第二間、退貨要去 B1」,體驗會很差;有了服務台,顧客只要走到服務台說出需求,服務台自然會引導到正確的專櫃。

這個服務台的角色,放進系統架構裡就是 API Gateway:一個放在 Client 與所有 Backend Service 之間的統一入口。

API Gateway 放在 Client 與 Backend Service 之間的架構圖

有了 API Gateway,Client 只需要知道一個 Endpoint,完全不用管背後到底有幾個 Service、各自部署在哪裡。

API Gateway 的功用​

Routing:把客人帶到對的專櫃​

Gateway 收到 Request 後,依照路徑(例如 /feed、/search)把它轉發到對應的 Service:

Client
↓
API Gateway
↓
┌──────────────┬──────────────┐
↓ ↓ ↓
User Service Feed Service Search Service

這帶來一個額外好處:Backend Service 可以自己升級版本、換位址,例如從 /feed/v1 變成 /feed/v2,只要 Gateway 這一層的路由規則跟著更新,Client 完全感覺不到任何變化。

Authentication:服務台先幫忙核對身份​

與其讓 User Service、Feed Service、Search Service 各自重寫一遍「怎麼驗證這個 Token」,不如讓 Gateway 在最外層先攔截、驗證身份,只有合法的 Request 才會被放行到後面的 Service,這樣可以避免同一段安全邏輯在到處複製貼上。

Rate Limiting:先幫忙控管人流​

服務台除了引導方向,也常常要顧著現場秩序,這正好是「流量控管」該放的位置。

Gateway 可以在這裡限制單一使用者、單一 IP 每秒可以打多少次 Request,超過就直接擋下,不讓大量 Request 一路衝到 Backend Service。Rate Limiting 的細節留到明天討論。

Request Aggregation:一次幫忙問完所有專櫃​

假設 Client 要打開一個 Profile 頁面,需要同時顯示 User 資料、最近貼文、Follower 數量。沒有 Gateway 的話,Client 得發三次 Request,分別打三個 Service;有了 Gateway,可以提供一支聚合過的 API:

Client
↓
GET /profile/123
↓
API Gateway
├──→ User Service
├──→ Post Service
└──→ Follow Service
↓
合併後的 Response

Client 只需要發一次 Request,剩下的工作交給 Gateway。

Monitoring:服務台看得到所有人流​

因為所有流量都會經過 Gateway,這裡自然也是收集請求量、回應時間、錯誤率這些指標的好地方,替日後要談的 Observability 先打好基礎。

API Gateway 不是 Load Balancer​

這兩個名詞常被搞混,但層次不一樣。

Load Balancer(Day 5)關心的是「怎麼把 Request 分配到某一群相同的 Server 上」,看的是 IP、連線這類基礎設施層級的資訊;API Gateway 關心的是「這個 Request 該送去哪一個 Service」,看的是 API 路徑、業務邏輯。

實務上兩者經常疊在一起用:

Client -> API Gateway -> [Feed Service 自己的 Load Balancer -> 多台 Feed Server]

Gateway 決定「這是 Feed 的請求」,之後才輪到 Feed Service 自己的 Load Balancer,決定「這次要分給哪一台 Feed Server」。

API Gateway 的代價​

又是一個 Single Point of Failure​

Load Balancer 是這樣,API Gateway 也是,哪次不是 SPOF?

如果服務台本身倒了,所有客人都進不了任何一家專櫃。所以 API Gateway 本身通常也需要做成一個具備 High Availability 的叢集,而不是單一個節點。

Request Aggregation 不能無限使用​

如果把太多業務邏輯(怎麼合併資料、怎麼處理 Partial Failure)都塞進 Gateway,它自己反而會膨脹成一個新的 Monolith。

我們才剛拆開的東西,不應該又在另一層重新長回來。

多一層介質就多一點延遲​

Request 要多經過 Gateway 這一層轉發,勢必比直接打 Service 多花幾毫秒,這是用「架構彈性」換來的合理代價。

小結​

加上 API Gateway 之後,Client 不用再記住一堆 Service 的位址,Authentication、Rate Limiting、Monitoring 這些跨服務都要做的事,也有了一個統一的地方可以集中處理。目前完整的架構長這樣:

加上 API Gateway 之後的完整架構圖

不過剛才在 Rate Limiting 那段只是輕輕帶過。

Gateway 到底要怎麼判斷「這個使用者是不是打太快了」?

這是明天要仔細拆解的問題。


系列導覽​