Хабрахабр

[Перевод] Как повысить производительность, используя бессерверную архитектуру


Фото: Jesse Darlandс Unsplash

В этой статье речь пойдёт о том, как перенести процесс предварительной обработки изображений с сервера приложений на абсолютно бессерверную архитектуру платформы AWS.

Суть проблемы

Если веб-приложение позволяет загрузить изображение, скорее всего его необходимо обработать перед тем, как показать пользователю.

В статье будет рассказано о бессерверной архитектуре на основе платформы AWS, которая предоставляет широкие возможности для масштабирования.

Затем оно преобразуется в три разных формата, используемых в различных элементах пользовательского интерфейса: 800x600, 400x300 и 200x150. В одном из моих последних проектов (веб-приложение для торговли, при работе с которым пользователю необходимо загружать изображение товара) исходное изображение сначала обрезается по соотношению сторон 4:3.

Будучи разработчиком фреймворка Ruby on Rails, я в первую очередь решил попробовать пакеты RubyGem, а именно Paperclip или Dragonfly, которые используют для обработки изображений набор ImageMagick.

Это довольно простой подход, но у него есть свои недостатки:

  1. Изображения обрабатываются на сервере приложений. Это может привести к увеличению общего времени отклика из-за повышенной нагрузки на процессор.
  2. Сервер приложений имеет ограниченную производительность и не подходит для скачкообразной обработки запросов. Если требуется одновременно обработать множество изображений, он может оказаться полностью загружен на долгое время. Повышение производительности сервера, в свою очередь, приведёт к увеличению затрат.
  3. Изображения обрабатываются последовательно. Опять же, если требуется сразу обработать много изображений, это будет долго.
  4. Если вышеописанные пакеты настроены неправильно, обработанные изображения будут сохраняться на диске, что может быстро привести к нехватке свободного места на сервере.

В общем, если в приложении обрабатывается достаточно большое количество изображений, это решение не масштабируется.

Решение

Если присмотреться к процессу предварительной обработки изображений, можно понять, что в большинстве случаев нет необходимости в его выполнении непосредственно на сервере приложений. Это особенно актуально, если всегда осуществляются одни и те же преобразования, которые не требуют других данных, кроме самого изображения. Так было и в моём случае: я всегда только и делал, что создавал несколько изображений разного размера, оптимизируя их уровень качества.

Как только стало понятно, что эту задачу можно легко отделить от остальной логики приложения, на ум сразу же пришла мысль о бессерверном решении, которое будет просто брать исходное изображение в качестве входных данных и выполнять все необходимые преобразования.

Он способен обрабатывать тысячи запросов в секунду, при этом платить надо только за фактическое время вычислений. Оказалось, сервис AWS Lambda идеально подходит для этой цели. Если же код не выполняется, то и денег с вас требовать не будут.

Наконец, AWS Cloudfront используется в качестве сети доставки контента для изображений, хранящихся в S3.
Сочетание этих четырёх услуг AWS даёт нам мощное решение для обработки изображений при минимальных затратах. Сервис AWS S3 предлагает неограниченное хранилище по низкой цене, а служба AWS SNS обеспечивает простой обмен сообщениями по модели «издатель-подписчик» для микросервисов, распределённых систем и бессерверных приложений.

Высокоуровневая архитектура

Создание различных версий изображения из одного исходного начинается с загрузки оригинала в AWS S3. Затем с помощью AWS SNS запускается функция AWS Lambda, которая отвечает за создание новых версий и их повторную загрузку в AWS S3. Более подробно процесс выглядит так:

  1. Изображения загружаются в определённую папку внутри бакета AWS S3.
  2. Каждый раз, когда в эту папку загружается новое изображение, сервис отправляет сообщение с S3-ключом созданного объекта в топике AWS SNS.
  3. AWS Lambda, настроенная как пользователь в том же топике SNS, считывает сообщение и использует этот ключ для извлечения нового изображения.
  4. AWS Lambda обрабатывает изображение, выполняя необходимые преобразования, и затем загружает его обратно в S3.
  5. Обработанные изображения демонстрируются пользователям. В целях оптимизации скорости загрузки для этого используется сеть доставки контента AWS Cloudfront.

Таким образом, множественное выполнение кода позволяет обрабатывать тысячи изображений одновременно. Эта архитектура легко масштабируется, так как каждое загруженное изображение инициирует новое выполнение кода Lambda для обработки конкретного запроса.

Дисковое пространство и вычислительные ресурсы сервера приложений не используются, так как все данные хранятся в S3 и обрабатываются службой Lambda.

И наконец, настройка сети доставки контента для S3 очень проста и позволяет поддерживать высокую скорость загрузки в любой точке мира.

Пошаговая инструкция

Реализация этого решения не очень сложная, поскольку в основном (за исключением кода Lambda, который выполняет предварительную обработку изображений) включает в себя лишь настройку. Далее в статье подробно описывается, как настроить архитектуру AWS. А чтобы вы могли в полной мере оценить её работу, также приводится код AWS Lambda для изменения размера загруженного изображения.

