Блог Amazon Web Services

Создание масштабируемого бессерверного (serverless) веб-приложения с определением местоположения – часть 3

Оригинал статьи: ссылка (James Beswick, Senior Developer Advocate)

Во второй части этой серии я рассказал о конфигурации API, алгоритме гео-хеширования (geohashing) и архитектуре обмена сообщениями в реальном времени, используемой в веб-приложении Ask Around Me. Они необходимы для получения и обработки вопросов и ответов, а также для отправки результатов пользователям в режиме реального времени.

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

Обработка вопросов

Фронтенд отправляет новые вопросы от пользователей в бэкенд через API вопросов с использованием метода POST. Несмотря на то, что прогнозируемый объём вопросов составляет всего 1000 в час, существует вероятность неожиданной повышенной нагрузки при резком увеличении количества пользователей приложения. Чтобы помочь справиться с такой нагрузкой, Lambda-функция PostQuestions помещает входящие вопросы в очередь Amazon SQS. Функция ProcessQuestions принимает сообщения из очереди Questions партиями по 10 штук и загружает их в таблицу Questions в Amazon DynamoDB.

Архитектура обработки вопросов

Такой асинхронный процесс сглаживает скачки трафика, чтобы избежать превышения выбранных лимитов DynamoDB и ограничения запросов из приложения. Он также обеспечивает консистентное время ответа на POST-запросы из фронтенда, так как ответ на API-вызов возвращается сразу после того, как сообщение сохранено в очереди.

В настоящее время функция ProcessQuestions не анализирует и не проверяет вопросы пользователей. Однако достаточно легко добавить фильтрацию сообщений на этом шаге, используя Amazon Comprehend для определения настроения или ненормативной лексики. Такие изменения увеличат время обработки каждого вопроса, но при асинхронной обработке это не приведёт к увеличению задержки ответа на POST API.

Функция ProcessQuestions использует библиотеку Geo Library for Amazon DynamoDB, которая преобразует широту и долготу локации вопроса в гео-хеш. Атрибут, хранящий этот гео-хеш, является одним из индексов в таблице DynamoDB. Функция GetQuestions, использующая ту же самую библиотеку, позволяет эффективно запрашивать список вопросов, основываясь на близости к пользователю.

Информация передаётся между фронтендом и бэкендом с использованием двух способов. Во время инициализации фронтенда он запрашивает текущее местоположение пользователя из браузера. Затем он вызывает API для получения списка вопросов в радиусе 5 миль (~8 км) от него. Таким образом, приложение получает данные на текущий момент времени. Чтобы получать уведомления о новых сообщениях недалеко от пользователя, фронтенд также подписывается на тему в AWS IoT Core, соответствующую текущему гео-хешу.

Обработка ответов

Архитектура обработки ответов

Приложение позволяет задавать два типа вопросов с разными видами ответов. Первый тип, вопросы о рейтинге, допускает ответы в виде числа в промежутке от 0 до 5. Второй тип, географические вопросы, допускает ответы в видео гео-локации, представленной с помощью широты и долготы.

Так же, как и вопросы, новые ответы сначала сохраняются в очереди перед дальнейшей обработкой. Отличие в том, что Lambda-функция PostAnswers отправляет ответы в две разные очереди в зависимости от типа вопроса. Вопросы о рейтинге добавляются в очередь StarAnswers, а вопросы о географическом положении добавляются в очередь GeoAnswers. Затем рейтинг в виде количества звёзд сохраняется в сыром виде в таблице Answers с помощью функции ProcessAnswerStar. В свою очередь, ответы на географические вопросы конвертируются в гео-хеш перед сохранением.

Пользователи также могут обновлять свои ответы. Для ответа в виде рейтинга обрабатывающая функция просто сохраняет новое значение. В случае ответа, указывающего на географическое положение, если широта и долгота указывают на точку достаточно близко к изначальному ответу, может получиться такой же гео-хеш, как и был до этого. Это связано с различными процессами агрегирования, используемыми для этих типов ответов.

Агрегация данных

В рассматриваемом приложении пользователи, задающие вопросы, ожидают получить агрегированные ответы, а не сырые данные. Например, на вопрос «Как бы вы оценили этот парк?» должно показываться среднее значение из всех ответов, а не тысячи отдельных рейтингов. Для улучшения производительности такая агрегация происходит при добавлении новых ответов в базу данных, а не во время получения приложением списка вопросов.

Таблица Answers отправляет информацию об изменениях в поток DynamoDB каждый раз, когда добавляются новые записи или обновляются старые. Параметр StreamSpecification в определении таблицы установлен в значение NEW_AND_OLD_IMAGES: это значит, что каждое сообщение в потоке содержит и старое, и новое значение записи в таблице.

Для новых ответов создаются новые записи в таблице, поэтому сообщение в потоке содержит только новое значение. Если же пользователи обновляют свои предыдущие ответы, то в таблице обновляется уже существующая запись, таким образом, сообщение в потоке содержит и старое, и новое значение.

При получении ответов с изменённым рейтингом функция Aggregation использует оба значения для подсчёта дельты. Например, если старый рейтинг был 2, а затем пользователь поменял его на 5, то дельта будет равна 3. Общее значение рейтинга ответов обновляется в таблице Questions с использованием DynamoDB update expression (выражения для обновления данных):

    const result = await myGeoTableManager.updatePoint({
      RangeKeyValue: { S: update }, 
      GeoPoint: {
        latitude: item.lat,
        longitude: item.lng
      },
      UpdateItemInput: {
        UpdateExpression: 'ADD answers :deltaAnswers, totalScore :deltaTotalScore',
        ExpressionAttributeValues: {
          ':deltaAnswers': { N: item.deltaAnswers.toString()},
          ':deltaTotalScore': { N: item.deltaValue.toString()}
        }
      }
    }).promise()

