Ngăn chặn các công việc cron trùng lặp đang chạy


92

Tôi đã lên lịch cho một công việc định kỳ để chạy mỗi phút nhưng đôi khi kịch bản mất hơn một phút để hoàn thành và tôi không muốn các công việc bắt đầu "chồng chất" lên nhau. Tôi đoán đây là một vấn đề tương tranh - tức là việc thực thi tập lệnh cần phải loại trừ lẫn nhau.

Để giải quyết vấn đề, tôi đã tạo tập lệnh tìm kiếm sự tồn tại của một tệp cụ thể (" lockfile.txt ") và thoát nếu nó tồn tại hoặc touchnếu nó không tồn tại . Nhưng đây là một semaphore khá tệ hại! Có một thực hành tốt nhất mà tôi nên biết về? Tôi có nên viết một daemon thay thế?

Câu trả lời:


118

Có một số chương trình tự động hóa tính năng này, loại bỏ sự phiền toái và các lỗi tiềm ẩn khi tự làm điều này và tránh sự cố khóa cũ bằng cách sử dụng đàn trong hậu trường (cũng là một rủi ro nếu bạn chỉ sử dụng cảm ứng) . Tôi đã từng sử dụng lockrunlckdotrong quá khứ, nhưng bây giờ có flock(1) (trong các phiên bản mới của linux-linux), điều này thật tuyệt. Nó thực sự dễ sử dụng:

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

2
lckdo sẽ bị xóa khỏi moreutils, bây giờ bầy (1) đang ở trong linux-linux. Và gói đó về cơ bản là bắt buộc trong các hệ thống Linux, vì vậy bạn sẽ có thể dựa vào sự hiện diện của nó. Để sử dụng, xem bên dưới.
jldugger

Vâng, đàn bây giờ là lựa chọn ưa thích của tôi. Tôi thậm chí sẽ cập nhật câu trả lời của tôi cho phù hợp.
womble

Có ai biết sự khác biệt giữa flock -n file commandflock -n file -c command?
Nanne

2
@Nanne, tôi phải kiểm tra mã để chắc chắn, nhưng phỏng đoán có giáo dục của tôi là -cchạy lệnh được chỉ định thông qua shell (theo manpage), trong khi dạng "trần" (không phải -c) chỉ execlà lệnh được đưa ra . Đưa thứ gì đó qua shell cho phép bạn thực hiện những việc giống như shell (chẳng hạn như chạy nhiều lệnh được phân tách bằng ;hoặc &&), nhưng cũng mở ra cho bạn các cuộc tấn công mở rộng shell nếu bạn đang sử dụng đầu vào không tin cậy.
womble

1
Đó là một lập luận cho lệnh (giả thuyết) frequent_cron_jobđã cố gắng cho thấy nó đang được chạy mỗi phút. Tôi đã xóa nó vì nó không có gì hữu ích và gây nhầm lẫn (của bạn, nếu không có ai khác trong nhiều năm qua).
womble

28

Cách tốt nhất trong vỏ là sử dụng đàn (1)

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

1
Tôi không thể khuyến khích việc sử dụng chuyển hướng fd một cách khó khăn. Nó quá tuyệt vời.
womble

1
Không phân tích cú pháp cho tôi trong Bash hoặc ZSH, cần phải loại bỏ khoảng cách giữa 99>như vậy99> /...
Kyle Brandt

2
@Javier: Không có nghĩa là nó không phức tạp và phức tạp, chỉ là nó được ghi chép lại , phức tạp và phức tạp.
womble

1
Điều gì sẽ xảy ra nếu bạn khởi động lại trong khi điều này đang chạy hoặc làm cho quá trình bị giết bằng cách nào đó? Nó sẽ bị khóa mãi mãi sau đó?
Alex R

5
Tôi hiểu cấu trúc này tạo ra một khóa độc quyền nhưng tôi không hiểu cơ chế về cách thực hiện. Chức năng của '99' trong câu trả lời này là gì? Bất cứ ai quan tâm để giải thích điều này xin vui lòng? Cảm ơn!
Asciiom

22

Trên thực tế, flock -ncó thể được sử dụng thay vì lckdo*, vì vậy bạn sẽ sử dụng mã từ các nhà phát triển kernel.

Dựa trên ví dụ của womble , bạn sẽ viết một cái gì đó như:

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

BTW, nhìn vào mã, tất cả flock, lockrunlckdolàm điều tương tự, vì vậy nó chỉ là một vấn đề trong số đó là dễ dàng nhất có sẵn cho bạn.


2

Bạn có thể sử dụng một tập tin khóa. Tạo tập tin này khi tập lệnh bắt đầu và xóa nó khi nó kết thúc. Kịch bản, trước khi nó chạy thường trình chính của nó, nên kiểm tra xem tệp khóa có tồn tại không và tiến hành tương ứng.

Lockfiles được sử dụng bởi initscripts và bởi nhiều ứng dụng và tiện ích khác trong các hệ thống Unix.


1
đây là chỉ cách tôi đã từng nhìn thấy nó được thực hiện, cá nhân. Tôi sử dụng theo đề nghị của người bảo trì như một tấm gương cho một dự án OSS
warren

2

