跳到主要内容
本页尚未翻译

下面显示的是英文原文。

PulsarPosition

PulsarPosition names a place in a topic's retained log: either end, a delivered message, or a publish time.

Pulsar keeps a topic's messages as a log, so a subscription can be moved through it, at startup with start_at or from a handler with a seek. PulsarPosition::earliest() is the oldest message the topic still retains, and every retained message is delivered again from there. PulsarPosition::latest() is the tip of the log: only messages published after the move are delivered. PulsarPosition::timestamp(millis) is the first message published at or after that time, in milliseconds since the Unix epoch. A delivery's own place is a position as well: Ctx<Position> binds it through the Position key, a copy the handler can keep. A seek to such a position delivers that message again, then the rest of the log in order. A position applies to every topic and every partition the subscription reads. A non-persistent topic retains nothing, so a move over it replays nothing.

Replay and seeking rebuilds balances from a ledger on every startup and indexes every order again with a seek.

Example​

A projection bookmarks the entry that closes a day; a rebuild request moves the ledger subscription back to it.

/// Where the projection last closed a day: the ledger subscription's seeker and the position
/// of the entry that closed it.
#[derive(Clone, Default)]
struct Bookmark(Arc<Mutex<Option<(PulsarSeeker, PulsarPosition)>>>);

#[derive(FromRef)]
struct AppState {
bookmark: Bookmark,
}

/// Projects each ledger entry and bookmarks the entry that closes a day.
#[subscriber(PulsarSubscription::new("ledger", "projection"))]
async fn project(
entry: &Entry,
Ctx(position): Ctx<Position>,
Ctx(seeker): Ctx<SeekHandle>,
State(bookmark): State<Bookmark>,
) -> HandlerOutcome {
tracing::info!(entry.id, "projected");
if entry.closes_day {
*bookmark.0.lock().await = Some((seeker, position));
}
HandlerOutcome::ack()
}

/// Replays the ledger from the last closed day: the entry at the bookmark comes again, then
/// every entry after it, in order.
#[subscriber(PulsarSubscription::new("ledger.rebuild", "projection"))]
async fn rebuild(request: &Rebuild, State(bookmark): State<Bookmark>) -> HandlerOutcome {
let Some((seeker, position)) = bookmark.0.lock().await.clone() else {
return HandlerOutcome::ack();
};
tracing::info!(reason = %request.reason, "rebuilding from the last closed day");
match seeker.seek(position).await {
Ok(()) => HandlerOutcome::ack(),
Err(err) => {
tracing::warn!(%err, "the ledger cannot be rewound");
HandlerOutcome::retry()
}
}
}