Один сервис отправляет сообщение в RabbitMQ или NATS и ждёт ответ.
Для вызывающего кода это выглядит почти как обычный
await, хотя под капотом работают две независимые очереди:
Requester -> request -> Responder
Requester <- response <- Responder
В RabbitMQ запрос обычно содержит
ReplyTo и уникальный CorrelationId, чтобы клиент понял, к какому запросу относится ответ.В NATS для ответа создаётся отдельный
inbox subject.Упрощённая идея на C#:
var requestId = Guid.NewGuid();
await bus.SendAsync(new PriceRequest(
RequestId: requestId,
ProductId: productId
));
var response = await responses
.WaitForAsync<PriceResponse>(
requestId,
timeout: TimeSpan.FromSeconds(2),
cancellationToken);
Плюсы действительно значительные:
- отправителю не нужно знать адрес обработчика;
- responder можно горизонтально масштабировать;
- брокер помогает пережить кратковременный сбой сервиса;
- приложения остаются слабо связанными.
Но это не «HTTP, только через RabbitMQ».
Пока requester ждёт результат, взаимодействие остаётся логически синхронным. Поэтому обязательно нужны:
- таймаут и
CancellationToken;- уникальный
CorrelationId;- обработка поздних и повторных ответов;
- идемпотентность responder;
- ограничение количества одновременных запросов;
- трассировка через обе очереди.
Особенно опасный случай:
Service A ждёт B
Service B ждёт C
Service C ждёт A
Брокер работает, сообщения доставляются, но вся система стоит.
Request-response через messaging полезен, когда нужны location transparency, балансировка обработчиков и единый транспорт между сервисами.
Для простого короткого запроса HTTP или gRPC часто остаются понятнее и быстрее.
Главное правило:
брокер убирает прямую связь между сервисами, но не убирает зависимость от ответа.
