open atlas
↑ К треку
Основы System Design SD · 06 · 08

Асинхронность и сообщения: событийный конвейер заказов

Практический проект: построй маленький событийный конвейер заказов с идемпотентным at-least-once consumer'ом, транзакционным outbox и ограниченной очередью, затем сломай его намеренно — убей relay, переотправь, перегрузи — чтобы доказать, что гарантии держатся.

SD Senior ◷ 240 min
Уровень
ОсновыJuniorMiddleSenior

Прочитать, что outbox побеждает проблему двойной записи, — не то же, что увидеть, как твой конвейер переживает падение relay, шторм переотправок и перегрузку — и остаётся корректным. Построй маленький событийный конвейер заказов, затем намеренно сломай его: убей процесс между commit и publish, переотправь каждое сообщение дважды и завали consumer’а нагрузкой. Смысл не в отполированном сервисе; смысл — увидеть, как твои гарантии держатся (или падают) под ровно теми падениями, о которых предупреждал этот раздел.

Этот проект делает весь раздел операционным: ты реализуешь паттерны — at-least-once с идемпотентностью, транзакционный outbox, ограниченную очередь с backpressure — и затем прогонишь инъекции сбоев, что отделяют код, который выглядит корректным, от кода, который является корректным под падениями.

Проект
0 из 9
Цель

Построй минимальный событийный конвейер заказов — сервис заказов, что пишет заказ и испускает событие OrderPlaced через транзакционный outbox, очередь/лог, несущий событие, и один-два идемпотентных consumer'а (например, склад, email) — затем впрысни сбои этого раздела (падение relay, переотправка, перегрузка) и продемонстрируй, что ни одно событие не потеряно, ни один эффект не дублирован и система деградирует мягко под нагрузкой.

Требования
Критерии приёмки
  • Работающий конвейер, где оформление заказа надёжно приводит к ровно-effectively-once downstream-эффектам, продемонстрированный end-to-end.
  • Инъекция сбоя A: записанный прогон (логи/скрины), где процесс убит в зазоре commit→publish и, после перезапуска, relay публикует ожидающую outbox-строку — доказывая 'никогда не потеряно' и что это обеспечивает outbox, а не везение.
  • Инъекция сбоя B: записанный прогон, где каждое сообщение доставлено дважды, и хранилище дедупа consumer'а показывает пропуск второй доставки — доказывая, что эффект случился раз, несмотря на доставку at-least-once.
  • Инъекция сбоя C: записанный прогон, где устойчивая перегрузка запускает backpressure (отказы/сброс или ограниченную блокировку) вместо вечно растущего бэклога, с графиком глубины очереди/лага и указанной выбранной границей.
  • Краткий разбор: семантика доставки, с которой ты начал, где именно outbox возвращает атомарность, почему consumer идемпотентен и от чего защищают твои граница + DLQ + аларм на лаг — плюс абзац об окне согласованности в конечном счёте, что испытал бы пользователь, и как бы ты показал его в UI.
Senior-стретч
  • Добавь вторую consumer-группу (аналитика) на том же потоке и продемонстрируй fan-out — обе группы видят каждое событие независимо — и на логе переиграй consumer аналитики с офсета 0 для бэкфилла.
  • Добавь шторм ретраев: включи агрессивные клиентские ретраи во время перегрузки и покажи ускорение коллапса очередей, затем укроти его экспоненциальным backoff + jitter и circuit breaker'ом, сравнив две кривые бэклога.
  • Преврати поток в маленькую оркестрацию (сагу): координируй заказ → списание → резерв координатором, что запускает компенсирующее действие (возврат/освобождение), когда поздний шаг падает, и покажи, что состояние процесса запрашиваемо ('где заказ #X?').
  • Добавь мультитенантную справедливость: shuffle-shard'ь арендаторов по малому пулу очередей и покажи, что всплеск одного арендатора больше не заморяет остальных.
Вспомните перед уходом
  1. 01
    Что доказывает каждая из трёх инъекций сбоя и зачем их впрыскивать?
  2. 02
    Где именно outbox возвращает атомарность и что consumer всё равно обязан делать?
Итог

Этот проект превращает асинхронный раздел в повторяемый рабочий процесс: построй минимальный событийный конвейер заказов — сервис заказов, что пишет заказ и outbox-строку в одной локальной транзакции, relay, что публикует OrderPlaced из outbox, брокер/лог и идемпотентный at-least-once consumer, что дедуплицирует по order_id — затем заставь каждую гарантию отработать, ломая систему намеренно. Инъекция A убивает процесс в зазоре commit→publish и показывает, что relay всё равно публикует после перезапуска (outbox = никогда не потеряно). Инъекция B доставляет каждое сообщение дважды и показывает, как хранилище дедупа пропускает второе (идемпотентность = once-эффект). Инъекция C перегружает consumer’а и показывает, как ограниченная очередь применяет backpressure вместо безграничного бэклога, с DLQ для ядовитых сообщений и алярмом на лаг как ранним предупреждением. Разбор связывает всё: семантика доставки, с которой ты начал, где возвращается атомарность, почему consumer идемпотентен и как бы ты показал окно согласованности в конечном счёте в UI. Инженер, что построил и сломал это однажды, проектирует асинхронные системы под падение, а не только под счастливый путь — что и есть весь смысл этого раздела.

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.