news 2026/10/17 4:53:51

从Quartz到AX:分布式任务调度框架的设计与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从Quartz到AX:分布式任务调度框架的设计与实践

凌晨两点被电话叫醒,冲进机房查了半天,结果发现不是业务代码的问题,而是一个批处理任务压根没被调度起来——数据没算,早上八点所有人都得等着看错误报表。这种经历我想不少做后端的同行都遇到过。定时任务这东西,平时存在感极低,但一旦出问题,永远是第一个被怀疑的对象,甚至比数据库还冤。今天想认真聊聊我们团队在生产环境里落地的一套分布式任务调度框架,代号叫AX调度。它不是那种大而全的商用平台,而是我们自己围绕实际业务打磨出来的轻量方案,核心解决三件事:任务怎么被精确触发、触发之后怎么被可靠执行、执行过程中出了乱子怎么快速定位并恢复。如果你是那种对Timer、xxl-job、Quartz这些组件有一定了解,但还没真正从头设计或改造过一套完整调度体系的人,这篇内容会比较对胃口。

1. 为什么业务上了一个量级,最先扛不住的是调度

先聊一个很现实的问题:你手上的定时任务到底是怎么跑起来的?很多团队早期的答案是crontab、Java里的ScheduledExecutorService,或者是直接在应用进程里丢一个Quartz。这些方案在小规模、单机、任务数少的时候一点问题都没有,但一旦业务场景变成多机房部署、上百个服务实例、任务之间还有依赖关系,老方案的短板会非常清晰地暴露出来。

单机定时器的核心问题在于它的"单机"属性。进程挂了,任务就没了;集群部署时如果每个节点都跑一遍定时器,同一个任务就会被重复执行多次,而重复执行对于很多场景来说是灾难性的——比如给用户发短信、扣减库存、生成对账单。为了避免重复,要么加分布式锁,要么搞一套复杂的选举逻辑,可分布式锁本身又是一堆坑:锁超时、误删锁、锁续期,每一条写出来都是血泪史。更麻烦的是,任务的管理界面通常是不存在的,你想看一眼下个执行时刻、上一次执行结果,得翻日志,而日志可能因为 hours 轮转已经找不到了。

Quartz虽然支持持久化和集群模式,但它的集群方案本质上是基于数据库锁的"伪集群",所有调度器实例争抢同一个数据库锁来获得触发权。这种方式在几十个任务、低频率调度时表现尚可,但在任务数上千、粒度细化到秒级、每分钟有几万个触发点时,数据库会成为明显的性能瓶颈。而且Quartz的任务模型是扁平的,没有父子依赖、没有分片执行、没有动态调整并发线程的能力,想用它来编排复杂的工作流,基本上要写大量外围代码。

这些痛点会集中在一个阶段爆发:当你的任务量从几十个涨到几百上千个,当你的服务从单机拆成几十个实例,当一个"定时跑一次"的需求逐渐演进成"每天按不同时间窗口跑几十万个分片任务"的时候。你想继续用老方案缝缝补补,会发现时间都花在解决调度本身的问题上,而不是业务逻辑。这个时候就必须换一套思路了。

AX调度的出发点也正是这个:把调度这件事从业务代码里完整剥离开来,做成一个独立的基础设施组件。它不管你的任务具体干什么,它只负责三件事——准确地在约定时间产生触发信号、把触发信号可靠地传给正确的执行节点、在节点执行完毕后如实记录结果。听起来简单,但每一环做扎实,都需要相当多的细节。

1.1 调度和消息队列的本质区别

很多人在做架构设计时会把调度器和消息队列混在一起,觉得"到点了往队列里丢一条消息不就行了"。这里要分清楚:如果只是延迟触发一次,用一个带延迟队列的消息中间件确实可行,但调度要解决的是周期性、时间表、依赖关系、失败重试等一连串场景,而这些不是"发一条消息"就完事的。

