Realtime
Изменение попадает на чужие экраны само, без запроса: приходит сообщение, обновляется список, меняется статус. Обычно это WebSockets, иногда server-sent events, изредка — честный polling.
Почему это важно. Realtime легко показать в демо и тяжело эксплуатировать. На мобильном соединение рвётся постоянно: приложение ушло в фон, сменилась сеть, погас экран. Сложность не в том, чтобы доставить сообщение, а в том, чтобы остаться корректным на разрывах.
Что нужно понимать
- Нужен ли здесь действительно push или хватило бы polling
- Кто имеет право получать какие сообщения и где это право проверяется
- Что происходит с сообщениями, отправленными, пока клиент был отключён
- Как клиент согласует устаревшее локальное состояние с серверным
- Во что обходится открытое соединение — по батарее и по серверу
Основные темы
Транспорт
- WebSockets, server-sent events и long polling
- Переподключение с backoff — чтобы после сбоя клиенты не обрушились на сервер разом
- Heartbeat и распознавание соединения, которое формально открыто, но мертво
- Уход в фон и ограничения операционной системы
Каналы и доставка
- Устройство каналов и топиков, подписка на минимально достаточную область
- Авторизация подписки — по тем же правилам, что и любое чтение
- Рассылка одного события множеству подписчиков
- Гарантии доставки: at most once, at least once — и что у вас на самом деле
Согласованность
- Догон после переподключения: воспроизвести пропущенное или перезапросить и заменить
- Порядок сообщений и сообщения, приходящие не по порядку
- Дедупликация сообщения, доставленного дважды
- Оптимистичные обновления и откат, когда сервер не согласен
Масштаб
- Сколько держит один сервер и что меняется, когда серверов два
- Состояние, которому нельзя жить внутри процесса
- Push-уведомления для случая, когда приложение не запущено вовсе
Уровни
| Уровень | Как это выглядит |
|---|---|
| Junior | Подписывается на поток и обновляет UI, когда приходит сообщение. |
| Middle | Обрабатывает переподключение, повторную подписку и догон пропущенного. Авторизует подписки и закрывает их за собой. |
| Senior | Проектирует гранулярность каналов и семантику доставки, рассуждает о порядке и дубликатах, удерживает корректность при нескольких инстансах. |
Практика
Для начала
-
Порвите соединение Отключите сеть посреди сессии и добейтесь, чтобы переподключение проходило без перезагрузки.
-
Сузьте подписку Замените широкую подписку на самую узкую из тех, при которых всё ещё работает.
-
Уберите за собой Убедитесь, что уход с экрана завершает его подписку.
Дальше
-
Догоните пропущенное Добейтесь, чтобы за две минуты отключения ничего не потерялось.
-
Обработайте дубликат Сделайте так, чтобы клиент оставался корректным, если одно и то же сообщение придёт дважды.
-
Обновите оптимистично Покажите изменение до подтверждения сервером и аккуратно откатитесь, если он его отклонит.
Проверьте себя
- Что потеряет ваше приложение, если соединение пропадёт на минуту?
- Кому разрешено подписываться на каждый канал и где это проверяется?
- Что будет, если одно и то же сообщение придёт дважды?
- Сколько открытых соединений держит один инстанс вашего сервера?
- Что делает приложение, когда возвращается из фона?
- Какие из ваших realtime-функций спокойно обошлись бы обновлением при открытии экрана?
Материалы
- WebSockets API — справочник MDN по протоколу и его жизненному циклу, включая коды закрытия: именно на них и строится вся логика переподключения.
- Serverpod: streams — realtime в Dart-backend: типизированные сообщения, подписки и авторизация.
- Designing Data-Intensive Applications — главы про потоки и порядок событий. Здешний словарь — at-least-once, идемпотентность, порядок событий — это ровно то, из чего состоят баги realtime.
- Idempotency keys — прямо применимо к обработке сообщений: единственный надёжный ответ на доставку at-least-once — обработчик, который не ломается от повторного запуска.
- Firebase Cloud Messaging — для случая, который realtime не покрывает: когда приложение не запущено. Стоит прочитать хотя бы ради раздела о гарантиях доставки.