一、痛點
遇到的一些問題
圖檔是一個網站提升使用者體驗必不可少的一部分,但是更多并不是最好。随手拍的照片,沒有任何加工的照片可能不需要花費太多的精力就可以內建到使用者界面中,但是這些大尺寸、高分辨率的圖檔會降低整個網頁的下載下傳速度,并且這些高分辨率并不能增加多少使用者體驗。
在移動網際網路如此發達的今天,幾乎人手一部手機,假如你運作了一個新聞站點,絕大部分使用者都在他們的手機上浏覽你的網站,他們并不需要高分辨率的圖檔,高分辨率對他們的顯示效果提升了不少,反而會影響加載速度。但是,還有一部分使用者使用桌面電腦閱讀,網絡更好,螢幕也更大更好,高分辨率的圖檔會提升他們的視覺體驗。
從使用者體驗角度來看,正确的做法是根據使用者的使用裝置提供不同尺寸的圖檔。但是我們并不能覆寫所有尺寸的裝置,新尺寸裝置可能在不斷被制造出,并且事先調整圖形大小以适應任何想到的螢幕尺寸幾乎是不可能的,并且存儲所有預先生成所有可能大小的圖檔會花費巨額的費用,這并不是一個好辦法。
一個比較好的辦法就是,我們在第一次請求時建立每個尺寸的圖形,然後将其儲存以備後用。這樣,每個裝置都可以得到正确的圖像大小,我們也可以節省大量的存儲成本和計算成本。
因為我們無法預測使用者的請求行為,哪次請求需要生成新尺寸的圖檔,如果準備一台伺服器專門處理生成新尺寸圖檔,可能使用率并不會很高,在請求高峰時期,單台機器的資源也有可能不夠。這時候 Serverless 就非常适合了,你隻需為你使用的計算付費,并且無伺服器應用程式已經設計為自動伸縮以滿足使用者需求,是以不需要預先準備大量伺服器,可以進一步降低成本。即使當使用者請求到一個新尺寸照片的時候,應用需要生成新尺寸照片,這個圖檔大小小圖由一個伺服器函數完成,計算成本也會低得多。
Serverless Framework
本文我們主要使用 Serverless Framework 和 Python 來建構一個自動調整圖像大小的系統,那麼 Serverless Framework 是什麼呢,我們看一下官方關于 AWS 部分的介紹:
The Serverless Framework helps you develop and deploy your AWS Lambda functions, along with the AWS infrastructure resources they require. It's a CLI that offers structure, automation and best practices out-of-the-box, allowing you to focus on building sophisticated, event-driven, serverless architectures, comprised of Functions and Events.
我們會使用 S3 作為我們的圖檔存儲,S3 是 AWS 雲中的一個對象存儲,在 S3 中存儲圖檔是一種簡單、可伸縮的方法,本文我們就是讓每個請求生成它所需要大小的圖檔,然後将結果存儲在 S3 中。
當下次有人請求相同的圖檔時,将會發生以下兩種情況之一:如果已經存在該大小的圖檔時,那麼相應的 S3 URI 直接為我們提供先前存儲的圖檔。但是如何我們還沒有這個尺寸的圖檔,S3 會觸發函數生産該尺寸的圖檔然後傳回給我們,同時也會把這個圖檔儲存在雲中以備将來使用。
這就是我們所說的“智能”調整大小系統,我們讓使用者請求他們實際需要的大小的圖檔,而不是為圖檔請求的每個可能結果做準備。
有關無伺服器架構的介紹與安裝,請參照官方文檔:
https://www.serverless.com/framework/docs/providers/aws/guide/quick-start/
wangzan:~ $ serverless -v
Framework Core: 1.78.1
Plugin: 3.7.0
SDK: 2.3.1
Components: 2.34.3
二、使用 Serverless 建構無伺服器應用程式
建立示例
開始我們可能沒有使用 serverless 建立過應用程式,我們這裡在 AWS 雲環境中建立一個 Hello world 函數示範一下。
- 建立一個服務
建立好之後,目錄中會有 Lambda 的運作檔案
handler.py
,還有一個重要的檔案是
serverless.yml
。
sls create --template aws-python --path myService
- 部署
将會基于建立的
serverless.yml
來吧函數部署到 Lambda 中。
wangzan:~/environment/myService $ sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service myservice.zip file to S3 (390 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
...............
Serverless: Stack update finished...
Service Information
service: myservice
stage: dev
region: us-east-1
stack: myservice-dev
resources: 6
api keys:
None
endpoints:
None
functions:
hello: myservice-dev-hello
layers:
None
**************************************************************************************************************************************
Serverless: Announcing Metrics, CI/CD, Secrets and more built into Serverless Framework. Run "serverless login" to activate for free..
**************************************************************************************************************************************
- 調用部署的函數
測試一下函數調用,是否傳回預期結果。
wangzan:~/environment/myService $ sls invoke -f hello
{
"body": "{\"input\": {}, \"message\": \"Go Serverless v1.0! Your function executed successfully!\"}",
"statusCode": 200
}
可以看到,通過 serverless 建立部署一個 Lmabda 函數變得很簡單,比較重要的就是
serverless.yml
這個檔案,我們簡單看下裡面的内容:
service: myservice
provider:
name: aws
runtime: python2.7
functions:
hello:
handler: handler.hello
建立 serverless.yml
我們将一步步來制作
serverless.yml
,它包含了函數所需的所有東西。
首先,我們指定服務的名稱,運作環境和位置,并授予未來函數通路 S3 的權限:
service: wzlinux-image-resizing
provider:
name: aws
runtime: python2.7
region: us-east-1
iamRoleStatements:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource: 'arn:aws:s3:::wzlinux-resized-images/*'
Resource 的桶名,大家修改為自己的桶名即可。
接下來我們說一說函數部分以及其參數:
functions:
resize:
handler: handler.call
environment:
BUCKET: wzlinux-resized-images
REGION: us-east-1
events:
- http:
path: /{size}/{image}
method: get
我們還需要一個 S3 存儲桶資源,用來存儲圖檔:
Resources:
ResizedImages:
Type: AWS::S3::Bucket
Properties:
BucketName: wzlinux-resized-images
這就是
serverless.yml
重要組成部分,下面我貼出 Github 上完整代碼:
https://github.com/wangzan18/serverless-image-resizing/raw/master/serverless.yml
建立圖檔處理函數
為了實作 python 可以處理圖檔,我們需要引入哪些子產品呢?
import json
import datetime
import boto3
import PIL
from PIL import Image
from io import BytesIO
import os
- json 和 datatime 子產品是 python 的内置子產品,不需要單獨安裝。
- boto3 是 python 用來操作 AWS 中資源的子產品。
- PIL 是 python 處理圖檔的子產品。
- io 是将函數檔案流式。
- os 可以用來擷取環境變量,比如 os.environ。
下面讓我們看看檔案中最外層函數。
def call(event, context):
key = event["pathParameters"]["image"]
size = event["pathParameters"]["size"]
result_url = resize_image(os.environ["BUCKET"], key, size)
response = {
"statusCode": 301,
"body": "",
"headers": {
"location": result_url
}
}
return response
這個函數在使用者在請求新尺寸圖檔的時候被調用,從傳入的 URI 中解析出圖像和大小屬性,然後調用另外一個函數 resize_image 對其進行處理,生成使用者需要的新尺寸,最後 301 重定向到新的位址傳回給使用者。
下面就讓我們看看這個 resize_image 函數是如何實作的:
def resize_image(bucket_name, key, size):
size_split = size.split('x')
s3 = boto3.resource('s3')
# 從 S3 中擷取圖像,并寫入到變量中
obj = s3.Object(
bucket_name=bucket_name,
key=key,
)
obj_body = obj.get()['Body'].read()
# 讀取圖像并調整到新的大小
img = Image.open(BytesIO(obj_body))
img = img.resize(
(int(size_split[0]), int(size_split[1])), PIL.Image.ANTIALIAS
)
buffer = BytesIO()
img.save(buffer, 'JPEG')
buffer.seek(0)
# 将調整好大小的圖檔上傳會 S3
resized_key="{size}_{key}".format(size=size, key=key)
obj = s3.Object(
bucket_name=bucket_name,
key=resized_key,
)
obj.put(Body=buffer, ContentType='image/jpeg')
# 傳回調整大小圖檔的 URL
return resized_image_url(
resized_key, bucket_name, os.environ["AWS_REGION"]
)
調整大小圖檔的 URL 是單獨放在一個函數中的,如下:
def resized_image_url(resized_key, bucket, region):
return "http://{bucket}.s3.{region}.amazonaws.com/{resized_key}".format(bucket=bucket, region=region, resized_key=resized_key)
至此,我們圖檔調整的函數寫好了,它會用外部函數來調整圖檔的大小,并執行 301 重定向到新的位置,代碼已經送出都了 Github,下面位址可以看到完整代碼:
https://github.com/wangzan18/serverless-image-resizing/raw/master/handler.py
因為函數還有一些依賴的子產品,我們把子產品寫在檔案
requirements.txt
裡面,一行一個子產品。
boto3
Pillow
部署圖檔調整函數
為了部署函數,我們需要擷取 AWS 的憑證,并且具有通路 AWS Lambda、S3、IAM 和 API Gateway 的權限,我這邊配置好了 awscli。
為了確定我們 python 依賴包在生産中可以正常引用,我們使用了 serverless-python-requirements 插件。它将確定 python 依賴項将被正确打包到 Lambda 環境中。
是以,為了部署我們的函數,我們需要運作
sls deploy
。在部署完成之後,将會輸出 API Gateway 上函數的 URL,它看起來像這樣:
https://XXXXX.execute-api.eu-west-1.amazonaws.com
開始部署
wangzan:~/environment/serverless-image-resizing (master) $ sls deploy
Serverless: Generated requirements from /home/ec2-user/environment/serverless-image-resizing/requirements.txt in /home/ec2-user/environment/serverless-image-resizing/.serverless/requirements.txt...
Serverless: Using static cache of requirements found at /home/ec2-user/.cache/serverless-python-requirements/780f69be60ef16a7f6639a5336b5f5e85188a84f8a321df2307c90d24883f346_slspyc ...
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Injecting required Python packages to package...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service wzlinx-image-resizing.zip file to S3 (10.74 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
....................................
Serverless: Stack update finished...
Service Information
service: wzlinx-image-resizing
stage: dev
region: us-east-1
stack: wzlinx-image-resizing-dev
resources: 13
api keys:
None
endpoints:
GET - https://yj0u7v8vh7.execute-api.us-east-1.amazonaws.com/dev/{size}/{image}
functions:
resize: wzlinx-image-resizing-dev-resize
layers:
None
配置 S3 存儲桶
為了使 S3 和我們的無伺服器函數配合工作,我們需要對 S3 做以下配置:
- 把 S3 存儲桶變為靜态站點托管
- 添加一個重定向規則
- 配置公開政策,打開對象公開通路權限

重定向規則如下,當請求的圖檔尺寸不存在的時候,幫我們重定向到 S3 的位址。
<RoutingRules>
<RoutingRule>
<Condition>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<Protocol>https</Protocol>
<HostName>yj0u7v8vh7.execute-api.us-east-1.amazonaws.com</HostName>
<ReplaceKeyPrefixWith>dev/</ReplaceKeyPrefixWith>
<HttpRedirectCode>307</HttpRedirectCode>
</Redirect>
</RoutingRule>
</RoutingRules>
存儲桶的配置政策如下
{
"Id": "Policy1597386227420",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1597386225957",
"Action": [
"s3:GetObject"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::wzlinux-resized-images/*",
"Principal": "*"
}
]
}
驗證效果
- 我先上傳一張照片到 S3 存儲桶。
打開效果如下:
- 我們去通路一個 200x200 的大小,看看效果,位址如下
https://yj0u7v8vh7.execute-api.us-east-1.amazonaws.com/dev/200x200/sls.jpg
我們可以看到裁切的很小了,然後再去看下 S3 中圖檔的大小:
可以看到圖檔隻有 8.8 KB 小了,實作了我們上面所說的功能,自動進行裁切并展示給使用者。
三、總結
在本文中,我們介紹了如何使用 Python 和 Serverless 架構設定動态圖像調整 API。
圖像調整大小是無伺服器的一個很好的用例。當使用無伺服器實作時,圖像的大小調整可以有效地随負載而縮放。這個函數将隻使用它需要的計算來快速調整圖像的大小,如果沒有調整大小的請求,就不會浪費計算時間。S3 和無伺服器功能的解決方案還提供了一個非常簡單的架構,減少了使用需要維護的伺服器,是以確定了系統的穩定性。
從工作流自動化和事件流到移動應用程式和日志處理的後端,還有許多其他用例可以從 Serverless 中受益。