2026 г.

Распределённые системы: обзор

Проект ЦИТадель

Введение

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

Распределяются по трём причинам: отказоустойчивость (один сервер — одна точка отказа), масштаб (нагрузка или данные не помещаются на одну машину) и география (пользователи в Новосибирске не должны ждать ответа из Франкфурта). Цена — качественный скачок сложности: у распределённой системы появляются виды отказов и парадоксы, которых в одиночном компьютере не существует в принципе. Эта статья — карта таких проблем и выработанных за полвека решений; она открывает новый подраздел «Инфраструктуры», за ней последует систематический курс.

1. Почему это трудно: три фундаментальных обстоятельства

Ненадёжная сеть

Сообщение по сети может потеряться, задержаться на неопределённое время, прийти в другом порядке или дважды. Хуже того: отправив запрос и не получив ответа, невозможно узнать, что произошло — потерялся запрос, упал получатель или потерялся ответ (и тогда операция выполнена!). Любой сетевой вызов обязан иметь тайм-аут и стратегию повтора, а любая операция, которую могут повторить, должна быть идемпотентной — безопасной при многократном исполнении. Народную мудрость на эту тему собирают «заблуждения распределённых вычислений» (Питер Дойч и коллеги, Sun, 1990-е): сеть надёжна, задержка нулевая, полоса бесконечна, топология неизменна и т.д. — восемь допущений, которые молча делает каждый новичок и которые все ложны.

Отсутствие общего времени

У каждой машины свои часы, и они расходятся — на миллисекунды при NTP-синхронизации, на что угодно при её сбое. Следствие: по меткам времени нельзя надёжно установить порядок событий на разных машинах — «кто записал последним» становится вопросом без ответа. Классическое решение — отказаться от физического времени в пользу логического: часы Лэмпорта (1978) нумеруют события так, что причинно связанные события упорядочены (каждое сообщение несёт счётчик; получатель берёт максимум своего и полученного плюс один), а векторные часы позволяют ещё и распознать, что два события конкурентны — произошли «одновременно» в строгом смысле независимости. Идея «упорядочивать причинность, а не время» — одна из самых глубоких в предмете, и мы встретим её следы везде: от репликации до журналов событий.

Частичные отказы

Одиночный компьютер отказывает целиком — программа либо работает, либо нет. Распределённая система отказывает частично: три узла из пяти живы, сеть между ЦОДами порвалась, а внутри каждого всё прекрасно (сетевое разделение, split brain). Знаменитый теоретический итог — теорема FLP (Фишер, Линч, Патерсон, 1985): в асинхронной системе, где нельзя отличить упавший узел от медленного, ни один детерминированный алгоритм не гарантирует достижения консенсуса за конечное время. Это не приговор практике: Raft и Paxos сохраняют безопасность без временных предположений, а живость получают при частичной синхронности, когда сеть достаточно долго ведёт себя предсказуемо. Случайные тайм-ауты уменьшают вероятность повторного раскола голосов, но не дают гарантии завершения при любом поведении сети.

2. CAP и спектр согласованности

Самая цитируемая (и самая перевираемая) теорема предмета — CAP (гипотеза Брюера 2000, доказательство Гилберта и Линч 2002): при сетевом разделении (Partition) система вынуждена выбирать между линеаризуемостью (Consistency — завершённая запись видна всем последующим чтениям) и доступностью (Availability — каждый запрос к неотказавшему узлу получает содержательный ответ). Популярная формулировка «выберите два из трёх» вводит в заблуждение: разделения не выбирают — они случаются; реальный выбор — это поведение системы в момент разделения: отвечать, рискуя отдать устаревшее (AP), или отказывать той части системы, которая не может подтвердить актуальное состояние (CP). Полезное уточнение — формула PACELC: при разделении (P) выбор A или C, а в остальное время (Else) — выбор между задержкой (L) и согласованностью (C): плата за строгую согласованность взимается не только в аварию, но и в каждом запросе, ожидающем подтверждения реплик.

Согласованность — не бинарный флаг, а спектр моделей:

  • Линеаризуемость (строгая): система ведёт себя как одна машина — запись, подтверждённая в 12:00:01, видна всем чтениям после этого момента. Дорого: требует координации на каждой операции.
  • Причинная согласованность: гарантируется порядок причинно связанных операций (ответ не виден раньше вопроса), независимые могут расходиться.
  • Итоговая согласованность (eventual): реплики сойдутся «когда-нибудь», если записи прекратятся; до того чтение может вернуть что угодно из истории. Дёшево и доступно; всю сложность (что делать с конфликтами) платит приложение.

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

