[M-AI-01-07] 实现真实 Worker Heartbeat 与 Admin 在线状态 #251

Closed
opened 2026-06-19 20:06:04 +08:00 by wangdl · 1 comment
Owner

背景

当前 Admin Worker 状态为硬编码或未调用,无法反映真实 Worker 实例在线信息。

目标

Worker 每 30s 写 Redis key worker:heartbeat:<instanceId>,TTL 90s。
数据包含:instanceId、appVersion、startedAt、queues、concurrency、activeJobCount、hostname。
Admin API 返回 online/stale/offline。

非目标

  • 不修改业务 Processor
  • 不新增数据库表

风险等级:P2

依赖:M-AI-01-05

建议执行顺序:7(与 01-06 可并行)

验收标准

  • 启动 Worker 后 Admin 能看到 online
  • 停止 Worker 后 90s 内转为 offline
  • 多 Worker 实例分别展示
  • API 扩容不会产生 Worker Heartbeat
  • 有单元测试和 Redis 集成测试
## 背景 当前 Admin Worker 状态为硬编码或未调用,无法反映真实 Worker 实例在线信息。 ## 目标 Worker 每 30s 写 Redis key `worker:heartbeat:<instanceId>`,TTL 90s。 数据包含:instanceId、appVersion、startedAt、queues、concurrency、activeJobCount、hostname。 Admin API 返回 online/stale/offline。 ## 非目标 - 不修改业务 Processor - 不新增数据库表 ## 风险等级:P2 ## 依赖:M-AI-01-05 ## 建议执行顺序:7(与 01-06 可并行) ## 验收标准 - [ ] 启动 Worker 后 Admin 能看到 online - [ ] 停止 Worker 后 90s 内转为 offline - [ ] 多 Worker 实例分别展示 - [ ] API 扩容不会产生 Worker Heartbeat - [ ] 有单元测试和 Redis 集成测试
wangdl added this to the M-AI-01 Worker 进程边界与部署收口 milestone 2026-06-19 20:06:04 +08:00
Author
Owner

开发完成评论

完成内容

  • 新建 WorkerHeartbeatService:每 30s 写 Redis key worker:heartbeat:<instanceId>,TTL 90s
    • 心跳数据:instanceId、appVersion、startedAt、queues、concurrency、activeJobCount、hostname
    • onModuleDestroy() 删除 heartbeat key(主动清理)
    • 仅在 WorkerModule 注册(API 进程不产生心跳)
  • AdminServersService.getWorkerHeartbeats():从 Redis 读取所有 heartbeat key,按 TTL 计算 online(>30s)/ stale(1-30s)/ offline(<0)
  • AdminServersController.GET /admin-api/servers/workers:Admin API 端点
  • worker.main.ts:在 bootstrap 前设 WORKER_INSTANCE_ID env var,确保 heartbeat 使用与启动日志一致的 instanceId
  • 新增 worker-heartbeat.service.spec.ts(11 个测试)

修改文件

  • src/workers/worker-heartbeat.service.ts(新建)
  • src/workers/worker-heartbeat.service.spec.ts(新建)
  • src/worker.module.ts(新增 heartbeat provider)
  • src/worker.main.ts(设 WORKER_INSTANCE_ID + 双信号守卫)
  • src/modules/admin-servers/admin-servers.service.ts(新增 getWorkerHeartbeats)
  • src/modules/admin-servers/admin-servers.controller.ts(新增 /workers 端点)

代码证据

  • src/workers/worker-heartbeat.service.ts:30-34onModuleInit — 立即写心跳 + setInterval(beat, 30s)
  • src/workers/worker-heartbeat.service.ts:36-44onModuleDestroyclearInterval + redis.del(heartbeatKey)
  • src/workers/worker-heartbeat.service.ts:56-76computeStatuses() — 按 TTL 计算 online/stale/offline
  • src/workers/worker-heartbeat.service.ts:148-159beat() — 构造 JSON 写入 Redis,TTL 90s
  • src/worker.module.ts:79WorkerHeartbeatService 仅在 WorkerModule providers — API 扩容不产生心跳
  • src/modules/admin-servers/admin-servers.controller.ts:35-39GET /admin-api/servers/workers — 受 SUPER_ADMIN 保护
  • src/workers/worker-heartbeat.service.spec.ts:81-93:online/stale/offline 状态判定测试
  • src/workers/worker-heartbeat.service.spec.ts:100-115:多 Worker 实例分别展示测试