举个例子,一个任务配置为"每个工作日早上9点执行,遇节假日跳过,上一个执行实例没结束则跳过本次"。消息队列里你得做一堆逻辑判断:判断今天是否工作日、查询上一个实例是否还在运行、通过消息重投机制处理超时。这套逻辑放在消息消费者里,很快就会把消费端代码搅成一锅粥,而且你很难看到全局的调度状态——下一个触发点是什么时候?历史上执行了几次?哪几次失败了?消息队列对这些问题给不出好的答案。

调度器从设计上就是一个"有状态"的系统。它要维护所有任务的元数据、时钟状态、执行历史和依赖关系;触发只是它的第一个动作,后面还跟着选节点、发命令、跟踪执行、收轨迹、判定结果。AX在设计上把"触发器"和"执行器"彻底解耦:调度器只负责算时间、发信号,执行器负责真正干活并上报状态。两者都可以独立水平扩展,任意一方挂了都不会让对方那边的数据丢失。这一点在后面讲架构时还会展开。

2. AX的总体架构:调度器、执行器、管理端三者是怎么分工的

AX的整体结构分三个角色:调度器(Scheduler)、执行器(Executor)、管理端(Console)。每个角色都是独立部署的进程,之间通过定义良好的协议通信。这种三角色分离的架构不是我们拍脑袋定的,而是从几套数据链路走下来之后确认的最合理切分方式。

调度器的职责非常纯粹:持有所有任务的调度计划,根据当前时刻扫描时间轮,找出所有"到点"的触发,生成一个待执行实例,并通过注册中心找到合适的执行器节点,把指令投递出去。调度器本身不执行业务逻辑,也不存储任务执行的结果数据,它只维护一个"待执行"状态。执行器的职责正好相对:接收调度指令,在本地线程池里运行业务逻辑,把开始时间、结束时间、执行结果、日志回传给调度器或者直接写入结果存储。

管理端是给人用的。它的核心价值是把散落在各个节点上的配置、运行状况和日志收敛到一个可视化的界面上。没有管理端之前,改一个任务的执行时间要改代码重新发布,这是很多团队的常态;有了管理端,定时规则、超时时间、重试次数都变成配置项,改完立即对调度器生效。这个能力对运维的影响是巨大的——线上任务需要临时调整的做法从原来的"走一个发布流程"变成了"打开控制台点一下保存"。

调度器和管理端之间、调度器和执行器之间,通信都走独立的RPC通道。我们没有把控制信令混杂到业务的HTTP请求里,因为调度信令的延迟要求很明确:触发时间误差要控制在秒级以内。如果调度器和执行器之间还要经过业务网关、鉴权、负载均衡那套链路,时序和超时都不好控制。直接在内网用长连接,信令一来一回的延迟基本在毫秒级别,这是保证调度准时性的基础。

关于注册和发现,这里多说一句:调度器怎么知道有哪些执行器可用?我们采用的是执行器启动时向注册中心上报自己的分组、地址和权重,同时把自己的状态做成心跳。调度器对执行器的感知是有一定延迟的,所以我们做了一层保护——调度器在投递指令时如果发现目标节点连续心跳超时,会立即把该节点的任务转移到同分组的其他节点,这个failover的过程从发现到完成大约在5秒内,对大多数业务场景是可接受的。

2.1 任务模型和数据表设计

任务模型是整套系统的地基。AX里一个任务的完整生命周期包含这样几个对象:任务定义(TaskDef)、任务实例(TaskInstance)、调度记录(TriggerRecord)。任务定义是你的配置:任务名、所属分组、定时策略、执行超时、失败重试次数、路由策略。任务实例是某一次具体的执行:由一次触发产生,有实例ID、开始时间、结束时间、状态、执行节点、日志路径。调度记录则是纯审计性质的数据,每次调度器产生一次触发,不管最后有没有成功执行,都会留一条记录。

数据库表设计上,我们用了三张核心表:task_def、task_instance、trigger_record。task_def里比较关键的两个字段是schedule_config和route_config。schedule_config存的是JSON格式的调度表达式,支持cron、固定间隔、固定时刻这三种模式;route_config决定这次任务的路由策略,是按轮询选择节点,还是hash到固定节点,还是广播到所有节点。

