open atlas
↑ К треку
AWS на практике AWS · 05 · 02

Распределённая трассировка и событийная обвязка: X-Ray и EventBridge

Метрики и логи говорят, что узлу плохо; распределённая трассировка говорит, какой хоп тормозит, а EventBridge развязывает хопы. Сэмплируй трассы и ограничивай кардинальность логов — иначе observability спишет с тебя дважды.

AWS Senior ◷ 20 min
Уровень
ОсновыJuniorMiddleSenior

API оформления заказа начинает отвечать за 1,8 секунды вместо 200 мс. Дашборды издевательски зелёные: CPU в норме, память в норме, p99 каждого сервиса «нормальный». Дежурный час грепает логи пяти сервисов, не находит ничего убедительного и начинает гадать — перезапустить сервис корзины, сбросить кэш, обвинить базу. Настоящей причиной был SDK платёжного провайдера, который тихо добавил синхронный вызов расчёта налога к четвёртому сервису, а тот ждал кросс-региональное чтение из DynamoDB. Ни одна строка лога не сказала «это я медленный», потому что сам по себе медленным не был ни один сервис — латентность жила в рёбрах между сервисами. У команды были метрики и логи, первые два столпа observability. Не хватало третьего: распределённой трассы, которая рисует весь запрос как единый таймлайн и указывает на точный хоп, где уходят миллисекунды. Они включили X-Ray, и следующий инцидент занял четыре минуты вместо часа.

Трассировка: третий столп, и почему одних логов мало

Метрики и логи — покомпонентные. Метрика — число с одного узла; строка лога — одно событие на одном узле. Ни то, ни другое не знает, что этот запрос к оформлению заказа развернулся в вызов корзины, затем платежей, затем сервиса налогов, затем DynamoDB — и что 1,6 секунды лишней латентности ушли на ожидание налогового хопа. Чтобы ответить «где в цепочке вызовов ушло время?», нужна запись, которая пронизывает сервисы и сшивает их в единый таймлайн. Это распределённая трассировка.

AWS X-Ray моделирует запрос как трассу: совокупность всей работы по обслуживанию одного запроса, идентифицируемую единым trace ID. Каждый сервис добавляет сегмент — документ, фиксирующий имя сервиса, запрос и ответ, время начала и конца и любые ошибки или фолты. Внутри сегмента каждый нижестоящий вызов твоего кода — это subsegment: вызов DynamoDB, внешнего HTTP API или SQL-запрос, каждый со своим таймингом. Для нижестоящего сервиса, который сам себя не трассирует (DynamoDB не отправляет сегментов), X-Ray строит выведенный (inferred) сегмент из твоего subsegment, чтобы зависимость всё равно появилась на карте. В этом структурное отличие от логов: трасса — это дерево сегментов и subsegment’ов, а не плоский поток строк, и это дерево в точности повторяет форму твоего графа вызовов.

Клей, связывающий сегменты через границы процессов, — проброшенный заголовок X-Amzn-Trace-Id. Первый интегрированный с X-Ray сервис, на который попадает запрос, штампует его корневым trace ID и решением о сэмплировании, а каждый инструментированный хоп пробрасывает его дальше, добавляя свой parent segment ID, чтобы X-Ray мог восстановить рёбра родитель/потомок.

X-Amzn-Trace-Id: Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1

X-Ray сворачивает сегменты всех сервисов по одному trace ID в карту сервисов (service map / service graph): узлы на каждый сервис, рёбра на каждый вызов между ними, и каждое ребро аннотировано латентностью и долей ошибок. Именно карта превращает «оформление тормозит» в «ребро оформление→налог держит 1,6 с p99 и 0% ошибок» — то, что логи структурно показать не могут, потому что строка лога живёт внутри одного узла и ничего не знает о ребре.

Сэмплирование: ты намеренно меняешь полноту на стоимость

Трассировать каждый запрос дорого вдвойне: счёт за трассу и латентность, которую SDK добавляет, собирая и отправляя сегменты. Поэтому X-Ray сэмплирует. Правило по умолчанию — резервуар плюс ставка: записывать первый запрос каждую секунду, затем 5% всего сверх того. Резервуар гарантирует, что ты всегда захватываешь базовый набор трасс даже при низком трафике; ставка 5% не даёт высоконагруженной точке записать миллионы почти одинаковых трасс. Ты настраиваешь правила по сервису или свойству запроса — отключаешь сэмплирование (трассируешь 100%) на путях, меняющих состояние, или платёжных, где важна каждая трасса, и сэмплируешь health-check’и и фоновый поллинг по низкой ставке, потому что это шум.

