This commit is contained in:
@@ -18,7 +18,7 @@ TBD - created by archiving change deployment-self-init. Update Purpose after arc
|
||||
#### Scenario: 嵌入配置包含完整结构
|
||||
|
||||
- **WHEN** 读取嵌入配置
|
||||
- **THEN** 配置包含所有配置节:server、database、redis、storage、logging、queue、jwt、middleware
|
||||
- **THEN** 配置包含所有配置节:server、database、redis、storage、logging、queue、jwt、middleware、worker
|
||||
|
||||
### Requirement: 环境变量覆盖
|
||||
|
||||
@@ -42,6 +42,31 @@ TBD - created by archiving change deployment-self-init. Update Purpose after arc
|
||||
- **WHEN** 未设置某个配置的环境变量
|
||||
- **THEN** 使用嵌入配置中的默认值
|
||||
|
||||
### Requirement: Worker 运行配置
|
||||
|
||||
系统 SHALL 在嵌入默认配置中提供 `worker` 配置节,并支持通过 `JUNHONG_WORKER_ROLE` 和 `JUNHONG_WORKER_INSTANCE_NAME` 环境变量覆盖。
|
||||
|
||||
默认配置:
|
||||
- `worker.role`: `all`
|
||||
- `worker.instance_name`: 空字符串
|
||||
|
||||
#### Scenario: 默认 Worker 角色
|
||||
- **WHEN** 未设置 `JUNHONG_WORKER_ROLE`
|
||||
- **THEN** `config.Load()` 返回的 `Config.Worker.Role` 为 `all`
|
||||
|
||||
#### Scenario: 环境变量覆盖 Worker 角色
|
||||
- **WHEN** 设置环境变量 `JUNHONG_WORKER_ROLE=consumer`
|
||||
- **THEN** `config.Load()` 返回的 `Config.Worker.Role` 为 `consumer`
|
||||
|
||||
#### Scenario: 环境变量覆盖实例名称
|
||||
- **WHEN** 设置环境变量 `JUNHONG_WORKER_INSTANCE_NAME=worker-consumer-1`
|
||||
- **THEN** `config.Load()` 返回的 `Config.Worker.InstanceName` 为 `worker-consumer-1`
|
||||
|
||||
#### Scenario: Worker 角色参与配置校验
|
||||
- **WHEN** 设置环境变量 `JUNHONG_WORKER_ROLE=invalid`
|
||||
- **THEN** `config.Load()` 返回错误
|
||||
- **AND** 错误信息明确指出 `worker.role` 只允许 `all`、`leader`、`consumer`
|
||||
|
||||
### Requirement: 配置优先级
|
||||
|
||||
系统 SHALL 按以下优先级应用配置(高到低):
|
||||
@@ -130,4 +155,3 @@ TBD - created by archiving change deployment-self-init. Update Purpose after arc
|
||||
- **WHEN** 读取嵌入配置
|
||||
- **THEN** 敏感配置项的值为空字符串
|
||||
- **AND** 必须通过环境变量提供实际值
|
||||
|
||||
|
||||
130
openspec/specs/worker-role-management/spec.md
Normal file
130
openspec/specs/worker-role-management/spec.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# worker-role-management Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change split-worker-roles. Update Purpose after archive.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Worker 角色配置
|
||||
|
||||
系统 SHALL 支持 `all`、`leader`、`consumer` 三种 Worker 运行角色,并在启动时根据配置决定当前进程职责。角色值 MUST 使用小写英文字符串,非法角色 MUST 使配置加载失败并返回明确错误。
|
||||
|
||||
#### Scenario: 默认兼容模式
|
||||
- **WHEN** 未设置 `JUNHONG_WORKER_ROLE`
|
||||
- **THEN** Worker 使用默认角色 `all`
|
||||
- **AND** 启动行为与变更前单实例 worker 保持一致
|
||||
|
||||
#### Scenario: 非法角色启动失败
|
||||
- **WHEN** 设置 `JUNHONG_WORKER_ROLE=scheduler`
|
||||
- **THEN** `config.Load()` 返回配置校验错误
|
||||
- **AND** Worker 不进入 Redis、PostgreSQL 或 Asynq 启动流程
|
||||
|
||||
#### Scenario: 实例名称可为空
|
||||
- **WHEN** 未设置 `JUNHONG_WORKER_INSTANCE_NAME`
|
||||
- **THEN** Worker 可以正常启动
|
||||
- **AND** 启动日志中的实例名称为空或显示默认占位值
|
||||
|
||||
### Requirement: 按角色启停 Worker 模块
|
||||
|
||||
系统 SHALL 将 Worker 启动流程拆分为共享依赖初始化和角色模块启停两部分。所有角色 MUST 启动 Asynq Worker Server;只有 `all` 和 `leader` SHALL 启动主动调度与初始化模块。
|
||||
|
||||
**共享模块**:
|
||||
- Redis 客户端
|
||||
- PostgreSQL 连接
|
||||
- Storage 服务(可选)
|
||||
- Gateway 客户端(可选)
|
||||
- Asynq Client
|
||||
- `BootstrapWorker`
|
||||
- Asynq Worker Server
|
||||
- `PollingConfigManager`
|
||||
- `PollingQueueManager`
|
||||
- `PollingBase`
|
||||
- `PollingLifecycleService`
|
||||
- TaskHandler 及全部任务 Handler 注册
|
||||
|
||||
**单例模块**:
|
||||
- `PollingInitializer`
|
||||
- `PollingScheduler`
|
||||
- Asynq Scheduler
|
||||
|
||||
#### Scenario: all 角色启动全部模块
|
||||
- **WHEN** `worker.role=all`
|
||||
- **THEN** Worker 启动 Asynq Worker Server
|
||||
- **AND** 启动 `PollingInitializer`
|
||||
- **AND** 启动 `PollingScheduler`
|
||||
- **AND** 启动 Asynq Scheduler
|
||||
|
||||
#### Scenario: leader 角色启动全部模块
|
||||
- **WHEN** `worker.role=leader`
|
||||
- **THEN** Worker 启动 Asynq Worker Server
|
||||
- **AND** 启动 `PollingInitializer`
|
||||
- **AND** 启动 `PollingScheduler`
|
||||
- **AND** 启动 Asynq Scheduler
|
||||
- **AND** 日志表达该实例承担单例主动调度职责
|
||||
|
||||
#### Scenario: consumer 角色只启动任务消费模块
|
||||
- **WHEN** `worker.role=consumer`
|
||||
- **THEN** Worker 启动 Asynq Worker Server
|
||||
- **AND** 注册全部任务 Handler
|
||||
- **AND** 不启动 `PollingInitializer`
|
||||
- **AND** 不启动 `PollingScheduler`
|
||||
- **AND** 不启动 Asynq Scheduler
|
||||
|
||||
### Requirement: consumer 保留轮询任务运行依赖
|
||||
|
||||
`consumer` 角色虽然不主动调度,但系统 SHALL 保留轮询任务 Handler 所需依赖,使其可以正常消费 Leader 投递的轮询任务并在任务完成后重新入队。
|
||||
|
||||
#### Scenario: consumer 消费轮询任务后可重新入队
|
||||
- **WHEN** `consumer` 实例消费 `TaskTypePollingCarddata` 任务
|
||||
- **THEN** Handler 可通过 `PollingBase` 读取轮询配置和 Redis 并发控制
|
||||
- **AND** Handler 可通过 `PollingQueueManager` 将卡重新写回分片 Sorted Set
|
||||
|
||||
#### Scenario: consumer 可处理导入任务的轮询生命周期回调
|
||||
- **WHEN** `consumer` 实例消费 IoT 卡导入任务并创建新卡
|
||||
- **THEN** 导入任务可通过 `PollingLifecycleService` 将新卡按配置加入轮询分片队列
|
||||
- **AND** 该操作不依赖本实例启动 `PollingScheduler`
|
||||
|
||||
### Requirement: Worker 启动日志
|
||||
|
||||
系统 SHALL 在 Worker 启动时输出当前角色、实例名称、启用模块和禁用模块,日志消息 MUST 使用中文,便于部署后通过日志确认实际运行职责。
|
||||
|
||||
#### Scenario: consumer 日志显示禁用单例模块
|
||||
- **WHEN** `worker.role=consumer`
|
||||
- **THEN** 启动日志包含 `Worker 角色`
|
||||
- **AND** 启动日志包含 `实例名称`
|
||||
- **AND** 启动日志显示已禁用 `polling_initializer`、`polling_scheduler`、`asynq_scheduler`
|
||||
|
||||
#### Scenario: leader 日志显示启用单例模块
|
||||
- **WHEN** `worker.role=leader`
|
||||
- **THEN** 启动日志显示已启用 `queue_server`、`polling_initializer`、`polling_scheduler`、`asynq_scheduler`
|
||||
|
||||
### Requirement: 按启动状态优雅关闭
|
||||
|
||||
系统 SHALL 只关闭当前角色实际启动过的后台模块,避免 `consumer` 对未启动模块执行 `Stop` 或 `Shutdown`。
|
||||
|
||||
#### Scenario: consumer 关闭不访问未启动调度器
|
||||
- **WHEN** `consumer` 收到退出信号
|
||||
- **THEN** Worker 关闭 Asynq Worker Server
|
||||
- **AND** 不调用未启动的 `PollingScheduler.Stop`
|
||||
- **AND** 不调用未启动的 Asynq Scheduler `Shutdown`
|
||||
|
||||
#### Scenario: leader 关闭全部已启动模块
|
||||
- **WHEN** `leader` 收到退出信号
|
||||
- **THEN** Worker 关闭 Asynq Scheduler
|
||||
- **AND** 停止 `PollingScheduler`
|
||||
- **AND** 优雅关闭 Asynq Worker Server
|
||||
|
||||
### Requirement: 多实例部署语义
|
||||
|
||||
系统 SHALL 支持“1 个 `leader` + N 个 `consumer`”的部署形态。部署时 SHOULD 固定只有一个 `leader` 或 `all` 实例承担主动调度职责,横向扩容 SHALL 优先增加 `consumer`。
|
||||
|
||||
#### Scenario: 新增 consumer 不复制主动调度
|
||||
- **WHEN** 生产已有一个 `leader`,再新增一个 `consumer`
|
||||
- **THEN** 定时任务仍只由 `leader` 主动提交
|
||||
- **AND** 轮询全量初始化仍只由 `leader` 执行
|
||||
- **AND** `consumer` 只从 Asynq 队列消费已有任务
|
||||
|
||||
#### Scenario: 回滚到单实例兼容模式
|
||||
- **WHEN** 多实例部署出现问题并停止所有 `consumer`
|
||||
- **THEN** 保留一个 `leader` 可继续运行完整 Worker 功能
|
||||
- **AND** 必要时可将唯一实例配置切回 `all` 恢复兼容模式
|
||||
Reference in New Issue
Block a user