跳到主要内容
版本:3.1

延时任务迁移指南(Aegis.Jobs.Migration)

从旧版 Aegis.Jobs(preview / 试用版,本地定时器 + KV 归属表架构)升级到 3.1.1-preview1(存储唯一真值 + 秒级扫描架构)时,业务代码零改动,需要动手的是数据库结构和几项必须知晓的行为变化。本页给出完整的对照清单和迁移脚本。

先说结论:

使用者类型改动量
只调用 Schedule / Cancel 的业务代码零行改动
配置无必改项,清理 3 个失效键即可
数据库重建 schema(无存量数据)或跑迁移脚本(有在途任务)
实现过自定义 Provider / 引用过框架内部类型中等改动,见破坏性变更清单

业务代码:零改动​

这些用法签名与语义完全不变:

  • AegisJob.Schedule(expr, TimeSpan delay) 的四种表达式形态(静态方法、Action、泛型实例 Action<T>、Func<T, Task>)
  • AegisJob.Cancel(jobId) 返回 bool,已终态任务视为幂等成功
  • Job 业务类的定义方式:公共方法 + 顶级类(顶级类是既有约束,不是本次新增)
  • Component.deps.json 里的 Jobs / Jobs.Postgres 组件注册

新版还多了 4 个绝对时间重载 Schedule(expr, DateTime executeAt)(过去时间点按立即执行处理),可选用,不用不影响旧代码。

行为变化:代码不用改,但必须逐条确认​

  1. 触发精度从约 100ms 变为秒级(Postgres 模式,默认 1 秒扫描间隔)。分钟级延迟业务无感;如果存在依赖亚秒触发的调用方,先确认可接受。
  2. 重复执行从边缘情况变为常态路径。旧架构由创建任务的实例本地定时触发;新架构任何实例都可领取执行,滚动发布、节点重启、租约过期接管都会导致任务重做。业务方法必须幂等,这条从建议升级为硬性前提。上线前逐个任务审查一次。
  3. 反序列化失败的任务直接标 Failed。旧版里坏任务(例如应用升级后类型或方法消失)会拖垮整批加载并无限重试;新版会把它标记为 Failed 终态,不再重试、不阻塞其他任务。发版流程要加一步:升级后执行 SELECT job_id FROM aegis_jobs.job_state WHERE state = 6 检查新产生的 Failed 任务,识别出的升级断链任务需要业务侧重新排期。
  4. 停机时排空在飞任务(默认等待 30 秒,超时放弃、由其他实例接管)。K8s 建议 terminationGracePeriodSeconds 不低于排空超时加 10 秒。
  5. Memory 模式(单节点纯内存)行为完全不变。

配置:清理 3 个失效键​

以下键对应的机制已删除,配置里残留不会报错(静默忽略),但应清理掉避免误导:

Jobs:Postgres:HeartbeatIntervalSeconds      # 心跳机制已删除
Jobs:Postgres:RecoveryScanIntervalSeconds # 改名 ScanIntervalSeconds,默认 1 秒
Jobs:Postgres:ServerTimeoutSeconds # 节点注册表机制已删除

Jobs:Postgres:ExecutionLeaseTimeoutSeconds 键名保留。新键(ScanIntervalSeconds / ExecutionLeaseRenewalIntervalSeconds / ShutdownDrainTimeoutSeconds / DataType)全部有默认值,不配也能跑。

数据库迁移​

结构对照​

对象旧版3.1.1-preview1
job_state6 列(含 state_name),归属信息存 KV 表新增 execute_at / owner_id / owner_token / lease_expires_at 四列;删除 state_name;state 收紧 NOT NULL;新增 2 个索引
job_data5 列列不变;新增 execute_at 索引
job_parameterKV 归属表整表删除
job_server_node节点注册表整表删除

情形 A:没有需要保留的存量数据​

直接按 Aegis.Jobs.Postgres/Entities/CrtSql/ 下的新脚本重建 aegis_jobs schema。试用环境建议走这条路径。

情形 B:有必须保留的在途任务​

按顺序执行以下脚本(执行前先停掉旧实例,避免迁移窗口内的重复执行):

-- 1. 补列并回填 execute_at
ALTER TABLE aegis_jobs.job_state
ADD COLUMN IF NOT EXISTS execute_at TIMESTAMP,
ADD COLUMN IF NOT EXISTS owner_id INT8,
ADD COLUMN IF NOT EXISTS owner_token VARCHAR,
ADD COLUMN IF NOT EXISTS lease_expires_at TIMESTAMP;
UPDATE aegis_jobs.job_state s
SET execute_at = d.execute_at
FROM aegis_jobs.job_data d
WHERE d.id = s.job_id;

-- 2. 在途 Processing 任务交给新扫描接管:租约置为过去时间即可被 ClaimStale 领取。
-- 旧归属信息存在 job_parameter,新模型没有对应物,按"原执行节点已死"处理,
-- 任务会被重新执行一次(at-least-once 语义内,业务需幂等)。
UPDATE aegis_jobs.job_state
SET lease_expires_at = now() - interval '1 hour',
owner_id = NULL,
owner_token = NULL
WHERE state = 2;

-- 3. 索引
CREATE UNIQUE INDEX IF NOT EXISTS jobstate_jobid_idx ON aegis_jobs.job_state USING btree (job_id);
CREATE INDEX IF NOT EXISTS jobstate_state_executeat_idx ON aegis_jobs.job_state USING btree (state, execute_at);
CREATE INDEX IF NOT EXISTS jobstate_state_lease_idx ON aegis_jobs.job_state USING btree (state, lease_expires_at);
CREATE INDEX IF NOT EXISTS jobdata_executeat_idx ON aegis_jobs.job_data USING btree (execute_at);

-- 4. 确认没有其他系统引用这两张表后删除
DROP TABLE IF EXISTS aegis_jobs.job_parameter;
DROP TABLE IF EXISTS aegis_jobs.job_server_node;

-- state_name 列新代码不写不读,可以先保留,择机执行:
-- ALTER TABLE aegis_jobs.job_state DROP COLUMN state_name;

迁移窗口内正被旧实例执行的任务可能重复执行一次:旧实例按旧表结构写入会失败,新实例接管后重做。停旧实例再迁移,这个窗口就消失了。

破坏性变更:只影响高级用法​

只在直接引用过框架内部类型时才相关,普通业务代码不会碰到:

被删除或改形的类型影响面
IPersistenceConnection 的 AnnounceServer / RemoveServer / Heartbeat自定义 Provider 实现者
IPersistenceConnection.CreateJob 语义现在原子写入任务数据与初始 Scheduled 状态
IDistributedJobCoordinator 接口改形旧四方法(TryClaimExecution 等)换成 ClaimDueJobs / ClaimStaleJobs / RenewLease / CompleteExecution
IDistributedJobCoordinatorDiagnostics已删除,三态结果并入主接口
JobData.Parameters 属性已删除(归属信息上列后不再有参数表)
BackgroundJob 四参构造(parametersSnapshot)已删除
JobPerformer 构造签名(JobActivator, int) 改为 (int)
状态机子系统(StateMachine / IStateHandler / StatusReport)整体删除

上线检查清单​

  1. 逐任务幂等审查(行为变化第 2 条,唯一可能造成业务事故的项)
  2. 数据库按情形 A 或 B 处理
  3. 清理 3 个失效配置键
  4. 发版流程加入"升级后检查 Failed 任务"
  5. 确认不存在依赖亚秒触发精度的调用方
  6. 如引用过内部类型,按破坏性变更清单改造

配套阅读​