Масштабирование II: Высокая нагрузка
Способность вашего бота выдерживать высокую нагрузку зависит от того, как вы запускаете бота через long polling или через вебхуки. В любом случае, вам следует ознакомиться с некоторыми подводными камнями ниже.
Long Polling
Большинству ботов никогда не требуется обрабатывать более нескольких сообщений в минуту (во время “пиковой нагрузки”). Другими словами, масштабируемость не является для них проблемой. Чтобы быть предсказуемым, grammY обрабатывает обновления последовательно. Вот порядок операций:
- Получите до 100 обновлений через
get
(ссылка на Telegram Bot API)Updates - Для каждого обновления
ожидайте
стек middleware для него
Однако если ваш бот обрабатывает одно сообщение в секунду (или что-то в этом роде) во время пиков нагрузки, это может негативно сказаться на скорости отклика. Например, сообщение Боба должно ждать, пока сообщение Алисы не будет обработано.
Эту проблему можно решить, не дожидаясь окончания обработки сообщения Алисы, т.е. обрабатывая оба сообщения одновременно. Для достижения максимальной скорости отклика мы также хотели бы получать новые сообщения, пока сообщения Боба и Алисы еще обрабатываются. В идеале мы также хотели бы ограничить параллельность некоторым фиксированным числом, чтобы ограничить максимальную нагрузку на сервер.
Параллельная обработка не поставляется из коробки. Вместо этого можно использовать плагин grammY runner для запуска вашего бота. Он поддерживает все вышеперечисленное из коробки и очень прост в использовании.
// Раньше
bot.start();
// Используя grammY runner, который экспортирует `run`.
run(bot);
2
3
4
5
По умолчанию ограничение параллельности составляет 500. Если вы хотите глубже изучить пакет, загляните на эту страницы.
Параллельность — это сложно, поэтому ознакомьтесь с подразделом ниже, чтобы узнать, что следует иметь в виду при использовании grammY-runner.
Вебхуки
Если вы запустите своего бота на вебхуках, он будет автоматически обрабатывать обновления одновременно, как только они будут получены. Естественно, чтобы это хорошо работало при высокой нагрузке, вам следует ознакомиться с использованием вебхуков. Это означает, что вы все еще должны знать о некоторых последствиях параллельности, см. раздел Параллельность
Также помните
Параллельность - это сложно
Если ваш бот обрабатывает все обновления одновременно, это может вызвать ряд проблем, требующих особого внимания. Например, если два сообщения из одного чата будут получены одним и тем же вызовом get
, они будут обрабатываться одновременно. Порядок сообщений внутри одного чата больше не может быть гарантирован.
Основной момент, когда это может привести к конфликту — использование сессий, которые могут столкнуться с опасностью записи после чтения. Представьте себе следующую последовательность событий:
- Алиса отправляет сообщение A
- Бот начинает обработку A
- Бот считывает данные сессии для Алисы из базы данных
- Алиса отправляет сообщение B
- Бот начинает обработку B
- Бот считывает данные сессии для Алисы из базы данных
- Бот завершает обработку A и записывает новую сессию в базу данных
- Бот завершает обработку B и записывает новую сессию в базу данных, тем самым перезаписывая изменения, сделанные при обработке A. Потеря данных из-за конфлитка перезаписи!
Примечание: Вы можете попытаться использовать транзакции базы данных для своих сессий, но тогда вы сможете только обнаружить опасность, но не предотвратить ее. Попытка использовать блокировку вместо этого приведет к полному исключению параллельности. Гораздо проще изначально избежать опасности.
Большинство других сессионных систем веб-фреймворков просто принимают риск возникновения условий гонки, поскольку они не слишком часто случаются в Интернете. Однако мы этого не хотим, потому что боты Telegram с гораздо большей вероятностью столкнутся с параллельными запросами на один и тот же ключ сессии. Следовательно, мы должны убедиться, что обновления, обращающиеся к одним и тем же данным сессии, обрабатываются последовательно, чтобы избежать этого опасного состояния гонки.
В комплект поставки grammY runner входит middleware sequentialize()
, который обеспечивает последовательную обработку обновлений, которые сталкиваются между собой. Вы можете настроить его на ту же функцию, которая используется для определения ключа сессии. Тогда она позволит избежать вышеупомянутого состояния гонки, замедляя те (и только те) обновления, которые могут вызвать столкновение.
import { Bot, Context, session } from "grammy";
import { run, sequentialize } from "@grammyjs/runner";
// Создайте бота.
const bot = new Bot("");
// Создайте уникальный идентификатор для объекта `Context`.
function getSessionKey(ctx: Context) {
return ctx.chat?.id.toString();
}
// Последовательность перед доступом к данным сессии!
bot.use(sequentialize(getSessionKey));
bot.use(session({ getSessionKey }));
// Добавьте обычный middleware, теперь с поддержкой безопасных сессий.
bot.on("message", (ctx) => ctx.reply("Получил ваше сообщение."));
// По-прежнему запускайте их одновременно!
run(bot);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const { Bot, Context, session } = require("grammy");
const { run, sequentialize } = require("@grammyjs/runner");
// Создайте бота.
const bot = new Bot("");
// Создайте уникальный идентификатор для объекта `Context`.
function getSessionKey(ctx) {
return ctx.chat?.id.toString();
}
// Последовательность перед доступом к данным сессии!
bot.use(sequentialize(getSessionKey));
bot.use(session({ getSessionKey }));
// Добавьте обычный middleware, теперь с поддержкой безопасных сессий.
bot.on("message", (ctx) => ctx.reply("Получил ваше сообщение."));
// По-прежнему запускайте их одновременно!
run(bot);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Bot, Context, session } from "https://deno.land/x/grammy@v1.30.0/mod.ts";
import { run, sequentialize } from "https://deno.land/x/grammy_runner@v2.0.3/mod.ts";
// Создайте бота.
const bot = new Bot("");
// Создайте уникальный идентификатор для объекта `Context`.
function getSessionKey(ctx: Context) {
return ctx.chat?.id.toString();
}
// Последовательность перед доступом к данным сессии!
bot.use(sequentialize(getSessionKey));
bot.use(session({ getSessionKey }));
// Добавьте обычный middleware, теперь с поддержкой безопасных сессий.
bot.on("message", (ctx) => ctx.reply("Получил ваше сообщение."));
// По-прежнему запускайте их одновременно!
run(bot);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Не стесняйтесь присоединиться к Telegram