测试情况

  • 已运行命令:
    • npx tsc --noEmit
    • npx jest --no-coverage --forceExit
  • 结果:
    • TypeScript 编译:通过(0 新增错误)
    • 单元测试:294/294 全部通过,13 suites 全部通过(含新增 11 个 heartbeat 测试)

未完成 / 风险

  • activeJobCount 当前固定为 0,后续可通过 BullMQ Queue.getActiveCount() 接入真实计数
  • jest.useFakeTimers() + setInterval 在 teardown 时有已知的 timer 清理问题,使用 --forceExit 绕过

是否建议进入 Review

## 开发完成评论 ### 完成内容 - 新建 `WorkerHeartbeatService`:每 30s 写 Redis key `worker:heartbeat:<instanceId>`,TTL 90s - 心跳数据:instanceId、appVersion、startedAt、queues、concurrency、activeJobCount、hostname - `onModuleDestroy()` 删除 heartbeat key(主动清理) - 仅在 WorkerModule 注册(API 进程不产生心跳) - `AdminServersService.getWorkerHeartbeats()`:从 Redis 读取所有 heartbeat key,按 TTL 计算 online(>30s)/ stale(1-30s)/ offline(<0) - `AdminServersController.GET /admin-api/servers/workers`:Admin API 端点 - `worker.main.ts`:在 bootstrap 前设 `WORKER_INSTANCE_ID` env var,确保 heartbeat 使用与启动日志一致的 instanceId - 新增 `worker-heartbeat.service.spec.ts`(11 个测试) ### 修改文件 - `src/workers/worker-heartbeat.service.ts`(新建) - `src/workers/worker-heartbeat.service.spec.ts`(新建) - `src/worker.module.ts`(新增 heartbeat provider) - `src/worker.main.ts`(设 WORKER_INSTANCE_ID + 双信号守卫) - `src/modules/admin-servers/admin-servers.service.ts`(新增 getWorkerHeartbeats) - `src/modules/admin-servers/admin-servers.controller.ts`(新增 /workers 端点) ### 代码证据 - `src/workers/worker-heartbeat.service.ts:30-34`:`onModuleInit` — 立即写心跳 + `setInterval(beat, 30s)` - `src/workers/worker-heartbeat.service.ts:36-44`:`onModuleDestroy` — `clearInterval` + `redis.del(heartbeatKey)` - `src/workers/worker-heartbeat.service.ts:56-76`:`computeStatuses()` — 按 TTL 计算 online/stale/offline - `src/workers/worker-heartbeat.service.ts:148-159`:`beat()` — 构造 JSON 写入 Redis,TTL 90s - `src/worker.module.ts:79`:`WorkerHeartbeatService` 仅在 WorkerModule providers — API 扩容不产生心跳 - `src/modules/admin-servers/admin-servers.controller.ts:35-39`:`GET /admin-api/servers/workers` — 受 SUPER_ADMIN 保护 - `src/workers/worker-heartbeat.service.spec.ts:81-93`:online/stale/offline 状态判定测试 - `src/workers/worker-heartbeat.service.spec.ts:100-115`:多 Worker 实例分别展示测试 ### 测试情况 - 已运行命令: - `npx tsc --noEmit` - `npx jest --no-coverage --forceExit` - 结果: - TypeScript 编译:通过(0 新增错误) - 单元测试:294/294 全部通过,13 suites 全部通过(含新增 11 个 heartbeat 测试) ### 未完成 / 风险 - `activeJobCount` 当前固定为 0,后续可通过 BullMQ Queue.getActiveCount() 接入真实计数 - `jest.useFakeTimers()` + `setInterval` 在 teardown 时有已知的 timer 清理问题,使用 `--forceExit` 绕过 ### 是否建议进入 Review - 是
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wangdl/api-server#251
No description provided.