Надёжность соединения: реконнект, смена сети и плавная деградация

Автор: Николай СапуновОбновлено: август 202617 мин чтения
Содержание статьи +

Это инженерные рекомендации, а не юридическая консультация. Детали уточняйте у квалифицированного юриста.

Кратко

Клинический видеозвонок должен пережить плохую сеть. Если приём оборвался, пациент теряет доверие к сервису, врач – время, а 152-ФЗ и приказ ФСТЭК № 21 требуют защищать медданные не только от утечки, но и от блокирования, то есть обеспечивать их доступность. Главный инструмент восстановления – ICE restart (restartIce()): браузер находит новый сетевой путь и переводит на него звонок, пока по старому ещё идёт медиа, поэтому пациент видит короткую паузу, а не обрыв. Если сеть не пропала, а просто слабая, звонок должен терять качество постепенно и по плану: сначала хуже видео, затем отключаются лишние видеослои, потом остаётся только звук, а в крайнем случае разговор переходит на телефон. В статье – машина состояний, код реконнекта, лестница деградации и резервные каналы: всё то, чем клинический продукт отличается от демоверсии.

Зачем это читать

Если вы решаете, строить телемед-платформу или покупать готовую, надёжность – как раз то, чего не видно на демонстрации и без чего не обойтись в продакшене. Демо проходит на офисном Wi-Fi, а реальные приёмы – на старом ADSL у пожилой пациентки, на мобильном интернете в машине и в больничной сети, где закрыта половина сайтов. Пока механизмы, удерживающие звонок в таких условиях, работают, их никто не замечает, а без них всё рассыпается: застывшая картинка во время сверки лекарств или оценки признаков инсульта – уже угроза безопасности пациента, а не мелкое неудобство. Статья написана для основателя, продакт-менеджера или руководителя клинической ИТ-службы, которому нужно задать вендору или инженеру правильные вопросы: что произойдёт, когда пациент выйдет из зоны Wi-Fi? Как быстро восстановится звонок? Что пациент увидит, пока идёт восстановление? Если внятного ответа нет, вы нашли слабое место.

Надёжность – требование защиты данных, а не только вопрос UX

Большинство команд относят надёжность звонка к пользовательскому опыту: меньше обращений в поддержку, довольнее пациенты. Это так, но цена вопроса выше. 152-ФЗ требует от оператора защищать персональные данные от неправомерного или случайного доступа, уничтожения, изменения, блокирования, копирования и распространения (ст. 19 ч. 1), а блокирование закон определяет как временное прекращение обработки (ст. 3). Сведения о здоровье конкретного человека относятся к специальной категории ПДн, и живой приём – это те же медданные, только передаваемые в реальном времени. В приказе ФСТЭК № 21 обеспечению доступности персональных данных посвящена отдельная группа мер, а ст. 19 152-ФЗ требует ещё и восстанавливать данные после инцидентов. Платформа, которая теряет звонки при обычных сетевых сбоях, не обеспечивает доступность данных, а это уже вопрос не только UX. Подробно об этих мерах – в статье о требованиях к защите ПДн в 2026 году.

У того же требования есть и клиническая сторона. Приказ Минздрава 193н требует проводить консультацию в экстренной форме в срок от 30 минут до 2 часов с момента поступления запроса, в неотложной – от 3 до 24 часов (п. 19), и время идёт независимо от того, работает ли ваша платформа. Если же вы обрабатываете данные по поручению клиники, доступность закреплена ещё и в SLA вашего договора. Поэтому работу над надёжностью стоит считать частью комплаенса: когда безопасник клиники или проверяющий спросит, как система сохраняет доступность медданных при сбое сети, «ICE restart плюс переход на звук и телефон» будет содержательным ответом.

Дальше будем исходить из этого: реконнект – мера обеспечения доступности, и на него стоит заложить время и бюджет.

Почему связь рвётся посреди приёма

Сеть может отказать по-разному, и способ восстановления зависит от типа отказа. Четыре сценария описывают почти всё, что встречается в телемедицине.

Первый – тайм-аут NAT. NAT (Network Address Translation, трансляция сетевых адресов) позволяет домашнему роутеру делить один публичный адрес между многими устройствами. Для этого роутер хранит временные привязки: какое соединение принадлежит какому внутреннему устройству. Если по соединению какое-то время ничего не передаётся, привязка истекает. В тихий момент приёма, когда врач читает карту и все молчат, она может пропасть, и медиапоток обрывается без всяких ошибок, хотя интернет у обеих сторон работает.

