"""自动化调度:作业注册、调度线程与各作业实现。 登记作业请放在 ``register_default_jobs()``,由 ``main.py`` 在启动时调用一次, 避免 import 顺序影响注册结果。 """ from __future__ import annotations from .jobs.account_lease_reaper import ( JOB_KEY as ACCOUNT_LEASE_REAPER_KEY, ) from .jobs.account_lease_reaper import ( run_account_lease_reaper, ) from .jobs.account_overview_refresh import ( JOB_KEY as ACCOUNT_OVERVIEW_REFRESH_KEY, ) from .jobs.account_overview_refresh import ( run_account_overview_refresh, ) from .jobs.ck_health import JOB_KEY as CK_HEALTH_KEY from .jobs.ck_health import run_ck_health from .jobs.ck_relogin import JOB_KEY as CK_RELOGIN_KEY from .jobs.ck_relogin import run_ck_relogin from .jobs.douyu_tasks_retention import ( JOB_KEY as DOUYU_TASKS_RETENTION_KEY, ) from .jobs.douyu_tasks_retention import ( run_douyu_tasks_retention, ) from .jobs.points_expire import JOB_KEY as POINTS_EXPIRE_KEY from .jobs.points_expire import run_points_expire_check from .scheduler import ( JobSpec, automation_enabled, ensure_jobs, register_job, registered_jobs, reset_registry, run_due_jobs, run_job_now, scheduler_status, start_scheduler, stop_scheduler, ) __all__ = [ "ACCOUNT_LEASE_REAPER_KEY", "ACCOUNT_OVERVIEW_REFRESH_KEY", "CK_HEALTH_KEY", "CK_RELOGIN_KEY", "DOUYU_TASKS_RETENTION_KEY", "POINTS_EXPIRE_KEY", "JobSpec", "automation_enabled", "ensure_jobs", "register_default_jobs", "register_job", "registered_jobs", "reset_registry", "run_account_lease_reaper", "run_account_overview_refresh", "run_ck_health", "run_ck_relogin", "run_douyu_tasks_retention", "run_due_jobs", "run_job_now", "run_points_expire_check", "scheduler_status", "start_scheduler", "stop_scheduler", ] def register_default_jobs() -> None: """登记内置作业(幂等)。""" register_job( JobSpec( key=POINTS_EXPIRE_KEY, title="积分到期补记", # 每天一次足够:到期时刻的精度是「天」级别,晚几小时记也不影响 interval_seconds=24 * 3600, batch_size=200, concurrency=1, run=run_points_expire_check, ) ) register_job( JobSpec( key=DOUYU_TASKS_RETENTION_KEY, title="任务日志清理", # 一天一次足够:这是**清理**,不是业务;跑太勤只是白扫索引。 interval_seconds=24 * 3600, batch_size=2000, concurrency=1, run=run_douyu_tasks_retention, ) ) register_job( JobSpec( key=CK_RELOGIN_KEY, title="CK 自动重登", # interval_seconds 同样是「同一行多久重试一次」的兜底(真实节奏由 # relogin_next_at 的退避表决定);批次小、跑得慢,单轮 10 条。 interval_seconds=6 * 3600, batch_size=10, concurrency=3, run=run_ck_relogin, ) ) register_job( JobSpec( key=ACCOUNT_OVERVIEW_REFRESH_KEY, title="账号资料定时补全", # 每 10 分钟一轮、每轮 400 个。 # # 依据是**试水实测**,不是拍脑袋:200 个并发 3 跑了 131 秒 # = **1.53 个/秒 ≈ 5,500 个/小时**。瓶颈不在查询速度,而在调度频率。 # 400 个 × 0.65 秒 ≈ **4.4 分钟**跑完,10 分钟一轮 # = **2,400 个/小时**,7,784 个约 **3.2 小时**补完。 # # ⚠️ 一轮时长必须**明显小于间隔**:调度器是**串行**的 # (见 scheduler.run_due_jobs 的注释:作业一个一个跑), # 我的作业跑着的时候 CK 巡检/重登都得等着。所以宁可轮次多一些, # 也不要一轮跑太久 —— 4.4 分钟 / 10 分钟是刻意留出的余量。 # # 想更快就调大 `batch_size`(账号列表里那套),但**跑得越快越容易被风控**, # 而风控是所有账号一起受影响。 # 「同一账号多久重检」= 查过的账号 7 天后再查(界面上的那个字段)。 # ⚠️ 它**不参与调度** —— 调度频率由 run_interval_seconds 决定。 interval_seconds=7 * 24 * 3600, run_interval_seconds=5 * 60, batch_size=400, concurrency=3, run=run_account_overview_refresh, ) ) register_job( JobSpec( key=CK_HEALTH_KEY, title="CK 自动巡检", # interval_seconds = 单账号多久重新检测一次(不是作业周期; # 作业每个 tick 都跑)。全库 12,881 条按 5 分钟一轮、单轮 200 条 # 反推,吞吐 40 条/分钟,够 6 小时内覆盖一遍(规划 §7.3)。 interval_seconds=6 * 3600, batch_size=200, concurrency=5, run=run_ck_health, ) ) register_job( JobSpec( key=ACCOUNT_LEASE_REAPER_KEY, title="账号租约回收", # 租约的主释放路径是任务终态钩子;本作业只是**保险丝**, # 兜住「进程重启 / 释放抛异常 / 任务行被清」三种漏网情况。 # 跑得勤一点(1 分钟)成本极低:只扫带索引的 status+expires_at, # 而且悬空租约会让账号白白锁着 —— 早收早能用。 # `interval_seconds` 对它是无意义的(本作业自己按三类规则选行), # 但 JobSpec 要求必填,给一个与 run_interval 同量级的值。 interval_seconds=60, run_interval_seconds=60, batch_size=500, concurrency=1, run=run_account_lease_reaper, ) )