task_instance表有点特殊:它的数据量会随着时间快速增长,所以我们默认按天做分区,数据保留策略是最近30天全量保留,超过30天的归档到冷存储。查询任务状态时最常碰到的就是"某个任务最近一次执行成功了吗",所以我们给task_def_id和created_at建了联合索引,并且把status字段一并放进去,三次查询基本都在100毫秒以内解决。有段时间我们没加这个索引,管理端上看任务状态慢得要命,加完之后骤降,属于那种最基础的优化但收益非常明显的场景。

trigger_record那边则多了一个心眼:因为它的写入频率很高(每次触发都要写),但查询频率不算高,所以我们把它的归档周期调短到7天,并且做了批量写入的优化。这里提醒一句,审计表和状态表如果混在一个库一个表,读写会互相干扰,量一大就容易出现慢查询拖垮管理端的现象,最好从一开始就分开。

2.2 时间轮与触发精度:调度准时性的底层逻辑

聊聊调度器最核心的部分——时间触发。在AX里,所有任务的下一次触发时间都会注册到一个时间轮(TimingWheel)上。时间轮这个东西,简单理解就是一个桶形的环形结构,每个桶对应一个时间刻度,到期的任务挂在桶里;推进指针每到一个刻度,就把这个桶里的任务取出来处理。复杂度是O(1)级别的,不管任务有多少,取任务的动作都不会变慢。

我们用的是分层时间轮,层次一共五层,从毫秒到天各一层。秒级任务挂在第一层,每分钟的任务在第二层,小时级、天级的往上走。每层时间轮的刻度数量不需要很大,靠"降层"机制把高层任务逐步塌缩到低层来触发。举个例子,一个每天凌晨3点执行的任务,平时挂在天级那层,当时间跨到当天时,它会降级到小时层;再降级到分钟层;最终在秒层被精确触发。这种设计的巧妙之处在于:无论一个任务的触发周期是一分钟还是一年,它在时间轮里占用的槽位始终就一个,内存开销是恒定的。

触发精度的保障另有一个细节:系统时钟的问题。物理机的时钟可能因为NTP对时、闰秒等原因出现突然跳变,如果调度器直接取系统时间做计算,可能出现任务提前触发或者延后触发。我们在AX的调度节点上单独做了一层"单调时钟"抽象,底层用CLOCK_MONOTONIC获取经过时间,再用CLOCK_REALTIME做绝对时间的校准,两者结合保证跳变时不会产生误触发。这个点刚开始做的时候觉得有点过度设计,后来真实遇到一次云主机时钟跳变60毫秒导致一批秒级任务提前触发,我们才确认这是必须的。实测下来,AX在线上的触发偏差控制在正负200毫秒内,跑秒级任务基本没有体感问题。

3. 部署AX并跑通第一个实时调度任务

下面直接讲落地步骤。我们以一套测试环境为例,说明怎么把AX完整搭起来,并让一个最简单的"每5秒打印一次当前时间"的任务跑起来。假设你的服务器是Linux x86_64,三台节点:一台跑调度器和管理端,另外两台注册为执行器。

第一步,初始化数据库。AX依赖MySQL存储元数据,生产环境建议至少用MySQL 8.0版本。建库之后,把项目里提供的schema.sql执行一遍,它会创建我们前面说的三张表以及一些辅助表。执行完之后确认几张表都建出来了,特别检查一下task_def表里的created_at字段是否有默认值,有些初始化脚本容易漏掉这个,后面会导致插入报错。

第二步,启动调度器。AX的调度器是一个Java进程,启动参数里指定两个配置项:数据库连接串和监听端口。注意数据库连接池的大小不要开太大,调度器本身对数据库的操作频率并不高,连接池默认10个就够用了。启动日志里看到"Scheduler started"并且没有连接数据库异常,就说明调度器起好了。

第三步,启动管理端。管理端是一个标准的Spring Boot应用,配置数据库和调度器地址,启动后浏览器访问它的HTTP端口。默认有一个管理员账号,第一次登录后建议立即把密码换掉。管理端本身没有数据库迁移的职责,所以它的启动顺序放在调度器之后不会有什么并发风险。

