How to schedule dynamic function with cron job?

后端 未结 6 1911
無奈伤痛
無奈伤痛 2021-01-02 06:02

I want to know how I can schedule a dynamic(auto populated data) function to auto run everyday at saved time?

Let\'s say I have a form that once the button is clicke

相关标签:
6条回答
  • 2021-01-02 06:46

    Cron tasks require you to preset the times at which they run, they cannot (yes you could hack this by having a script which edits your crontab, but I would not say that is a very good idea) have their time to run decided dynamically. This means you essentially have two options:

    1) Set a cronjob to run every minute and use a temp file which you touch to tell the last time that it ran one of the scheduled tasks Each time that it runs it checks if there was a task to run in between the last timestamp of your temp file and the current time, and if there is it runs the task. This is a gross but simple solution.

    2) Don't use cron. Create a daemon which checks what times tasks need to be run and puts them into a priority queue, then it pops the earliest element and sleeps until it is time to run that task. It runs the task and reinserts it to be run 24 hours in the future and repeats. This solution is by far more elegant, but it also requires more work.

    0 讨论(0)
  • 2021-01-02 06:48

    I am suggesting you create Cron Entries dynamically through a wrapper script which configure the cron entry to run your particular function when you actually wanted it to run.

    For your specific case here Below is what I would suggest :

    1. Create a wrapper script And schedule it in Cron to run every Second.
    2. This wrapper script will talk to MySQL and fetch the time at which a specific function is to run.
    3. Then it will Dynamically create a Cron Entry to run that function at that specific retrieved timestamp. You can dynamically add and remove the Cron Entry using the shell script. Please see references below for details.
    4. Once your function is completed, There should be some indication like status stored somewhere maybe in your DB, or some file, so that the wrapper can get/know the status and remove the respective cron entry.

    References
    1. Create Cron Using Bash
    crontab -l | { cat; echo "0 0 0 0 0 some entry"; } | crontab -
    2. Delete/Automate Cron
    crontab -l -u | grep -v <unique command> | crontab -

    0 讨论(0)
  • 2021-01-02 06:53

    with nodeJS, Using node-schedule solved the issue:

    start the custom Job:

      const campaignId = "MY_CUSTOM_ID"
      let job = schedule.scheduleJob(campaignId, '* * * * * *', function () {
        console.log('running campaign: ' + campaignId)
      })
    

    stop the custom Job:

      const campaignId = "MY_CUSTOM_ID"
      let current_job = schedule.scheduledJobs[campaignId]
      current_job.cancel()
    
    0 讨论(0)
  • 2021-01-02 06:59

    you have 2 ways, although only one will do exactly what you want to do;

    • 1st way requires that you have access and privileges to change cron-jobs server side (example via PHP or other). Depending on what OS there are tutorials: Win , Nix

    • 2nd way will do something close to what you want but without the minutes precision, you will loose at max 2 minutes each cycle. (see exaplanation below).

    1st Way perfect way

    • As soon as the user hit the form create a unique cron-task for that user using the desired datatime.

    if you don't have those privileges you can use 3d part service like www.easycron.com they also offer a Free version with limited query. they also provide a REST API method to manage (CRUDE) cron-tasks.

    2nd Way imperfect way

    • add a new VARCHAR column, i called it today with this we will ensure that the task will run only once per day.

    -

    +----+---------+----------+------------+--------+----------+
    | iD | timeDay | timeHour | timeMinute | postiD |   today  |
    +--------------------------------------+--------+----------+
    | 1  | *       | 9        | 0          | 21     | 30-05-04 |
    |----|---------|----------|------------|--------|----------+
    | 2  | *       | 10       | 30         | 22     |          |
    |----|---------|----------|------------|--------|----------+
    | 3  | *       | 11       | 0          | 23     |          |
    +----|---------+----------+------------+--------+----------+
    
    • after that create a php file i called it crontask.php we will call it each 5 minutes

    • add this to your cronjob panel:

    • 0,5 * * * * /usr/bin/php /www/virtual/username/crontask.php > /dev/null 2>&1

    • in the crontask.php file

    -

    <?php
    // include() Database Config file here with mysql_connect etc...
    // include() the required files ex. the file where myFunction reside...
    
    $cron_cycle = 5; // set it equal to what used in cron command-line
    $today = date('Y-m-d');
    if($result = mysql_query("SELECT * FROM postDataTime WHERE today != '{$today}'")){
        while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) { 
            $postID = $row['postID'];
            $timeHour = (int) $row['timeHour'];
            $current_hours = (int) date('H'); // current hours
            $current_minutes = (int) date('i'); // current minutes
            $timeMinute = (int) $row['timeMinute'];
            // force to run at the closest cycle
            $timeMinute = ($timeMinute % $cycle === 0) ? $timeMinute : toCloser($timeMinute, $cron_cycle); 
            if( $current_hours === $timeHour && $current_minutes === $timeMinute ){
                // ensure that we have already runned a cron for this user...
                mysql_query("UPDATE postDataTime SET today = '{$today}' WHERE postID = '{$postID}'");
                myFunction($postID);
            }
        }
    }
    function toCloser($n,$x=5) {
        $j = (round($n)%$x === 0) ? round($n) : (round(($n+$x/2)/$x)*$x);
        return ($j-$n) >= round($x/2) ? ($j-$x) : $j;
    }
    
    ?>
    

    Explanation of the function:

    Assuming that the cron shedule runs each 5 minutes, lat's say we are at 20:00 o'clock, now the cron will run at 20:05, 20:10, 20:15, 20:20 and so on...

    then assuming in our DB we have those time

    Jonh  : 20:05, 
    Mario : 20:32, 
    luke  : 20:48, 
    David : 20:57, 
    Jimmy : 20:06, 
    Eddy  : 20:16
    

    when the script checks against those times it will run as below:

    at 20:05 -> run 20:05 Jonh, 20:06 Jimmy
    at 20:10 -> run null
    at 20:15 -> run 20:16 Eddy
    at 20:20 -> run null
    and so on....
    

    As you see you would loose in the worst case 2 minutes each time. I think it's fair enough! ;)

    0 讨论(0)
  • 2021-01-02 06:59

    It is possible to set up a cron job that runs every minute and when triggered it checks what jobs are scheduled for that moment.

    As a simple idea which could be easily modified to push through the run time details for a particular script if you wanted:-

    <?php
    
    include '/core/config.php');
    
    // Test script to allow jobs to be set up (cron style) on a database, but with the addition that jobs can be made
    // dependent on other jobs completing first.
    // Currently does not support jobs being dependent on more than one parent job.
    // It uses a database of 2 tables. One for servers and the other for jobs.
    // The server is selected as the one that matches the value of php_uname('n') (hence this can be run on many servers accessing a single database and only executing jobs for the particular server an instance is running on)
    // Time ranges are specified in the same way as on CRON jobs:-
    //  *   = no restriction based on that field
    //  x   = when the value of that time parameter matches x
    //  /x  = every x of that field (ie, mod current of that field by x and match if result is 0)
    //  x-y = when the value of that time parameter is between x and y
    //  x,y = when the value of the time parameter matches x or y (or z, etc)
    // The script field on the scheduling table contains the script / command to be executed. For example if a php script then it might be 'php /usr/webdata/cron_scripts/some_script.php
    // Parentid is the id of a job that must have finished before the job is executed.
    
    class scheduling extends core_class
    {
    
        public $connections;
        private $db;
    
        private $year;
        private $month;
        private $day;
        private $hour;
        private $minute;
        private $second;
        private $day_of_week;
        private $background_kick_off = true;
    
        private $completed_jobs = array();
    
        function __construct($connections, $background_kick_off = true) 
        {
            parent::__construct($connections);
    
            $this->background_kick_off = $background_kick_off;
    
            $this->debug_time_start();
    
            $this->connections = $connections;
            $this->db = new database($connections['EO'], 'em_scheduling');
            if (!$this->db->no_error)
                $this->error('E_ERROR', $this->db->error());
    
            $run_date = date('Y/m/d H:i:s w');
    
            list($date_part, $time_part, $this->day_of_week) = explode(' ', $run_date);
            list($this->year, $this->month, $this->day) = explode('/', $date_part);
            list($this->hour, $this->minute, $this->second) = explode(':', $time_part);     
    
            $this->find_jobs(0);
        }
    
        function find_jobs($parent_id)
        {
            $sql = "SELECT a.id, a.script, a.parent_id, a.minutes, a.hours, a.day_of_month, a.months, a.day_of_week, a.script_description, COUNT(DISTINCT b.id) AS child_count
                    FROM scheduling a
                    ON s.id = a.server_id
                    LEFT OUTER JOIN scheduling b
                    ON a.id = b.parent_id
                    AND b.enabled = 1
                    AND (b.minutes = '*' OR FIND_IN_SET('".$this->minute."', b.minutes) OR (SUBSTR(b.minutes, 1, 1) = '/' AND (".$this->minute." % CAST(SUBSTR(b.minutes, 2) AS UNSIGNED)) = 0) OR (b.minutes LIKE '%-%' AND ".$this->minute." BETWEEN CAST(SUBSTRING_INDEX(b.minutes, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(b.minutes, '-', -1) AS UNSIGNED)))
                    AND (b.hours = '*' OR FIND_IN_SET('".$this->hour."', b.hours) OR (SUBSTR(b.hours, 1, 1) = '/' AND (".$this->hour." % CAST(SUBSTR(b.hours, 2) AS UNSIGNED)) = 0) OR (b.hours LIKE '%-%' AND ".$this->hour." BETWEEN CAST(SUBSTRING_INDEX(b.hours, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(b.hours, '-', -1) AS UNSIGNED)))
                    AND (b.months = '*' OR FIND_IN_SET('".$this->month."', b.months) OR (SUBSTR(b.months, 1, 1) = '/' AND (".$this->month." % CAST(SUBSTR(b.months, 2) AS UNSIGNED)) = 0) OR (b.months LIKE '%-%' AND ".$this->month." BETWEEN CAST(SUBSTRING_INDEX(b.months, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(b.months, '-', -1) AS UNSIGNED)))
                    AND ((b.day_of_month = '*' OR FIND_IN_SET('".$this->day."', b.day_of_month) OR (SUBSTR(b.day_of_month, 1, 1) = '/' AND (".$this->day." % CAST(SUBSTR(b.day_of_month, 2) AS UNSIGNED)) = 0) OR (b.day_of_month LIKE '%-%' AND ".$this->day." BETWEEN CAST(SUBSTRING_INDEX(b.day_of_month, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(b.day_of_month, '-', -1) AS UNSIGNED)))
                    OR (b.day_of_week = '*' OR FIND_IN_SET('".$this->day_of_week."', b.day_of_week) OR (SUBSTR(b.day_of_week, 1, 1) = '/' AND (".$this->day_of_week." % CAST(SUBSTR(b.day_of_week, 2) AS UNSIGNED)) = 0) OR (b.day_of_week LIKE '%-%' AND ".$this->day_of_week." BETWEEN CAST(SUBSTRING_INDEX(b.day_of_week, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(b.day_of_week, '-', -1) AS UNSIGNED))))
                    WHERE a.parent_id = ".(int)$parent_id."
                    AND a.enabled = 1
                    AND (a.minutes = '*' OR FIND_IN_SET('".$this->minute."', a.minutes) OR (SUBSTR(a.minutes, 1, 1) = '/' AND (".$this->minute." % CAST(SUBSTR(a.minutes, 2) AS UNSIGNED)) = 0) OR (a.minutes LIKE '%-%' AND ".$this->minute." BETWEEN CAST(SUBSTRING_INDEX(a.minutes, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(a.minutes, '-', -1) AS UNSIGNED)))
                    AND (a.hours = '*' OR FIND_IN_SET('".$this->hour."', a.hours) OR (SUBSTR(a.hours, 1, 1) = '/' AND (".$this->hour." % CAST(SUBSTR(a.hours, 2) AS UNSIGNED)) = 0) OR (a.hours LIKE '%-%' AND ".$this->hour." BETWEEN CAST(SUBSTRING_INDEX(a.hours, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(a.hours, '-', -1) AS UNSIGNED)))
                    AND (a.months = '*' OR FIND_IN_SET('".$this->month."', a.months) OR (SUBSTR(a.months, 1, 1) = '/' AND (".$this->month." % CAST(SUBSTR(a.months, 2) AS UNSIGNED)) = 0) OR (a.months LIKE '%-%' AND ".$this->month." BETWEEN CAST(SUBSTRING_INDEX(a.months, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(a.months, '-', -1) AS UNSIGNED)))
                    AND ((a.day_of_month = '*' OR FIND_IN_SET('".$this->day."', a.day_of_month) OR (SUBSTR(a.day_of_month, 1, 1) = '/' AND (".$this->day." % CAST(SUBSTR(a.day_of_month, 2) AS UNSIGNED)) = 0) OR (a.day_of_month LIKE '%-%' AND ".$this->day." BETWEEN CAST(SUBSTRING_INDEX(a.day_of_month, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(a.day_of_month, '-', -1) AS UNSIGNED)))
                    OR (a.day_of_week = '*' OR FIND_IN_SET('".$this->day_of_week."', a.day_of_week) OR (SUBSTR(a.day_of_week, 1, 1) = '/' AND (".$this->day_of_week." % CAST(SUBSTR(a.day_of_week, 2) AS UNSIGNED)) = 0) OR (a.day_of_week LIKE '%-%' AND ".$this->day_of_week." BETWEEN CAST(SUBSTRING_INDEX(a.day_of_week, '-', 1) AS UNSIGNED) AND CAST(SUBSTRING_INDEX(a.day_of_week, '-', -1) AS UNSIGNED))))
                    GROUP BY a.id, a.script, a.parent_id, a.minutes, a.hours, a.day_of_month, a.months, a.day_of_week
                    ORDER BY child_count
                    ";
    
            //echo "\r\n $sql \r\n";
    
            $this->db->query($sql) or die($this->db->error());
    
            $process_array = array();
    
            while ($row = $this->db->fetch_assoc())
            {
                $process_array[] = $row;
            }   
    
            foreach($process_array as $aProcess)
            {
                if ($this->background_kick_off and $aProcess['child_count'] == 0)
                {
                    // No jobs to follow so just kick them off as a background task
                    $this->launchBackgroundProcess($aProcess['script']);
                    $completed_jobs[$aProcess['id']] = $aProcess['script_description'];
                }
                else
                {
                    passthru($aProcess['script'].'', $return_var);
                    if ($return_var == 0)
                    {
                        $completed_jobs[$aProcess['id']] = $aProcess['script_description'];
                        $this->find_jobs($aProcess['id']);
                    }
                }
            }
        }
    
        private function launchBackgroundProcess($call) 
        {
    
            // Windows
            if($this->is_windows())
            {
                pclose(popen('start /b '.$call, 'r'));
            }
    
            // Some sort of UNIX
            else 
            {
                pclose(popen($call.' /dev/null &', 'r'));
            }
            return true;
        }
    
        private function is_windows()
        {
            if(PHP_OS == 'WINNT' || PHP_OS == 'WIN32')
            {
                return true;
            }
            return false;
        }
    }
    
    $Scheduling = new scheduling($connections, true);
    
    ?>
    

    Tables like this:-

    CREATE TABLE IF NOT EXISTS `scheduling` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `enabled` tinyint(1) NOT NULL DEFAULT '1',
      `script` varchar(255) DEFAULT NULL,
      `parent_id` int(11) DEFAULT NULL,
      `minutes` varchar(5) DEFAULT NULL,
      `hours` varchar(5) DEFAULT NULL,
      `day_of_month` varchar(5) DEFAULT NULL,
      `months` varchar(5) DEFAULT NULL,
      `day_of_week` varchar(5) DEFAULT NULL,
      `script_description` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `parent_id` (`server_id`,`parent_id`,`enabled`)
    ) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=15 ;
    
    --
    -- Dumping data for table `scheduling`
    --
    
    INSERT INTO `scheduling` (`id`,  `enabled`, `script`, `parent_id`, `minutes`, `hours`, `day_of_month`, `months`, `day_of_week`, `script_description`) VALUES
    (1, 1, 'php download.php', 0, '*', '*', '*', '*', '*', 'Download files'),
    (2, 1, 'php load_data.php', 1, '*', '*', '*', '*', '*', 'Load files to database'),
    (3, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
    (4, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
    (5, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
    (6, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
    (7, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
    (8, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
    (9, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
    (10, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
    (11, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
    (12, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
    (13, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL),
    (14, 1, 'php file_test.php', 1, '*', '*', '*', '*', '*', NULL);
    
    0 讨论(0)
  • 2021-01-02 07:00

    If I understand correctly, I think something like this may work for you, using the hours as keys to the function you want run, in a cron set to run every two hours:

    $listArray = Array(8=>"list1_function",10=>"list2_function");//etc...
    $hour = Date("G");
    
    if(array_key_exists($hour,$listArray))
    {
        $listArray[$hour]();
    }
    
    function list1_function()
    {
         echo "do list 1 stuff";
    }
    
    function list2_function()
    {
         echo "do list 2 stuff";
     }
    
    0 讨论(0)
提交回复
热议问题