3. Репликация

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

  • Ведущий–ведомые (primary–backup, лидерская): все записи идут через один узел, реплики применяют его журнал. Просто, порядок записей единый; узкие места — отказ лидера (нужны выборы нового — см. консенсус) и выбор между синхронной репликацией (медленнее; подтверждённая запись переживает отказ лидера, если новым лидером выбирают подтвердившую её реплику) и асинхронной (быстро, но при аварии лидера свежие записи могут потеряться). Так устроена репликация PostgreSQL, MySQL, Redis.
  • Кворумная (Dynamo-стиль, без выделенного лидера): запись принимается любыми W из N реплик, чтение опрашивает R; при W + R > N множества пересекаются, поэтому чтение встретит хотя бы одну копию, принявшую завершённую запись. Координатору ещё нужно сравнить версии, а одного пересечения недостаточно для линеаризуемости при незавершённых и конкурентных записях. Регулируемый компромисс: W = N, R = 1 — дорогие записи, дешёвые чтения; W = R = кворум — баланс. Наследники Dynamo — Cassandra, ScyllaDB, Riak.
  • Конфликты. Приём записей в нескольких точках (мульти-лидер, leaderless) означает конкурентные версии одного объекта. Стратегии: «последняя запись побеждает» (LWW — просто и молча теряет данные: помните про часы!), версионирование с разрешением в приложении, и красивый теоретический ответ — CRDT, структуры данных (счётчики, множества, тексты), математически гарантирующие сходимость реплик при любом порядке слияний; на них строятся системы совместного редактирования.

4. Консенсус

Ряд задач не решается без строгого согласия узлов: выбор лидера (два лидера — split brain и порча данных), членство в кластере, атомарная фиксация. Консенсус — протокол, приводящий узлы к единому решению несмотря на отказы части из них — вершина инженерной мысли предмета.

Классика — Paxos (Лэмпорт, 1989/1998), доказуемо корректный и легендарно трудный для понимания и реализации. Ответом на его сложность стал Raft (Онгаро и Аустерхаут, 2014), спроектированный с явной целью «быть понятным» — и потому разберём его идею в три абзаца.

Время делится на нумерованные термы; в каждом терме не более одного лидера. Узел, не слышавший лидера дольше случайного тайм-аута, объявляет новый терм и просит голоса; получив большинство — становится лидером. Случайность тайм-аутов разводит конкурентов, а единственность обеспечивается тем, что любые два большинства пересекаются и каждый узел голосует не более одного раза за терм.

Все изменения идут через лидера, который дописывает их в реплицируемый журнал и рассылает подписчикам; запись считается зафиксированной, когда её подтвердило большинство, — и лишь тогда применяется к состоянию. Журнал — первичен, состояние машины — производное от него (парадигма реплицируемого автомата: одинаковый журнал + детерминированное применение = одинаковое состояние).

При выборах побеждать разрешено лишь кандидату, чей журнал не отстаёт от журналов голосующих узлов; вместе с правилом фиксации записей текущего терма это гарантирует сохранность зафиксированного префикса. Кластер из 2f+1 узлов сохраняет безопасность при любых f отказах и продолжает работу, пока связное большинство доступно: три узла терпят один отказ, пять — два.

Практика: консенсус дорог (кворум на каждую запись), поэтому область его применения стараются ограничивать. Координационные сервисы хранят маленькое критичное ядро: etcd (сердце Kubernetes) и Consul реализуют Raft, ZooKeeper — родственный протокол ZAB; поверх них строятся выборы лидеров, распределённые блокировки и конфигурация. Распределённые СУБД применяют тот же принцип непосредственно к пользовательским данным, разбивая их на небольшие группы репликации, каждая со своим журналом консенсуса.

5. Шардирование

Репликация копирует данные целиком; когда данные или нагрузка на запись не влезают в один узел, их шардируют — режут на части по ключу. Два базовых способа: по диапазону ключа (эффективные сканы соседних ключей, но риск «горячих» диапазонов) и по хешу (равномерно, но диапазонные запросы рассыпаются по всем шардам). Наивный hash(key) mod N обладает пороком: смена N перетасовывает почти все ключи. Согласованное хеширование (consistent hashing) чинит это: узлы и ключи отображаются на кольцо, ключ принадлежит ближайшему по кольцу узлу, и добавление узла трогает лишь соседний участок — классика Dynamo, memcached-клиентов и CDN. Зрелые системы ведут явную таблицу принадлежности шардов с фоновым ребалансом. Вечная головная боль шардирования — операции поверх нескольких шардов: они превращаются в распределённые транзакции.

6. Распределённые транзакции

Атомарность «всё или ничего» поверх нескольких узлов — исторически самое слабое место. Классический двухфазный коммит (2PC): координатор спрашивает участников «готовы?», собрав единогласие — рассылает «фиксируй». Порок в середине: участник, ответивший «готов», обязан ждать вердикта — а если координатор в этот момент погиб, участник блокирован с удержанными блокировками на неопределённый срок. 2PC жив в корпоративных стыковках (XA), но в веб-масштабе его избегают. Современные ответы с двух сторон:

  • Сага: длинную операцию режут на последовательность локальных транзакций, каждая с компенсацией; при сбое исполняются компенсации уже сделанного (бронь отеля отменяется, если не вышло с билетом). ACID-атомарности здесь нет: промежуточные состояния видимы, а неустранимые ошибки компенсаций требуют сверки и ручного разрешения. Это явный бизнес-процесс, а не прозрачная замена транзакции.
  • Распределённые СУБД нового поколения (Spanner, CockroachDB, YDB, TiDB): честные ACID-транзакции поверх шардов и реплик, внутри — консенсус на группы реплик плюс тщательная работа с порядком транзакций. Spanner использует TrueTime, CockroachDB — гибридные логические часы, TiDB — выдаваемые кластером метки TSO, YDB — логическое время координаторов. Дорого, но переносит сложность из приложений в СУБД — направление, где за последнее десятилетие прогресс наибольший.

