【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 架构动态调整图片大小

相关文章
相关标签/搜索