iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
Software Development

系統設計就像九頭蛇:打造社群網站的 30 天系列 第 10

Day 10 Media Pipeline 上傳一張圖片,中間發生了什麼事?

  • 分享至 

  • xImage
  •  

昨天把 CDN 接上了 PokeThreads,圖片跟影片不再經過 Application Server,直接由 CDN 搭配 Object Storage 提供服務:

CDN 搭配 Object Storage 提供媒體服務的架構圖

但這裡其實跳過了一個環節:

Pikachu 上傳一張 20MB 的原始照片,到這張照片變成 CDN 上那張輕巧的圖片之間,中間到底發生了什麼事?

總不可能不處理 20MB 的原始檔案,直接拿去讓全世界的人下載吧。

上傳跟「能被 CDN 快取的檔案」是兩回事

先釐清一件事:使用者上傳的檔案,跟系統實際拿來服務的檔案,通常不是同一份。

原始檔案可能:

  • 解析度過高(一張照片動輒 4000x3000)
  • 格式沒有優化(JPEG、PNG 沒轉成更小的 WebP/AVIF)
  • 影片還沒轉成適合不同網路狀況的多種畫質

如果什麼都不做,直接把原始檔案丟給 CDN 快取,代價是每個人下載的流量都變大、PokeThreads 的頻寬帳單也會跟著變大。

所以在「上傳」跟「CDN 提供服務」之間,需要一段 Processing(處理) 的過程。

最簡單可行實作:Upload → Processing → CDN

Step 1:上傳,但不要經過 Application Server

延續昨天的原則——媒體檔案不應該經過 Application Server,這個原則在「上傳」這一端一樣適用。

如果讓 Client 把檔案傳給 Application Server,再由 Server 轉傳到 Object Storage,等於讓 Server 白白扛了一次檔案傳輸的 Network Load。

更好的做法是 Pre-signed URL(預先簽署網址)

  1. Client 跟 Application Server 說「我要上傳一個檔案」
  2. Server 驗證身份與權限後,回傳一個短效期的簽署網址
  3. Client 拿著這個網址,直接把檔案上傳到 Object Storage

圖片上傳處理流程圖

Application Server 全程只做兩件事:驗證身份、簽發網址,真正的檔案位元組完全不會經過它。

這跟昨天「CDN 讓下載不經過 Server」是一樣概念,只是換成上傳方向。

Step 2:Processing,非同步進行

檔案進到 Object Storage 之後,會觸發一個 Processing Worker,執行:

  • 圖片:裁切不同尺寸(Thumbnail)、壓縮、轉成 WebP / AVIF
  • 影片:Transcoding(轉碼)、產生多種解析度、Bitrate optimization

這些工作有一個共同點:都不需要讓使用者在原地等待

這其實跟「事件發生後,丟給背景服務慢慢處理」是同一種模式,會在後面的系列文章討論 Notification 時,把這種「非同步 + Worker」的做法正式定義清楚;這裡先知道 Processing 是用類似的方式在背景執行就好。

Step 3:處理完成,才交給 CDN

Worker 處理完畢後,把結果寫回另一個位置(或加上版本化的檔名),之後 CDN 快取的就是這份處理過、體積小很多的檔案,而不是使用者上傳的原始檔。

處理期間,使用者看到什麼?

這裡有個很實際的問題:

Processing 需要時間,這段期間 Pikachu 的貼文該顯示什麼?

最簡單的做法,是幫 Post 加一個處理狀態欄位:

  • Post(延伸 Day 9 的欄位)
    • id:BIGINT(Primary Key)
    • author_id:BIGINT(Foreign Key → User.id)
    • content:TEXT
    • media_status:VARCHAR(20)(PENDING / PROCESSING / READY / FAILED
    • original_url:VARCHAR(500)
    • processed_url:VARCHAR(500)(可為 NULL,處理完成才會有值)
    • created_at:TIMESTAMP

狀態的轉換大致是:

PENDING(剛上傳,等待處理)
   ↓
PROCESSING(Worker 正在處理)   --失敗--> FAILED
   | 處理成功
   ↓
READY(處理完成,可以顯示 processed_url)

media_status 還不是 READY 之前,Feed 上可以先顯示一個 Placeholder(例如模糊縮圖或載入中的動畫),等狀態變成 READY,前端才把畫面換成真正的 processed_url

這樣 Pikachu 發文之後不需要卡住等處理完成,貼文的文字內容可以馬上出現,圖片則是晚一點點才補上。

業界常見方法比較:處理要放在上傳前還是上傳後?

除了「先上傳原始檔、再非同步處理」,另一種常見做法是 Client-side Processing:在使用者裝置上先做一輪基本壓縮、裁切,再上傳。

Server-side Processing Client-side Processing
上傳流量 較大(傳原始檔) 較小(先在裝置端壓縮過)
處理能力 可以用較強的運算資源,效果較一致 受限於使用者裝置效能,結果可能不一致
實作複雜度 需要 Worker、狀態機 需要在每個前端平台各自實作
常見場景 需要多種輸出格式、影片轉碼 想先擋掉明顯過大的檔案,減少上傳等待時間

實務上這兩者通常會搭配使用:Client 端先做基本的裁切跟壓縮,減少上傳時間;真正決定最終畫質、格式、多解析度輸出的工作,還是交給 Server 端的 Processing Worker 統一處理。

與其他元件串接

把今天的流程和昨天的 CDN 架構接在一起看:

媒體上傳流程與 CDN 架構整合圖

Application Server 在這整條路徑上,只出現在最開頭簽發網址、以及讀寫 media_status 這個欄位,真正的檔案傳輸與運算,全部交給 Object Storage、Worker 跟 CDN 分工完成。

小結

今天把「使用者上傳一個檔案」到「CDN 上出現一份優化過的檔案」之間的空白補上了:

Upload(Pre-signed URL,繞過 App Server)
      ↓
Processing(非同步:resize / compress / transcode)
      ↓
CDN(服務處理後的檔案)

到目前為止,PokeThreads 已經能好好處理「使用者主動看到」的內容:文字、圖片、影片。

但社群平台還有一種訊息,是系統要「主動推給使用者」的,例如「有人按讚了你的貼文」。這是接下來要處理的功能。


上一篇
Day 9 運用 CDN 處理多媒體素材
系列文
系統設計就像九頭蛇:打造社群網站的 30 天10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言