2026 г.
Курс «Распределённые системы». Глава 1. Модель системы
Цели главы. Договориться о языке, на котором будет говорить весь курс: что такое процесс, сообщение и событие; какими бывают отказы и модели времени; почему асинхронная модель с отказами — честная модель интернета. В конце — строгий разбор «заблуждений распределённых вычислений» и первые следствия, которые можно вывести из одной только модели, не написав ни строчки кода: например, что семантика «ровно один раз» для удалённого вызова недостижима в принципе.
1.1. Процессы, каналы, события
Распределённая система в нашей модели — это набор процессов (узлов) p1, …, pn, каждый со своим локальным состоянием и памятью, взаимодействующих только обменом сообщениями по каналам. Общей памяти нет; общих часов нет; всё, что процесс знает о других, он узнал из сообщений. Исполнение системы — последовательность событий трёх видов: отправка сообщения, получение сообщения, внутреннее событие (локальное вычисление). Эта скупая модель покрывает всё: микросервисы, реплики СУБД, узлы Kafka, мобильный клиент с сервером — и потому выводы из неё универсальны.
Важное методологическое замечание. Модель — это контракт с реальностью: алгоритм, доказанный в модели, корректен ровно настолько, насколько реальность соответствует её допущениям. Половина инцидентов в распределённых системах — это реальность, нарушившая допущение, о котором проектировщик не знал, что он его сделал. Поэтому первую главу курса мы тратим на то, чтобы все допущения назвать по имени.
1.2. Модели каналов
Что канал может сделать с сообщением? Перечислим по нарастающей враждебности:
- Надёжный канал с сохранением порядка (FIFO): всё доставляется, по одному разу, в порядке отправки. Такой канал даёт TCP-соединение — пока оно живо.
- Надёжный без порядка: доставляется всё, но в произвольном порядке.
- Канал с потерями (fair-loss): отдельное сообщение может потеряться, но сообщение, отправляемое бесконечно много раз, будет доставлено бесконечно много раз; канал не создаёт сообщения из ничего. Это удобная абстракция IP-сети при длительно сохраняющейся связности.
- Произвольный канал: потери, дублирование, переупорядочение, задержка на неограниченный срок.
Ловушка, которую надо увидеть в упор: TCP даёт FIFO-надёжность внутри соединения, но соединение рвётся, и после переустановки отправитель не знает, что из «отправленного» было доставлено и обработано. На уровне запросов приложения даже поверх TCP канал — с потерями и дублями (повторы!). Именно поэтому канальная надёжность не отменяет ни идемпотентности, ни всего, что построено на ней (см. статью «Надёжность поверх ненадёжной сети»).
1.3. Виды отказов
Иерархия отказов процесса, от безобидного к худшему:
- Отказ-остановка (crash-stop): процесс молча останавливается навсегда. Простейшая модель.
- Отказ-восстановление (crash-recovery): процесс падает и возвращается, сохранив (или потеряв) часть состояния на диске. Модель реального рестарта; появляется различие энергозависимого и стабильного хранилища.
- Пропуски (omission): процесс жив, но часть сообщений не отправляет или не принимает (переполненные буферы, паузы GC, перегрузка). Коварство в том, что снаружи неотличимо от медленной сети.
- Византийские отказы: процесс ведёт себя произвольно, в том числе злонамеренно — лжёт, шлёт разным адресатам разное. Актуально при недоверенных участниках (блокчейны, межорганизационные системы); в пределах одного ЦОДа обычно принимают модель crash-recovery, и главы 5–6 живут в ней; византийскому случаю посвящена глава 7.
Ключевое отличие от одиночного компьютера — отказы частичны: в любой момент какое-то подмножество узлов и каналов отказало, а остальные работают, и ни один узел не имеет достоверного глобального знания, кто именно жив. При этом отказы вовсе не обязаны быть независимыми: одна авария питания, ошибка конфигурации или потеря зоны доступности может одновременно затронуть множество компонентов. Проектирование распределённой системы — это проектирование поведения при каждом реалистичном, в том числе коррелированном, наборе отказов.
1.4. Модели времени: синхронная, асинхронная, частично синхронная
Три модели предположений о времени:
- Синхронная: известны верхние границы задержки сообщений и относительной скорости процессов. Роскошная модель: тайм-аут в ней — совершенный детектор отказа (не ответил за границу — точно мёртв). В интернете границы задержки не существует, поэтому модель применима разве что к специализированным шинам.
- Асинхронная: никаких границ нет — сообщение может идти сколь угодно долго, процесс может думать сколь угодно медленно. Честная модель худшего случая: пауза сборщика мусора на 40 секунд, ретрансмиссии, перегруженный канал — всё это законные исполнения. Плата за честность — суровые теоремы невозможности (глава 3).
- Частично синхронная: границы существуют, но неизвестны, либо начинают выполняться после неизвестного момента стабилизации. Модель «интернет обычно приличен, но иногда сходит с ума на неизвестный срок» — именно в ней доказывается живость (liveness) Raft и его родни.
Следствие, которое стоит выучить наизусть: в асинхронной модели тайм-аут не доказывает отказ. Истёкший тайм-аут означает «ответ не пришёл вовремя» — узел мог умереть, задуматься или быть отрезанным; все три случая неотличимы для наблюдателя. Половина алгоритмической изощрённости курса (кворумы, термы, эпохи) — это техника безопасного принятия решений на основе недоказуемых подозрений. Формализация этой идеи — детекторы отказов — появится в главе 3.
1.5. Заблуждения распределённых вычислений — построчно
Список Питера Дойча и коллег (Sun Microsystems, 1990-е) обычно цитируют как фольклор; пройдём по нему как по перечню нарушаемых допущений модели, с указанием, какая часть курса отвечает за последствия.
- «Сеть надёжна» — ложно: канал с потерями (1.2). Ответ: повторы, идемпотентность, подтверждения (статья о надёжности; глава 10).
- «Задержка равна нулю» — ложно: асинхронная модель (1.4). Ответ: проектирование под round-trip, батчинг, локальность данных (главы 5, 8).
- «Полоса бесконечна» — ложно. Ответ: дельта-синхронизация, сжатие, разумный размер сообщений.
- «Сеть безопасна» — ложно. Ответ: шифрование и аутентификация каналов — вне рамок курса (см. раздел «Безопасность»), но допущение назвать обязаны.
- «Топология неизменна» — ложно: узлы приходят и уходят (1.3). Ответ: обнаружение сервисов, членство в кластере (главы 6, 8).
- «Администратор один» — ложно: чужие сервисы, чужие сети, чужие регламенты. Ответ: тайм-ауты и предохранители на каждой границе доверия.
- «Стоимость передачи равна нулю» — ложно: и в деньгах (межзоновый трафик облаков тарифицируется), и во времени сериализации.
- «Сеть однородна» — ложно: от 10G внутри стойки до GPRS в лифте. Ответ: часть II статьи о надёжности.
1.6. Первые следствия: семантики удалённого вызова
Уже введённой модели достаточно для первого содержательного результата. Рассмотрим удалённый вызов (RPC) по каналу с потерями. Клиент послал запрос и не получил ответ за тайм-аут. Возможны три мира: потерялся запрос (операция не выполнялась), упал сервер после выполнения (выполнена), потерялся ответ (выполнена). Клиент их не различает — см. 1.4. На уровне попыток доставки и наблюдаемого эффекта обычно различают:
- Не более одного раза (at-most-once): операция даёт эффект 0 или 1 раз. Простейший вариант — не повторять; практический вариант допускает повторы с идентификатором запроса, пока сервер надёжно помнит обработанные идентификаторы.
- Хотя бы один раз (at-least-once): при доступном сервере и fair-loss-канале повторять до подтверждения. Операция даст эффект один или несколько раз, если получатель не подавляет дубли.
«Ровно один раз» как свойство самой ненадёжной доставки недостижимо: отправитель не может одновременно получить абсолютную уверенность в доставке и исключить повтор (формальная граница — задача двух генералов в главе 3). Но эффект «ровно один раз» в ограниченной системе реализуем: at-least-once-доставка сочетается с долговечной дедупликацией, а запись идентификатора и бизнес-операция выполняются атомарно. Гарантия действует ровно в пределах хранилища, срока дедупликации и транзакционной границы; внешний побочный эффект снова возвращает неопределённость. Этот механизм мы разберём на Kafka и outbox в главах 9–10.
Итоги главы
- Система = процессы + каналы + события; никакой общей памяти и общих часов; всё знание — из сообщений.
- Каналы теряют, дублируют и переупорядочивают; TCP спасает только внутри соединения, на уровне приложения допущения надёжности незаконны.
- Отказы частичны и могут быть коррелированы. Crash-stop, crash-recovery, пропуски и византийское поведение — разные модели с разными допущениями, а не безусловная вероятностная шкала; основная часть курса живёт в crash-recovery, глава 7 — в византийской модели.
- Интернет асинхронен: тайм-аут — подозрение, а не доказательство; алгоритмы обязаны быть корректными при ложных подозрениях.
- Из модели немедленно следует: «ровно один раз» не является свойством ненадёжного канала; ограниченный эффект «ровно один раз» строится поверх повторов, долговечной дедупликации и атомарной записи результата.
Упражнения
- Клиент послал запрос «создать заказ» и получил сетевую ошибку. Перечислите все возможные состояния сервера и покажите, что никакая дополнительная информация, доступная клиенту в этот момент, их не различает. Какие два безопасных действия у него остаются и какой семантике соответствует каждое?
- Сервис A вызывает B, B вызывает C; доступность каждого — 99,9% (независимо), сетевых потерь нет. Какова доступность ответа A? Обобщите на цепочку из k сервисов и посчитайте, сколько «девяток» останется у цепочки из 30 микросервисов по 99,9%.
- Процесс завис в паузе сборщика мусора на 50 секунд, затем продолжил работу. К какому виду отказов из 1.3 это ближе всего с точки зрения остальных узлов? Придумайте сценарий, в котором «воскресший» узел делает опасное действие, полагая себя лидером (мы вернёмся к этому сценарию в главе 6 под именем fencing).
- Покажите, что в асинхронной модели никакой протокол «пинг раз в секунду, три пропуска — узел мёртв» не является корректным детектором отказов: постройте исполнение, где живой узел объявлен мёртвым, и исполнение, где мёртвый долго числится живым. Почему первый род ошибок опаснее для алгоритмов с одним лидером?
- Технолог предлагает добиться exactly-once так: сервер пишет результат в БД и отправляет ответ в одной локальной транзакции. Найдите изъян: какое допущение о канале «ответ → клиент» здесь молча сделано?
- Классифицируйте по моделям 1.2–1.4: (а) два контейнера на одном хосте через localhost; (б) сервисы в одном ЦОДе; (в) мобильный клиент и сервер; (г) узлы в разных юрисдикциях с недоверенными операторами. Для каждого — реалистичная модель канала, отказов и времени.
Ответы и указания. 1: запрос мог не дойти, выполняться, завершиться с потерянным ответом или завершиться с ответом, ещё находящимся в сети; локальное состояние клиента после сетевой ошибки одинаково. Не повторять — риск пропуска, повторять без защиты — риск дубля, повторять с тем же ключом идемпотентности — безопасный эффект при сохранной таблице ключей. 2: 0,9993 ≈ 99,7%; для 30 сервисов 0,99930 ≈ 97% — глубина синхронной цепочки является бюджетом доступности. 3: с точки зрения других это временное молчание живого процесса; без ограждения проснувшийся старый лидер может продолжить запись параллельно с новым. 4: задержите каждый ответ на четыре секунды — живой узел будет объявлен мёртвым; после настоящего отказа последнее успешное наблюдение ничего не сообщает о будущем, а при большом тайм-ауте подозрение возникнет поздно. Ложное подозрение опаснее тем, что может породить второго лидера, тогда как запоздалое обнаружение обычно лишь продлевает недоступность. 5: локальная транзакция не включает канал ответа. Если ответ потерян, клиент не отличит выполненную операцию от невыполненной и может повторить её; серверу нужна атомарная дедупликация. 6: localhost обычно моделируют надёжным FIFO-каналом до отказа процесса или хоста, но контейнеры имеют общий домен отказа; один ЦОД — частичная синхронность, crash/omission и коррелированный отказ стойки или сети; мобильная связь — длительные задержки, разрывы, повторы после переподключения и offline-first клиент; недоверенные операторы требуют византийской модели, аутентификации и криптографической целостности, при этом временная модель всё равно остаётся частично синхронной.
Литература к главе
- C. Cachin, R. Guerraoui, L. Rodrigues, "Introduction to Reliable and Secure Distributed Programming," 2nd ed., Springer, 2011 — гл. 2: модели процессов, каналов и отказов.
- M. Kleppmann, "Designing Data-Intensive Applications," O'Reilly, 2017 — гл. 8 «Проблемы распределённых систем».
- P. Deutsch et al., "The Eight Fallacies of Distributed Computing," Sun Microsystems, 1994–1997.
- M. van Steen, A. Tanenbaum, "Distributed Systems," 4th ed., 2023 (свободно распространяется авторами) — гл. 1–2.
Содержание курса || Следующая глава