Назад | Перейти на главную страницу

Предотвратить выполнение повторяющихся заданий cron

Я запланировал запуск задания cron каждую минуту, но иногда для завершения сценария требуется больше минуты, и я не хочу, чтобы задания начинали «накладываться друг на друга». Я предполагаю, что это проблема параллелизма, т.е. выполнение скрипта должно быть взаимоисключающим.

Чтобы решить эту проблему, я заставил скрипт проверять наличие определенного файла ("lockfile.txt") и выйти, если он существует, или touch это, если это не так. Но это довольно паршивый семафор! Есть ли лучшая практика, о которой я должен знать? Должен ли я вместо этого написать демона?

Есть несколько программ, которые автоматизируют эту функцию, устраняют раздражение и потенциальные ошибки, связанные с выполнением этого самостоятельно, и избегают проблемы устаревшей блокировки, также используя flock за кулисами (что является риском, если вы просто используете сенсорное управление) . Я использовал lockrun и lckdo в прошлом, но теперь есть flock(1) (в новых версиях util-linux), и это здорово. Пользоваться действительно просто:

* * * * * /usr/bin/flock -n /tmp/fcj.lockfile /usr/local/bin/frequent_cron_job

Лучший способ в оболочке - использовать стадо (1)

(
  flock -x -w 5 99
  ## Do your stuff here
) 99>/path/to/my.lock

Фактически, flock -n может использоваться вместо lckdo*, поэтому вы будете использовать код от разработчиков ядра.

Опираясь на пример Уомбла, вы бы написали что-то вроде:

* * * * * flock -n /some/lockfile command_to_run_every_minute

Кстати, глядя на код, все flock, lockrun, и lckdo делайте то же самое, так что это просто вопрос, который вам наиболее доступен.

Вы не указали, должен ли сценарий ждать завершения предыдущего запуска или нет. Говоря «Я не хочу, чтобы задания начинали« накладываться друг на друга », я предполагаю, что вы подразумеваете, что хотите, чтобы сценарий завершился, если он уже запущен,

Итак, если вы не хотите зависеть от lckdo или подобного, вы можете сделать это:


PIDFILE=/tmp/`basename $0`.pid

if [ -f $PIDFILE ]; then
  if ps -p `cat $PIDFILE` > /dev/null 2>&1; then
      echo "$0 already running!"
      exit
  fi
fi
echo $$ > $PIDFILE

trap 'rm -f "$PIDFILE" >/dev/null 2>&1' EXIT HUP KILL INT QUIT TERM

# do the work

Вы можете использовать файл блокировки. Создайте этот файл при запуске сценария и удалите его по завершении. Сценарий перед запуском своей основной процедуры должен проверить, существует ли файл блокировки, и действовать соответствующим образом.

Файлы блокировки используются сценариями инициализации и многими другими приложениями и утилитами в системах Unix.

Это также может быть признаком того, что вы поступаете неправильно. Если ваши задания выполняются так часто и так часто, возможно, вам стоит рассмотреть возможность де-хронирования и сделать из нее программу в стиле демона.

Ваш демон cron не должен вызывать задания, если их предыдущие экземпляры все еще работают. Я разработчик одного демона cron dcron, и мы специально пытаемся предотвратить это. Я не знаю, как Vixie cron или другие демоны справляются с этим.

Я бы рекомендовал использовать один пробег команда - намного проще, чем разбираться с замками. Из документов:

один пробег - сценарий-оболочка, который запускает не более одного уникального экземпляра некоторой команды с уникальным набором аргументов. Это часто бывает полезно с cronjobs, когда вы хотите, чтобы одновременно выполнялось не более одной копии.

беги-это-один точно так же, как run-one, за исключением того, что он будет использовать pgrep и kill для поиска и уничтожения любых запущенных процессов, принадлежащих пользователю и соответствующих целевым командам и аргументам. Обратите внимание, что run-this-one будет блокироваться при попытке убить соответствующие процессы, пока все соответствующие процессы не будут мертвы.

бегать-один-постоянно работает точно так же, как run-one, за исключением того, что он воспроизводит "COMMAND [ARGS]" каждый раз, когда COMMAND завершается (ноль или ненулевое значение).

бежать это псевдоним для run-one-always.

беги один до успеха работает точно так же, как run-one-always, за исключением того, что он возрождает "COMMAND [ARGS]" до тех пор, пока COMMAND не завершится успешно (т.е. не завершится с нуля).

пробежать один до отказа работает точно так же, как run-one-always, за исключением того, что он возрождает «COMMAND [ARGS]» до тех пор, пока COMMAND не завершится с ошибкой (т. е. завершится ненулевым).

Теперь, когда systemd отсутствует, в системах Linux появился еще один механизм планирования:

А systemd.timer

В /etc/systemd/system/myjob.service или ~/.config/systemd/user/myjob.service:

[Service]
ExecStart=/usr/local/bin/myjob

В /etc/systemd/system/myjob.timer или ~/.config/systemd/user/myjob.timer:

[Timer]
OnCalendar=minutely

[Install]
WantedBy=timers.target

Если сервисный блок уже активируется, когда таймер в следующий раз активируется, тогда другой экземпляр сервиса будет не быть запущенным.

Альтернативный вариант, при котором задание запускается один раз при загрузке и через одну минуту после завершения каждого запуска:

[Timer]
OnBootSec=1m
OnUnitInactiveSec=1m 

[Install]
WantedBy=timers.target

Я создал одну банку для решения такой проблемы, как запущенные дубликаты crons, это может быть java или оболочка cron. Просто передайте имя cron в Duplicates.CloseSessions ("Demo.jar"), это будет искать и уничтожать существующий pid для этого cron, кроме текущего. Я реализовал метод для этого. String proname = ManagementFactory.getRuntimeMXBean (). GetName (); Строка pid = proname.split ("@") [0]; System.out.println ("Текущий PID:" + pid);

            Process proc = Runtime.getRuntime().exec(new String[]{"bash","-c"," ps aux | grep "+cronname+" | awk '{print $2}' "});

            BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            String s = null;
            String killid="";

            while ((s = stdInput.readLine()) != null ) {                                        
                if(s.equals(pid)==false)
                {
                    killid=killid+s+" ";    
                }
            }

А затем убить строку killid с помощью команды оболочки снова

Ответ @Philip Reynolds в любом случае начнет выполнение кода после 5 секунд ожидания без блокировки. Следующий Флок не работает Я изменил ответ @Philip Reynolds на

(
  flock -w 5 -x 99 || exit 1
  ## Do your stuff here
) 99>/path/to/my.lock

так что код никогда не будет выполняться одновременно. Вместо этого через 5 секунд ожидания процесс завершится с 1, если к тому времени он не получит блокировку.