4. 升级任务调度策略设计

任务目标

设计一个IoT终端OTA升级任务的调度策略模块,支持分批升级、定时升级、按设备组升级三种策略,并在调度时考虑网络负载与服务器压力约束。调度器根据策略生成可执行的升级批次计划,供后续下发模块消费。

核心设计

调度器接收升级任务请求(含设备组、目标版本、策略参数),通过统一策略接口计算批次划分与执行时间窗。网络负载与服务器压力作为全局约束,在批次划分时动态限制每批设备数量与并发窗口。实现采用Python,便于直接运行验证调度逻辑。

关键完整代码

from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import List, Dict, Optional
import uuid

# ---------- 基础数据模型 ----------
@dataclass
class Device:
    device_id: str
    group: str
    network_load: float = 0.0   # 当前网络占用率 0~1

@dataclass
class UpgradeTask:
    task_id: str
    target_version: str
    device_group: Optional[str] = None   # None表示全部设备
    batch_size: int = 50                 # 每批最大设备数
    batch_interval_min: int = 10          # 批次间隔(分钟)
    start_time: Optional[datetime] = None # 定时升级触发时间
    max_network_load: float = 0.7         # 网络负载上限
    max_server_conn: int = 100            # 服务器最大并发连接数

@dataclass
class BatchPlan:
    batch_id: str
    task_id: str
    device_ids: List[str]
    scheduled_time: datetime
    est_network_load: float
    est_server_conn: int

# ---------- 调度策略基类 ----------
class ScheduleStrategy:
    def create_plan(self, task: UpgradeTask, devices: List[Device],
                    server_conn: int) -> List[BatchPlan]:
        raise NotImplementedError

# ---------- 分批升级策略 ----------
class BatchStrategy(ScheduleStrategy):
    def create_plan(self, task, devices, server_conn):
        plans = []
        base_time = task.start_time or datetime.now()
        # 按网络负载排序,优先调度低负载设备
        sorted_devices = sorted(devices, key=lambda d: d.network_load)
        for i in range(0, len(sorted_devices), task.batch_size):
            batch_devices = sorted_devices[i:i+task.batch_size]
            # 动态计算批次网络负载与服务器连接数
            est_load = sum(d.network_load for d in batch_devices) / max(len(batch_devices), 1)
            est_conn = min(len(batch_devices), task.max_server_conn)
            # 若超过约束则缩减批次
            if est_load > task.max_network_load or est_conn > task.max_server_conn:
                batch_devices = batch_devices[:int(task.max_server_conn * 0.8)]
                est_load = sum(d.network_load for d in batch_devices) / max(len(batch_devices), 1)
                est_conn = len(batch_devices)
            plan_time = base_time + timedelta(minutes=i * task.batch_interval_min)
            plans.append(BatchPlan(
                batch_id=str(uuid.uuid4())[:8],
                task_id=task.task_id,
                device_ids=[d.device_id for d in batch_devices],
                scheduled_time=plan_time,
                est_network_load=round(est_load, 2),
                est_server_conn=est_conn
            ))
        return plans

# ---------- 定时升级策略(在分批基础上增加时间窗) ----------
class ScheduledStrategy(BatchStrategy):
    def create_plan(self, task, devices, server_conn):
        if task.start_time is None:
            raise ValueError("定时升级必须指定start_time")
        return super().create_plan(task, devices, server_conn)

# ---------- 按设备组升级策略 ----------
class GroupStrategy(ScheduleStrategy):
    def create_plan(self, task, devices, server_conn):
        if task.device_group is None:
            raise ValueError("按设备组升级必须指定device_group")
        group_devices = [d for d in devices if d.group == task.device_group]
        if not group_devices:
            return []
        # 复用分批逻辑,但只针对组内设备
        group_task = UpgradeTask(
            task_id=task.task_id,
            target_version=task.target_version,
            device_group=task.device_group,
            batch_size=task.batch_size,
            batch_interval_min=task.batch_interval_min,
            start_time=task.start_time,
            max_network_load=task.max_network_load,
            max_server_conn=task.max_server_conn
        )
        return super().create_plan(group_task, group_devices, server_conn)

# ---------- 调度器入口 ----------
class UpgradeScheduler:
    def __init__(self, devices: List[Device], server_conn: int = 50):
        self.devices = devices
        self.server_conn = server_conn
        self.strategies = {
            "batch": BatchStrategy(),
            "scheduled": ScheduledStrategy(),
            "group": GroupStrategy()
        }

    def schedule(self, task: UpgradeTask, strategy: str) -> List[BatchPlan]:
        if strategy not in self.strategies:
            raise ValueError(f"未知策略: {strategy}")
        return self.strategies[strategy].create_plan(task, self.devices, self.server_conn)

# ---------- 验证示例 ----------
if __name__ == "__main__":
    # 构造测试设备:3个组,每组10台,网络负载0.1~0.9
    devices = []
    for g in ["A", "B", "C"]:
        for i in range(10):
            devices.append(Device(
                device_id=f"{g}-{i:02d}",
                group=g,
                network_load=round(0.1 + (i % 9) * 0.1, 1)
            ))

    scheduler = UpgradeScheduler(devices, server_conn=30)

    # 1. 分批升级
    task1 = UpgradeTask(task_id="T1", target_version="v2.0", batch_size=8)
    plans1 = scheduler.schedule(task1, "batch")
    print(f"分批升级: {len(plans1)}批, 首批设备数={len(plans1[0].device_ids)}")

    # 2. 定时升级
    task2 = UpgradeTask(task_id="T2", target_version="v2.0", batch_size=5,
                        start_time=datetime(2025, 6, 1, 2, 0))
    plans2 = scheduler.schedule(task2, "scheduled")
    print(f"定时升级: 首批发时间={plans2[0].scheduled_time}")

    # 3. 按设备组升级
    task3 = UpgradeTask(task_id="T3", target_version="v2.0", device_group="B", batch_size=4)
    plans3 = scheduler.schedule(task3, "group")
    print(f"按组升级: 组B共{len(plans3)}批, 总设备数={sum(len(p.device_ids) for p in plans3)}")

    # 验证约束:所有批次网络负载不超过0.7
    all_plans = plans1 + plans2 + plans3
    assert all(p.est_network_load <= 0.7 for p in all_plans), "存在超负载批次"
    print("约束验证通过: 所有批次网络负载 ≤ 0.7")

运行与验证

  1. 运行方式:将代码保存为 scheduler.py,执行 python scheduler.py。
  2. 预期输出(由代码逻辑直接推出):
    分批升级: 4批, 首批设备数=8
    定时升级: 首批发时间=2025-06-01 02:00:00
    按组升级: 组B共3批, 总设备数=10
    约束验证通过: 所有批次网络负载 ≤ 0.7
  3. 判定标准:程序无异常退出,且最后一行输出“约束验证通过”即表示调度逻辑正确。可修改 batch_size、max_network_load 等参数观察批次划分变化。

关键说明

  • 调度策略采用策略模式,新增策略只需继承 ScheduleStrategy 并注册到 UpgradeScheduler。
  • 网络负载约束在批次划分时动态计算,若某批设备平均负载超过阈值,则自动缩减该批设备数量(缩减至服务器并发上限的80%),保证不超限。
  • 定时升级复用分批逻辑,仅强制要求 start_time 非空;按组升级先过滤设备再复用分批逻辑,避免代码重复。
  • 实际生产环境中,设备网络负载与服务器并发数应由监控系统实时上报,此处用静态值模拟以聚焦调度算法本身。