Второй – смена сети. Пациент выходит из клиники или из дома на улицу, телефон теряет Wi-Fi и переключается на мобильную сеть. Устройство получает новый IP-адрес, и все медиапути, привязанные к старому адресу, сразу перестают работать.

Третий – перегрузка и потери пакетов: сеть работает, но полный поток не вытягивает. Кто-то в квартире смотрит видео в 4K, базовая станция перегружена, DSL-линия теряет качество в дождь. Здесь звонок не должен обрываться: ему нужно снижать качество, об этом – в разделе о деградации.

Четвёртый – полный обрыв: устройство совсем теряет связь. Тоннель, лифт, зависшая точка доступа, закрытая крышка ноутбука. Пока связь не вернётся, восстанавливать нечего: задача в том, чтобы дождаться её и аккуратно вернуть пациента в звонок.

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

Машина состояний: как заметить, что начались проблемы

Прежде чем восстанавливать звонок, код должен заметить, что со связью что-то не так. WebRTC (Web Real-Time Communication – браузерная технология для видео- и аудиосвязи в реальном времени) сообщает о состоянии соединения через два связанных свойства объекта RTCPeerConnection: iceConnectionState и более общее connectionState. По ним приложение и узнаёт, что происходит в сети.

Важных состояний немного. connected и completed: медиа идёт, всё в порядке. disconnected: связь прервалась, но может восстановиться сама – при коротком сбое, при NAT-привязке, которая ещё может обновиться, при мгновенной потере пакетов. failed: связь потеряна и сама не восстановится, пора вмешиваться. closed: соединение закрыто окончательно.

Рисунок 1. Машина состояний. В `disconnected` ждём и наблюдаем, в `failed` действуем сразу. Вся логика реконнекта строится на этих переходах.

На практике реакцию делают двухступенчатой. Когда соединение перешло в disconnected, не спешите: запустите короткий таймер на несколько секунд и покажите пациенту ненавязчивый индикатор «Переподключение…». Многие такие сбои проходят сами, прежде чем таймер сработает. Если соединение дошло до failed или таймер истёк, а связь не вернулась, запускайте активное восстановление. Две ступени нужны потому, что восстановление не бесплатно: оно добавляет сигнальный трафик и само может ненадолго сбить картинку, так что запускать его стоит, только когда ожидание не помогло. Некоторые команды сознательно начинают восстановление уже на disconnected, не дожидаясь failed: немного лишнего сигнального трафика в обмен на более быстрый возврат. В клинике, где застывший звонок тревожит и врача, и пациента, обычно лучше выбрать быстрое восстановление.

// Двухступенчатое восстановление: сначала наблюдаем, потом действуем.
let recoveryTimer = null;

pc.addEventListener("connectionstatechange", () => {
  if (pc.connectionState === "disconnected") {
    showPatientNotice("Переподключение…");
    recoveryTimer = setTimeout(() => attemptRepair(pc), 3000);
  }
  if (pc.connectionState === "connected") {
    clearTimeout(recoveryTimer);
    hidePatientNotice();
  }
  if (pc.connectionState === "failed") {
    clearTimeout(recoveryTimer);
    attemptRepair(pc); // действуем сразу
  }
});

Одно правило избавит от целого класса ошибок: не пишите в незащищённое хранилище логов сырые данные о соединении, IP-адреса и любые метаданные сессии, которые можно связать с пациентом. События реконнекта хочется логировать подробно, но IP-адрес вместе с учётной записью пациента – уже персональные данные, а в контексте приёма – медданные. Храните такие логи в защищённом контуре, как любые другие данные пациента: в России, с контролем доступа и записью в журнал аудита.

ICE restart: главный способ восстановления

Если медиапуть перестал работать, звонок почти никогда не нужно сбрасывать и начинать заново. Достаточно найти новый путь и перевести звонок на него. Этот приём называется ICE restart, и в клиническом WebRTC это главный механизм надёжности.

Сначала коротко о терминах. ICE (Interactive Connectivity Establishment) – процедура, с помощью которой WebRTC в начале звонка перебирает возможные маршруты между устройствами (напрямую, через домашний роутер или через сервер-ретранслятор) и выбирает рабочий. ICE restart запускает этот поиск заново посреди звонка, чтобы найти маршрут взамен сломавшегося. Механизм описан в стандарте IETF RFC 8445, а в браузере вызывается одним методом – RTCPeerConnection.restartIce(), который с апреля 2021 года поддерживают все основные браузеры.