第四步,启动执行器。执行器在代码里的接入很简单:加一个@AxExecutor("demoGroup")注解在Spring Bean上,方法里写业务逻辑就行。执行器启动时会自动把自己注册到注册中心,并和调度器建立长连接。验证注册是否成功,可以在管理端的"执行器管理"页面看到demoGroup里出现了两个节点,状态为"在线"。

配置任务这一步,我建议直接走管理端界面操作,顺便体验一下更改配置的实时生效效果。新建一个任务,任务名随便起,分组选择demoGroup,调度策略选"固定间隔",间隔填5秒,路由策略选"轮询",超时时间默认60秒,重试次数先设0。保存之后几秒钟内,你在执行器的日志里就会看到任务每隔5秒被触发一次。这里有一个特别好的验证点:两个执行器节点会交替收到任务,而不是固定在一个节点上跑,说明轮询路由策略已经生效。

整个过程跑通之后,再做一个小实验测试故障转移:手动把其中一台执行器进程kill掉,等5秒让它心跳超时,再去管理端看任务状态,你会发现任务自动集中到了另一台节点上继续执行,没有出现断档超过一个周期的情况。这个实验比较直观地说明了"调度器通过心跳感知节点状态、自动failover"这件事在实践中确实有效。

3.1 路由策略和分片执行:让任务不只是均匀分发

任务的执行节点选择不是一把梭,不同业务适合不同策略,AX支持五种路由策略:轮询(RoundRobin)、加权轮询(WeightedRR)、一致性哈希(ConsistentHash)、广播(Broadcast)、分片(Sharding)。

轮询适合那些处理逻辑完全无状态的场景,比如清理临时文件、拉取增量数据。加权轮询在节点性能差异大的时候有用,比如两台机器一台是8核一台是32核,权重可以按核数比例去调。一致性哈希适合"同一个业务主键必须打到同一个节点"的场景,比如按用户ID处理的任务,保证同一个用户的数据总是落在同一台机器上处理,避免多机并发操作同一份数据导致锁冲突。

广播和分片是任务量上来之后的两大杀器。广播的含义是同组的每个节点都执行一遍,典型场景是所有节点都需要执行本机上的操作,比如清理本机磁盘缓存、刷新本地配置。分片是另一个思路:把任务的数据空间切成片,每片交给一个节点执行。在AX里,分片是通过给每个执行器分配一个index和total来实现的——调度器告诉每个节点"你是3个节点里的第2个",节点拿到这个信息后,自己在本地算出应该处理哪一段数据。

我们有一个实际的例子:每天要同步一百万个用户的数据到下游系统。没分片的时候,单个节点跑完需要四个多小时,中间一旦失败就要从头再来,非常痛苦。改成10片、10个执行器并行的方案后,单次同步时间压缩到了30分钟以内,而且每个分片都是独立的执行记录,单独失败单独重试,互不影响。这种改造的价值不是说提高了多少并发,而是把"一个大而脆弱的过程"拆成了"若干个小而可靠的单元",运维心态会完全不一样。

分片粒度怎么定,我自己的经验是一条任务的分片数不要超过执行节点数的10倍。分片太多,单片的处理时间过短,路由、调度、建实例的消耗占的比重会变大,反而失真;分片太少,又压不住热点数据倾斜。10倍以内是一个在均衡度和开销之间都比较舒服的范围。

4. 调度系统线上实战:那些坑,以及我怎么一步步定位的

AX在我们线上稳定跑了大半年,但中间踩过的坑绝对不少。挑几个对大家可能最有参考价值的讲,重点不是直接说"答案是什么",而是复盘当时的排查链路,因为很多问题如果只看结论,换一个环境可能就套不上了。

