第四章 组件职责清单
队列调度器、优先级管理器、并发控制器与状态追踪器是 DownloadManager 队列核心层的四个基础构件。它们共同决定了任务的调度顺序、并发水位与状态可见性。本章逐一拆解每个组件的职责边界、分层位置、交互链路,并给出工程落地时的选型权衡与风险应对。
4.1 模块边界与分层
四个组件均位于 队列管理层(Queue Core),处于任务提交层与执行器层之间。它们属于同一水平分层,但职责域互不重叠:
| 组件 | 职责域 | 不负责 |
|---|---|---|
| 队列调度器 | 决定任务执行顺序 | 优先级赋值、并发限制、状态存储 |
| 优先级管理器 | 维护/调整任务优先级 | 调度算法、并发控制、持久化 |
| 并发控制器 | 保证同时执行的任务数≤上限 | 顺序选择、优先级计算、状态变更 |
| 状态追踪器 | 记录任务生命周期状态 | 调度逻辑、优先级变更、并发许可 |
边界清晰是避免循环依赖和职责混乱的根基。每个组件只通过定义好的接口与相邻组件通信。
4.2 组件职责详解
4.2.1 队列调度器(Queue Scheduler)
核心职责:从待执行任务集中选出下一个应执行的任务。典型调度策略包括:
- FIFO:按提交时间排序,简单公平但无法处理优先级。
- 优先级抢占:高优先级任务可插队,需配合优先级管理器。
- 加权轮询:按优先级或权重分配时间片,避免饥饿。
关键约束:调度动作必须触发状态变更(由状态追踪器记录);调度器不直接持有任务列表,而是通过 PollableQueue 接口从状态追踪器中过滤出就绪任务。
4.2.2 优先级管理器(Priority Manager)
核心职责:为每个任务分配初始优先级、处理动态调整(如老化、用户升级)。
- 初始赋值:可根据任务类型、资源要求、用户等级等规则设置。
- 老化算法:每隔一段时间将等待超时的任务优先级提升一个台阶,防止饥饿。
- 冲突解决:当两个任务优先级相同时,由调度器使用次关键字(如创建时间)决定顺序。
风险提示: 老化粒度太粗会导致频繁重排序,增加调度开销;太细则缓解饥饿效果不足。建议使用指数退避式老化(每次等待时间翻倍后提升一级)。
4.2.3 并发控制器(Concurrency Controller)
核心职责:维护全局并发许可,确保活跃任务数不超过 max_concurrency。
- 信号量/MVar:经典实现,每次调度前 tryAcquire,执行完毕 release。
- 工作池:固定数量 worker 从调度器取出任务直接执行。
- 动态调整:根据系统负载(CPU、IO)实时调整最大并发数,需引入反馈控制。
与调度器的协作:调度器在选定任务后,必须先尝试获取并发许可;获取成功后方可通知执行器,否则任务仍留在就绪队列。
4.2.4 状态追踪器(State Tracker)
核心职责:维护每个任务的精确状态转换,并提供事务性查询与更新能力。
典型状态机:Pending → Ready → Running → Completed | Failed(含暂停、重试等分支)。
- 存储:内存中可使用
HashMap<TaskId, TaskState>,需要持久化时接入数据库。 - 原子性:状态变更必须原子执行(如
CAS或锁),防止并发更新导致状态丢失。 - 查询接口:支持按状态、优先级范围、提交时间等过滤,供调度器拉取就绪任务列表。
4.3 数据流 / 控制流
下图展示了四个组件之间的核心交互路径:
信任链路:调度器依赖状态追踪器提供“就绪任务快照”,依赖优先级管理器给出“排序权重”,依赖并发控制器返回“许可是否成功”。三者之间通过明确定义的接口契约交互,不共享内部状态。
4.4 选型权衡
| 决策点 | 方案A | 方案B | 推荐场景 |
|---|---|---|---|
| 调度算法 | 优先级抢占(可能 starvation) | 加权轮询 + 老化 | 需要严格区分 SLA 时选 A;避免饥饿选 B |
| 并发控制 | Semaphore(灵活) | 固定工作池(简单) | 动态扩缩容需要选 A;资源固定、希望可预测选 B |
| 状态存储 | 内存 HashMap(高性能) | 数据库(持久化、多进程) | 单进程测试用内存;生产环境必须持久化 |
| 优先级老化方案 | 固定间隔提升一级 | 指数退避(等待越长升级越快) | 高优先级任务突发不多时选固定;防止长尾饥饿选指数退避 |
4.5 风险点与应对
- 优先级反转: 低优先级任务持有锁被高优先级任务等待。应对:引入优先级继承协议(Priority Inheritance Protocol),由并发控制器检查锁持有者优先级。
- 并发死锁: 调度器与并发控制器循环等待。应对:统一加锁顺序,或使用无锁信号量(如
java.util.concurrent.Semaphore)。 - 状态不一致: 状态追踪器与执行器之间的更新竞争。应对:所有状态变更必须通过状态追踪器的原子 CAS 接口,执行器回调必须重试直至成功。
- 饥饿: 高优先级任务持续抢占,低优先级任务永远无法执行。应对:启用老化机制,并在调度器中设置时间片上限。
4.6 落地步骤
| 步骤 | 内容 | 交付件 |
|---|---|---|
| 1 | 定义 TaskState 枚举与状态转换矩阵 | 状态机文档 + 单元测试 |
| 2 | 实现 StateTracker(内存版)并提供原子更新接口 | StateTracker.java / .rs / .py |
| 3 | 实现 PriorityManager(初始赋值 + 老化线程) | PriorityManager + TimerTask |
| 4 | 实现 ConcurrencyController(基于信号量) | ConcurrencyController + 测试用例 |
| 5 | 实现 QueueScheduler,接入前三组件 | 队列调度主逻辑 + 集成测试 |
| 6 | 压力测试:高并发下验证状态一致性、无饥饿、无死锁 | 测试报告 + 边界用例 |
4.7 验收标准
- 功能性: 任务按照优先级顺序执行,同优先级 FIFO;高优先级任务可插队(若启用抢占)。
- 并发性: 活跃执行任务数始终 ≤
max_concurrency;释放许可后下一个就绪任务立即被调度。 - 稳定性: 连续运行 24 小时无饥饿、无状态不一致、无死锁(使用多组随机线性代数负载测试)。
- 可观测: 状态追踪器支持按 TaskId 查询任意时刻状态,并提供历史转换日志。
核心原则: 四个组件之间只通过接口契约耦合,禁止直接访问对方内部数据结构。这样后续替换算法(如从FIFO切换到加权轮询)时,只需替换调度器实现,其余组件零修改。