Giải pháp chung để ngăn chặn một công việc cron dài chạy song song?


27

Tôi đang tìm kiếm một giải pháp đơn giản và chung chung cho phép bạn thực thi bất kỳ tập lệnh hoặc ứng dụng nào trong crontab và ngăn không cho nó chạy hai lần.

Giải pháp nên độc lập với lệnh đã thực hiện.

Tôi giả sử nó sẽ trông giống như lock && (command ; unlock)nơi khóa sẽ trả về sai nếu có khóa khác.

Phần thứ hai sẽ giống như nếu nó thu được khóa, chạy lệnh và mở khóa sau khi lệnh được thực thi, ngay cả khi nó trả về lỗi.

Câu trả lời:


33

Hãy xem gói run-oneCài đặt run-one . Từ trang hướng dẫn cho run-onelệnhBiểu tượng trang :

run-one là một tập lệnh bao bọc chạy không quá một phiên bản duy nhất của một số lệnh với một bộ đối số duy nhất.

Điều này thường hữu ích với cronjobs, khi bạn muốn không có nhiều hơn một bản sao chạy cùng một lúc.

Giống như timehoặc sudo, bạn chỉ cần đưa nó vào lệnh. Vì vậy, một cronjob có thể trông giống như:

  */60 * * * *   run-one rsync -azP $HOME example.com:/srv/backup

Để biết thêm thông tin và thông tin cơ bản, hãy xem bài đăng trên blog giới thiệu nó bởi Dustin Kirkland.


5

Một cách rất đơn giản để giải quyết một khóa:

if mkdir /var/lock/mylock; then
  echo "Locking succeeded" >&2
else
  echo "Lock failed - exit" >&2
  exit 1
fi

Một tập lệnh muốn chạy cần te tạo khóa. Nếu khóa tồn tại, một tập lệnh khác đang bận, vì vậy tập lệnh đầu tiên không thể chạy. Nếu tập tin không tồn tại, không có tập lệnh nào có được khóa. Vì vậy, kịch bản hiện tại mua lại khóa. Khi tập lệnh kết thúc, khóa cần được thực hiện bằng cách tháo khóa.

Để biết thêm thông tin về khóa bash, kiểm tra này trang


1
Bạn cũng sẽ muốn một cái bẫy EXIT loại bỏ khóa khi thoát. echo "Locking succeeded" >&2; trap 'rm -rf /var/lock/mylock' EXIT
geirha

1
Lý tưởng nhất là bạn muốn sử dụng đàn tư vấn từ một quy trình chạy lệnh bạn muốn như một nhiệm vụ con. Theo cách đó, nếu tất cả chúng chết, đàn sẽ được giải phóng tự động, điều này sử dụng sự hiện diện của tệp khóa không làm được. Sử dụng một cổng mạng sẽ làm việc theo một cách tương tự - dù đó là một cách namespace nhỏ hơn, đó là một vấn đề.
Alex North-Keys

3

Không cần phải cài đặt một số gói ưa thích:

#!/bin/bash
pgrep -xf "$*" > /dev/null || "$@"

Tự viết kịch bản đó nhanh hơn chạy "apt-get install", phải không? Bạn có thể muốn thêm "-u $ (id -u)" vào pgrep để kiểm tra các phiên bản chỉ do người dùng hiện tại chạy.


2
điều này không đảm bảo một trường hợp duy nhất. hai tập lệnh có thể chuyển sang phía bên kia của ||toán tử cùng một lúc, trước khi có cơ hội để bắt đầu tập lệnh.
Sedat Kapanoglu

@SedatKapanoglu Được cấp, kịch bản đó không phải là bằng chứng phân biệt chủng tộc, nhưng câu hỏi ban đầu là về các công việc định kỳ chạy dài (được chạy nhiều nhất một lần một phút). Nếu hệ thống của bạn cần nhiều hơn một phút để tạo quy trình, bạn có một số vấn đề khác. Tuy nhiên, nếu cần vì một số lý do khác, bạn có thể sử dụng đàn (1) để bảo vệ tập lệnh trên trước các điều kiện cuộc đua.
Michael Kowhan

