背景:A6-BE-01..05 与 A6-INF-01,打通「提交 → 队列 → SSE 进度 → 完成/失败 → 取消 / 仅重试失败项」闭环,为 B3 / M1 的真实任务提供统一协议。 改动: - API media 模块:POST/GET /media-tasks、详情、SSE(Last-Event-ID 续推 + 15s 心跳 + 800ms 落库轮询)、取消(只影响未开始项)、仅重试失败项;Idempotency-Key 24h(异步提交 202;同 key 同 body 返回同一任务且不重复审计,不同 body 40902);范围外统一 404; - 队列骨架:BullMQ 5 九队列(5 组独立启停)、Outbox dispatcher(Redis 锁 + FOR UPDATE SKIP LOCKED + jobId 幂等 + 指数退避/死信)、worker 按 QUEUE 启停、demo 演练处理器(事务 + 行锁 + 取消/重试/幂等)、优雅退出与心跳; - 监控:GET /api/v1/health/queues、GET /api/v1/metrics(Prometheus 0.0.4)、infra/monitoring 告警规则与接入说明; - 通用:AuditInterceptor 支持幂等重放 skipAudit(默认行为不变)、AppModule 接线、.env.example 的 QUEUE 语义、bullmq 5.81.5 / worker @prisma/client / worker 覆盖率依赖。 影响面:OpenAPI 新增 6 个端点(openapi:diff 无破坏性变更);API 进程新增 outbox dispatcher 与 metrics/health 路由;worker 新增 BullMQ 消费;未改旧审计导出链路与既有接口。 回滚:回滚提交并停止 worker 新队列消费;task/outbox 新表数据不影响旧功能,可按需保留。 验证:api 单测 257、集成 92(连跑 2 次)、worker 27、shared 197;typecheck / lint / format:check / build / openapi:diff 全绿;本地真实 worker 启用 9 队列心跳,/health/queues、/metrics 实测,/media-tasks 未登录 401。
This commit is contained in:
1 parent
4a29cf815e
commit
633765f10f
70 files changed
+8674
-45
No files matched your search
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 队列 / Outbox 投递协议常量(技术架构 §2.4 / §3.7,A6-BE-02 / A6-BE-05 / A6-INF-01)。
|
||||
*
|
||||
* key 统一 `lingxi:` 前缀;心跳 key 与 worker 侧(apps/worker/src/heartbeat.ts)保持一致,
|
||||
* 跨 app 契约变更时必须两端同步。
|
||||
*/
|
||||
|
||||
/** Outbox 投递分布式锁:同一时刻只允许一个 dispatcher 抢到本轮投递权 */
|
||||
export const OUTBOX_DISPATCH_LOCK_KEY = 'lingxi:lock:outbox-dispatch';
|
||||
|
||||
/** 锁 TTL:既覆盖单轮投递耗时,又保证实例崩溃后 5s 内可被其他实例接管 */
|
||||
export const OUTBOX_DISPATCH_LOCK_TTL_MS = 5_000;
|
||||
|
||||
/** 单轮最多认领的事件数(避免一轮占满连接 / 内存) */
|
||||
export const OUTBOX_DISPATCH_BATCH_SIZE = 20;
|
||||
|
||||
/** 死信阈值:投递尝试达到该次数后标记 dead_lettered,避免单条事件永久阻塞 outbox 表 */
|
||||
export const OUTBOX_MAX_ATTEMPTS = 10;
|
||||
|
||||
/** 默认调度间隔(可被环境变量 OUTBOX_DISPATCH_INTERVAL_MS 覆盖) */
|
||||
export const OUTBOX_DISPATCH_DEFAULT_INTERVAL_MS = 1_000;
|
||||
|
||||
/** 死信标记:写入 outbox_events.last_error,便于监控与人工排查 */
|
||||
export const OUTBOX_DEAD_LETTER_MARKER = 'dead_lettered';
|
||||
|
||||
/** worker 心跳 key 前缀:`lingxi:worker:heartbeat:<queueName>` */
|
||||
export const WORKER_HEARTBEAT_KEY_PREFIX = 'lingxi:worker:heartbeat:';
|
||||
|
||||
/** worker 心跳 TTL(秒);worker 每 10s 续写一次,超过 TTL 视为心跳缺失 */
|
||||
export const WORKER_HEARTBEAT_TTL_SECONDS = 30;
|
||||
|
||||
/** 心跳年龄超过 TTL 的倍数视为陈旧(用于健康检查降级判定) */
|
||||
export const WORKER_HEARTBEAT_STALE_MULTIPLIER = 2;
|
||||
|
||||
/** 单轮投递 / 单次队列命令的超时,避免 Redis 异常时无限挂起 */
|
||||
export const QUEUE_OPERATION_TIMEOUT_MS = 5_000;
|
||||
|
||||
/** Lua:仅当锁 value 与 token 相等时释放,避免误删其他实例持有的锁 */
|
||||
export const RELEASE_LOCK_SCRIPT =
|
||||
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
|
||||
Reference in new issue
Block a user