Auto-Scaling в телеграм-ботах

от автора

Салют, Хабр!

Auto-scaling, или автоматическое масштабирование, — это механизм, позволяющий гибко адаптироваться к изменяющимся условиям нагрузки, автоматически расширяя или сокращая ресурсы. Эта технология очень актуальна в нашем мире.

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

Auto-scaling позволит не только поддерживать стабильную работу бота при всплесках активности, но и существенно снизит затраты на поддержание избыточной инфраструктуры в периоды низкой активности.

База Auto-Scaling:

Основная цель auto-scaling — поддержание стабильной производительности при минимизации затрат.

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

Auto-scaling основывается на мониторинге ключевых показателей производительности, таких как CPU, память, сетевая активность или даже специфичные для приложения метрики, к примеру в случае с телеграм ботами — количество одновременных пользовательских сессий.

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

Горизонтальное масштабирование заключается в добавлении или удалении экземпляров серверов или контейнеров.

Вертикальное масштабирование включает увеличение или уменьшение ресурсов для существующих экземпляров (например, увеличение ОЗУ или CPU).

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

Определяются правила или политики, определяющие, когда и как следует проводить масштабирование. Эти правила могут быть основаны на расписании (например, разные параметры масштабирования для часов пик и внепиковых часов) или на адаптивных алгоритмах, реагирующих на изменения в реальном времени.

Когда и почему вашему боту может потребоваться Auto-Scaling.

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

Типы нагрузки:

Пиковая нагрузка: Временные всплески активности, часто вызванные специфическими событиями или маркетинговыми акциями. Постоянная нагрузка: Регулярное количество запросов, которое поддерживается в течение длительного времени. Переменная нагрузка: Нерегулярные изменения в активности, часто связанные с временем суток или днями недели.

Метрики для анализа нагрузки:

Число активных пользователей (DAU/MAU): Ежедневно и ежемесячно активные пользователи. Количество сообщений в секунду/минуту (MPS/MPM): Среднее количество сообщений, обрабатываемых за секунду или минуту. Время отклика: Время, необходимое боту для ответа на запрос пользователя. Частота ошибок: Процент запросов, завершающихся неудачей.

Если метрики нагрузки превышают заранее определенные пороговые значения (например, время отклика или MPS), это является звоночком к масштабированию.

Как реализовать

Безсостоянийная архитектура (stateless architecture)

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

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

К примеру это может выглядеть так:

from telegram.ext import Updater, CommandHandler, MessageHandler, Filters import os import psycopg2  # Подключение к базе данных PostgreSQL DATABASE_URL = os.getenv('DATABASE_URL') conn = psycopg2.connect(DATABASE_URL, sslmode='require')  # Функция обработки команды /start def start(update, context):     user_id = update.effective_user.id     with conn.cursor() as cur:         cur.execute("INSERT INTO users (user_id) VALUES (%s) ON CONFLICT DO NOTHING", (user_id,))         conn.commit()     update.message.reply_text("Привет! Я безсостоянийный телеграм-бот.")  # Функция для обработки обычных сообщений def handle_message(update, context):     user_id = update.effective_user.id     # Получение данных пользователя из базы данных     with conn.cursor() as cur:         cur.execute("SELECT * FROM users WHERE user_id = %s", (user_id,))         user_data = cur.fetchone()         # Здесь можно обработать данные пользователя     update.message.reply_text("Ваше сообщение обработано!")  # основа примера def main():     TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')     updater = Updater(TOKEN, use_context=True)      dp = updater.dispatcher     dp.add_handler(CommandHandler("start", start))     dp.add_handler(MessageHandler(Filters.text & ~Filters.command, handle_message))      updater.start_polling()     updater.idle()  if __name__ == '__main__':     main()

Используется PostgreSQL для хранения данных о пользователях. Подключение осуществляется через psycopg2. start и handle_message являются обработчиками команд и сообщений соответственно. Они взаимодействуют с базой данных для чтения или записи данных о пользователе.

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

