cron 如何在内部调度作业?

Posted

技术标签:

【中文标题】cron 如何在内部调度作业?【英文标题】:How does cron internally schedule jobs? 【发布时间】:2011-04-28 07:58:09 【问题描述】:

“现代”cron 守护进程内部如何安排他们的工作?一些cronds 过去常常通过at 安排一次运行。所以在写出 crontab 之后,crond:

    解析所有未来事件的 crontab 和间隔的睡眠? 每分钟轮询一次聚合的 crontab 数据库以确定当前时间是否与计划模式匹配? 其他?

谢谢,

【问题讨论】:

【参考方案1】:

在这个问题中听到了一些蟋蟀。不错的 RTFC,带有一些离散事件模拟论文和***:

http://en.wikipedia.org/wiki/Cron#Multi-user_capability

这个 cron 使用的算法是 如下:

    在启动时,在主目录中查找名为 .crontab 的文件 所有帐户持有人。 对于找到的每个 crontab 文件,确定未来的下一次 每个命令都将被运行。 将这些命令放在 Franta-Maly 事件列表中 相应的时间和他们的“五 字段”时间说明符。 进入主循环:
      检查队列头部的任务条目,计算在队列中的距离 未来它将被运行。 睡那段时间。 在唤醒并验证正确时间后,在 队列的头(在后台) 具有用户的权限 创建它。 确定下次运行此命令并放置 它回到了当时的事件列表中

【讨论】:

基于cronman 页面,它看起来每分钟都会醒来以检查它是否应该运行作业:“cron 然后每分钟醒来,检查所有存储的 crontab,检查每个命令以查看它是否应该在当前分钟内运行。”这是来自安装在 Debian 上的 Vixie Cron(由 Paul Vixie 编写)。我认为你描述的算法在每分钟醒来时使用的成本太高了。 我希望我能在网上的某个地方找到关于 franta-maly 事件列表的描述。这是常见的数据结构吗? @TylerBrock,我相信 OP 指的是 Franta、W. R. 和 Kurt Maly。模拟事件集的高效数据结构。明尼苏达大学,staff.ii.pw.edu.pl/~gjb/aal/index_lists.pdf. 太棒了,谢谢@AaronBlenkush!一段时间以来一直在尝试回答这个问题。 @ifly6 简短的过于简单的回答是,进程可以告诉内核的进程调度程序在一定时间后唤醒它。进程调度程序使用 CPU 的硬件来跟踪时间。当然,处理调度的方式比评论中的要多。【参考方案2】:

我写了一个blog post 来描述它。 从那里引用相关文本:

我们可以有一个有限的线程池,它将执行所有任务,方法是从优先于 job.nextExecutionTime()PriorityBlockingQueue(线程安全堆)中提取它们。 这意味着此堆的顶部元素将始终是最快触发的元素。 我们将遵循标准的线程池生产者-消费者模式。 我们将有一个线程在无限循环中运行,并在使用队列中的新作业后将它们提交到线程池。 让我们称之为 QueueConsumerThread
void goToSleep(job, jobQueue)
    jobQueue.push(job);
    sleep(job.nextExecutionTime() - getCurrentTime());


void executeJob(job, jobQueue)
    threadpool.submit(job); // async call
    if (job.isRecurring()) 
        job = job.copy().setNextExecutionTime(getCurrentTime() + job.getRecurringInterval());
        jobQueue.add(job);
    


@Override
void run()
    while(true)
    
        job = jobQueue.pop()
        if(job.nextExecutionTime() > getCurrentTime())
            // Nothing to do
            goToSleep(job, jobQueue)
        
        else
            executeJob(job, jobQueue)
        
    

还有一个线程将监视 crontab 文件中是否有任何新作业添加并将它们推送到队列中。 我们称之为 QueueProducerThread
@Override
void run()

    while(true)
    
        newJob = getNewJobFromCrontabFile() // blocking call
        jobQueue.push(newJob)
    

但是,这样做有一个问题: 假设 Thread1 正在休眠,一个小时后会醒来。 同时有一个新任务到达,它应该每分钟运行一次。 这个新任务要在一小时后才能开始执行。 为了解决这个问题,我们可以让 ProducerThread 将 ConsumerThread 从睡眠中强行唤醒,只要新任务必须比队列中的前面任务运行得更快:
@Override
void run()

    while(true)
    
        newJob = getNewJobFromCrontabFile() // blocking call
        jobQueue.push(newJob)
        if(newJob == jobQueue.peek())
        
            // The new job is the one that will be scheduled next.
            // So wakeup consumer thread so that it does not oversleep.
            consumerThread.interrupt()
        
    

请注意,这可能不是 cron 在内部实现的方式。 但是,这是我能想到的最优化的解决方案。 它不需要轮询,所有线程都会休眠,直到它们需要做任何工作。

【讨论】:

对,但是 getNextExecutionTime 是如何工作的?我试图弄清楚它如何合并所有这些时间表以获得下一次,但我不知道。 nextExecutionTime 的初始值必须从 cron 字符串中解析。例如,如果 cron 字符串说作业应该每 10 分钟执行一次,那么当我们从 cron 文件中读取它时,我们会将下一次执行时间设置为 currentTime + 10 minutes。由于它是一个循环作业,在第一次运行后,将创建一个具有下一个最接近执行时间的新作业:job = job.copy().setNextExecutionTime(getCurrentTime() + job.getRecurringInterval());

以上是关于cron 如何在内部调度作业?的主要内容,如果未能解决你的问题,请参考以下文章

使用 aws eb 和 laravel 任务调度的 Cron 作业

使用cron进行调度时如何在shell脚本中grep命令的输出

如何设计分布式作业调度器? [关闭]

如何使用 Google Cloud 调度程序 Python api 创建作业

cron 作业或 PHP 调度程序

Elastic Beanstalk 未调度 cron.yaml 中定义的作业