Стоимость реальна, и это самый частый режим отказа observability. X-Ray тарифицирует за трассу записанную и, отдельно, за трассу извлечённую или просканированную. Иллюстративные цифры со страницы тарифов CloudWatch (US East / Сев. Вирджиния — цены зависят от региона, всегда сверяйся со своим): записанные трассы стоят около $5,00 за миллион ($0,000005 за штуку) сверх free tier в 100 000 в месяц, а извлечённые/просканированные — около $0,50 за миллион сверх 1 000 000 бесплатных. Эти числа кажутся пустяком, пока кто-нибудь не выставит сэмплирование на 100% на пути, делающем 5000 запросов/с: это ~13 миллиардов трасс в месяц, примерно $65 000 только за запись — классическая история «включили полную трассировку в проде и получили пятизначный сюрприз». Сэмплирование — не компромисс по качеству, за который надо извиняться; это намеренный размен, делающий трассировку доступной.

EventBridge: развяжи хопы, которые ты только что протрассировал

У инцидента с налогом был и второй урок: тот синхронный вызов вообще не должен был быть синхронным. Когда сервис платежей вынужден вызвать сервис налогов inline и ждать, эти двое жёстко связаны — медленный тащит за собой быстрый, а деплой любого из них может сломать другой. Amazon EventBridge — это serverless шина событий (event bus), которая ломает эту связанность. Производитель вызывает PutEvents, чтобы опубликовать событие; он не знает и не заботится, кто его потребит. Правила (rules) на шине сопоставляют паттерн события с полями события — source, detail-type и произвольный JSON в detail — и маршрутизируют каждое совпадение к одной или нескольким целям (targets: Lambda, очередь SQS, машина состояний Step Functions, другая шина), которые выполняются параллельно.

{
  "Source": "checkout.service",
  "DetailType": "OrderPlaced",
  "Detail": {
    "orderId": "o-4821",
    "region": "eu-west-1",
    "amountCents": 7400,
    "needsTax": true
  }
}

Правило, разводящее это на Lambda расчёта налога и аналитическую очередь, сопоставляется по паттерну ниже; всё, что не совпало, молча игнорируется, поэтому добавление потребителя никогда не трогает производителя:

{
  "source": ["checkout.service"],
  "detail-type": ["OrderPlaced"],
  "detail": { "needsTax": [true] }
}

Шина по умолчанию (default bus) получает события от самих сервисов AWS (смена состояния EC2, загрузка в S3) — так ты строишь автоматизацию, реагирующую на собственную инфраструктуру; кастомные шины несут доменные события твоего приложения, изолируя их от потока событий AWS-сервисов. Реестр схем (schema registry) может вывести и версионировать форму событий, чтобы потребители кодировались под контракт, а не под догадку. Выигрыш — хореография: оформление эмитит OrderPlaced и уходит; налог, аналитика и почта подписываются независимо. Ни один сервис не держит список «кого звать дальше», поэтому ты добавляешь и убираешь потребителей без передеплоя производителей — ровно та связанность, которая превратила изменение стороннего SDK в межсервисный инцидент.

Почему это работает

EventBridge, SNS и SQS пересекаются достаточно, чтобы путать, но отвечают на разные вопросы. SNS — pub/sub-фанаут: одно сообщение, много подписчиков, без фильтрации по контенту дальше грубых атрибутов сообщения — бери, когда просто надо разослать быстро и дёшево. SQS — очередь: она буферизует и развязывает во времени, давая медленному потребителю вычерпывать работу в своём темпе с ретраями и dead-letter-очередью — бери, когда нужны надёжность и backpressure ровно между двумя сторонами. EventBridge — маршрутизатор: богатый JSON-матчинг по контенту события, много источников ко многим целям, реестр схем и события AWS-сервисов из коробки — бери, когда логика маршрутизации живёт в контенте события и ты хочешь полностью развязать производителей с меняющимся набором потребителей. Они компонуются: EventBridge маршрутизирует событие в очередь SQS, которая буферизует его для медленного воркера.

Структурированные логи: третий столп, сделанный запрашиваемым

Трассировка говорит, какой хоп; логи всё ещё говорят, что произошло внутри этого хопа — но только если они запрашиваемы. Сеньор эмитит структурированные логи: JSON-объекты с именованными полями, а не человеческую прозу. Строка вроде «пользователь 4821 не оплатил, код 51» непоискабельна; запись вроде {“level”:“error”,“traceId”:“1-5759e988”,“userId”:4821,“event”:“payment_failed”,“declineCode”:51} позволяет вытащить все логи по одному trace ID и сджойнить их с таймлайном X-Ray. Протаскивание trace ID в логи и сплавляет второй и третий столпы. Embedded Metric Format (EMF) (встроенный формат метрик CloudWatch) идёт дальше: эмитишь специально оформленный JSON-лог, и CloudWatch автоматически извлекает из него метрики — так из одной записи получаешь и метрику, и подпирающий её лог.