第一个坑是任务的重复执行。现象:某个银行对账单任务,每周一凌晨跑一次,但有一个月用户收到两份对账单。翻开task_instance表发现同一条task_def居然同时存在两个"运行中"的实例,而且都执行完了。排查链路是这样走的:先怀疑是不是调度器重复触发了,于是把该任务的trigger_record拉出来看,发现确实有两次触发记录,时间相隔不到3秒。继续追,发现问题不在触发,而在执行器宕机恢复。第一次触发后,节点A拿到任务但在执行过程中进程卡死了,心跳断掉,调度器等了几秒判定A失联,就把任务重新投给了节点B;结果节点A在挂在半路之后又活了过来,把后半段给跑完了。两个节点都认为自己合法地执行了任务,产生了重复。

这个问题的本质不是调度器的问题,而是我们的任务没有幂等保障。调度系统能做的最多是"至少一次"的投递语义,要保证"最终只执行一次",必须在任务代码里做幂等控制。对于上述对账单场景,我们用数据库唯一键做的幂等:每次执行实例ID写入一个唯一键表,处理前先插入,插成功才继续;插失败说明已处理过,直接忽略。这个表即便以后写入量变大也不怕,因为它只存两个月内的实例ID,超过两月的数据可以定期清掉。

第二个坑是任务执行超时,但调度器迟迟不知道。现象:一个日期的数据重算任务跑了快俩小时,远远超过设定好的10分钟超时,但管理端的显示一直是"运行中",另外报警也没触发。查代码后发现是我们超时检测的实现有缺陷:检测线程只是检查"当前时间减去开始时间是否超过超时阈值",但根本没考虑执行器的心跳是否还活着。如果执行器和调度器的连接断开了,执行器那边的任务可能已经死了,但调度器只盯着墙钟时间算,自然不会判定超时。

修复方案是双因子判定:超时检测同时要求"超过设定时间"且"节点心跳正常";如果心跳已经停了,走失联处理,而不是等超时。这样改完之后,任务假死的情况会在心跳超时的那一刻就被接管,而不是拖到超时阈值才反应。经验是:超时是执行层的概念,失联是调度层的概念,两者不能互相替代,必须分开处理。凡是把这两个状态混在一起的任务系统,线上多多少少都会出问题。

第三个坑比较隐蔽,是关于cron表达式的边界条件。用户配置了一个"每天23:59执行"的任务,但某天出现了两次执行记录:23:59一次,第二天的00:00一次。查cron解析库的日志发现,问题出在表达式"59 23 * * *"遇到DST(夏令时)切换时产生了歧义。但我们服务器用的不是夏令时,怎么会触发这条路径?再往下挖发现是cron解析库内部对"本地时区内一天只存在一次的某些时刻"做了特殊处理,导致在特定周几和特定日期组合下,解析结果里出现了一个起点和终点都完全一样的区间,库里对这类区间的处理是"触发一次并补偿一次"。

解决办法不是改解析库,而是约定:AX里的cron表达式统一使用Asia/Shanghai时区,并在文档里明确标注"夏令时地区使用方需要自行评估影响"。同时我们在管理端对每次触发记录的"预计触发时间"和"实际触发时间"都做了展示,哪一次是异常补偿产生的,一眼就能看出来。这个案例给我的启发是:调度系统往往在"时间"这种想当然的领域里藏最深的雷。

问题表象真实原因修复手段
任务重复执行用户收到两份对账单节点失联后恢复,两端各执行一段业务侧幂等,唯一键防重
超时检测失效任务挂死但状态一直是运行中超时判断没融合心跳状态超时+失联双因子判定
cron边界错乱23:59的任务出现两次解析库在特定日期产生补偿触发统一时区+展示实际触发时间

这三个问题有一个共性:它们在理论层面都是"可预见的",但如果你没有真正在线上被坑过一次,很难在系统设计初期就留意到它们。调度系统的价值很多时候不是体现在平时,而是体现在出事之后你还能快速定位原因——所以日志要结构化、状态要有记录、审计要不可删改,这些基础工作真的别省。

4.1 任务执行日志的采集与千亿级排查思路

任务一旦多了以后,"排查一个问题"会变成一件相当恐怖的事:你有几千个任务,每个任务每天可能执行几十上百次,产生的日志分布在几十台机器上。要定位"昨天凌晨这个任务为什么失败了",如果用传统方式ssh到机器上翻日志,那效率低到没法用。

