背景: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,79 @@
|
||||
# 队列监控接入说明(A6-INF-01)
|
||||
|
||||
本目录提供 BullMQ 队列与 Outbox 投递的 Prometheus 告警规则。M0 阶段**只交付指标端点与规则文件**,
|
||||
本机尚未部署 Prometheus / Grafana(技术架构 §4 的观测套件在 T1 阶段落地),后续按下面的步骤接入。
|
||||
|
||||
## 1. 指标端点
|
||||
|
||||
| 用途 | 地址 | 说明 |
|
||||
| --------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| Prometheus 抓取 | `GET /api/v1/metrics` | `text/plain; version=0.0.4; charset=utf-8`,由 `apps/api/src/infra/queue/queue-metrics.controller.ts` 提供 |
|
||||
| 队列健康检查 | `GET /api/v1/health/queues` | 9 个队列的计数 / 队首等待秒数 / worker 心跳;Redis 不可用返回 503(错误码 50002) |
|
||||
|
||||
`/health/queues` 的 HTTP 状态:依赖异常 503;`status=degraded`(存在积压但 worker 心跳缺失或陈旧)仍返回 200,由告警规则负责通知。
|
||||
指标采集失败不会让 `/metrics` 返回 5xx,而是输出 `#` 注释行并记日志,保证抓取链不断。
|
||||
|
||||
> 注意:`/health`、`/metrics` 均为**只读公开端点**(`@Public()`),部署时应在网关限制为内网访问;
|
||||
> 进程启动时 `@nestjs/swagger` 会扫描控制器,`/metrics` 已用 `@ApiExcludeController()` 排除出 OpenAPI 契约。
|
||||
|
||||
## 2. 指标清单
|
||||
|
||||
| 指标 | 类型 | 标签 | 含义 |
|
||||
| ------------------------------------- | ----- | ----------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| `lingxi_queue_jobs` | gauge | `queue`、`state`(waiting/active/completed/failed/delayed) | 队列内各状态任务数 |
|
||||
| `lingxi_queue_oldest_waiting_seconds` | gauge | `queue` | 队首等待任务的等待秒数(无等待任务为 0) |
|
||||
| `lingxi_queue_failure_ratio` | gauge | `queue` | `failed/(failed+completed)`,无样本为 0 |
|
||||
| `lingxi_worker_heartbeat_age_seconds` | gauge | `queue` | worker 心跳年龄(秒);缺失为 `-1` |
|
||||
| `lingxi_outbox_pending_total` | gauge | — | `outbox_events.published_at IS NULL` 的事件数 |
|
||||
| `lingxi_outbox_dead_letter_total` | gauge | — | `outbox_events.last_error='dead_lettered'` 的累计事件数 |
|
||||
|
||||
心跳 key:`lingxi:worker:heartbeat:<queueName>`(worker 每 10s 续写,TTL 30s)。
|
||||
|
||||
> 队列名口径:指标 / 告警 / 心跳 / `QUEUE` 环境变量都用**逻辑名**(`media:verify` 这类 `<域>:<动作>`);
|
||||
> BullMQ 5.x 禁止队列名与自定义 jobId 含冒号,因此 BullMQ 内部物理队列名把 `:` 换成 `-`(`media-verify`),
|
||||
> jobId 用 `-` 连接(`<eventId>-<itemId>`)。排查 Redis key 时按物理名找,看指标与告警时按逻辑名。
|
||||
|
||||
## 3. Prometheus 接入步骤
|
||||
|
||||
1. 启动 Prometheus(本机示例;生产按运维 Runbook 挂到 Compose):
|
||||
|
||||
```yaml
|
||||
# prometheus.yml
|
||||
scrape_configs:
|
||||
- job_name: lingxi-api
|
||||
metrics_path: /api/v1/metrics
|
||||
static_configs:
|
||||
- targets: ['127.0.0.1:3000']
|
||||
```
|
||||
|
||||
```bash
|
||||
prometheus --config.file=prometheus.yml \
|
||||
--rules.file=infra/monitoring/queue-alerts.yml # 或使用 rule_files 引入
|
||||
```
|
||||
|
||||
> `rule_files` 更常用:在 `prometheus.yml` 写 `rule_files: ['/etc/prometheus/queue-alerts.yml']`。
|
||||
> 本仓库的 CI / 本地校验可用 `promtool check rules infra/monitoring/queue-alerts.yml`。
|
||||
|
||||
2. 校验规则语法:
|
||||
|
||||
```bash
|
||||
promtool check rules infra/monitoring/queue-alerts.yml
|
||||
```
|
||||
|
||||
3. 验证数据:`curl -s http://127.0.0.1:3000/api/v1/metrics | grep lingxi_`,
|
||||
再在 Prometheus UI → Graph 查询 `lingxi_queue_oldest_waiting_seconds`。
|
||||
|
||||
## 4. 告警规则
|
||||
|
||||
| 告警 | 表达式 | 持续 | 级别 | 处置 |
|
||||
| ------------------------------ | ------------------------------------------- | ---- | -------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| `LingxiQueueBacklog` | `lingxi_queue_oldest_waiting_seconds > 600` | 5m | warning | 确认对应队列组的 worker 是否启动 / 并发是否被长任务占满;必要时扩容 worker(`QUEUE=<组名>` 单独扩容) |
|
||||
| `LingxiQueueHighFailureRatio` | `lingxi_queue_failure_ratio > 0.05` | 10m | warning | 看失败 job 的 `last_error` 与 worker 日志;区分业务失败与依赖故障 |
|
||||
| `LingxiWorkerHeartbeatMissing` | `heartbeat_age < 0` 或 `> 60` | 2m | critical | 检查 worker 进程 / `QUEUE` 分组 / Redis 连通性 |
|
||||
| `LingxiOutboxDeadLetter` | `lingxi_outbox_dead_letter_total > 0` | 1m | critical | 查 `outbox_events WHERE last_error='dead_lettered'`,修复 payload / 队列配置后人工补投 |
|
||||
|
||||
## 5. Grafana(后续)
|
||||
|
||||
- 数据源:Prometheus;面板建议:按 `queue` 分组的任务数堆叠图、队首等待时长、
|
||||
失败率、worker 心跳热力图、outbox 待投递 / 死信。
|
||||
- 本机暂无 Grafana 实例,M0 验收只要求 `curl /api/v1/metrics` 可读到上述指标、规则文件可加载。
|
||||
@@ -0,0 +1,52 @@
|
||||
# 灵犀 · 队列监控告警规则(A6-INF-01)
|
||||
# 指标来源:API 的 Prometheus 端点 GET /api/v1/metrics(text/plain; version=0.0.4)
|
||||
# 指标定义:lingxi_queue_jobs / lingxi_queue_oldest_waiting_seconds / lingxi_queue_failure_ratio /
|
||||
# lingxi_worker_heartbeat_age_seconds / lingxi_outbox_pending_total / lingxi_outbox_dead_letter_total
|
||||
#
|
||||
# 加载方式见同目录 README.md;阈值调整需走配置评审(R-26 精神:阈值集中管理)。
|
||||
groups:
|
||||
- name: lingxi-queue
|
||||
rules:
|
||||
# 队列积压:队首任务等待超过 10 分钟
|
||||
- alert: LingxiQueueBacklog
|
||||
expr: lingxi_queue_oldest_waiting_seconds > 600
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
component: queue
|
||||
annotations:
|
||||
summary: '队列 {{ $labels.queue }} 积压'
|
||||
description: '队列 {{ $labels.queue }} 队首任务已等待 {{ $value | humanizeDuration }}(阈值 600s,持续 5m);请检查 worker 是否在跑、并发是否被占满。'
|
||||
|
||||
# 失败率:failed/(failed+completed) 超过 5%
|
||||
- alert: LingxiQueueHighFailureRatio
|
||||
expr: lingxi_queue_failure_ratio > 0.05
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
component: queue
|
||||
annotations:
|
||||
summary: '队列 {{ $labels.queue }} 失败率偏高'
|
||||
description: '队列 {{ $labels.queue }} 失败率 {{ $value | humanizePercentage }}(阈值 5%,持续 10m);请查看失败 job 与 worker 日志。'
|
||||
|
||||
# worker 心跳缺失(-1)或超过 60s 未续写
|
||||
- alert: LingxiWorkerHeartbeatMissing
|
||||
expr: lingxi_worker_heartbeat_age_seconds < 0 or lingxi_worker_heartbeat_age_seconds > 60
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
component: worker
|
||||
annotations:
|
||||
summary: '队列 {{ $labels.queue }} 的 worker 心跳缺失'
|
||||
description: '队列 {{ $labels.queue }} 心跳年龄 {{ $value }}s(缺失为 -1,阈值为 60s,持续 2m);worker 可能已退出或 Redis 不可用。'
|
||||
|
||||
# outbox 死信:出现即需要人工介入(避免事件永久卡住)
|
||||
- alert: LingxiOutboxDeadLetter
|
||||
expr: lingxi_outbox_dead_letter_total > 0
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
component: outbox
|
||||
annotations:
|
||||
summary: 'Outbox 出现死信事件'
|
||||
description: '累计死信 {{ $value }} 条(outbox_events.last_error=dead_lettered);请核对 payload 与目标队列配置后人工补投。'
|
||||
Reference in new issue
Block a user