feat(server): outbox relay 改为 LISTEN/NOTIFY + 30s 兜底轮询

- EventBus::publish() 持久化后执行 NOTIFY outbox_channel
- outbox relay 使用 sqlx::PgListener 监听 + tokio::select! 竞争
- 30s 兜底轮询防止 NOTIFY 丢失,断线自动重连
- 轮询间隔从 5s 提升到 30s,事件延迟降至 <100ms
This commit is contained in:
iven
2026-04-27 17:50:38 +08:00
parent 8d55d98f4f
commit d31d7beb1f
5 changed files with 79 additions and 10 deletions

View File

@@ -1,5 +1,5 @@
use chrono::Utc;
use sea_orm::{ActiveModelTrait, Set};
use sea_orm::{ActiveModelTrait, ConnectionTrait, Set};
use serde::{Deserialize, Serialize};
use tokio::sync::{broadcast, mpsc};
use tracing::{error, info};
@@ -70,7 +70,7 @@ impl EventBus {
}
/// 发布事件:先持久化到 domain_events 表pending 状态),再内存广播,
/// 最后更新为 published。
/// 最后更新为 published 并 NOTIFY outbox relay
///
/// 两阶段提交保证:即使广播后服务崩溃,事件仍为 pending 状态,
/// 重启后 outbox relay 会重新广播。
@@ -110,6 +110,15 @@ impl EventBus {
if let Err(e) = active.update(db).await {
tracing::warn!(event_id = %event_id, error = %e, "领域事件状态更新为 published 失败");
}
// 4. NOTIFY outbox relay通知 outbox relay 有新事件到达)
let notify_sql = sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
format!("NOTIFY outbox_channel, '{}'", event_id),
);
if let Err(e) = db.execute(notify_sql).await {
tracing::debug!(event_id = %event_id, error = %e, "NOTIFY outbox_channel 失败(非致命)");
}
}
/// 仅内存广播(不持久化,用于内部测试等场景)。