Bạn chưa chỉ định nếu bạn muốn tập lệnh đợi lần chạy trước hoàn thành hay không. Bởi "Tôi không muốn các công việc bắt đầu" chồng chất "lên nhau", tôi đoán bạn đang ám chỉ rằng bạn muốn tập lệnh thoát ra nếu đã chạy,

Vì vậy, nếu bạn không muốn phụ thuộc vào lckdo hoặc tương tự, bạn có thể làm điều này:


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


Cảm ơn ví dụ của bạn rất hữu ích - Tôi thực sự muốn tập lệnh thoát nếu đã chạy. Cảm ơn đã đề cập đến ickdo - nó dường như thực hiện các mẹo.
Tom

FWIW: Tôi thích giải pháp này vì nó có thể được bao gồm trong một tập lệnh, do đó, khóa hoạt động bất kể tập lệnh được gọi như thế nào.
David G

1

Đây cũng có thể là một dấu hiệu cho thấy bạn đang làm sai. Nếu công việc của bạn chạy rất chặt chẽ và thường xuyên, có lẽ bạn nên xem xét hủy đăng ký và biến nó thành một chương trình kiểu daemon.


3
Tôi chân thành không đồng ý với điều này. Nếu bạn có thứ gì đó cần chạy định kỳ, làm cho nó trở thành một daemon là một giải pháp "búa tạ cho một hạt". Sử dụng lockfile để ngăn ngừa tai nạn là một giải pháp hoàn toàn hợp lý mà tôi chưa bao giờ gặp vấn đề khi sử dụng.
womble

@womble Tôi đồng ý; nhưng tôi thích đập hạt bằng búa tạ! :-)
wzzrd

1

Trình nền cron của bạn không nên gọi công việc nếu các phiên bản trước của chúng vẫn đang chạy. Tôi là nhà phát triển của một cron daemon dcron và chúng tôi đặc biệt cố gắng ngăn chặn điều đó. Tôi không biết làm thế nào Vixie cron hoặc các daemon khác xử lý việc này.


1

Tôi sẽ khuyên bạn nên sử dụng lệnh run-one - đơn giản hơn nhiều so với xử lý các khóa. Từ các tài liệu:

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.

run-this-one giống hệt như run-one, ngoại trừ việc nó sẽ sử dụng pgrep và kill để tìm và giết bất kỳ tiến trình đang chạy nào do người dùng sở hữu và khớp với các lệnh và đối số đích. Lưu ý rằng run-this-one này sẽ chặn trong khi cố gắng giết các quy trình khớp, cho đến khi tất cả các quy trình khớp đều chết.

run-one-liên tục hoạt động chính xác như run-one ngoại trừ việc nó phản hồi lại "LỰA CHỌN [ARGS]" bất cứ khi nào thoát ra (không hoặc không bằng 0).

keep-one-running là một bí danh cho run-one-liên tục.

run-one-Until-thành công hoạt động chính xác như run-one-liên tục ngoại trừ việc nó phản hồi lại "LỰA CHỌN [ARGS]" cho đến khi thoát ra thành công (nghĩa là thoát không).

run-one-Until-fail hoạt động chính xác như run-one-liên tục ngoại trừ việc nó phản hồi lại "LỰA CHỌN [ARGS]" cho đến khi thoát ra với thất bại (nghĩa là thoát ra khác không).


1

Bây giờ systemd đã hết, có một cơ chế lập lịch trình khác trên các hệ thống Linux:

Một systemd.timer

Trong /etc/systemd/system/myjob.servicehoặc ~/.config/systemd/user/myjob.service:

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

Trong /etc/systemd/system/myjob.timerhoặc ~/.config/systemd/user/myjob.timer:

[Timer]
OnCalendar=minutely

[Install]
WantedBy=timers.target

Nếu đơn vị dịch vụ đã kích hoạt khi bộ hẹn giờ kích hoạt tiếp theo, thì một phiên bản khác của dịch vụ sẽ không được khởi động.

Một thay thế, bắt đầu công việc một lần khi khởi động và một phút sau mỗi lần chạy kết thúc:

[Timer]
OnBootSec=1m
OnUnitInactiveSec=1m 

[Install]
WantedBy=timers.target

0

Tôi đã tạo một jar để giải quyết vấn đề như các crons trùng lặp đang chạy có thể là java hoặc shell cron. Chỉ cần vượt qua tên cron trong trùng lặp. Đóng cửa Tôi đã thực hiện phương pháp để làm công cụ này. Chuỗi đại từ = ManagementFactory.getR nbMXBean (). GetName (); Chuỗi pid = negame.split ("@") [0]; System.out.println ("PID hiện tại:" + 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+" ";    
                }
            }

Và sau đó giết chuỗi killid bằng lệnh shell một lần nữa


Tôi không nghĩ rằng điều này thực sự trả lời câu hỏi.
kasperd

0

Câu trả lời @Philip Reynold sẽ bắt đầu thực thi mã sau 5 giây chờ đợi mà không nhận được khóa. Theo dõi Flock dường như không hoạt động, tôi đã sửa đổi câu trả lời @Philip Reynold thành

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

để mã sẽ không bao giờ được thực thi đồng thời. Thay vào đó sau 5 giây chờ đợi, quá trình sẽ thoát với 1 nếu sau đó nó không nhận được khóa.

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.