Для вопросов о географическом положении используется похожий подход, но при этом, если гео-хеш поменялся, то для старого хеша дельта составляет -1, а для нового – +1. Указанное выражение для обновления данных автоматически создаёт новый атрибут для гео-хеша в записи DynamoDB, если он отсутствует:

    const result = await myGeoTableManager.updatePoint({
      RangeKeyValue: { S: item.ID }, 
      GeoPoint: {
        latitude: item.lat,
        longitude: item.lng
      },
      UpdateItemInput: {
        UpdateExpression: `ADD ${item.geohash} :deltaAnswers, answers :deltaAnswers`,
        ExpressionAttributeValues: {
          ':deltaAnswers': { N: item.deltaAnswers.toString() }          
        }
      }
    }).promise()

Благодаря использованию Lambda-функции в качестве обработчика потока DynamoDB, вы можете агрегировать большие объёмы данных практически в режиме реального времени. Таблицы Questions и Answers имеют соотношение «один ко многим», то есть, на каждый вопрос приходится несколько ответов. При сохранении ответов агрегирующий процесс обновляет суммарные значения в таблице Questions.

Таблица Questions также публикует свои обновления в потоке DynamoDB. Они используются Lambda-функцией, которая отправляет их в темы (topics) AWS IoT Core. Таким образом, обновлённые данные об агрегированных ответах отправляются назад во фронтенд клиентского приложения.

Публикация в производственную среду с помощью Amplify Console

На этом этапе вы можете запустить приложение на своей локальной машине и открыть его с помощью сервера Vue.js, запущенного на localhost. Когда вы будете готовы запустить приложение для пользователей, вы должны развернуть его в производственной среде.

Одностраничные приложения (single-page application, SPA) достаточно легко опубликовать в общий доступ. Процесс сборки создает статические файлы HTML, JS и CSS. Их можно предоставлять пользователям через Amazon S3 и Amazon CloudFront вместе с изображениями и другими медиа-файлами. Процесс сборки и развёртывания можно автоматизировать с помощью AWS Amplify Console.

В этой статье я использую GitHub в качестве репозитория. Вы также можете использовать AWS CodeCommitBitbucketGitLab или загрузить директорию с собранным приложением с вашего компьютера.

Для установки фронтенда с помощью Amplify Console выполните следующие шаги:

  1. В консоли AWS выберите в выпадающем меню Services пункт AWS Amplify. На открывшейся странице нажмите Get Started в разделе Deploy.Amplify Console getting started
  2. Выберите GitHub в качестве репозитория, затем нажмите Continue:Выберите GitHub в качестве репозитория
  3. Следуйте подсказкам, чтобы включить доступ к GitHub, затем выберите необходимый репозиторий в первом выпадающем списке. В выпадающем списке Branch выберите master. После этого нажмите Next.Добавьте необходимую ветку репозитория
  4. На странице App build and test settings нажмите Next.
  5. На странице Review нажмите Save and deploy.
  6. На последнем экране показан конвейер (pipeline) развёртывания приложения из указанного репозитория. Он начинается с шага Provision:Конвейер развёртывания в Amplify Console

Через несколько минут около шагов BuildDeploy и Verify должны появиться зелёные галочки. Теперь вы можете открыть URL в браузере и убедиться, что ваше приложение доступно по публичному адресу:

Установленное приложение Ask Around Me

Наконец, перед тем как войти в приложение, вам необходимо добавить его URL в список разрешённых адресов в настройках Auth0:

  1. Зайдите в Auth0 и перейдите в панель управления.
  2. Выберите пункт меню Applications, а затем Ask Around Me из списка приложений.
  3. На вкладке Settings добавьте URL приложения в поля Allowed Callback URLs, Allowed Logout URLs и Allowed Web Origins. Для разделения значений используйте запятую.Конфигурация Auth0
  4. Нажмите Save changes. Теперь вы разрешили для нового домена, на котором опубликовано приложение, доступ к Auth0, чтобы аутентифицировать пользователей.

Каждый раз, когда вы вносите изменения в исходный код в репозитории, Amplify Console обнаруживает это и заново развёртывает приложение. При возникновении ошибок, пользователи продолжают работать с предыдущей версией приложения. Если же ошибок нет, то новая версия становится доступна для пользователей.

Заключение

В последней части этой серии постов я показал, как вопросы и ответы добавляются в очереди приложений. Я объяснил, как такой асинхронный подход позволяет сгладить всплески трафика и помогает поддерживать консистентную скорость ответов в API.

Я рассказал, как ответы собираются у тысяч пользователей, а затем агрегируются с помощью потоков DynamoDB. Эти суммарные значения сохраняются в таблице Questions, при этом обновления поступают через AWS IoT Core назад во фронтенд практически в режиме реального времени.

Наконец, я показал, как вы можете автоматизировать развёртывание приложения с помощью Amplify Console. Благодаря возможности подключить сервис напрямую к репозиторию с кодом, он может опубликовывать ваше приложение без необходимости вручную копировать файлы.

Чтобы узнать больше об этом приложении, посетите репозиторий GitHub.