Системы управления очередями

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

Они также увеличивают устойчивость системы к сбоям, поскольку сообщения могут быть сохранены и повторно обработаны в случае ошибок.

Позволяет легко добавлять дополнительные рабочие узлы для обработки сообщений.

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

Производитель: Телеграм-бот

import os from telegram.ext import Updater, CommandHandler, MessageHandler, Filters import pika  # Подключение к RabbitMQ connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) channel = connection.channel() channel.queue_declare(queue='telegram_queue')  # Функция обработки сообщений def handle_message(update, context):     message = update.message.text     channel.basic_publish(exchange='',                           routing_key='telegram_queue',                           body=message)     update.message.reply_text('Сообщение отправлено в очередь!')  def main():     TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')     updater = Updater(TOKEN, use_context=True)      dp = updater.dispatcher     dp.add_handler(MessageHandler(Filters.text & ~Filters.command, handle_message))      updater.start_polling()     updater.idle()  if __name__ == '__main__':     main() 

Потребитель: Рабочий процесс обработки сообщений

import pika  def callback(ch, method, properties, body):     print(f"Получено сообщение: {body.decode()}")  connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) channel = connection.channel() channel.queue_declare(queue='telegram_queue')  channel.basic_consume(queue='telegram_queue', on_message_callback=callback, auto_ack=True)  print('Ожидание сообщений. Для выхода нажмите CTRL+C') channel.start_consuming()

RabbitMQ юзается как система управления очередями. Сообщения от телеграм-бота помещаются в очередь telegram_queue. Бот отправляет полученные сообщения в очередь RabbitMQ. Каждое сообщение от пользователя становится отдельным сообщением в очереди.

Отдельный процесс (или несколько процессов) подписан на очередь и обрабатывает сообщения по мере их поступления.

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

Думаю, микросервисы не нуждаются в представление.

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

  1. TextCommandService: Обрабатывает текстовые команды от пользователей.

  2. ImageProcessingService: Обрабатывает запросы на обработку изображений.

Оба сервиса могут быть развернуты независимо и масштабироваться в зависимости от нагрузки.

Напишем простой код, где оба микросервиса взаимодействуют через HTTP-запросы.

TextCommandService

# text_command_service.py from flask import Flask, request app = Flask(__name__)  @app.route('/process_text', methods=['POST']) def process_text():     text = request.json.get('text')     # Обработка текстовой команды     response = f"Обработан текст: {text}"     return response  if __name__ == '__main__':     app.run(port=5000)

ImageProcessingService

# image_processing_service.py from flask import Flask, request app = Flask(__name__)  @app.route('/process_image', methods=['POST']) def process_image():     image_data = request.json.get('image_data')     # Обработка изображения     response = f"Обработано изображение: {image_data[:10]}..."     return response  if __name__ == '__main__':     app.run(port=5001)

Телеграм-бот

# telegram_bot.py import requests from telegram.ext import Updater, CommandHandler, MessageHandler, Filters  def handle_text(update, context):     text = update.message.text     response = requests.post("http://localhost:5000/process_text", json={"text": text})     update.message.reply_text(response.text)  def handle_photo(update, context):     photo_file = update.message.photo[-1].get_file()     image_data = photo_file.download_as_bytearray()     response = requests.post("http://localhost:5001/process_image", json={"image_data": image_data})     update.message.reply_text(response.text)  def main():     TOKEN = 'YOUR_TELEGRAM_BOT_TOKEN'     updater = Updater(TOKEN, use_context=True)      dp = updater.dispatcher     dp.add_handler(MessageHandler(Filters.text & ~Filters.command, handle_text))     dp.add_handler(MessageHandler(Filters.photo, handle_photo))      updater.start_polling()     updater.idle()  if __name__ == '__main__':     main() 

TextCommandService и ImageProcessingService запущены на разных портах и обрабатывают разные типы запросов. бот определяет тип входящего сообщения (текст или изображение) и перенаправляет запрос к соответствующему микросервису.

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