AX从设计之初就把执行日志的采集做成了标准流程。执行器打印业务日志时统一通过日志框架的appender输出到本地文件,同时异步发送一份给日志采集组件,采集组件按"任务实例ID"来组织索引——不是按机器、不是按时间,而是按实例ID整理你本次运行的所有日志内容。这样一来,在管理端看到某一次执行失败时,点击实例ID就能直接看到这次运行的完整日志,不用再去机器上人工找。

日志采集的异步投递有个问题,就是有丢失的可能。我们的对策是双写:本地磁盘文件始终保留至少7天,采集组件作为加速通道而不是唯一通道。万一采集通道断了,管理端提示"日志暂未同步",运维人员可以手动回看本地文件。这个设计的取舍很清楚:完全可靠的日志系统成本极高,实时可见的价值又太大,用双写方案折中,既能快速排查大多数问题,又能保证极端情况下的兜底。

说到查询,我们数据规模大概是这样:执行记录日均千万级,日志条目日均亿级。一开始用MySQL直接查日志,查询接口慢到无法接受;后来把日志索引迁移到ES,状态类查询仍然留在MySQL,才算把体验救回来。这里又是一个分库分场景的案例:日志这类追加写为主的冷数据,和状态这类热读冷写的数据,天然不适合放在同一套存储方案里。

5. 生产环境容量评估与调优方向

聊完踩坑,讲一点正向的东西:调度系统上线前,怎么做容量估算和参数调优,让它在稳定性的基础上还能预留一些扩展空间。

调度器的压力决定于两个指标:触发频率和任务总量。触发频率是所有任务每天触发次数的总和,比如你有1万个每天执行一次的任务,那平均每秒触发约0.12次,毫无压力;但如果这些任务里有1000个是每5秒执行一次的,触发频率就是每天约1730万次,平均每秒约200次,这个量级就需要认真对待线程池的配置了。

关于线程池参数,AX里有两类线程:一种是时间轮推进线程,不管任务量多大,只会有一个,所以它的瓶颈不在线程数而在CPU单核能力;另一种是任务投递线程池,负责把触发到的任务发给执行器并等待确认。投递线程池的默认核心线程数按CPU核数配置,最大线程数控制在CPU核数的2倍;队列深度给了5000。为什么控制得这么保守?因为调度器本身不是业务执行单元,它的任务就是把信号送出去,如果送得慢,队列堆起来,再多的线程也解决不了问题,只会让系统整体过热,最终调度时延变长。

执行器的线程池是另一套逻辑。它执行的是真正的业务任务,如果配置太大会把机器资源吃满,影响同机其他进程;太小的话任务又会积压。我们的实践是默认核心线程10、最大线程50、队列深度2000,但对于一些长耗时任务(超过10分钟),建议单独建一个执行分组,用更小的并发数去跑,避免长任务占着工作线程导致短任务排队。这一点在调度里常常被忽略,但实际上对用户体验影响极大——一个本该秒级的任务如果前面排着一个两小时的大家伙,那秒级也变两小时了。

存储层的评估维度主要看表数据增长速度。task_instance按天分区后,我们评估过单机MySQL撑到日均5000万条实例记录问题不大;再往上涨,就需要做主子分库或者搬迁到分布式数据库。什么时候该搬?我的标准不是看总量,而是看单条查询的P99延迟。如果P99超过200毫秒,就该认真考虑拆分或搬迁了。至于扩容,AX的调度器节点是可以无脑加的——多个调度器节点之间通过数据库锁抢占任务域,某一个域的调度只会由一个调度器节点负责,保证同一个任务不会出现两个节点同时触发的情况。

5.1 重试、告警与自愈机制的边角设计

最后聊一个容易被忽略但每个人都会碰到的问题:任务失败之后怎么办。AX的重试机制和很多系统的理解不一样——它不是在调度器里定时重发,而是把失败重试包装成一次新实例。具体来说,一个任务实例失败了,如果配置了重试次数和重试间隔,AX不会原地重试,而是生成一个重试子实例,这个子实例有自己独立的实例ID、触发记录和日志链路。这样设计的好处是:一次任务的完整历史就是一条链——原始实例、若干重试子实例、最终结果,任何环节出现疑问都能在链路上复盘,而不是挤在一个实例对象里来回改状态。

