【AWS徵文】使用 AWS Serverless 架構動態調整圖片大小

1、痛點

遇到的一些問題

圖片是一個網站提高用戶體驗必不可少的一部分,可是更多並非最好。隨手拍的照片,沒有任何加工的照片可能不須要花費太多的精力就能夠集成到用戶界面中,可是這些大尺寸、高分辨率的圖片會下降整個網頁的下載速度,而且這些高分辨率並不能增長多少用戶體驗。python

在移動互聯網如此發達的今天,幾乎人手一部手機,假如你運行了一個新聞站點,絕大部分用戶都在他們的手機上瀏覽你的網站,他們並不須要高分辨率的圖片,高分辨率對他們的顯示效果提高了很多,反而會影響加載速度。可是,還有一部分用戶使用桌面電腦閱讀,網絡更好,屏幕也更大更好,高分辨率的圖片會提高他們的視覺體驗。linux

從用戶體驗角度來看,正確的作法是根據用戶的使用設備提供不一樣尺寸的圖片。可是咱們並不能覆蓋全部尺寸的設備,新尺寸設備可能在不斷被製造出,而且事先調整圖形大小以適應任何想到的屏幕尺寸幾乎是不可能的,而且存儲全部預先生成全部可能大小的圖片會花費鉅額的費用,這並非一個好辦法。git

一個比較好的辦法就是,咱們在第一次請求時建立每一個尺寸的圖形,而後將其保存以備後用。這樣,每一個設備均可以獲得正確的圖像大小,咱們也能夠節省大量的存儲成本和計算成本。github

由於咱們沒法預測用戶的請求行爲,哪次請求須要生成新尺寸的圖片,若是準備一臺服務器專門處理生成新尺寸圖片,可能利用率並不會很高,在請求高峯時期,單臺機器的資源也有可能不夠。這時候 Serverless 就很是適合了,你只需爲你使用的計算付費,而且無服務器應用程序已經設計爲自動伸縮以知足用戶需求,所以不須要預先準備大量服務器,能夠進一步下降成本。即便當用戶請求到一個新尺寸照片的時候,應用須要生成新尺寸照片,這個圖片大小小圖由一個服務器函數完成,計算成本也會低得多。json

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.api

咱們會使用 S3 做爲咱們的圖片存儲,S3 是 AWS 雲中的一個對象存儲,在 S3 中存儲圖片是一種簡單、可伸縮的方法,本文咱們就是讓每一個請求生成它所須要大小的圖片,而後將結果存儲在 S3 中。bash

當下次有人請求相同的圖片時,將會發生如下兩種狀況之一:若是已經存在該大小的圖片時,那麼相應的 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

2、使用 Serverless 構建無服務器應用程序

建立示例

開始咱們可能沒有使用 serverless 建立過應用程序,咱們這裏在 AWS 雲環境中建立一個 Hello world 函數演示一下。

  1. 建立一個服務

建立好以後,目錄中會有 Lambda 的運行文件handler.py,還有一個重要的文件是serverless.yml

sls create --template aws-python --path myService
  1. 部署

將會基於建立的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..
**************************************************************************************************************************************
  1. 調用部署的函數

測試一下函數調用,是否返回預期結果。

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、S三、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 存儲桶變爲靜態站點託管
  • 添加一個重定向規則
  • 配置公開策略,打開對象公開訪問權限

image-20200814143812933

重定向規則以下,當請求的圖片尺寸不存在的時候,幫咱們重定向到 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": "*"
    }
  ]
}

驗證效果

  1. 我先上傳一張照片到 S3 存儲桶。

image-20200814150212689

打開效果以下:

image-20200814150614423

  1. 咱們去訪問一個 200x200 的大小,看看效果,地址以下
https://yj0u7v8vh7.execute-api.us-east-1.amazonaws.com/dev/200x200/sls.jpg

image-20200814150745110

咱們能夠看到裁切的很小了,而後再去看下 S3 中圖片的大小:

image-20200814150821671

能夠看到圖片只有 8.8 KB 小了,實現了咱們上面所說的功能,自動進行裁切並展現給用戶。

3、總結

在本文中,咱們介紹瞭如何使用 Python 和 Serverless 框架設置動態圖像調整 API。

圖像調整大小是無服務器的一個很好的用例。當使用無服務器實現時,圖像的大小調整能夠有效地隨負載而縮放。這個函數將只使用它須要的計算來快速調整圖像的大小,若是沒有調整大小的請求,就不會浪費計算時間。S3 和無服務器功能的解決方案還提供了一個很是簡單的架構,減小了使用須要維護的服務器,所以確保了系統的穩定性。

從工做流自動化和事件流到移動應用程序和日誌處理的後端,還有許多其餘用例能夠從 Serverless 中受益。

若是您想了解 Serverless,能夠從無服務器文檔開始,並查看使用無服務器框架的 AWS 介紹。

參考地址:https://github.com/wangzan18/serverless-image-resizing

歡迎你們掃碼關注,獲取更多信息

【AWS徵文】使用 AWS Serverless 架構動態調整圖片大小

相關文章
相關標籤/搜索