透過 serverless 建立 AsiaYo 的圖片處理系統

Nick Huang
AsiaYo Engineering
Published in
10 min readJan 19, 2022

作為一個訂房體驗平台,AsiaYo 需要讓旅客們可以找到他們心儀的旅宿/飯店,因此需要盡量提供足夠清楚的資訊讓使用者選擇,而再多的文字描述都不會比幾張美麗的照片更能讓人對在這樣舒適的房間睡一覺充滿想像。

前情提要

首先我們需要定義我們需要的圖片資料

  • 圖片檔案
  • 檔案名稱
  • 圖片敘述
  • 圖片連結的物件(房間、民宿、飯店)

將圖片儲存在雲端儲存空間配合 CDN 服務是常用的做法。CDN(Content Delivery Network) 可以將檔案(網頁、程式碼、圖片)部署到不同地理位置的伺服器,藉以加快不同地區的人讀取的速度、也減少自己的伺服器的負擔,圖片上傳到 S3 儲存後也可以部屬到 CDN 上讓圖片讀取速度更快也節省網路傳輸的成本。

AsiaYo 主要使用了 AWS 的服務,將圖片儲存在 S3 上並且透過 CloudFront 加速圖片讀取的速度。但隨著業務增長原本的架構開始出現問題,圖片上傳容易卡住或者造成後端 server 的負擔,使用不合尺寸的圖片浪費了傳輸成本、過大的圖片檔案、上傳不支援的圖片格式。因此我們規劃了幾個重點來調整圖片的上傳和呈現

  • 將圖片上傳從後端 server 抽離
  • 壓縮圖片並統一格式
  • 裁切圖片符合呈現需求

將圖片的壓縮、上傳獨立出來避免耗用後端機器的資源。但圖片上傳不太可能是一個 24/7 發生的事情,用一個 microservice 來處理未必符合成本,因此我們選擇了 serverless 的架構。

serverless 的好處在他只會在需要時觸發時執行,畢竟圖片上傳不會是隨時且常態發生的事情,serverless 會在需要時快速的完成任務且只有執行時收費,相當適合用在這個情境,另外 serverless 也簡化了一個 server 的基本部署,讓我們專注在功能開發而不需要煩惱 infrastructure。

適當的壓縮圖片可以節省儲存空間也減少傳輸成本,當然不能因此讓圖片失真,統一的格式在處理上比較不容易出現意外。

照片裁切讓我們不需要用很大的原圖卻只用在小小的區塊,減少圖片的大小等於節省了傳輸頻寬,對網路成本跟圖片讀取速度都很有幫助,為了 RWD 我們也設計讓圖片可以在需要時根據需求的尺寸即時裁切。

我們採用了 AWS Lambda 來實做 serverless 的圖片處理,Lambda 可以透過簡易的設定配合 AWS 的

各種服務使用,Lambda 原生支援 Java、Go、PowerShell、Node.js、C#、Python 和 Ruby 程式碼,並提供 Runtime API。根據需求選擇配置需要的機器資源並且只在使用時計費,很適合使用在這樣子頻率不高但需要快速完成的使用情境。

接下來我們分成三個部分介紹:上傳、轉移、顯示

圖片上傳

前端將圖片跟其他資訊一起傳給後端,後端先將圖片轉成 base64 格式後交給 lambda 存到 S3 (注意 Lambda 有接受的資料大小上限為 6 MB) 之後後端再把圖片資訊寫入資料庫。

將圖片處理跟圖片資料分開處理,因為流程解耦所以在管理上比較容易,查找問題時也更容易分辨錯誤發生在哪個段落。

pseudo code for upload image

透過 API Gateway 建立一個 endpoint 導向這個 Lambda function,然後將檔案從 base64 轉成 buffer 之後上傳到 S3,在 Lambda 中我們會重新命名這個檔案並且最後回給後端 server 寫入資料庫,從後端的角度就像呼叫了一個 API,但實際上我們並不是真的有一個 server 隨時等著做圖片上傳,而是在收到呼叫時才啓動,完成後就關閉。只要做幾個簡單的設定跟寫好上傳圖片處理的程式就完成了!

scenario in upload image by Lambda

圖片轉移

除了上傳圖片,我們也必須把原先的圖片搬移到新的 S3 bucket 中,最大的問題在過往的 dirty data 需要很多 try-catch 處理,像是檔案過大、格式錯誤等。我們做了另一個 Lambda function 來處理,從後端讀出一個物件的所有照片後將物件資訊跟原本 S3 bucket 的連結交給 Lambda 後,先從原本的 bucket 下載下來再轉到新的 bucket 中,中間跟上傳做了一樣的圖片處理確保照片格式正確且經過壓縮。

image migration workflow

圖片顯示

為了應對前端可能有不同尺寸的圖片需求,我們的設計希望能夠接受不同的尺寸要求並且即時的處理,從 request 來定義需要的圖片尺寸

https://{domain}/{path}/{size}/image_namesize 由 request 定義需要的圖片尺寸
image resize workflow