ICE restart подходит для этой задачи по двум причинам. Во-первых, медиа продолжает идти во время перезапуска: браузер держит старый путь открытым, пока не готов новый, и только потом переключается, поэтому пациент замечает короткую паузу, а не обрыв. Во-вторых, перезапуск обходится дёшево: меняются только сетевые учётные данные и маршруты-кандидаты. Ключи шифрования (DTLS-SRTP, которым защищено медиа) и согласованные кодеки сохраняются, так что не нужно ни заново проходить полное рукопожатие, ни пересогласовывать параметры звонка. Поэтому восстановление занимает секунду-две, а не несколько секунд, как при полной пересборке.

Рисунок 2. Переход с Wi-Fi на мобильную сеть через ICE restart. Медиа идёт по старому пути, пока новый не проверен, затем звонок переключается, а старый путь закрывается. Пациент видит короткую паузу, а не оборванный приём.

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

function attemptRepair(pc) {
  // restartIce() запускает повторный сбор сетевых маршрутов у обеих сторон.
  // Перезапуск уйдёт со следующим offer, который отправит ваш сигналинг.
  pc.restartIce();
  // Затем обработчик negotiationneeded отправляет новый offer
  // по сигнальному каналу и завершает перезапуск.
}

Сам вызов restartIce() ничего не отправляет другому устройству. Он лишь помечает соединение, чтобы следующий offer, отправленный по сигнальному каналу, содержал новые ICE-учётные данные. Значит, ICE restart работает, только пока жив сигнальный канал. Если он тоже упал (например, WebSocket оборвался одновременно с медиа), сначала переподключите сигналинг, затем перезапускайте ICE. К надёжности сигналинга вернёмся ниже.

Медиасервер – частая ловушка. В звонке один на один два браузера договариваются о перезапуске напрямую. Но большинство клинических платформ пропускает медиа через сервер – SFU (Selective Forwarding Unit), который раздаёт поток каждого участника остальным; он нужен, как только участников трое или требуется запись. Чтобы ICE restart работал через SFU, его должен поддерживать сам SFU. Одни медиасерверы, в том числе open-source, делают это хорошо, другие, особенно построенные вокруг SIP, – нет. Если вы покупаете видео-API или выбираете SFU, вопрос «поддерживает ли сервер ICE restart для восстановления посреди звонка?» должен войти в требования к закупке, а не остаться в сноске. Устройство протоколов ICE, STUN и TURN разобрано в разделе о видеостриминге, здесь повторять не будем; см. NAT, STUN, TURN и ICE простыми словами.

Лестница эскалации: от ICE restart до полной пересборки

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

Первая ступень – сам ICE restart: секунда-две, медиа не прерывается, пациент почти ничего не замечает. Если перезапуск не сработал (например, потому что сеть у устройства ещё не поднялась), вторая ступень – пересоздать RTCPeerConnection: удалить зависший объект соединения и создать новый. Это медленнее (заново рукопожатие и согласование), пауза для пациента длиннее, зато так можно выйти из состояний, с которыми ICE restart не справляется. Третья ступень – полный перезаход клиента: приложение пациента переподключает сигналинг, заново входит в комнату приёма и заново поднимает медиа. Это тот же вход, только автоматический и с восстановленным состоянием клинической сессии, чтобы никому не пришлось ничего вводить снова. Последняя ступень нужна, когда у устройства просто нет связи: ждать и возобновить. Сессия остаётся открытой на сервере, пациент видит понятное сообщение «Ожидаем переподключения…», а когда связь вернётся, приложение само войдёт обратно.

Рисунок 3. Дерево решений реконнекта. Первым идёт самый дешёвый шаг – ICE restart; каждый следующий запускается, только если предыдущий не помог.
Шаг восстановленияТипичное времяНепрерывность медиаКогда применять
ICE restart (restartIce())~1–2 сСохраняется при переключенииПуть сменился или перестал работать; сигналинг жив
Пересоздать RTCPeerConnection~3–6 сКороткий чёрный экранICE restart не помог; объект завис
Полный перезаход клиента~5–10 сЧёрный экран, состояние восстановленоСигналинг упал; нужно перезайти в комнату
Ждать и возобновитьДо возврата сетиПаузаПолный обрыв: восстанавливать нечего

Таблица 1. Лестница восстановления. Начинайте с самой дешёвой ступени, которая может устранить отказ, и переходите к следующей, только если она не сработала.

Посчитаем, окупается ли эта работа. Пусть платформа проводит 1 000 приёмов в день, и без логики восстановления в 4% из них сетевой сбой обрывает звонок. Это 40 сорванных приёмов в день: 40 врачей ждут, 40 пациентов записываются заново. Допустим, ICE restart незаметно спасает 80% таких звонков: 40 × 0,80 = 32, остаётся 8. Ступени пересоздания и перезахода вернут почти весь остаток. Оборвавшийся звонок превращается в обращение в поддержку, а двухсекундную паузу никто и не вспомнит.