Но поля стоят денег, и ловушка — кардинальность. CloudWatch Logs тарифицирует за ГБ принятого (иллюстративно около $0,50/ГБ сверх free tier в 5 ГБ в US East, зависит от региона), а EMF превращает высококардинальные измерения в кастомные метрики по примерно $0,30 за метрику в месяц. Поставь userId или requestId измерением EMF — и каждое отдельное значение становится своей тарифицируемой метрикой: миллион пользователей превращается в миллион метрик и четырёхзначную месячную строку счёта из-за одного неосторожного поля. Логируй поля свободно; держи измерения метрик низкокардинальными (статус, маршрут, регион — а не id пользователя или запроса). Переинструментирование — режим отказа ровно как пересэмплирование.

Какой вопрос задаёшьСтолп, который отвечаетПоверхность AWSРычаг стоимости
Сервису плохо прямо сейчас?МетрикиМетрики / алармы CloudWatchКардинальность измерений кастомных метрик (~$0,30/метрика/мес)
Что именно произошло внутри узла?Логи (структурированные)CloudWatch Logs / EMFГБ принятого (~$0,50/ГБ сверх free tier)
Какой хоп в цепочке тормозит?ТрассыКарта сервисов X-RayСтавка сэмплирования (~$5/М записанных трасс)
Как развязать хопы A и B?Событийный дизайнШина + правила EventBridgePutEvents за событие + вызовы за цель
Выбери лучший вариант

Событие OrderPlaced должно разойтись на три независимых потребителя (расчёт налога, аналитика, почта), каждый принадлежит своей команде, которая приходит и уходит; маршрутизация зависит от полей внутри события (region, needsTax). Выбери примитив развязки.

Викторина

Цепочка запроса через пять сервисов периодически тормозит, но CPU, память и собственная метрика p99 каждого сервиса выглядят нормально. Какой инструмент observability создан, чтобы локализовать медленный хоп?

Викторина

Команда выставляет сэмплирование X-Ray на 100% запросов на пути, обслуживающем 5000 запросов/с, и шокирована счётом. Какой принцип они нарушили?

Вспомните перед уходом
  1. 01
    Что распределённая трасса показывает такого, чего метрики и логи структурно не могут, и как X-Ray это моделирует?
  2. 02
    Почему observability — это решение о стоимости, и как сюда вписываются сэмплирование и EventBridge?
Итог

Observability стоит на трёх столпах, и сеньоры держат их раздельно: метрики отвечают «сервису плохо?» (покомпонентные числа, алармы CloudWatch), структурированные логи отвечают «что именно произошло внутри узла?» (запрашиваемый JSON с протащенным trace ID, EMF для чеканки метрик из логов), а распределённые трассы отвечают «какой хоп в цепочке запроса тормозит?» — то, что логи и метрики структурно не могут, потому что они живут внутри одного узла, а латентность часто живёт в рёбрах между сервисами. X-Ray моделирует запрос как трассу с одним trace ID: каждый сервис эмитит сегмент своей работы, каждый нижестоящий вызов — subsegment, заголовок X-Amzn-Trace-Id пробрасывает трассу через границы процессов, а X-Ray рисует карту сервисов с рёбрами, аннотированными латентностью и ошибками, которая указывает прямо на медленный хоп. Поскольку трассировка и высококардинальное логирование стоят денег и добавляют латентность, observability — это бюджетное решение: сэмплируй трассы (правило по умолчанию «первый запрос/с плюс 5%» намеренно меняет полноту на стоимость; 100% на занятом пути — пятизначная ошибка) и ограничивай кардинальность логов/метрик (userId измерением EMF взрывается в миллион тарифицируемых метрик). Наконец, EventBridge не даёт хопам, которые ты трассируешь, связаться изначально: производители PutEvents и уходят, правила матчат по source/detail-type/detail и разводят на цели параллельно — маршрутизатор по контенту, к которому тянешься вместо SNS (простой фанаут) и SQS (буфер/backpressure), когда маршрутизация живёт в контенте события, а потребители приходят и уходят. Теперь, когда встречаешь многосервисную загадку латентности, где метрики каждого сервиса выглядят нормально, первым делом открываешь карту сервисов X-Ray — а когда видишь синхронный вызов, способный замедлить несвязанный сервис, спрашиваешь себя, стоит ли его развязать через EventBridge.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено

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

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

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

Trademarks belong to their respective owners. Editorial reference only.