一開始的結構(不含虛線)前端打了後經過 API gateway 到了 Lambda 後從原檔 resize 並且回傳 CDN 的網址,前端再 redirect 到 CDN 取得圖片並顯示。

上線後馬上發現兩個嚴重的問題。

  1. 錢噴的超級快!每一張圖片都需要跑過 API Gateway 跟 Lambda,這就已經違背我們一開始用 Lambda 省錢的目標了,之後我們做了第一次修正(上圖的虛線),當 S3 中有符合尺寸的圖片時會直接回傳 CDN 網址,減少 Lambda 的使用時間跟存取 S3 的成本,但整體花費依舊偏高。
  2. CDN 是關聯到 S3 bucket 的,可以想像成在各地的 server 都保留一份 bucket 的 cache,但上圖的流程並不會在 CDN 上產生檔案,造成每次有一個新的圖片 request 進來時打到 CDN 都是 404,第二次才會顯示圖片。

為了修正上面兩個問題,第二次修正採用了 Lambda@Edge 來重構整個流程。

Lambda@Edge的使用

scene with Lambda@Edge, from AWS blog

Lambda@Edge 是 CloudFront 的功能,讓你可以在 CDN 的 request/response中間使用 Lambda function。Lambda@Edge 可以在四個時機點使用

  • Viewer request(end user 向 CloudFront 送出請求)
  • Origin request(CloudFront 向 Server 送出請求)
  • Origin response(Server 給 CloudFront 的 response)
  • Viewer response(CloudFront 給 end user 的 response)

透過插入 Lambda@edge 我們可以對原本的 request/response 做調整,這樣我們就能夠把 resize 綁在 CloudFront 上面,省下了一開始 API Gateway 的流程可以讓整個架構大幅簡化。

Image Generation Workflow, from AWS blog

流程變得簡潔許多,省下了 API Gateway 跟 redirect 的成本,讓前端直接對 CloudFront 發請求。在 上圖 1 的階段可以先對 request 做調整,例如根據使用者的裝置決定現在應該要取得什麼尺寸的圖片。

如果 CloudFront 上沒有圖片會從 S3 bucket 取得(上圖 2 的階段),這時候在 3 會觸發 Lambda@Edge,此時 Lambda function 會處理兩種狀況

1. S3 上有裁切後的圖片,直接回傳給 CloudFront 建立 cache 後回傳給使用者2. S3 上沒有圖片,在 Lambda 上取得原圖 resize 後存回 S3 再回傳 CloudFront

對 CloudFront 來說,不論哪種狀況他都會一樣拿到圖片並且回傳給使用者,我們只是置換了原本 S3 給他的 response,在 Lambda function 中做圖片裁切後回存 S3 並且交給 CloudFront,對 CloudFront 來說沒有差異。

pseudo code for resize image in Lambda@Edge

到底差多少?

兩次的修正過程到底有多少成本差異呢?就用圖片這個最直接的方式呈現!

API Gateway cost by daily
Lambda cost by daily
S3 cost by daily

從 2020–05 開始,一開始做圖片轉移後可以看到 S3 有小量的 cost,到了 5 月底系統上線後,API Gateway, Lambda, S3 的 cost 都是爆發性的上漲,到了 6 月底第一次的 refactor 後因為觸及 S3 的頻率降低且 Lambda 的執行時間縮短,可以看到這兩者的 cost 明顯少了很多,但因為都還是會過 API Gateway 所以 cost 沒有變化,直到 8 月初用了 Lambda@edge 後 API Gateway 幾乎直接歸零,S3 也幾乎只剩下基本的儲存的花費。

簡單來說最後的結果讓我們用較為單純的結構完成有彈性的圖片呈現,一開始的設計省下了伺服器的成本。第一次重構減少了 S3 的存取跟 Lambda 的執行時間。第二次重構讓 S3 跟 Lambda@edge 只在需要時取用也大幅減少 API Gateway 的呼叫次數,絕大多數的圖片取用只會碰到 CDN,在新的圖片上傳時也可以在呼叫時做 resize 來即時呈現。

結語

去年年中,我們重新建立了圖片從上傳到取得的流程

  • 透過 serverless 的方式上傳圖片減少後端 server 的負擔和維護難度
  • 在 Lambda@Edge 上做了圖片壓縮減少圖片大小
  • 裁切圖片以符合畫面需求

對比原先的結構,現在的結構讓圖片顯示更有彈性且降低了成本,對 AsiaYo 的使用者來說網站速度更快且可以看到一樣清楚好看的照片。用更低的成本得到更好的體驗,善用 serverless 可以讓系統更加彈性且簡潔,不論是小專案或是大型系統都可以考慮導入來提升整個系統的效能。

最後感謝您的收看!

最後的最後,如果你決定這篇文章有幫助,歡迎分享或者給我一些回饋。

最後的最後的最後,AsiaYo 現在正在擴編,我們目前正在徵求

  • Backend Engineer
  • Site Reliability Engineer
  • Frontend Engineer
  • UI/UX Designer
  • QA Engineer
  • Product Manager

詳細資訊在 CakeResume 上,歡迎大家成為我們的夥伴!

--

--