Плавная деградация: уступить, но не сломаться

Полная потеря пути – лишь один вид отказа. Чаще бывает менее заметный: сеть работает, но полный поток не вытягивает. Здесь звонок нужно удержать, то есть плавно деградировать: снижать качество по заранее заданному плану, а не зависать и не отключаться.

Главный принцип понятен и врачу, и пациенту: звук важнее видео. Приём можно продолжать с застывшей или размытой картинкой, пока собеседники хорошо слышат друг друга, а с пропадающим звуком – нельзя. Поэтому при любом снижении качества в первую очередь защищают звук, а жертвуют видео.

Рисунок 4. Лестница деградации. Чем хуже канал, тем больше звонок отдаёт: сначала качество видео, затем слои, затем само видео, а звук сохраняет до последнего. Когда WebRTC уже не справляется, звонок переходит на телефон, а не обрывается.

Переходом между ступенями управляет оценка доступной полосы. WebRTC постоянно пересчитывает её сам по обратной связи от получателя (механизмы управления перегрузкой TWCC и REMB; как они работают, рассказано в разделе о видеостриминге, см. оценка полосы в WebRTC). По мере того как оценка падает:

Сначала снижаются разрешение и частота кадров: кодер выдаёт картинку поменьше. Приоритет задаёт настройка degradationPreference: maintain-framerate сохраняет плавность движения за счёт разрешения (подходит, когда пациент двигается), maintain-resolution сохраняет детализацию за счёт частоты кадров (подходит для осмотра кожи или раны). Что выбрать, зависит от клинической задачи, и это полноценное продуктовое решение; подробнее – в статье о клинической планке «достаточно хорошо».

Затем медиасервер отключает лишние видеослои. В современных клинических системах клиент отправляет свой поток сразу в нескольких вариантах качества (simulcast) или одним многослойным потоком (SVC, Scalable Video Coding). Когда сеть у получателя слабеет, SFU просто отдаёт этому участнику слой пониже, не трогая качество у остальных. Подробности – в статье о simulcast и SVC.

Дальше видео ставится на паузу, остаётся только звук. Если даже небольшой видеопоток мешает звуку, перестаньте отправлять видео и прямо скажите об этом пациенту («Видео приостановлено, чтобы звук был чистым»): приём при этом продолжается. Чтобы режим «только звук» работал и на плохой линии, нужна хорошая обработка звука: эхоподавление, шумоподавление, jitter-буфер, который выравнивает неравномерное поступление пакетов. Об этом – в разделе Аудио для видео.

Наконец, когда WebRTC не вытягивает даже звук, разговор переходит на телефонную линию. Это последняя ступень лестницы, и о ней стоит рассказать отдельно.

Переход на телефон: когда интернета не хватает

У некоторых пациентов интернет-звонок не работает вообще: в сёлах и на трассах есть зоны без мобильного интернета, публичный Wi-Fi бывает перегружен, устройство может оказаться слишком старым. Если платформе нечего им предложить, консультация просто не состоится. Последнее средство – самая старая из сетей, телефонная.

Голосовой режим можно обеспечить двумя способами. Проще – оставить только звук в том же WebRTC-соединении (это третья ступень лестницы). Надёжнее – полноценный дозвон через телефонную сеть общего пользования (PSTN dial-in): платформа подключает приём к обычной телефонной сети через SIP-шлюз и транк оператора связи, и пациент входит в консультацию, набрав номер с любого телефона, без приложения и без интернета. Архитектурно шлюз медиасервера связывает WebRTC-звонок с обычным телефонным, а врач остаётся в том же веб-интерфейсе приёма.

Телефонный дозвон продолжает ту же консультацию, а не открывает новую в обход правил телемедицины. Пациент уже вошёл через ЕСИА в начале сессии (ч. 6 ст. 36.2 323-ФЗ). Привяжите дозвон к этой сессии одноразовым PIN-кодом, чтобы идентификация, журнал и будущий протокол относились к ней же, а заключение врач, как обычно, оформит и подпишет УКЭП (ч. 7). Если сессия записывается, аудиозапись считается сопутствующим материалом консультации и по 193н хранится один год (п. 49–51). Допустим ли формат «только голос» для конкретного вида консультации и как он оплачивается (по ОМС, ДМС или как платная услуга), уточняйте у юриста и в договоре со страховщиком; варианты оплаты разобраны в статье оплата телемедицины: ОМС, ДМС, платные услуги. Проектируйте переход на телефон как полноценный сценарий, а не как вынужденную уступку.