Вы можете создать её и воспользоваться бесплатным стартовым пакетом AWS здесь. Чтобы опробовать его самостоятельно, вам понадобится учётная запись AWS.

Шаг 1: создание топика в AWS SNS

Прежде всего необходимо настроить новый топик SNS (Simple Notification Service), куда AWS будет публиковать сообщения каждый раз, когда в S3 загружается новое изображение. Такое сообщение содержит S3-ключ объекта, используемый впоследствии функцией Lambda для извлечения и обработки изображения.

Из консоли AWS зайдите на страницу SNS, нажмите Create topic и введите название топика, например, image-preprocessing.

Затем нужно изменить политику топика, чтобы позволить бакету S3 публиковать сообщения.
На странице топика нажмите Actions -> Edit Topic Policy, выберите Advanced view, добавьте следующий блок JSON (с указанием собственных имён ресурсов Amazon (arn) в строках Resource и SourceArn) в массив Statement и обновите политику:

, "Action": [ "SNS:Publish", ], "Resource": "arn:aws:sns:us-east-1:AWS-OWNER-ID:image-preprocessing", "Condition": { "StringLike": { "aws:SourceArn": "arn:aws:s3:*:*:YOUR-BUCKET-NAME" } }
}

Пример полного JSON-текста политики здесь.

Шаг 2: создание структуры папок AWS S3

Теперь нужно подготовить структуру папок в S3, в которых будут храниться исходные и обработанные изображения. В данном примере мы будем создавать версии изображения в двух размерах: 800x600 и 400x300.

Я назову его image-preprocessing-example. Из консоли AWS откройте страницу S3 и создайте новый бакет. Далее нужно создать в бакете папки с названиями originals, 800x600 и 400x300.

Шаг 3: настройка событий AWS S3

Каждый раз при загрузке нового изображения в папку originals S3 должен публиковать сообщение в топике image-preprocessing, чтобы это изображение можно было обработать.

Для настройки публикации таких сообщений откройте бакет S3 через консоль AWS, нажмите Properties -> Events -> Add notification и заполните следующие поля:

image

Здесь мы задаём правило для генерации события каждый раз, когда создаётся новый объект (чекбокс ObjectCreate) внутри папки originals (поле Prefix), и публикации этого события в SNS-топике image-preprocessing.

Шаг 4: настройка роли IAM для предоставления Lambda доступа к папке S3

Требуется создать функцию Lambda, которая будет скачивать изображения из папки S3, обрабатывать их и загружать обработанные версии обратно в S3. Но сначала необходимо настроить роль IAM, чтобы функция Lambda могла получить доступ к необходимой папке S3.

Из консоли AWS перейдите на страницу IAM:

  • Нажмите Create Policy.
  • Нажмите JSON и введите название своего бакета:

{ "Version": "2012-10-17", "Statement": [ { "Sid": "Stmt1495470082000", "Effect": "Allow", "Action": [ "s3:*" ], "Resource": [ "arn:aws:s3:::YOUR-BUCKET-NAME/*" ] } ]
}

Строка Resource относится к нашему бакету в S3. Нажмите Review, введите имя политики, например, AllowAccessOnYourBucketName, и создайте политику.

  • Нажмите Roles -> Create role.
  • Выберите AWS Service -> Lambda (служба, которая будет использовать политику).
  • Выберите созданную ранее политику (AllowAccessOnYourBucketName).
  • Теперь нажмите review, введите имя (LambdaS3YourBucketName) и нажмите Сreate role.

Создание роли Lambda

Прикрепление политики к роли Lambda

Сохранение роли

Шаг 5: создание функции AWS Lambda

Далее необходимо настроить функцию Lambda, чтобы она считывала сообщения из топика image-preprocessing и генерировала изменённые версии изображений.

Начнём с создания новой функции Lambda.

Выберите среду выполнения (в данном случае Node.js 6. Из консоли AWS перейдите на страницу Lambda, нажмите Create function и введите имя новой функции, например, ImageResize. 10) и ранее созданную роль IAM.

Затем нужно добавить SNS в число триггеров, чтобы функция Lambda вызывалась каждый раз, когда новое сообщение публикуется в теме image-preprocessing.

Для этого нажмите SNS в списке триггеров, выберите image-preprocessing в списке топиков SNS и нажмите Add.

Теперь нужно загрузить код, который будет обрабатывать событие S3 ObjectCreated, что включает в себя получение загруженного изображения из папки originals, его обработку и повторную загрузку в соответствующие папки для изменённых изображений.

Код можно скачать здесь.

1.zip, который содержит файл index.js и папку node_modules. Единственный элемент, который необходимо загрузить в функцию Lambda, — это архив version1.

Потребность в ресурсах зависит от размера изображения и сложности преобразований. Чтобы предоставить функции Lambda достаточное количество ресурсов для обработки изображения, можно увеличить объём памяти до 256 Мб, а максимальное время выполнения (timeout) до 10 секунд.

