Добавить в корзинуПозвонить
Найти в Дзене
Цифровая Переплавка

💡 Почему WebSockets — не всегда лучшее решение для realtime-приложений и какие есть альтернативы?

За последние годы WebSockets стали стандартом де-факто для реализации приложений, работающих в режиме реального времени: чаты, биржевые терминалы, совместные редакторы документов. Но несмотря на популярность, у технологии есть свои недостатки, из-за которых WebSockets не всегда оправдывают возложенные ожидания. Попробуем разобраться, почему иногда стоит отказаться от WebSockets в пользу более простых и проверенных решений, например HTTP-стриминга, и какие технические особенности стоят за таким выбором. Идея WebSockets выглядит привлекательно: устанавливаем одно постоянное двунаправленное соединение между сервером и клиентом и свободно обмениваемся сообщениями. Однако практика оказывается сложнее: Основная проблема возникает в случаях, когда важно подтвердить, дошло ли сообщение до сервера и корректно ли оно было обработано. Например, если клиент отправляет команду уменьшить счётчик, и сервер отвечает ошибкой, то становится сложно понять, какое конкретно сообщение вызвало проблему. Для
Оглавление

За последние годы WebSockets стали стандартом де-факто для реализации приложений, работающих в режиме реального времени: чаты, биржевые терминалы, совместные редакторы документов. Но несмотря на популярность, у технологии есть свои недостатки, из-за которых WebSockets не всегда оправдывают возложенные ожидания.

Попробуем разобраться, почему иногда стоит отказаться от WebSockets в пользу более простых и проверенных решений, например HTTP-стриминга, и какие технические особенности стоят за таким выбором.

🔌 WebSockets: преимущества и «подводные камни»

Идея WebSockets выглядит привлекательно: устанавливаем одно постоянное двунаправленное соединение между сервером и клиентом и свободно обмениваемся сообщениями. Однако практика оказывается сложнее:

🚩 Отсутствие транзакционности сообщений

Основная проблема возникает в случаях, когда важно подтвердить, дошло ли сообщение до сервера и корректно ли оно было обработано. Например, если клиент отправляет команду уменьшить счётчик, и сервер отвечает ошибкой, то становится сложно понять, какое конкретно сообщение вызвало проблему. Для решения этого нужно добавлять специальные идентификаторы запроса (requestId) и усложнять клиентскую логику, которая должна помнить все отправленные сообщения.

🚩 Сложность управления состоянием соединения

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

🚩 Усложнение серверного кода

WebSockets не просто устанавливаются и работают — для их использования сервер обязан обрабатывать специальный HTTP-запрос «Upgrade». Этот «рукопожатный» механизм требует дополнительных заголовков, проверки валидности запроса и управления состоянием, что заметно усложняет архитектуру.

🌊 HTTP Streaming как элегантная альтернатива

HTTP часто воспринимается исключительно как запрос-ответный протокол, где клиент отправляет запрос и сразу же получает ответ. Однако, HTTP прекрасно подходит для потоковой передачи данных. Так работают стриминговые платформы, например YouTube и Netflix. Но то же самое можно использовать и для простых realtime-приложений.

Рассмотрим пример реализации:

Сервер:

Используя асинхронные генераторы, сервер легко отправляет клиенту изменения состояния сразу же, как только они появляются:

async function* valueGenerator() {
while (true) {
yield await new Promise(resolve => resolvers.add(resolve));
}
}

app.get("/stream", (req, res) => {
const stream = ReadableStream.from(valueGenerator());
return new Response(stream);
});

Клиент:

На клиенте обработка также проста — браузер читает поток по мере поступления:

const response = await fetch("/stream");
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
updateUI(chunk);
}

Такой подход решает сразу несколько проблем:

  • Транзакционность — HTTP-запрос на изменение состояния сразу возвращает ответ о его корректности.
  • Проще обработка ошибок — если что-то идёт не так, сервер просто возвращает HTTP-статус ошибки.
  • Простое управление жизненным циклом — клиент не беспокоится о переподключении и сложных событиях. Соединение закрыто — поток закончился.

🚀 Технологические детали и тонкости реализации

Используя HTTP-стриминг, важно учитывать некоторые технические моменты:

  • 🌐 Поддержка на стороне клиента: Потоковый HTTP работает во всех современных браузерах через стандартный Fetch API и ReadableStream.
  • 🧑‍💻 Асинхронные генераторы: Использование генераторов в JavaScript позволяет просто и элегантно реализовывать асинхронные потоки данных.
  • 🎯 Лёгкость масштабирования: В отличие от WebSockets, HTTP-стриминг значительно проще масштабируется — для него подходят стандартные решения, такие как балансировщики нагрузки, прокси и CDN.

🔧 Библиотека Eventkit как удобный инструмент

Автор оригинальной статьи, Хантер Ловелл, представил собственную библиотеку — Eventkit, которая упрощает работу с асинхронными потоками и делает реализацию потоковых HTTP-серверов максимально простой.

Пример использования Eventkit на сервере:

import { Stream } from "eventkit";

let counter = 0;
const stateUpdates$ = new Stream();

stateUpdates$.subscribe(value => { counter = value });

app.post("/increment", async (req, res) => {
const { value } = await req.json();
stateUpdates$.push(counter + value);
return new Response("OK", 200);
});

app.get("/stream", (req, res) => {
const stream = ReadableStream.from(stateUpdates$);
return new Response(stream);
});

Это значительно сокращает объём кода и упрощает поддержку.

🔖 Заключение и личный взгляд автора

Как показывает практика, не стоит бросаться на каждую новую технологию лишь из-за её популярности. WebSockets отлично решают узкий круг задач, связанных с настоящей двунаправленной связью (например, интерактивные игры или высокочастотный трейдинг), но во многих приложениях достаточно куда более простых решений, таких как HTTP-стриминг.

Мне лично кажется, что для большинства типовых задач, особенно в веб-приложениях с не самым высоким трафиком, WebSockets — это избыточная сложность, которая только вредит проекту. Используя простые и надёжные подходы, такие как HTTP-стриминг, мы экономим время разработки, повышаем надёжность и уменьшаем количество ошибок.

🔗 Полезные ссылки:

Надеюсь, статья была полезной! 😊