Tôi đã sử dụng điều này nhưng đối với một tập lệnh bash nên tự kiểm tra. Mã này là: v = $ (pgrep -xf "/ bin / bash $ 0 $ @") ["$ {v / $ BASHPID /}"! = ""] && exit 2
ahofmann

3

Xem thêm Tim Kay's solo, thực hiện khóa bằng cách ràng buộc một cổng trên địa chỉ loopback duy nhất cho người dùng:

http://timkay.com/solo/

Trong trường hợp trang web của anh ấy bị sập:

Sử dụng:

solo -port=PORT COMMAND

where
    PORT        some arbitrary port number to be used for locking
    COMMAND     shell command to run

options
    -verbose    be verbose
    -silent     be silent

Sử dụng nó như thế này:

* * * * * solo -port=3801 ./job.pl blah blah

Kịch bản:

#!/usr/bin/perl -s
#
# solo v1.7
# Prevents multiple cron instances from running simultaneously.
#
# Copyright 2007-2016 Timothy Kay
# http://timkay.com/solo/
#
# It is free software; you can redistribute it and/or modify it under the terms of either:
#
# a) the GNU General Public License as published by the Free Software Foundation;
#    either version 1 (http://dev.perl.org/licenses/gpl1.html), or (at your option)
#    any later version (http://www.fsf.org/licenses/licenses.html#GNUGPL), or
#
# b) the "Artistic License" (http://dev.perl.org/licenses/artistic.html), or
#
# c) the MIT License (http://opensource.org/licenses/MIT)
#

use Socket;

alarm $timeout                              if $timeout;

$port =~ /^\d+$/ or $noport                     or die "Usage: $0 -port=PORT COMMAND\n";

if ($port)
{
    # To work with OpenBSD: change to
    # $addr = pack(CnC, 127, 0, 1);
    # but make sure to use different ports across different users.
    # (Thanks to  www.gotati.com .)
    $addr = pack(CnC, 127, $<, 1);
    print "solo: bind ", join(".", unpack(C4, $addr)), ":$port\n"   if $verbose;

    $^F = 10;           # unset close-on-exec

    socket(SOLO, PF_INET, SOCK_STREAM, getprotobyname('tcp'))       or die "socket: $!";
    bind(SOLO, sockaddr_in($port, $addr))               or $silent? exit: die "solo($port): $!\n";
}

sleep $sleep if $sleep;

exec @ARGV;

Đối với Mac OSX, điều này sẽ thất bại với Lỗi solo(3801): Can't assign requested addresstrừ khi bạn buộc 0tham số thứ 3 của phương thức pack. Những gì tốt cho BSD cũng tốt cho Mac.
Eric Leschinski

1

Bạn cần một khóa. run-onethực hiện công việc, nhưng bạn cũng có thể muốn xem xét flocktừ util-linuxgói.

Nó là một gói tiêu chuẩn được cung cấp bởi các nhà phát triển kernel, cho phép tùy biến nhiều hơn run-onevà vẫn rất đơn giản.


0

Một giải pháp đơn giản từ bash-hackers.org phù hợp với tôi là sử dụng mkdir . Đây là một cách dễ dàng để đảm bảo rằng chỉ có một phiên bản chương trình của bạn đang chạy. Tạo một thư mục với mkdir .lock sẽ trả về

  • đúng nếu sáng tạo thành công và
  • sai nếu tệp khóa tồn tại, chỉ ra rằng hiện tại có một phiên bản đang chạy.

Vì vậy, chức năng đơn giản này đã làm tất cả logic khóa tệp:

if mkdir .lock; then
    echo "Locking succeeded"
    eval startYourProgram.sh ;
else
    echo "Lock file exists. Program already running? Exit. "
    exit 1
fi


echo "Program finished, Removing lock."
rm -r .lock

0

Giải pháp này dành cho một tập lệnh bash cần tự kiểm tra

v=$(pgrep -xf "/bin/bash $0 $@")
[ "${v/$BASHPID/}" != "" ] && exit 0
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.