#ruby-on-rails #server-sent-events
#ruby-on-rails #отправленные сервером события
Вопрос:
Я читаю этот пост, в котором говорится о соединениях SSE без блокировки серверных потоков, Автор описывает, как решить проблему блокировки.
Мое замешательство заключается в том, что если я закрываю поток на стороне сервера (с sse.close
) и на стороне клиента (с source.close()
), почему возникает проблема в первую очередь? Почему сервер зависает при подключении?
Комментарии:
1. Не было бы более полезным прокомментировать этот пост и задать этот вопрос?
Ответ №1:
Проблема не в закрытии соединения, а в том, когда вы это делаете — это происходит намного позже (десятки секунд, может быть, даже в часы), чем для обычного запроса, который обычно обрабатывается за пару секунд даже для самых тяжелых.
Например, скажем (очень упрощенный и приближенный):
- у вас 10 потоков / рабочих, нет SSE
- вы обслуживаете каждую HTML-страницу менее чем за 0,1 секунды
- пользователь будет ждать загрузки страницы до 1 секунды, прежде чем разразиться слезами и уйти с вашего сайта
- затем пользователь будет читать страницу в течение 9 секунд, прежде чем запросить следующую
таким образом, каждый поток может обслуживать 10 страниц в секунду, все потоки — 100 страниц в секунду, поскольку каждый пользователь запрашивает не более одного в 10 секунд — вы можете обрабатывать около 1000 пользователей, использующих ваше приложение одновременно.
Теперь добавляем обновления SSE на эти страницы, шаг за шагом:
- первый пользователь подключается, получает html за 0.1 сек., затем один поток занят запросом SSE в течение 9 секунд до перезагрузки страницы, и после этой перезагрузки он снова будет заблокирован по запросу того же пользователя
- у вас всего 9 незанятых потоков
- подключается второй пользователь, повторяется то же самое
Таким образом, одна и та же система может обрабатывать не более 10 пользователей, что в 100 раз меньше. И вы не можете просто увеличить потоки до 1000, потому что они не свободны (память, накладные расходы планировщика и т.д.).
Загвоздка в том, что большую часть времени большинство таких соединений ничего не делают, просто ждут событий, поэтому им фактически не нужен поток, зарезервированный для них. Поэтому логично освободить поток для других запросов, не закрывая соединение, это то, что делает hijack.
PS. Этот подход можно использовать еще дальше — соединение с текущими обновлениями клиента может оставаться открытым процессом, отличным от rails server (не ruby и более эффективным), при этом все еще выполняя всю логику событий в rails. Например, с помощью серверной части anycable для ActionCable вы можете легко поддерживать тысячи одновременных подключений
Комментарии:
1. Спасибо за такие подробности! Так что просто для подтверждения. Когда я закрываю соединение на серверной части и интерфейсной части, я не освобождаю поток? Чтобы добавить некоторый контекст. Мой вариант использования очень прост. У меня есть форма для публикации с функцией предварительного просмотра. Предварительный просмотр попадает на сервер для интенсивной компиляции данных. Я открываю соединение только тогда, когда кто-то нажимает предварительный просмотр btn, устанавливает EventSource, получает данные, затем закрывается.
2. Когда соединение закрыто — вы действительно освобождаете его, но в любом случае длинные запросы к серверу rails являются злом и могут заблокировать ваш сервер. Если они редки, и вы не ожидаете более 1-2 параллельно — вы можете обойтись без текущей настройки, в противном случае лучше обрабатывать их в фоновых заданиях с выполнением, доставляемым через anycable (пользователи увидят, что их задание ожидает запуска, затем прогресс и т.д.).
Ответ №2:
Вы выполняете sse.close
после завершения всей связи с клиентом через SSE (после того, как вся тяжелая работа выполнена и все результаты отправлены клиенту). Но до тех пор поток Rails (Puma) занят.
Подход, описанный в сообщении, показывает, как вы можете немедленно освободить потоки Rails (Puma) и продолжать выполнять свою работу, все еще соединяясь с клиентом.
Используя Rack Hijacking API, вы можете немедленно освободить потоки Rails (Puma), сразу после того, как сервер получит запрос, и до того, как вы фактически закроете выполнение соединения SSE sse.close
.