7. Асинхронная связь: очереди и журналы

Синхронный вызов сервис–сервис распространяет отказы: упавший получатель валит отправителя. Асинхронная альтернатива — брокер сообщений между ними: отправитель кладёт сообщение и свободен; получатель разбирает в своём темпе; пик нагрузки превращается в очередь, а не в каскад тайм-аутов. Два жанра: классические очереди (например, очереди RabbitMQ: подтверждённое сообщение удаляется; у RabbitMQ есть и отдельный журнальный режим Streams) и журналы событий (Kafka: сообщения хранятся упорядоченным журналом, потребители независимо движутся по нему каждый со своей позицией — доступную по политике удержания историю можно перечитать заново). Отрезвляющее замечание о семантике доставки: «ровно один раз» как общее свойство ненадёжной доставки недостижимо; реальные сквозные решения используют повторы и идемпотентность или дедупликацию в явно очерченной транзакционной границе.

8. Ландшафт-2026: кто есть кто

Соберём упомянутое в один абзац-путеводитель. Координация и метаданные: etcd, ZooKeeper, Consul (Raft/ZAB). Kubernetes — по сути огромная распределённая система над etcd, превратившая выборы лидеров и желаемое-состояние в бытовую практику. Журналы событий: Kafka и совместимые (Redpanda). Кворумные NoSQL: Cassandra, ScyllaDB. Распределённый SQL: Spanner, CockroachDB, TiDB, YDB (последняя — открытая российская разработка, что для нашей аудитории существенно). Объектные хранилища требуют чтения конкретного контракта: Amazon S3 сегодня даёт сильную согласованность операций с объектами и списков, тогда как некоторые S3-совместимые реализации и вспомогательные конфигурационные операции могут иметь более слабые гарантии. Практически каждая из этих систем — воплощение двух-трёх разделов этой статьи, и в курсе мы будем разбирать их именно так: как теоремы, дожившие до продакшена.

9. Когда НЕ распределяться

Финальный раздел — противоядие от моды. Каждый механизм этой статьи — консенсус, кворумы, саги — это цена, которую платят за необходимость, а не признак зрелой архитектуры. Один сервер 2026 года — это сотни ядер, терабайты памяти и NVMe-массивы: монолит с грамотной вертикалью и простой репликацией «лидер + реплика» покрывает потребности подавляющего большинства систем, оставаясь на порядок проще в отладке, эксплуатации и рассуждении о корректности. Микросервисы решают организационную проблему (независимые команды и релизы) ценой превращения вызова функции в сетевой RPC со всеми заблуждениями Дойча в комплекте. Честное инженерное правило: распределяйтесь от необходимости, а не от архитектурной моды; каждая сетевая граница в системе должна уметь назвать причину своего существования.

Заключение

Полвека распределённых систем дали удивительно устойчивый корпус знаний: часы Лэмпорта (1978), FLP (1985), Paxos (1989) актуальны сегодня буквально — в отличие от почти любой другой области ИТ, здесь фундамент не устаревает, потому что вырастает из физических ограничений, частичных и нередко коррелированных отказов, а не из технологической конъюнктуры. Карта, которую стоит унести из этой статьи: сеть ненадёжна и времени нет → порядок даёт причинность и журналы → копии требуют выбора модели согласованности → строгое согласие требует консенсуса и большинства → масштаб требует шардирования → атомарность поверх шардов дорога, и её либо покупают у новых СУБД, либо разменивают на саги. В курсе, который последует за этим обзором, каждый пункт карты развернётся в главу с разбором алгоритмов и лабораторными на реальных системах.

Список литературы

  1. M. Kleppmann, "Designing Data-Intensive Applications," O'Reilly, 2017 (русский перевод: «Высоконагруженные приложения», Питер) — лучшая современная книга по теме.
  2. L. Lamport, "Time, Clocks, and the Ordering of Events in a Distributed System," CACM 21(7), 1978.
  3. M. Fischer, N. Lynch, M. Paterson, "Impossibility of Distributed Consensus with One Faulty Process," JACM 32(2), 1985.
  4. S. Gilbert, N. Lynch, "Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services," ACM SIGACT News 33(2), 2002.
  5. D. Ongaro, J. Ousterhout, "In Search of an Understandable Consensus Algorithm (Raft)," USENIX ATC, 2014. raft.github.io/raft.pdf
  6. G. DeCandia et al., "Dynamo: Amazon's Highly Available Key-value Store," SOSP, 2007.
  7. J. Corbett et al., "Spanner: Google's Globally-Distributed Database," OSDI, 2012.
404 Not Found

404 Not Found


nginx/1.24.0 (Ubuntu)

Связь с редакцией