Quartz.NET 作业存储(Job Stores)完全指南:RAMJobStore 与 ADO.NET 持久化存储实战
发布时间:2026/10/7 2:10:06 作者:尧图编辑部 阅读量:1,286
完全指南:RAMJobStore 与 ADO.NET 持久化存储实战)
任务调度后端【免费下载链接】quartznetQuartz Enterprise Scheduler .NET项目地址https://gitcode.com/gh_mirrors/qu/quartznet点击查看免费下载Job Store 是 Quartz.NET 调度器的数据仓库负责保存作业、触发器、日历等全部调度状态。本指南以官方 Job Stores 教程为主体结合仓库源码与配置参考系统讲解RAMJobStore内存存储与AdoJobStoreADO.NET 数据库持久化存储的选择、配置、序列化方案以及事务集成读完即可为你的应用挑选并落地正确的存储方案。Job Store 是什么Job Store 负责跟踪调度器的全部工作数据作业jobs、触发器triggers、日历calendars等。Quartz.NET 通过IJobStore接口抽象这一能力你只需要在交给 SchedulerFactory 的属性文件或配置对象中选定IJobStore实现及其设置即可。::: warning 永远不要在业务代码中直接使用 JobStore 实例。Quartz 在幕后使用它你只需配置选用哪个 JobStore然后只与IScheduler接口交互。 :::从源码结构看JobStore 是 Quartz 的 SPI服务提供接口之一src/Quartz/Impl/下既有内存实现 RAMJobStore.cs也有 ADO.NET 实现目录 src/Quartz/Impl/AdoJobStore二者共同实现IJobStore接口调度器自身只依赖抽象不关心具体存储介质。RAMJobStore最快的选择但数据易失RAMJobStore将全部数据保存在内存中是配置最简单、CPU 开销最低的 Job Store。代价是当应用程序退出或崩溃时所有调度信息都会丢失因此它无法兑现作业和触发器上的非易失性non-volatility承诺。对某些应用这可以接受甚至正合需求对另一些应用则是灾难。配置 Quartz 使用 RAMJobStore// 这实际上就是默认值因此通常无需显式设置 quartz.jobStore.type Quartz.Simpl.RAMJobStore, QuartzRAMJobStore是StdSchedulerFactory的默认 Job Store所以不配置任何东西即可工作。从源码实现看RAMJobStore.cs 的内存是具体的作业按ConcurrentDictionaryJobKey, JobWrapper和按组字典组织触发器除了按键、按组索引还有一个按下次触发时间排序的SortedSetTriggerWrappertimeTriggers用于快速取出到期触发器日历按名存放。所有操作由一把内部锁串行化——文档注释说明其每段受保护的临界区都是微秒级的同步内存操作因此用同步锁比异步等待更划算。这解释了它最快的定性没有数据库往返、没有网络开销。对需要进程重启后保留调度的场景请继续看下一节。ADO.NET Job StoreAdoJobStoreAdoJobStore通过 ADO.NET 把全部数据存进数据库。它比RAMJobStore配置更复杂、速度也慢一些——不过只要表在主键上有索引差距并不大。三步启用持久化存储第 1 步创建 Quartz.NET 数据表建表 SQL 脚本位于 Quartz.NET 发行包的 database/tables 目录。如果找不到你所用数据库的现成脚本可以改写一份相近的。仓库中已内置的脚本包括tables_sqlServer.sql另有tables_sqlServer_Below2016.sql、tables_sqlServerMOT.sqltables_postgres.sqltables_mysql_innodb.sqltables_oracle.sqltables_sqlite.sqltables_firebird.sql这些脚本给每张表加了QRTZ_前缀如QRTZ_TRIGGERS、QRTZ_JOB_DETAIL。前缀可以是任意字符串只要你把它告诉 AdoJobStore 即可不同的前缀允许多套表、多个调度器实例共享同一个数据库。第 2 步配置 JobStore 类型JobStoreTX自己创建事务是正常情况下你想要的实现。若要把调度提交与应用自身的数据库工作合并在一起JobStoreTX也可以加入你拥有的事务见后文加入已有事务。quartz.jobStore.type Quartz.Impl.AdoJobStore.JobStoreTX, Quartz从源码看JobStoreTX在 Quartz.NET 3.x 中的真实实现是 LocalTransactionJobStore.cs文档注释明确它是默认持久化存储每次操作自行开启 ADO.NET 事务并在操作结束后提交或回滚。而JobStoreCMT容器管理事务对应 ExternalTransactionJobStore.cs适用于事务由容器如应用服务器管理的场景。第 3 步配置驱动委托DriverDelegate、表前缀与数据源# 驱动委托 quartz.jobStore.driverDelegateType Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz # 表前缀 quartz.jobStore.tablePrefix QRTZ_ # 数据源需在属性中另行定义这里引用名为 myDS 的数据源 quartz.jobStore.dataSource myDS选择合适的 DriverDelegateIDriverDelegate实现负责针对你所用数据库执行 ADO.NET 操作。StdAdoDelegate使用原味的 ADO.NET 代码与 SQL仅在没有任何针对该数据库的专用委托时才建议使用——专用委托通常性能更好或能绕开特定数据库的坑。其余委托位于Quartz.Impl.AdoJobStore命名空间或其子命名空间下仓库中实际存在的委托包括委托类型适用数据库Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz通用无专用实现时的默认Quartz.Impl.AdoJobStore.SqlServerDelegate, QuartzMicrosoft SQL ServerQuartz.Impl.AdoJobStore.PostgreSQLDelegate, QuartzPostgreSQLQuartz.Impl.AdoJobStore.OracleDelegate, QuartzOracleQuartz.Impl.AdoJobStore.SQLiteDelegate, QuartzSQLiteQuartz.Impl.AdoJobStore.MySQLDelegate, QuartzMySQLQuartz.Impl.AdoJobStore.FirebirdDelegate, QuartzFirebird::: tip Quartz.NET 会在你使用默认的StdAdoDelegate时给出警告当待选触发器很多时它的性能较差。专用委托的 SQL 会限制结果集长度如SqlServerDelegate用TOP n、PostgreSQLDelegate用LIMIT n、OracleDelegate用ROWCOUNT() n等可参考 SqlRowLimit.cs 中对该特性的抽象。 :::配置数据源DataSource数据源在 Quartz.NET 属性中定义为 AdoJobStore 提供数据库连接包含连接字符串与 ADO.NET 委托信息。设置连接字符串与数据库提供程序quartz.dataSource.myDS.connectionString Serverlocalhost;Databasequartz;Uidquartznet;Pwdquartznet quartz.dataSource.myDS.provider MySql受支持的数据库提供程序SqlServer- SQL Server 驱动完整框架full framework下默认使用 System.Data.SqlClientQuartz 3.1 除外从 Quartz 3.2 起.NET Core 下默认使用 Microsoft.Data.SqlClientSystemDataSqlClient- .NET Core 上单独可用完整框架的默认MicrosoftDataSqlClient- 完整框架上单独可用.NET Core 的默认OracleODP- Oracle 官方驱动OracleODPManaged- Oracle 11 的托管驱动MySql- MySQL Connector/.NETSQLite- SQLite ADO.NET ProviderSQLite-Microsoft- Microsoft SQLite ADO.NET ProviderFirebird- Firebird ADO.NET ProviderNpgsql- PostgreSQL Npgsql::: tip 社区还贡献了许多其他提供程序例如面向 NoSQL 数据库的但 Quartz.NET 项目并不对它们提供支持。 :::尽量使用最新版驱动必要时添加程序集绑定重定向。如果调度器非常繁忙几乎始终以线程池大小的并发量运行作业建议把数据源的连接数设置为线程池大小 1。这通常通过 ADO.NET 连接字符串设置具体请查阅所用驱动的文档。quartz.jobStore.useProperties设为true默认false会告诉 AdoJobStore所有 JobDataMap 值都是字符串于是它们以键值对而非序列化对象的形式存入 BLOB 列。从长期看这更安全因为它避免了把非 String 类序列化进 BLOB 所带来的类版本兼容问题。quartz.jobStore.useProperties true::: tip 推荐开启能大幅降低类型序列化出问题的风险。 :::表前缀的高级用法配置参考文档 补充说明在支持 schema 的数据库如 SQL Server上表前缀可以包含 schema 名例如[foo].QRTZ_。注意如果建表脚本是用显式 schema如dbo执行的前缀必须与其保持一致。选择序列化器Quartz.NET 支持二进制与 JSON 两种序列化。二进制序列化已不推荐未来版本将不再支持它。可选的 JSON 方案基于 System.Text.Json 的 JSON 序列化Quartz.Serialization.SystemTextJson NuGet 包基于 Newtonsoft.Json 的 JSON 序列化Quartz.Serialization.Json NuGet 包::: tip JSON 是全新项目greenfield推荐使用的持久化格式同时强烈建议开启useProperties把键值限制为字符串。 :::使用代码配置var config SchedulerBuilder.Create(); config.UsePersistentStore(store { // 通常建议序列化时坚持使用字符串类型的键与值 store.UseProperties true; // ... 其他设置 ... store.UseSystemTextJsonSerializer(); }); ISchedulerFactory schedulerFactory config.Build();使用属性配置// stj 是 Quartz.Simpl.SystemTextJsonObjectSerializer, Quartz.Serialization.SystemTextJson 的别名 // newtonsoft 和 json 是 Quartz.Simpl.JsonObjectSerializer, Quartz.Serialization.Json 的别名 quartz.serializer.type stj经典属性式配置的完整写法见 system-text-json.mdvar properties new NameValueCollection { [quartz.jobStore.type] Quartz.Impl.AdoJobStore.JobStoreTX, Quartz, [quartz.serializer.type] stj }; ISchedulerFactory schedulerFactory new StdSchedulerFactory(properties);从包文档可知从 Quartz 3.10 起更推荐使用明确的别名newtonsoft而非json。另外若需从二进制序列化平滑迁移包文档还给出了混合序列化器思路先尝试按 JSON 反序列化失败则回退二进制并打上脏标记让数据在下次写入时自动转为 JSON。加入已有事务Joining an existing transaction默认情况下AdoJobStore 会自己打开连接并在调度操作完成后立刻提交。于是保存业务数据与调度处理这些数据的作业是两个事务可能出现一个成功、另一个失败的不一致局面。将quartz.jobStore.acceptEnlistedTransactions设为true可让 JobStore 加入你的应用程序拥有的事务使调度提交与其余工作要么一起提交、要么一起回滚var config SchedulerBuilder.Create(); config.UsePersistentStore(store { store.UsePostgres(connectionString); store.AcceptEnlistedTransactions(); });然后在作用域持续期间把你的连接和事务交给调度器await using var tx await dbContext.Database.BeginTransactionAsync(); dbContext.Add(entity); await dbContext.SaveChangesAsync(); using (scheduler.EnlistTransaction(tx.GetDbTransaction())) { await scheduler.ScheduleJob(job, trigger); await tx.CommitAsync(); }任意DbConnection与DbTransaction都可用无论来自 EF Core、Dapper 还是纯 ADO.NET。::: warning 交出连接是参与的唯一途径。仅靠环境TransactionScope是不够的JobStore 为自己打开的那个连接被有意排除在环境事务之外因此调度仍会单独提交。请在作用域内打开连接并登记这一条连接。 :::在TransactionScope内部写法相同只是由连接携带事务var options new TransactionOptions { IsolationLevel IsolationLevel.ReadCommitted }; using var scope new TransactionScope(TransactionScopeOption.Required, options, TransactionScopeAsyncFlowOption.Enabled); using var connection new NpgsqlConnection(connectionString); await connection.OpenAsync(); // ... 在此连接上做你自己的业务工作 ... using (scheduler.EnlistConnection(connection)) { await scheduler.ScheduleJob(job, trigger); } scope.Complete();共享同一条连接还能避免事务被提升为分布式事务——分布式事务在 Windows 之外不可用Npgsql 等提供程序也不支持。从源码看这一机制由 AdoJobStoreBase.cs 的AcceptEnlistedTransactions属性与AmbientConnection机制落实登记连接后该异步上下文内的调度操作复用这条连接与事务未登记的操作仍使用 JobStore 自己的连接。EnlistTransaction/EnlistConnection扩展方法定义在 SchedulerEnlistmentExtensions.cs。启用前必须知道的注意事项原文档给出了非常详细的警告清单逐条照录并补充说明登记会随当前异步上下文流动。必须在调度器调用所覆盖的同一作用域内建立登记这与TransactionScope需要TransactionScopeAsyncFlowOption.Enabled是同一个原因。在async辅助方法内部登记不会把登记带回调用方。锁会一直持有到提交或回滚因为 JobStore 在你的事务里取锁。保持登记事务短小长事务会阻塞触发器获取、misfire 处理和集群签到。同样地在登记作用域内首次启动调度器会被拒绝并报错从 standby 恢复调度器虽不被拒绝也请避免。使用自己的DbTransaction时要在提交之后释放登记作用域。释放会向调度器发出存在待定调度变更的信号提前释放会把调度器指向它尚看不到的行。在TransactionScope下由作用域报告结果因此登记可以先关闭如上面示例且事务回滚时不会发出任何信号。在作用域内一次只 await 一个调度器调用。一条连接只携带一个事务无法同时服务两个操作。你的事务内部不会重试瞬时数据库错误。在多数提供程序上第一次失败就已让事务寿终正寝所以该错误以及该事务中你自己的业务改动都需要自行处理。半途失败的操作会把已执行的语句留在你的事务里没有可回滚的保存点。此模式即使调度器未集群也使用数据库锁因为你的事务比调度操作活得更久进程内锁会在你提交前就被释放。SQLite 是例外它始终在进程内加锁因此并发的调度操作可能一直报 database is locked直到你的事务完成——Quartz 启动时会打印一条相关警告。调度器自身的工作获取触发器、处理 misfire、集群签到始终使用自己的连接不受影响。JobStoreCMT是上述一点的例外运行在容器管理事务内是该存储的契约因此它自己的连接一如既往地登记进环境事务。其他重要属性速查以下属性在 配置参考文档 中有完整定义此处摘录 AdoJobStore 常用项属性默认值说明quartz.jobStore.commandTimeout0用提供程序默认值语句执行超时毫秒向上取整到秒3.22.0 起支持quartz.jobStore.dbRetryInterval15000失去数据库连接后的重试间隔毫秒quartz.jobStore.driverDelegateTypenull驱动委托类型必填quartz.jobStore.dataSourcenull数据源名称必填quartz.jobStore.tablePrefixQRTZ_表前缀quartz.jobStore.usePropertiesfalseJobDataMap 值是否一律按字符串存储quartz.jobStore.misfireThreshold60000触发器超过下次触发时间多少毫秒算 misfirequartz.jobStore.clusteredfalse是否启用集群quartz.jobStore.clusterCheckinInterval7500集群签到间隔毫秒quartz.jobStore.acceptEnlistedTransactionsfalse是否加入应用自有事务quartz.jobStore.acquireTriggersWithinLockfalse触发器获取是否在显式数据库锁内进行批量获取 1 时须为 truequartz.jobStore.lockHandler.typenull锁处理器信号量类型高级设置quartz.jobStore.driverDelegateInitStringnull传给 DriverDelegate 的初始化参数|分隔关于集群clustered true官方建议集群仅对 AdoJobStore 生效所有节点共享同一套数据库表各节点属性必须一致仅线程池大小与quartz.scheduler.instanceId允许不同每个节点要有唯一instanceId可用AUTO自动生成。集群提供自动负载均衡与故障转移——作业标记为请求恢复requests recovery时节点故障后其进行中的作业会由剩余节点重新执行。一个典型的集群配置示例可见 reference.md 的 Clustering 一节。小结如何选择开发调试、进程生命周期与调度生命周期一致、可接受重启丢失调度→ 直接用默认的RAMJobStore零配置。需要跨进程重启保留调度、多实例集群、与应用共享数据库事务→ 选用JobStoreTX默认持久化存储按本节步骤创建表、配置驱动委托、表前缀与数据源。全新项目→ 推荐useProperties true JSON 序列化System.Text.Json 或 Newtonsoft 皆可。需要与业务数据在同一事务中提交调度→ 开启acceptEnlistedTransactions通过scheduler.EnlistTransaction/EnlistConnection交出连接并严格遵守上文注意事项。需要容器管理事务→ 使用JobStoreCMTExternalTransactionJobStore。调度器本身对存储是无感知的——无论背后是内存字典还是关系数据库你的业务代码始终只面向IScheduler接口这正是 Job Store 设计最核心的价值。赞分享任务调度后端【免费下载链接】quartznetQuartz Enterprise Scheduler .NET项目地址https://gitcode.com/gh_mirrors/qu/quartznet点击查看免费下载相关推荐Quartz.NET持久化存储与数据库集成Quartz.NET持久化存储与数据库集成 本文深入探讨了Quartz.NET的两种主要作业存储机制RAMJobStore内存存储和AdoJobStore数据任务调度后端Microsoft Orleans 关系数据库持久化基于 ADO.NET 的 Grain 状态存储实战指南Microsoft Orleans 关系数据库持久化基于 ADO.NET 的 Grain 状态存储实战指南 本文以 Microsoft Orleans 官方后端微服务Quartz.NET JobStore 完全指南从 RAMJobStore 到 AdoJobStore 的持久化调度配置Quartz.NET JobStore 完全指南从 RAMJobStore 到 AdoJobStore 的持久化调度配置 JobStore作业存储是 Qu任务调度后端上一篇微服务调试新范式DUBBO-POSTMAN架构解析与技术实现下一篇VPaint核心技术解析深入理解Vector Graphics Complex (VGC)架构创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考