Перейти к основному содержимому
Нормативная версия документации - английская

Эта страница переведена с английского языковой моделью. При любом расхождении верен английский оригинал.

Завершение доставки: retry, retry_after, недоставленные сообщения

charge отвечает на каждый отказ платёжного провайдера нужным исходом: переобработкой сразу или позже, отбрасыванием или темой недоставленных сообщений.

Каждая доставка завершается одним исходом. ack завершает её, drop отбрасывает, а retry просит брокер доставить сообщение повторно. retry_after доставляет его повторно после задержки и не задерживает доставки, идущие следом. Переобработка, которая никогда не завершается успехом, повторялась бы бесконечно, поэтому точка монтирования ограничивает число попыток. Когда попытки исчерпаны, доставка уходит в тему для недоставленных сообщений (dead letter), где её может изучить оператор или другой сервис.

Подписчик​

Платёжный провайдер отказывает в списании тремя способами, и каждому нужен свой исход. Busy означает, что лимит запросов провайдера пока исчерпан. Немедленная переобработка снова получит отказ, поэтому retry_after ждёт секунду. Unavailable означает, что провайдер не ответил: retry повторяет попытку сразу. Declined означает, что на карте не хватает средств. Повторная попытка получит тот же ответ, поэтому платёж отбрасывается. Клиент провайдера берётся из состояния приложения, о котором рассказывает глава 7.

payments.rs
/// How long a busy provider asks its callers to wait.
pub(crate) const BACKOFF: Duration = Duration::from_secs(1);

/// Charges an order through the payment provider.
#[subscriber("payments.requested", reply)]
async fn charge(
request: &PaymentRequest,
State(gateway): State<Arc<Gateway>>,
) -> Result<Paid, HandlerOutcome> {
match gateway.charge(request).await {
Ok(()) => Ok(Paid {
order: request.order,
customer: request.customer.clone(),
lines: request.lines.clone(),
}),
// Past the rate limit: a redelivery right now would be refused again.
Err(Refusal::Busy) => Err(HandlerOutcome::retry_after(BACKOFF)),
// No answer: try again at once, up to the attempt cap.
Err(Refusal::Unavailable) => Err(HandlerOutcome::retry()),
// A refused card stays refused.
Err(Refusal::Declined) => {
tracing::warn!(order = request.order, "card declined, payment dropped");
Err(HandlerOutcome::drop())
}
}
}

Ограничение попыток и тема недоставленных сообщений​

max_attempts считает все доставки одного сообщения, включая первую. Пятая доставка не переобрабатывается: dead_letter публикует её в payments.dead с полезной нагрузкой и заголовками в том виде, в каком они пришли. Роутер декодирует запросы из CBOR и так же кодирует ответ.

payments.rs
/// The payments side's subscriber: five deliveries at most, then the dead-letter topic.
pub(crate) fn routes() -> impl RouterDef<MemoryBroker, Shop> {
Router::new()
.with_codec(INTERNAL)
.include(charge)
.max_attempts(nonzero!(5u32))
.dead_letter("payments.dead")
.out_reply(Publish)
.codec(INTERNAL)
.build()
}

In-memory брокер сам доставляет сообщение повторно после задержки, которую запросил retry_after. Если брокер не умеет откладывать доставку, рантайм публикует копию сообщения обратно в подписку. Подробнее об этом - в разделе куда уходит копия для переобработки. Глава 8 проверяет все три пути, включая задержку, и не ждёт её истечения.