image

Сам код довольно прост и предназначен для демонстрации интеграции AWS.

Она вызывается внешним триггером. Сначала определяется функция-обработчик (export.handler). В данном случае — сообщением, опубликованным в SNS, которое содержит S3-ключ объекта загруженного изображения.

В первую очередь, она анализирует JSON-текст сообщения о событии, чтобы извлечь имя бакета S3, S3-ключ объекта загруженного изображения, а также имя файла (последняя часть ключа).

Переменная SIZE содержит размеры изображений, которые нужно получить. После получения имени бакета и ключа объекта загруженное изображение извлекается с помощью операции s3.getObject, а затем передаётся в функцию для изменения размера. Они соответствуют именам папок S3, в которые загружаются преобразованные изображения.

var async = require('async');
var AWS = require('aws-sdk');
var gm = require('gm').subClass({ imageMagick: true });
var s3 = new AWS.S3();
var SIZES = ["800x600", "400x300"];
exports.handler = function(event, context) { var message, srcKey, dstKey, srcBucket, dstBucket, filename; message = JSON.parse(event.Records[0].Sns.Message).Records[0];
srcBucket = message.s3.bucket.name; dstBucket = srcBucket; srcKey = message.s3.object.key.replace(/\+/g, " "); filename = srcKey.split("/")[1]; dstKey = ""; ... ... // Download the image from S3 s3.getObject({ Bucket: srcBucket, Key: srcKey }, function(err, response){ if (err){ var err_message = 'Cannot download image: ' + srcKey; return console.error(err_message); } var contentType = response.ContentType; // Pass in our image to ImageMagick var original = gm(response.Body); // Obtain the size of the image original.size(function(err, size){ if(err){ return console.error(err); } // For each SIZES, call the resize function async.each(SIZES, function (width_height, callback) { var filename = srcKey.split("/")[1]; var thumbDstKey = width_height +"/" + filename; resize(size, width_height, imageType, original, srcKey, dstBucket, thumbDstKey, contentType, callback); }, function (err) { if (err) { var err_message = 'Cannot resize ' + srcKey; console.error(err_message); } context.done(); }); }); });
}

Функция изменения размера преобразует исходное изображение при помощи библиотеки gm, в частности, она изменяет размер изображения, при необходимости обрезает его и снижает качество до 80%. Затем она загружает изменённое изображение в S3, используя операцию s3.putObject, и указывает ACL: public-read, чтобы новое изображение стало общедоступным.

var resize = function(size, width_height, imageType, original, srcKey, dstBucket, dstKey, contentType, done) { async.waterfall([ function transform(next) { var width_height_values = width_height.split("x"); var width = width_height_values[0]; var height = width_height_values[1]; // Transform the image buffer in memory original.interlace("Plane") .quality(80) .resize(width, height, '^') .gravity('Center') .crop(width, height) .toBuffer(imageType, function(err, buffer) { if (err) { next(err); } else { next(null, buffer); } }); }, function upload(data, next) { console.log("Uploading data to " + dstKey); s3.putObject({ Bucket: dstBucket, Key: dstKey, Body: data, ContentType: contentType, ACL: 'public-read' }, next); } ], function (err) { if (err) { console.error(err); } done(err); } );
};

Шаг 6: тестирование

Теперь можно проверить, всё ли работает верно, загрузив изображение в папку originals. Если всё было сделано правильно, мы получим соответствующие преобразованные версии загруженного изображения в папках 800x600 и 400x300.

После загрузки файла в папку originals два других окна обновляются, чтобы проверить, были ли созданы изображения. В видеоролике ниже вы можете увидеть три окна: слева — папка originals, посередине — папка 800x600, а справа — папка 400x300.

И вуаля, вот они 😉

(Опционально) Шаг 7: добавление сети доставки контента Cloudfront

Теперь, когда изображения созданы и загружены в S3, их необходимо доставить конечным пользователям. Чтобы повысить скорость загрузки, можно использовать сеть доставки контента Cloudfront. Для этого:

  1. Откройте страницу CloudFront.
  2. Нажмите Create Distribution.
  3. При запросе метода доставки выберите Web Distribution.
  4. В поле Origin Domain Name выберите требуемый бакет S3 и нажмите Create Distribution.

Процесс создания сети займёт какое-то время, поэтому подождите, пока статус CDN не изменится с In Progress на Deployed.

Например, если имя вашего домена Cloudfront — 1234-cloudfront-id.cloudfront.net, то вы сможете получить доступ к папке обработанных изображений по ссылкам 1234-cloudfront-id.cloudfront.net/400x300/FILENAME и 1234-cloudfront-id.cloudfront.net/800х600/FILENAME После того как сеть развёрнута, можно использовать имя домена вместо ссылки на бакет S3.

Чтобы получить более подробную инструкцию по настройке вашей сети доставки контента, обратитесь к Руководству от Amazon. В Cloudfront есть множество других важных параметров, но в рамках этой статьи мы их рассматривать не будем.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть