Курс «Архитектура мессенджера» · Часть 2 · Соединение и доставка

Раздел 6. Гарантии доставки и порядок

At-least-once против exactly-once на практике; идемпотентность и дедупликация по messageid; упорядочивание сообщений; откуда берутся повторы.

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

Откуда берутся повторы

Начнём с неочевидного: почему вообще появляются дубликаты. Причина — сеть ненадёжна. Сообщение или подтверждение к нему может потеряться по дороге.

Чтобы не потерять сообщение, отправитель (или сервер) поступает логично: повторяет отправку, если не дождался подтверждения. Но вот ловушка: подтверждение могло потеряться уже после того, как сообщение дошло. Тогда оригинал на месте, а отправитель, не зная этого, шлёт его заново — и получается дубликат.

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

Три уровня гарантий

Есть три классических варианта, и у каждого свой компромисс:

  • «Максимум один раз» (at-most-once): отправили и не повторяем. Дублей не будет, но можем потерять. Для мессенджера не годится.
  • «Хотя бы один раз» (at-least-once): повторяем, пока не придёт подтверждение. Не теряем — но возможны дубликаты. Это практичный выбор.
  • «Ровно один раз» (exactly-once): идеал — ни потерь, ни дублей.

Важная мысль для интервью: честного «ровно один раз» на ненадёжной сети не существует. Его не делают напрямую — его эмулируют: берут «хотя бы один раз» (чтобы не терять) и добавляют отбрасывание повторов на приёме (чтобы не двоить). В сумме получается эффект «ровно один раз». Если на интервью спросят, что выбрать, — правильный ответ примерно такой: «at-least-once плюс защита от повторов».

Как убирают дубликаты: идемпотентность и номера

«Защита от повторов» работает так. Каждому сообщению отправитель присваивает уникальный номер (его называют message_id). Важно, что номер генерируется на устройстве отправителя — тогда повтор несёт тот же номер, что и оригинал.

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

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

Порядок сообщений

Третья задача — чтобы Боб увидел сообщения в той же последовательности, в какой Алиса их отправила. Порядок может сломаться из-за тех же повторов, переотправок и того, что сообщения иногда идут разными путями.

Главная ошибка, которую тут можно допустить, — полагаться на время. Часы на разных серверах слегка расходятся, и «отсортировать по времени» — ненадёжно. Сортировать по «времени прихода на устройство» тоже нельзя: оно зависит от того, что когда долетело.

Надёжное решение: порядковый номер в рамках чата. Каждому сообщению внутри конкретного чата присваивают возрастающий номер (его называют sequence number), и устройство сортирует сообщения по этому номеру, а не по времени.

Два важных момента:

  • Номер выдаёт Message-сервис при сохранении — это та самая ценность «единой точки логики» из Раздела 4: раз всё проходит через него, он может выдавать последовательные номера.
  • Порядок нужен внутри чата, а не глобально по всему миру. Это сильно упрощает задачу: не нужен один общий счётчик на все чаты планеты — достаточно своей нумерации в каждом чате.

Как всё складывается вместе

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

Вопросы интервьюера (с разбором)

«Как гарантируешь, что сообщение не потеряется и не задвоится?» Образец: «Не потеряем за счёт двух вещей: сначала надёжно сохраняем сообщение, а доставку ведём по принципу „хотя бы один раз“ — повторяем, пока не придёт подтверждение. Повторы неизбежно дают дубликаты, поэтому добавляем дедупликацию: каждому сообщению — уникальный номер, сгенерированный на устройстве отправителя; приёмник запоминает виденные номера и отбрасывает повторы. Доставка становится идемпотентной.»

«Как обеспечишь правильный порядок?» Образец: «Не по времени — часы серверов расходятся. Вместо этого Message-сервис при сохранении выдаёт каждому сообщению возрастающий порядковый номер в рамках конкретного чата, и устройство сортирует по нему. Порядок нужен внутри чата, а не глобально, поэтому единый мировой счётчик не требуется.»

«Что выберешь — at-least-once или exactly-once, и какой ценой?» Образец: «Честный exactly-once на ненадёжной сети недостижим, поэтому на практике берут at-least-once и сверху накручивают защиту от повторов по уникальным номерам — это даёт эффект exactly-once. Цена — нужно хранить, какие номера уже видели, и платить за дедупликацию, но это дёшево по сравнению с риском потерь или дублей.»


#мессенджер #гарантии-доставки #идемпотентность #дедупликация #порядок-сообщений #подготовка-к-интервью