Контейнеризация и оркестрация

Контейнеризация — это процесс упаковки приложения вместе со всеми его зависимостями и конфигурациями в изолированный контейнер.

Пример Dockerfile для телеграм-бота:

FROM python:3.12  # Устанавливаем рабочую директорию в контейнере WORKDIR /bot  # Копируем файлы проекта в контейнер COPY . /bot  # Устанавливаем зависимости RUN pip install -r requirements.txt  CMD ["python", "./my_telegram_bot.py"]

Создаем образ Docker для телеграм-бота, начиная с базового образа Python, устанавливая необходимые зависимости и определяя команду для запуска бота.

Оркестрация контейнеров — это процесс управления жизненным циклом контейнеров.

Для развертывания телеграм-бота в Kubernetes можно создать файл конфигурации deployment.yaml:

apiVersion: apps/v1 kind: Deployment metadata:   name: telegram-bot-deployment spec:   replicas: 2 # Начинаем с двух экземпляров   selector:     matchLabels:       app: telegram-bot   template:     metadata:       labels:         app: telegram-bot     spec:       containers:       - name: telegram-bot         image: my_telegram_bot:latest         ports:         - containerPort: 80

Для включения автоскейлинга в Kubernetes, можно использовать Horizontal Pod Autoscaler (HPA), который автоматически масштабирует количество подов в ответ на наблюдаемую нагрузку:

apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata:   name: telegram-bot-hpa spec:   scaleTargetRef:     apiVersion: apps/v1     kind: Deployment     name: telegram-bot-deployment   minReplicas: 2   maxReplicas: 10   targetCPUUtilizationPercentage: 80

Этот HPA будет масштабировать количество подов для telegram-bot-deployment от 2 до 10, основываясь на использовании CPU.

CI/CD

Непрерывная интеграция (CI) подразумевает автоматическое тестирование кода при каждом коммите в систему контроля версий.

Рассмотрим пример, где мы используем GitHub Actions для автоматического тестирования телеграм-бота при каждом коммите.

Создадим файл .github/workflows/python-app.yml в репозитории на GitHub:

name: Python application  on:   push:     branches: [ master ]   pull_request:     branches: [ master ]  jobs:   build:      runs-on: ubuntu-latest      steps:     - uses: actions/checkout     - name: Set up Python 3.12       uses: actions/setup-python       with:         python-version: 3.12     - name: Install dependencies       run: |         pip install -r requirements.txt     - name: Run tests       run: |         pytest

on: Определяет, когда должны выполняться действия (на push и pull request в ветку master).

jobs: Определяет набор задач, которые должны быть выполнены (здесь только build).

steps: Последовательность шагов, включая проверку кода из репозитория, установку питона, установку зависимостей и запуск тестов с помощью pytest.

Непрерывная доставка (CD) автоматизирует развертывание приложения в тестовую или рабочую среду после успешного прохождения тестов.

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

За пример возьмем докерфайл, который рассматривали выше. Настроим GitHub Actions для сборки и публикации Docker образа:

- name: Build and Push Docker image   if: github.event_name == 'push'   run: |     echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin     docker build -t my_telegram_bot .     docker tag my_telegram_bot ${{ secrets.DOCKER_USERNAME }}/my_telegram_bot:latest     docker push ${{ secrets.DOCKER_USERNAME }}/my_telegram_bot:latest

Автоматическое развертывание в Kubernetes:

- name: Deploy to Kubernetes   if: github.event_name == 'push'   run: |     kubectl apply -f k8s-deployment.yaml

k8s-deployment.yaml — это файл конфигурации Kubernetes, который описывает, как должен быть развернут и масштабирован телеграм-бот.


Грамотный автоскейлинг позволяет справляться с изменяющимися нагрузками и значительно повышает устойчивость и адаптивность бота.

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


ссылка на оригинал статьи https://habr.com/ru/articles/781458/


Комментарии

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

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