如何调度数十万任务?

17 投票
5 回答
3136 浏览
提问于 2025-04-15 20:30

我们有成千上万的任务需要在不同的时间间隔内运行,有些任务每小时执行一次,有些每天执行一次,等等。这些任务消耗资源比较多,需要分布到很多机器上去处理。

现在,这些任务存储在一个数据库里,每个任务都有一个“在这个时间执行”的时间戳。为了找到需要执行的任务,我们会查询数据库,找出那些到时间的任务,然后在任务完成后更新它们的时间戳。这样一来,数据库的写入负担就变得很重。

根据我的理解,我们希望有一种方法,可以在设定的时间间隔内把任务放入一个队列中。(然后工作程序可以从这个队列中请求任务。)

那么,如何在大规模的情况下安排定期任务的执行呢?

顺便提一下,我们主要使用Python,不过如果需要的话,也可以使用其他语言写的组件(比如RabbitMQ)。

更新:目前我们大约有350,000个任务,每半小时执行一次,具体时间会有些变化。350,000个任务 * 每天48次,就是每天执行16,800,000个任务。

更新2:这些任务之间没有依赖关系。任务不需要按顺序执行,也不依赖于之前的结果。

5 个回答

1

35万个任务每天执行48次,总共就是每天执行1680万个任务。

要安排这些任务,其实不需要用数据库。

数据库主要是用来存储那些需要更新的数据。在这里,唯一需要更新的就是任务的安排,比如添加、删除或重新安排任务。

Cron可以用一个简单的文件来高效地完成这个任务。

把整个文件读到内存里,然后开始执行任务。定期检查文件是否有变化,或者更好的办法是等待一个HUP信号,这样就可以用这个信号重新读取文件。可以用kill -HUP命令来通知调度器重新读取文件。

不太明白你为什么要更新数据库。

如果数据库是用来根据任务完成情况来决定未来的安排,那用一个单一的数据库其实是个很糟糕的主意。

如果你是用数据库来分析任务的历史记录,那你就有了一个简单的数据仓库。

  1. 在一个简单的日志文件中记录完成信息(开始时间、结束时间、退出状态等等)。

  2. 处理这些日志文件,创建事实表和维度更新。

当有人想要进行分析时,可以把相关的日志文件部分加载到数据集市,这样他们就可以进行查询、统计和计算平均值等操作。

不要直接把每天1700万条记录存入关系型数据库。没有人需要那么多数据,他们只想要总结信息:统计和平均值。

3

如果你不想用数据库,可以只在内存中存储下一个运行的时间戳和任务ID。每个任务的属性可以保存在一个名为 [task_id].txt 的文件里。你需要一个数据结构来在内存中存储所有任务,并按时间戳排序,AVL树似乎是个不错的选择,这里有一个简单的Python实现:http://bjourne.blogspot.com/2006/11/avl-tree-in-python.html。希望Linux(我猜你是在用这个)能处理目录下的数百万个文件,否则你可能需要根据任务ID来创建子文件夹。

你的主服务器只需要运行一个循环,从AVL树中取出任务,直到下一个任务的时间戳在未来。然后你可以休眠几秒钟,再开始检查。当一个任务运行时,你需要更新任务文件中的下一个运行时间戳,并把它重新插入到AVL树中。

当主服务器重启时,需要重新加载所有任务ID和下一个运行时间戳到内存中,这对于数百万个文件来说可能会很麻烦。也许你可以把所有信息放在一个大文件里,每个任务占用1K的空间,用来存储属性和下一个运行时间戳,然后用 [task_id] * 1K 来找到任务属性的正确位置。

如果你愿意使用数据库,我相信MySQL可以处理你提到的情况,前提是你的主服务器有4GB以上的内存,并且有几个硬盘以RAID 0+1的方式连接。

最后,如果你真的想让事情变得复杂,Hadoop也许可以用:http://hadoop.apache.org/

5

因为你不需要ACID特性,并且可以接受任务可能会运行两次,所以我建议你根本不在数据库里保存时间戳。对于每个任务,创建一个列表,里面包含[下次运行的时间戳, 任务ID],然后用一个最小堆来存储这些列表。Python的heapq模块可以帮你管理这个堆。这样你就能很高效地取出下一个要运行的任务。当你需要执行某个任务时,使用它的任务ID去数据库查找这个任务需要做什么。当任务完成后,更新它的时间戳,然后把它放回堆里。(只要小心不要改变当前在堆里的项目,因为那样会破坏堆的特性。)

数据库只用来存储那些在崩溃和重启后你仍然关心的信息。如果重启后你不需要这些信息,就不要浪费时间写入磁盘。你仍然会有很多数据库读取操作来加载需要运行的任务的信息,但读取比写入便宜多了。

如果你的内存不够大,无法同时存储所有任务,你可以采用一种混合方案,比如把接下来24小时内的任务保存在内存中,其他的则放在数据库里。或者,你也可以用C或C++重写代码,这两种语言对内存的需求更少。

撰写回答