Наверняка вы слышали мнение, что доставка exactly-once (или однократная доставка) — невозможна. Это одна из самых сложных систем в сфере распределенных приложений, а ее практическое использование стоит слишком дорого. Существуют и логические ограничения — и сегодня речь пойдет именно о них.
- Доказательство противопоставлением и две общие проблемы
- Прямое доказательство
- Если гарантировать доставку exactly-once невозможно, что делать?
- Доставка at-most-once
- Доставка at-least-once
- Доставка без гарантии
- Deus ex machina: обработка exactly-once
Это адаптированный перевод статьи The impossibility of exactly-once Савваса Клеантоуса, программиста и специалиста по архитектуре CQRS. Повествование ведётся от лица автора оригинала.
Начнем с определения. Доставка exactly-once — это подход, при котором сообщение может быть доставлено получателю строго один раз, без дублей и потери данных. Проблема в том, что в реальном мире гарантировать это невозможно.
Доказательство противопоставлением и две общие проблемы
Мы будем рассматривать проблему на примере задачи про двух генералов — мыслительного эксперимента, суть которого заключается в разработке алгоритма связи между двумя армиями, готовящимися атаковать один и тот же город. Противник находится строго между армиями, а атака только одной армией обречена на провал. Хотя организовать доставку сообщений между ними достаточно просто, гарантировать успех передачи данных — невозможно.
Семантика доставки exactly-once очень похожа на задачу про двух генералов. И в том, и в другом случае есть две стороны, которые пытаются о чем-то сообщить друг-другу. В этом процессе пакеты данных могут потеряться: как два генерала должны согласовать время атаки, так и два процесса должны связаться с друг другом.
Предположим, существует протокол, который гарантирует, что получатель точно получит сообщение — и это произойдет только один раз. Такой протокол мог бы решить проблему двух генералов — если это произойдет, первому генералу (отправителю) для доставки сообщения об атаке нужно будет только придерживаться протокола, чтобы второй генерал (получатель) получил информацию ровно один раз.
Прямое доказательство
Ниже я попытаюсь продемонстрировать, что гарантировать доставку сообщения ровно один, используя метод прямого доказательства, невозможно. Сначала уточним условия:
Отправитель и получатель работают в реальном мире, что означает ненулевое время транспортировки и обработки (и отсутствие строгой согласованности)
Отправитель и получатель не имеют доступа к внутреннему состоянию друг друга. Это означает, что получатель не знает о намерении отправить сообщение, если отправитель об этом не сообщил
Транспортировка сообщений ненадежна
Получатель обязательно должен выполнить обработку сообщения — то есть сообщение нельзя просто удалить или потерять
Операция ограничена по времени: обработка должна быть завершена в течение не бесконечного периода времени.
Пункты один и два исключают существование тривиального решения, которое основывалось бы на проверке внутреннего состояния получателя.
Транспортировка сообщений ненадежна, поэтому отправитель не может заранее знать, что сообщение доставлено: для проверки нужно одно или несколько подтверждающих сообщений. Кроме того, невозможно отличить ошибку доставки или задержки, которая возникает при обработке или из-за ненадежности сети, от глобальной ошибки в доставке. Получатель, в свою очередь, вынужден отправлять ответное сообщение только после обработки (или сохранения для обработки), — то есть подтверждение перед обработкой в данном случае не сработает. Если получатель найдет ошибку перед обработкой, он потеряет сообщение.
Отправитель не имеет доступа к состоянию получателя, поэтому не может знать причину потери сообщения: например, сбой мог произойти в процессе подготовки к отправке, либо получатель просто работает слишком медленно.
Тут уже отправитель встанет перед выбором: продублировать сообщение (и адресат получит его дважды) или не делать этого. Во втором случае получатель может не обработать сообщение, если решить, что проблема возникла в процессе планирования обработки.
Если гарантировать доставку exactly-once невозможно, что делать?
Результат эксперимента, описанного выше, стоит принять во внимание при проектировании распределенных систем. При отсутствии логических ошибок система будет доставлять сообщение только один раз. Но если они возникнут, она будет действовать по одному из двух сценариев:
Доставка at-most-once. Сообщение не доставляется вовсе или доставляется, но теряется до того, как у получателя произойдут какие-либо изменения состояния. В результате получатель останется в том же состоянии, в котором он был до получения сообщения;
Доставка at-least-once. Сообщение доставится как минимум один раз.
Доставка at-most-once
При доставке at-most-once мы либо успешно доставляем сообщение, либо не доставляем его вовсе. Это простая гарантия для реализации и поддержки, поскольку она требует минимальных усилий:
- Отправитель отправляет сообщение получателю. Оба они игнорируют любые ошибки и тайм-ауты, а отправитель не требует подтверждения
- Получатель также не делает ничего, либо подтверждает получение сообщение до того, как возникнут какие-либо проблемы.
Это означает, что доставка at-most-once — самый простой вариант гарантии доставки как для разработчиков, так и для проектировщиков системы. Если этот подход соответствует условиям задачи, которая перед вами стоит, выбирайте его.
Важно отметить, что в большинстве случаев требуется вести учет потерь при отправке сообщений. Это позволит сообщить о проблеме и даст возможность исправить ее вручную.
Доставка at-least-once
Хотя предыдущий подход проще, на практике чаще применяется доставка at-least-once. Вот несколько причин, почему это происходит:
- Пользовательский опыт улучшается: им не нужно повторять действие вручную, а единственное неудобство — небольшая задержка
- Потери пакетов сводятся к минимуму
- Изменения, которые необходимо внести, действительно вносятся
Доставка at-least-once не добавляет алгоритмической сложности, а для ее внедрения достаточно реализовать следующее:
- Отправитель ожидает подтверждение сообщения с учетом задержки. Если подтверждение не получено, доставка повторяется
- Получатель сначала обрабатывает сообщение, реагируя на любые ошибки, и только в случае успеха подтверждает сообщение
Часто описанные выше процессы обрабатываются на уровне инфраструктуры, чтобы результат был прозрачен на прикладном и доменном уровнях. Получая сообщение на уровне инфраструктуры, можно преодолеть проблемы интеграции, а затем передать его на уровень приложения, и уже там подтвердить получение.
Доставка без гарантии
Третья и самая простая альтернатива — доставка без гарантии. Проблема с таким подходом в отсутствии согласованности системы.
Deus ex machina: обработка exactly-once
Составьте свое первое резюме: Вы можете бесплатно опубликовать свое резюме в нашем сервисе «Хекслет-CV» и получить советы по его улучшению от разработчиков и HR-менеджеров
Как уже говорилось ранее, единственные подходящие варианты реализации доставки — либо потерять сообщение, либо доставить его более одного раза. В случае с доставкой at-least-once мы сильно рискуем доставить сообщение дважды, что может катастрофические последствия для системы. Неужели мы обречены?
Не совсем: хотя доставка exactly-once невозможна, но ведь обработка exactly-once вполне реализуема. Этот подход гарантирует, что мы обработаем сообщение только один раз — вне зависимости от того, сколько раз оно было отправлено. Реализовать его можно двумя способами:
- Дедупликация: удаление сообщений, если они получены более одного раза
- Идемпотентная обработка: прием сообщения более одного раза имеет такой же эффект, как и их применение ровно один раз
Никогда не останавливайтесь: В программировании говорят, что нужно постоянно учиться даже для того, чтобы просто находиться на месте. Развивайтесь с нами — на Хекслете есть сотни курсов по разработке на разных языках и технологиях