Надёжность сигналинга: канал, о котором забывают

Всё описанное выше молчаливо предполагает, что сигнальный канал работает. Сигналинг – отдельное соединение (обычно WebSocket), через которое приложение настраивает звонок и управляет им: обменивается offer/answer, передаёт сообщения для ICE restart, отслеживает, кто находится в комнате. Медиа и сигналинг – два разных соединения, и отказать они могут независимо друг от друга.

Отсюда неприятная ситуация: тот же сбой, что оборвал медиапуть, часто обрывает и WebSocket. Если логика восстановления вызывает restartIce(), а сигнальный канал недоступен, offer с перезапуском некуда отправить, и восстановление тихо проваливается. Поэтому порядок такой: сначала переподключите сигналинг, затем перезапустите ICE. Сигнальный клиент должен переподключаться автоматически с экспоненциальной задержкой (сначала часто, потом всё реже, чтобы не добивать и без того перегруженный сервер), а после переподключения заново проходить авторизацию и входить в комнату приёма. Повторный вход должен быть идемпотентным: если пациент уже «числится» в комнате, ещё один вход ничего не должен ломать, ведь при нестабильной связи клиент может попытаться войти несколько раз.

Поэтому стоит продумать возобновление сессии. Храните состояние клинической сессии (кто участвует в приёме, что показано на экране, переписку в чате звонка) на сервере, а не только в браузере пациента. Тогда после переподключения комната восстанавливается в прежнем виде: пациент, у которого телефон на полминуты ушёл в спящий режим, возвращается в тот же приём, а не в пустоту.

Частые ошибки

В надёжности соединения много ловушек: на демонстрации всё работает, а в продакшене ломается. Чаще всего встречаются такие.

Пересборка звонка при любом сбое. Получив disconnected, приложение уничтожает RTCPeerConnection и собирает соединение с нуля. Сохранённые ключи шифрования теряются, а двухсекундное восстановление через ICE restart превращается в шестисекундный чёрный экран. Сначала вызывайте restartIce(), а пересобирайте соединение, только если он не помог.

disconnected принимают за failed. Состояние disconnected часто проходит само, а мгновенная реакция на каждое такое событие лишь добавляет сбоев. Используйте двухступенчатую схему с таймером.

Забыли про SFU. На клиенте ICE restart реализован безупречно, а медиасервер его не поддерживает. Клиент запускает перезапуск, сервер на него не отвечает, и восстановление не срабатывает как раз в групповых клинических сессиях, где оно важнее всего. Проверьте поддержку на стороне сервера до релиза.

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

Медданные в логах реконнекта. Подробные логи реконнекта с идентификаторами, IP-адресами или содержимым сессии уходят в незащищённое хранилище или в зарубежный сервис логирования. Такие логи – медданные: храните их в защищённом контуре и в России.

Где здесь Фора Софт

Потребовать надёжности легко, обеспечить её трудно, и этот опыт достался нам не сразу. Фора Софт разрабатывает видеосвязь реального времени на WebRTC с тех пор, как технология только появилась: видеоконференции, e-learning, стриминг и телемедицина, где застывший звонок – клиническое событие, а не мелкий сбой. Реконнект, смену сети и плавную деградацию мы проектируем так же тщательно, как основные функции: запускаем ICE restart на нужных переходах состояний, проверяем поддержку на стороне медиасервера, строим лестницу деградации вплоть до телефонного дозвона, привязанного к сессии с ЕСИА, и храним все логи реконнекта в защищённом контуре на российской инфраструктуре. Когда доступность данных пациента – одновременно обещание продукта и обязанность по 152-ФЗ, демоверсию от клинического продукта отличает как раз этот слой.

Главное

  • Надёжность – требование защиты медданных от блокирования (ст. 19 152-ФЗ, меры доступности приказа ФСТЭК № 21), а не только вопрос UX.
  • Следите за connectionState: в disconnected ждите и наблюдайте, в failed действуйте сразу.
  • ICE restart (restartIce()) устраняет большинство отказов сетевого пути за 1–2 с, не прерывая медиа.
  • Для групповых клинических звонков SFU должен поддерживать ICE restart: проверьте это до покупки.
  • Снижайте качество по порядку: разрешение, слои, пауза видео, только звук. Звук берегите до последнего.
  • Сделайте телефонный (PSTN) дозвон, привязанный к той же сессии с ЕСИА; допустимость формата «только голос» и его оплату сверьте с юристом.

Что читать дальше

Строите такую систему?

Подберём параметры кодирования под ваш контент и посчитаем стоимость доставки до старта разработки.