在分布式企业级系统中,很多团队在做后台任务时经常踩进几个大坑:
- 依赖外部系统 Crontab:在服务器 Linux crontab 里写一堆
curl http://localhost/api/cron/sync,一旦服务器漂移或扩容,任务全部失控; - 内存定时器(Timer)并发击穿:单机
System.Threading.Timer遇到大任务执行慢时,前一个任务还没跑完,后一个又被触发,导致数据库连接池被直接打满撑爆; - 多实例重复执行:服务部署了 3 个副本,零点一到,3 台服务器同时执行“自动扣费”,导致客户被扣了 3 次钱!
BitzOrcas.Modern 采用数据库驱动的 Quartz 集群 + 调度器中立的 IJobExecutor<T> 架构:业务只写纯粹的用例执行器,底层由持久化集群保证全局唯一触发与故障自动转移(Failover)。
后台作业调度与执行全景
第一步:声明类型化任务身份与调度器中立执行器
BitzOrcas 的第一原则是:业务用例绝对不依赖 Quartz 具体的 IJob 或 JobExecutionContext。
在模块中,我们定义一个强类型的任务身份与对应的 IJobExecutor<T>:
using BitzOrcas.Application.Abstractions.Jobs;using BitzOrcas.Domain.Results;
namespace BitzOrcas.Auditing.Application.Jobs;
// 1. 声明稳定全局唯一的 Job 身份public static class AuditJobIdentities{ public sealed class RetentionPrune : IBackgroundJobIdentity { public static string JobName => "audit-retention"; // 与 BackgroundJobIdentities 内置身份一致 }}
// 2. 编写纯粹业务执行器:只依赖领域端口与 AppClock,零 Quartz 依赖public sealed class AuditRetentionJobExecutor( IAuditRetentionPort retentionPort, IAppClock clock) : IJobExecutor<AuditJobIdentities.RetentionPrune>{ public async Task<Result> ExecuteAsync(CancellationToken ct) { // 计算归档保留阈值(清理 180 天前的历史日志) var cutoffTime = clock.UtcNow.AddDays(-180);
// 调用应用端口执行分批裁剪 await retentionPort.PruneExpiredLogsAsync(cutoffTime, ct);
return Result.Success(); }}第二步:在 JobHost 中装配与声明式配置
在 JobHost 启动时,框架读取 appsettings.json 并注册调度规则:
{ "BackgroundJobs": { "audit-retention": { "Enabled": true, "CronExpression": "0 0 2 * * ?", "IntervalSeconds": 86400 // 覆盖入口只读 CronExpression/IntervalSeconds/Enabled 三键; // Description 属模块声明层参数,不能被配置覆盖。 // IntervalSeconds 与 CronExpression 互斥,同时给出会 fail-loud。 } }}- 类型安全绑定:编译期捕获错误,未绑定的任务在启动时直接报错,绝不等到运行时才抛异常;
- 配置即生效:无需修改 C# 代码即可通过环境变量或配置中心调整 Cron 表达式或临时停用任务。
第三步:执行安全与优雅停机(Graceful Shutdown)
在长时间运行的批处理任务中,必须遵循以下安全准则:
- 游标分批处理:每次只拉取 1,000 条,处理完毕提交事务并更新游标,杜绝一次性将百万级大表加载到内存;
- 响应取消信号:在批次循环中检查
cancellationToken.IsCancellationRequested,收到容器终止信号(SIGTERM)时安全退出当前批次,避免数据处于中间悬挂状态。
总结
BitzOrcas 的后台作业构建块消除了传统调度的不确定性:
- 调度器中立:业务执行器纯净无污染,单元测试毫秒级秒跑;
- 集群持久化防重:底层基于数据库锁保证同一时刻单实例执行;
- 全链路可观测:每次执行的耗时、结果与异常自动进入运维大盘。