awesome-everything EN
↑ Обратно к восхождению

Распределённые системы

Как Raft реплицирует log entry и решает, что его безопасно коммитить

Суть RPC AppendEntries, проверка целостности, удерживающая логи идентичными, commit index и паттерн replicated state machine, построенный сверху.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min

Клиент отправляет SET x = 42 Raft-кластеру. Лидер дописывает это в свой лог. Но только лидер не может коммитить эту запись — ему нужно подтверждение от достаточного числа follower-ов, что они тоже её получили. Если лидер упадёт сразу после дописи, но до того как хоть один follower ответит, что происходит с записью?

Лог и AppendEntries

Лидер Raft хранит упорядоченный лог из entry. Каждый entry — тройка: (term, index, command). Когда клиент отправляет запись:

  1. Лидер дописывает entry в свой лог и выполняет fsync на диск.
  2. Лидер шлёт RPC AppendEntries каждому follower, передавая: новый entry, плюс prevIndex и prevTerm — index и term entry, предшествующего новому.
  3. Каждый follower проверяет: есть ли в моём логе entry на prevIndex с prevTerm? Если да — дописывает и делает fsync. Если нет — отвечает mismatch.
  4. Как только лидер получает подтверждение от большинства (включая себя), он помечает entry закоммиченным.
  5. Лидер пристёгивает свой commit index к следующему heartbeat. Follower-ы применяют entry до этого index к своим state machine.
ШагКто действуетЧто происходит
1ЛидерДописывает (term=7, idx=101, cmd=“SET x=42”), fsync
2ЛидерШлёт AppendEntries B, C, D, E с prevIdx=100, prevTerm=7
3Follower-ы B, C, D, EКаждый проверяет: idx=100 имеет term 7. Дописывает, fsync, отвечает success
4ЛидерПолучает 2 success (плюс своё = 3 из 5). Помечает idx=101 закоммиченным
5ЛидерОтвечает клиенту success
6Следующий heartbeatНесёт commitIndex=101 — follower-ы применяют “SET x=42” к state machine

Проверка целостности: Log Matching

Проверка prevIndex/prevTerm — не бюрократия, это механизм, удерживающий лог каждого follower идентичным логу лидера. Если лог follower-а расходится (потому что предыдущий лидер записал другие entry перед падением), ответ mismatch сигнализирует лидеру откатиться и повторить с более ранним prevIndex. Лидер продолжает уменьшать до нахождения последней точки согласия, затем перезаписывает расходящийся хвост follower-а своим.

Это приводит к идентичным логам, потому что: каждое закоммиченное entry в предыдущем term было сохранено большинством нод. Лог нового лидера (избранного большинством) пересекается с этим предыдущим большинством, поэтому новый лидер имеет закоммиченные entry. Незакоммиченные entry на диске старого лидера перезаписываются — они никогда не были подтверждены клиенту, поэтому потери данных нет.

Паттерн replicated state machine

Raft не интересует, что означают команды. Твоё приложение определяет state machine — key-value-маппу, дерево конфига, отношение — и команды, мутирующие её. Raft гарантирует: каждая нода применяет каждое закоммиченное entry в одном и том же порядке. Поскольку команды детерминированы, каждая state machine заканчивает в одном состоянии. Это паттерн replicated state machine: упорядоченный реплицированный лог + детерминированная state machine = консистентный распределённый сервис.

Важные следствия:

  • Не используй NOW(), random() или внешние API-вызовы внутри обработчиков команд — они нарушают детерминизм между репликами.
  • State machine специфична приложению; Raft агностичен к ней.
  • Чтения для линеаризуемости должны идти через лидера (ReadIndex/lease, разбираются в следующем уроке); чтения с follower-ов могут вернуть stale-данные.
Викторина

Follower Raft получает AppendEntries с prevIndex=50, prevTerm=4, но его лог на index 50 имеет term 3. Что делает follower?

Проследи клиентскую запись через 5-нодовый Raft-кластер

1/3
Расставь шаги по порядку

Поставь шаги проверки целостности AppendEntries в правильном порядке:

  1. 1 Лидер выбирает следующий log index для отправки follower-у
  2. 2 Лидер шлёт AppendEntries с prevIndex, prevTerm и новым entry
  3. 3 Follower проверяет: есть ли в моём логе entry на prevIndex с prevTerm?
  4. 4 Если да — follower дописывает и отвечает success
  5. 5 Если нет — follower отвечает mismatch со своим конфликтующим index/term
  6. 6 Лидер уменьшает nextIndex для этого follower и повторяет с более ранним prevIndex
  7. 7 В итоге лидер находит последнюю точку совпадения; follower обрезает хвост и принимает версию лидера
Вспомните перед уходом
  1. 01
    Почему log entry Raft не является закоммиченным в момент, когда лидер записывает его на диск?
  2. 02
    Follower был offline 2 минуты и пропустил 500 log entry. Он возвращается. Как он догоняет?
  3. 03
    Почему обработчики команд в Raft state machine должны быть детерминированы?
Итог

Лидер реплицирует записи через AppendEntries, который несёт проверку целостности prevIndex/prevTerm, заставляющую лог каждого follower-а сходиться к лидерскому. Entry закоммичено, когда большинство персистировало его; лидер затем транслирует commit index, чтобы follower-ы применили entry к своим state machine. Это паттерн replicated state machine: идентичные упорядоченные логи плюс детерминированные обработчики равно идентичное состояние на каждой реплике. Незакоммиченные entry от упавшего лидера безопасно отбрасывать — они никогда не были подтверждены. Стоимость fsync при каждом коммите — доминирующая операционная цена, поэтому Raft-нагрузки требуют dedicated NVMe, а не shared cloud-объёмов.

Связанные уроки
встречается в178
Продолжить восхождение ↑Выборы лидера в Raft: таймауты, правила голосования и четыре свойства безопасности
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.