Распределённые системы
| Определение | несколько узлов, общающихся сообщениями, без общей памяти и общих часов |
|---|---|
| Ключевые проблемы | частичные сбои, ненадёжная сеть, отсутствие глобального времени |
| Консенсус | Paxos (1989), Raft (2014) |
| Основные работы | Лэмпорт — «Time, Clocks…» (1978); FLP (1985) |
| Книги | «Designing Data-Intensive Applications» (Клеппман) |
| Примеры | Kubernetes, etcd, Kafka, Cassandra, Spanner |
Распределённая система — набор независимых компьютеров (узлов), которые общаются только сообщениями по сети и вместе решают одну задачу так, что для пользователя они выглядят как единое целое. Почти любой современный сервис распределён: веб-приложение с базой данных и кэшем, кластер Kubernetes, реплицированная база, очередь сообщений, CDN. Распределение нужно ради масштабирования, отказоустойчивости и близости к пользователям, но приносит фундаментальные трудности, которых нет в программе на одном компьютере.
Почему это трудно
В одном процессе вызов функции либо выполняется, либо программа падает целиком. В распределённой системе возможны частичные сбои: один узел работает, другой завис, сеть между ними теряет пакеты. Отправив запрос и не получив ответа, узел не может отличить три ситуации — запрос потерялся, получатель упал, или ответ потерялся по дороге, а действие уже выполнено.
Известный список заблуждений распределённых вычислений (Питер Дойч и коллеги из Sun, 1990-е) перечисляет ложные предположения новичков:[5]
- сеть надёжна;
- задержка равна нулю;
- пропускная способность бесконечна;
- сеть безопасна;
- топология не меняется;
- администратор один;
- передача данных бесплатна;
- сеть однородна.
Время и порядок
У узлов нет общих часов: системное время расходится, синхронизация по NTP даёт точность в миллисекунды, а часы могут прыгать назад. Поэтому нельзя надёжно упорядочить события по меткам времени с разных машин. Лесли Лэмпорт в работе 1978 года предложил логические часы — счётчики, отражающие причинно-следственный порядок «произошло раньше» без физического времени.[1] Развитие идеи — векторные часы и гибридные логические часы. Google Spanner решает задачу иначе — атомными часами и GPS с явной оценкой погрешности (TrueTime).
Репликация
Хранение копий данных на нескольких узлах обеспечивает отказоустойчивость и масштабирование чтения (см. Репликация баз данных):
| Схема | Как работает | Компромисс |
|---|---|---|
| Лидер — последователи | записи идут на лидера, реплики копируют | просто; при отказе лидера нужно переключение |
| Несколько лидеров | запись в любом дата-центре | конфликты одновременных изменений |
| Без лидера (Dynamo-стиль) | запись и чтение кворумом из N узлов | гибко; согласованность в конечном счёте |
Синхронная репликация гарантирует, что данные не потеряются, но медленнее и зависит от доступности реплик; асинхронная быстрее, но при отказе лидера последние записи могут пропасть. Выбор между согласованностью и доступностью при сбоях сети формализует CAP-теорема. Для данных, которые не помещаются на один узел, применяют шардирование.
Консенсус
Многие задачи — выбор лидера, распределённые блокировки, атомарная фиксация, хранение конфигурации — сводятся к консенсусу: несколько узлов должны договориться об одном значении, даже если часть из них отказала. Теорема FLP (1985) доказывает, что в полностью асинхронной сети детерминированный консенсус невозможен даже при одном сбое;[3] практические алгоритмы обходят это с помощью тайм-аутов и гарантируют безопасность всегда, а продвижение — когда сеть ведёт себя разумно.
- Paxos (Лэмпорт) — классический алгоритм, известный сложностью понимания.
- Raft (2014) — создан специально понятным: выбор лидера по срокам (terms), репликация журнала, большинство голосов.[2] Используется в etcd (хранилище Kubernetes), Consul, CockroachDB, Kafka (KRaft).
Кластер из 2f + 1 узлов переживает отказ f узлов: из трёх — одного, из пяти — двух. Поэтому кластеры консенсуса имеют нечётный размер.
Надёжная работа с сетью
- Тайм-ауты на каждый сетевой вызов — без них один зависший сервис останавливает всю цепочку.
- Повторы с экспоненциальной задержкой и случайным разбросом (jitter), чтобы не создать лавину запросов.
- Идемпотентность — повторный запрос не должен списать деньги дважды; используют ключи идемпотентности.
- Автоматический выключатель (circuit breaker) — перестать вызывать сервис, который явно не работает.
- Асинхронный обмен через очереди и журналы событий (Kafka, RabbitMQ) — развязывает сервисы по времени.
- Семантика доставки: «не более одного раза», «хотя бы один раз», а «ровно один раз» на практике достигается сочетанием повторов и идемпотентной обработки.
Многие из этих механизмов вынесены в инфраструктуру — Service mesh, балансировщики и клиентские библиотеки.
Наблюдаемость и тестирование
Отладка распределённой системы требует распределённой трассировки и сквозных идентификаторов запросов. Для проверки поведения при сбоях используют хаос-инжиниринг (намеренное отключение узлов и внесение задержек), тесты в духе Jepsen и формальную верификацию алгоритмов — например, на языке TLA+, который разработал Лэмпорт и применяют AWS и другие компании.
Литература
Основное современное введение — книга Мартина Клеппмана «Designing Data-Intensive Applications» («Высоконагруженные приложения»).[4] Классические работы — статьи Лэмпорта, работа FLP и статья о Raft вместе с его интерактивной визуализацией.[6]
См. также
Источники
- Lamport — Time, Clocks, and the Ordering of Events in a Distributed System (1978)
- Ongaro, Ousterhout — In Search of an Understandable Consensus Algorithm (Raft, 2014)
- Fischer, Lynch, Paterson — Impossibility of Distributed Consensus with One Faulty Process (1985)
- Kleppmann — Designing Data-Intensive Applications (O'Reilly)
- Deutsch et al. — Fallacies of Distributed Computing
- Raft — визуализация и реализации