第四章 组件职责清单

队列调度器、优先级管理器、并发控制器与状态追踪器是 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切换到加权轮询)时,只需替换调度器实现,其余组件零修改。