告警方面,AX内置对三类事件提供通知:任务失败、任务执行超时、调度器节点失联。通知通道支持邮件、企微机器人、Webhook三类。这里有一条值得说的实践:告警文案必须带上任务名和实例ID,否则接收告警的人还要再去查一次系统才能开始定位,效率直接砍半。我们的告警信息统一格式化成一个链接,点击直接跳到管理端的实例详情页,运维同学从收到告警到定位到代码,基本不需要超过1分钟。

自愈机制我们做了一部分:对于失败类型是"超时"的任务,系统会自动把失败信息打标记,下次该任务触发时自动降低其路由节点的选择范围,优先派给历史上执行较快且没有失败的节点。这个逻辑本质是一个简单的实时统计排序,不用很复杂的机器学习,但效果很明显——线上某类任务因为个别慢节点导致的连环超时,从每天好几起降到了几乎为0。核心思路就是调度器本身要收集执行记录的质量数据,基于这些数据去做下一轮调度决策。调度不应该是"配置后就不管"的静态系统,它应该越长越懂自己的运行环境。

关于AX后续的扩展方向,我们目前在做两件事,一是把任务间的依赖关系做成DAG编排,而不是现在这种简单的前置任务完成后触发后置任务的硬编码方式;另一个是尝试对运行中的任务做资源配额管理,让同组任务不互相挤占。这两块做完之后,"调度"这个词对我们来说就真的不只是一个定时触发器了。从最初为了解决重复触发和缺失触发的问题起步,到现在这套系统已经成为所有数据流转任务的事实标准,回头看,把一个看似不起眼的组件做深做大,收益远比想象中高。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 4:53:34

VLAN核心知识实战总结:从原理到配置与排障

项目标题: VLAN知识点归纳与总结(实战篇) 项目正文: VLAN(Virtual Local Area Network,虚拟局域网)知识点归纳与总结,偏向实战应用,包含VLAN基础概念、工作原理、配置方法、常见问题排查等内容&…

作者头像 李华
网站建设 2026/10/17 4:51:49

hyperdbg 内核调试器源码拆解:ring0 断点、EPT 钩子与 VM-Exit 机制

简介:Hyperdbg 是一款面向操作系统内核开发与安全研究人员的 ring0 级内核调试器,定位与 SoftIce、WinDbg 相仿,可在最高特权层直接跟踪系统调用、中断处理等内核事件,适用于驱动开发、恶意软件行为分析与系统性能优化等场景。资源…

作者头像 李华
网站建设 2026/10/17 4:50:17

MATLAB数字信号处理实战:滤波、调制解调与小波分析全链路解析

先说一个我憋了很久的结论:真正让MATLAB数字信号处理变难的,从来不是某个函数不会调用,而是滤波、调制解调、小波分析这三件事凑在一起时,信号链路到底该怎么组织。滤波是基础,调制解调是通信的核心,小波分…

作者头像 李华
网站建设 2026/10/17 4:50:01

Superpowers 上手指南:为 Codex CLI 注入工程实战能力

Superpowers 完全上手指南:给 Codex CLI 注入工程实战能力如果你最近关注 AI 编程工具,应该会注意到 Codex CLI 这类终端 AI 助手正火。但很多人用着用着就发现一个问题:它能聊天、能改代码、能解释概念,可真让它干活的时候&#…

作者头像 李华
网站建设 2026/10/17 4:49:48

单机游戏网站模板:JSON驱动的纯静态网页实战指南

简介:单机游戏网站模板是一套面向游戏行业建站需求的网页设计资源包,适合个人开发者、游戏工作室或资讯站点快速搭建包含资讯、评测、社区等模块的专业站点。模板基于HTML/CSS/JS构建,内置首页、游戏分类、详情页、新闻评测、社区互动、用户中…

作者头像 李华