iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0

前面幾天,PokeThreads 的架構已經逐漸長成分散式系統的樣子,但焦點始終放在文字貼文要怎麼被處理跟讀取,現在想加入一個很自然的新功能:讓使用者也能上傳圖片與影片。

新問題馬上出現:這些圖片跟影片應該放在哪裡?

先別把媒體檔案塞進 Database

最直覺的做法是跟文字貼文一樣,直接存進 Database,但圖片、影片動輒幾 MB 甚至幾百 MB,塞進 Database 會讓它承擔非常大的 Storage 與 I/O 壓力。

更常見的做法是把檔案本體放進 Object Storage(例如 S3),Database 只保留跟這篇貼文有關的中繼資料:

  • Post
    • id:BIGINT(Primary Key)
    • author_id:BIGINT(Foreign Key → User.id)
    • content:TEXT
    • media_url:VARCHAR(500)(可為 NULL)
    • created_at:TIMESTAMP

真正的圖片或影片檔案則另外放在 Object Storage:

Post
├── content
└── media_url
         ↓
    Object Storage
         ↓
    image.jpg

不過馬上又遇到跟 Database 一樣的老問題:如果同一張圖片在短時間內被大量使用者觀看,這些 Request 其實都在重複要求同一份資料。

有沒有辦法讓取得圖片更快,同時也減輕 Object Storage 的負擔?答案就是 CDN。

比喻:連鎖便利商店先鋪貨到離你最近的分店

CDN(Content Delivery Network) 的概念,很像連鎖便利商店的鋪貨邏輯:與其讓每個人都跑一趟遠在外縣市的總倉庫,不如先把常賣的商品鋪到離顧客最近的分店,顧客到附近分店就能買到,不用捨近求遠。

在 CDN 架構裡,我們通常會區分兩個角色:

  • Origin:真正保存原始內容的來源(也就是我們的 Object Storage)
  • Edge:散布在各地、離使用者較近的 CDN 節點(也就是分店)

實際運作起來像這樣:

第一次有人請求圖片:
瀏覽器 -> CDN(Edge)-Cache Miss-> Object Storage(Origin)
CDN 拿到圖片後,順便把它留在 Edge 備用。

之後再有人請求同一張圖片:
瀏覽器 -> CDN(Edge)-Cache Hit-> 直接回傳圖片
這次不需要再跑一趟 Origin。

CDN 解決了哪些問題

1. 縮短使用者與內容的距離

假設某位使用者上傳一支新影片,Origin 放在美國西岸,如果觀眾分布在歐洲、亞洲各地,每次 Request 都要繞地球一圈才能拿到內容,Latency 自然很高。CDN 讓使用者可以就近從 Edge 取得內容,大幅降低延遲。

2. 降低 Origin 的流量壓力

同一張圖片如果被大量使用者重複瀏覽,大部分 Request 可以直接由 Edge 的 Cache 回應,Origin 不需要為每一次瀏覽都服務一次,壓力自然小很多。

3. 讓 Application Server 少做事

更重要的是,圖片與影片完全不需要經過 Application Server,這跟 Cache、Database Replication 的精神很像:把不同性質的 Traffic 分流處理。

圖片與影片改由 CDN 搭配 Object Storage 提供服務的架構圖

Application Server 只需要專心處理這些事:

GET /feed
POST /posts
POST /follow

媒體檔案的傳輸則完全交給 CDN 與 Object Storage。

實務上更進一步的做法,是讓 Client 直接把檔案上傳到 Object Storage,Application Server 只負責驗證與授權,真正的檔案傳輸完全不經過它,藉此大幅降低 Server 的 Network Load。

不過跟 Cache 一樣,CDN 也會遇到「使用者可能拿到舊資料」的問題,一樣需要設定 TTL,決定檔案在 CDN 上該保留多久。

使用 CDN 的成本考量

CDN 不是免費資源,計費通常跟流量、Request 數量、地區、快取命中率有關,實際計價方式依供應商而定,常見的有 Cloudflare、Akamai、Amazon CloudFront、Google Cloud CDN 等。

檔案本身也要優化

CDN 能降低 Origin 的負擔,但如果每支影片都是幾百 MB,網路傳輸量還是很可觀。

CDN 並不能解決「檔案太大」這件事,它只是讓傳輸路徑更有效率。真正要控制成本,得從資料本身下手:

  • 圖片
    • 裁切不同大小圖片,例如 Thumbnail
    • 壓縮
    • 改用 WebP / AVIF 等更有效率的格式
  • 影片
    • Transcoding(轉碼)
    • 製作不同解析度
    • Bitrate optimization(位元率優化)
    • Adaptive streaming(自適應串流)

核心概念很單純:少傳一點資料,通常比想辦法傳更多資料還要划算。

不是所有東西都適合放 CDN

CDN 適合存放的是不太會變、大家都在看的靜態內容:

  • 圖片、影片
  • JavaScript、CSS
  • 字型

不適合放的則包括:

  • 使用者個資
  • 即時性很高的資訊
  • 高度個人化的動態內容

PokeThreads 的 Feed 本身是高度個人化的,例如 Pikachu 跟 Charmander 看到的內容通常不一樣,所以整份 Feed 不適合直接放進 CDN,但 Feed 裡某一篇貼文附帶的圖片,還是可以單獨放進 CDN。

另外,如果某個檔案幾乎只會被請求一次,放進 CDN 帶來的效益也很有限。

清除 CDN 快取

Purge CDN Cache 是指手動或透過程式,強制要求 CDN 的 Edge 節點刪除舊檔案,常見時機包括:

  • 即時更新:修正排版或圖片後,需要 Purge 才能讓使用者看到最新版本
  • 修復錯誤:程式錯誤導致畫面異常,修完 Bug 後要清掉 Cache,使用者才能看到正常畫面
  • 確保正確性:避免新舊版本混用造成衝突

不過大量執行 Purge 可能引發連鎖反應:

大量 Cache Miss
       ↓
大量 Origin Requests
       ↓
Origin Traffic 上升
       ↓
成本也跟著上升

有些供應商甚至會針對頻繁的 Purge 指令額外收費,所以更實用的做法是使用版本化 URL

不要用固定的 image.jpg,而是附帶版本號的 image-a83f12.jpg,檔案更新後產生新的網址 image-b72c91.jpg。CDN 會把它們視為不同資源,舊資源自然留在 Cache,新資源則用新網址提供,不需要特地清除舊的。

小結

加入 CDN 之後,整體架構變成:

加入 CDN 之後的完整架構圖

CDN 幫我們處理了:

  • 縮短內容傳遞的延遲
  • 降低 Origin 的流量壓力
  • 減輕 Application Server 的負擔
  • 提升媒體傳遞的 Scalability

但也帶來新的課題要考慮:TTL、大檔案優化、Purge 策略、CDN 成本。

不過今天其實跳過了一個環節:使用者上傳的原始檔案,到真正能被 CDN 快取的圖片之間,中間到底發生了什麼事?一張使用者上傳的 20MB 原始照片,總不會原封不動就拿去讓全世界的人下載吧?這是明天要處理的問題。


上一篇
Day 8 Cache 讓你不用每次都要讀 Database
下一篇
Day 10 Media Pipeline 上傳一張圖片,中間發生了什麼事?
系列文
系統設計就像九頭蛇:打造社群網站的 30 天10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言