ป้องกันไม่ให้งาน cron ซ้ำกัน


92

ฉันได้กำหนดเวลางาน cron ให้ทำงานทุกนาที แต่บางครั้งสคริปต์ใช้เวลานานกว่าหนึ่งนาทีในการเสร็จสิ้นและฉันไม่ต้องการให้งานเริ่ม "เรียงซ้อน" กัน ฉันเดาว่านี่เป็นปัญหาที่เกิดขึ้นพร้อมกัน - นั่นคือการดำเนินการสคริปต์จะต้องไม่เกิดร่วมกัน

เพื่อแก้ปัญหาฉันทำสคริปต์ค้นหาการมีอยู่ของไฟล์เฉพาะ (" lockfile.txt ") และออกจากถ้ามันมีอยู่หรือtouchถ้ามันไม่ได้ แต่นี่เป็นสัญญาณที่น่ากลัวมาก! มีวิธีปฏิบัติที่ดีที่สุดที่ฉันควรรู้หรือไม่? ฉันควรจะเขียนดีมอนแทนหรือไม่

คำตอบ:


118

มีโปรแกรมสองโปรแกรมที่ทำให้คุณสมบัตินี้โดยอัตโนมัติขจัดความรำคาญและข้อบกพร่องที่อาจเกิดขึ้นจากการทำเช่นนี้ด้วยตนเองและหลีกเลี่ยงปัญหาการล็อคเก่าด้วยการใช้ฝูงแกะที่อยู่ด้านหลังฉากเช่นกัน . ฉันเคยใช้lockrunและlckdoในอดีต แต่ตอนนี้มีflock(1) (ในเวอร์ชันใหม่ของ util-linux) ซึ่งดีมาก มันใช้งานง่ายมาก:

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

2
lckdo กำลังจะถูกลบออกจาก moreutils ตอนนี้ฝูง (1) อยู่ใน util-linux และแพคเกจนั้นจำเป็นต้องมีในระบบ Linux ดังนั้นคุณควรที่จะเชื่อถือได้ สำหรับการใช้งานดูด้านล่าง
jldugger

ใช่ฝูงตอนนี้เป็นตัวเลือกที่ฉันชอบ ฉันยังจะปรับปรุงคำตอบของฉันให้เหมาะกับ
womble

ไม่มีใครรู้ถึงความแตกต่างระหว่างflock -n file commandและflock -n file -c command?
Nanne

2
@Nanne ฉันต้องตรวจสอบรหัสเพื่อให้แน่ใจ แต่การเดาที่ได้รับการศึกษาของฉันคือ-cเรียกใช้คำสั่งที่ระบุผ่านเชลล์ (ตาม manpage) ในขณะที่รูปแบบ "เปลือย" (ไม่ใช่ - -c) เพียงแค่execคำสั่งที่กำหนด . การวางบางสิ่งไว้ในเปลือกช่วยให้คุณทำสิ่งที่คล้ายกับเชลล์ (เช่นการรันหลายคำสั่งที่คั่นด้วย;หรือ&&) แต่ยังเปิดให้คุณโจมตีการขยายเชลล์ด้วยหากคุณใช้อินพุตที่ไม่น่าเชื่อถือ
womble

1
มันเป็นข้อโต้แย้งของfrequent_cron_jobคำสั่ง(สมมุติ) ที่พยายามแสดงว่ามันถูกเรียกใช้ทุกนาที ฉันได้ลบมันออกไปเพราะมันไม่มีประโยชน์อะไรเลยและทำให้เกิดความสับสน
womble

28

วิธีที่ดีที่สุดในเชลล์คือการใช้ฝูง (1)

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

1
ฉันไม่สามารถใช้การเปลี่ยนเส้นทาง fd ที่ยุ่งยากได้ มันยอดเยี่ยมมากเกินไป
womble

1
ไม่ได้แยกสำหรับฉันในทุบตีหรือ ZSH ต้องขจัดช่องว่างระหว่าง99และ>ดังนั้นจึงเป็น99> /...
ไคล์ Brandt

2
@ จาเวียร์: ไม่ได้หมายความว่ามันไม่ยุ่งยากและเป็นความลับ แต่เพียงว่ามันเป็นเอกสารที่ยุ่งยากและเป็นความลับ
womble

1
จะเกิดอะไรขึ้นถ้าคุณรีสตาร์ทในขณะที่ทำงานอยู่หรือทำให้กระบวนการถูกฆ่าอย่างใด มันจะถูกล็อคตลอดไปหรือไม่
Alex R

5
ฉันเข้าใจว่าโครงสร้างนี้สร้างการล็อคแบบเอกสิทธิ์ แต่ฉันไม่เข้าใจกลไกของการทำสิ่งนี้ให้สำเร็จ ฟังก์ชั่นของ '99' ในคำตอบนี้คืออะไร? ใครสนใจที่จะอธิบายเรื่องนี้โปรด? ขอบคุณ!
Asciiom

22

ที่จริงแล้วflock -nอาจใช้แทนlckdo* ดังนั้นคุณจะต้องใช้รหัสจากผู้พัฒนาเคอร์เนล

จากตัวอย่างของ wombleคุณจะต้องเขียน:

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

BTW กำลังมองหาที่รหัสทั้งหมดflock, lockrunและlckdoทำสิ่งเดียวที่แน่นอนจึงเป็นเพียงเรื่องของการที่เป็นส่วนใหญ่พร้อมที่จะให้คุณ


2

คุณสามารถใช้ไฟล์ล็อค สร้างไฟล์นี้เมื่อสคริปต์เริ่มต้นและลบเมื่อเสร็จสิ้น สคริปต์ก่อนที่จะรันรูทีนหลักควรตรวจสอบว่ามีไฟล์ล็อกอยู่หรือไม่และดำเนินการต่อไป

Lockfiles ถูกใช้โดย initscripts และแอพพลิเคชั่นและยูทิลิตี้อื่น ๆ อีกมากมายในระบบ Unix


1
นี่เป็นวิธีเดียวที่ฉันเคยเห็นมันใช้งานเป็นการส่วนตัว ผมใช้ในการตามข้อเสนอแนะของผู้ดูแลที่เป็นกระจกสำหรับโครงการ OSS
วอร์เรน

2

คุณไม่ได้ระบุหากคุณต้องการให้สคริปต์รอให้การทดสอบก่อนหน้านี้เสร็จสมบูรณ์หรือไม่ โดย "ฉันไม่ต้องการให้งานเริ่มต้น" ซ้อนกัน "ทับกัน" ฉันเดาว่าคุณกำลังบอกว่าคุณต้องการให้สคริปต์ออกหากทำงานอยู่แล้ว

ดังนั้นถ้าคุณไม่ต้องการพึ่งพา 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


ขอบคุณตัวอย่างของคุณมีประโยชน์ - ฉันต้องการให้สคริปต์ออกหากทำงานอยู่แล้ว ขอบคุณสำหรับการกล่าวถึงickdo - ดูเหมือนว่าจะทำเคล็ดลับ
Tom

FWIW: ฉันชอบโซลูชันนี้เพราะสามารถรวมอยู่ในสคริปต์ได้ดังนั้นการล็อกจะทำงานโดยไม่คำนึงถึงวิธีการเรียกใช้สคริปต์
David G

1

นี่อาจเป็นสัญญาณว่าคุณกำลังทำสิ่งผิด หากงานของคุณทำงานอย่างใกล้ชิดและบ่อยครั้งคุณอาจต้องพิจารณายกเลิกและสร้างโปรแกรม daemon-style


3
ฉันไม่เห็นด้วยอย่างยิ่งกับสิ่งนี้ หากคุณมีบางสิ่งบางอย่างที่จำเป็นต้องเรียกใช้เป็นระยะทำให้ daemon เป็น "sledgehammer for nut" การใช้ล็อกไฟล์เพื่อป้องกันการเกิดอุบัติเหตุเป็นวิธีการแก้ปัญหาที่สมเหตุสมผลอย่างสมบูรณ์แบบฉันไม่เคยมีปัญหาในการใช้
womble

@ womble ฉันเห็นด้วย; แต่ฉันชอบทุบถั่วด้วยค้อนขนาดใหญ่! :-)
wzzrd

1

cron daemon ของคุณไม่ควรเรียกใช้งานหากอินสแตนซ์ก่อนหน้านี้ยังคงทำงานอยู่ ฉันเป็นผู้พัฒนาหนึ่ง cron daemon dcronและเราพยายามป้องกันเป็นพิเศษ ฉันไม่รู้ว่า Vixie cron หรือ daemons อื่นจัดการเรื่องนี้อย่างไร


1

ฉันอยากจะแนะนำให้ใช้คำสั่งrun-one - ง่ายกว่าจัดการกับล็อค จากเอกสาร:

run-oneเป็นสคริปต์ wrapper ที่รันอินสแตนซ์ที่ไม่ซ้ำกันของคำสั่งบางคำสั่งพร้อมชุดอาร์กิวเมนต์ที่ไม่ซ้ำกัน สิ่งนี้มักจะมีประโยชน์สำหรับ cronjobs เมื่อคุณต้องการให้มีมากกว่าหนึ่งสำเนาที่รันในแต่ละครั้ง

run-this-oneนั้นเหมือนกับ run-one ยกเว้นว่ามันจะใช้ pgrep และ kill เพื่อค้นหาและฆ่ากระบวนการที่กำลังรันใด ๆ ที่ผู้ใช้เป็นเจ้าของและจับคู่คำสั่งเป้าหมายและอาร์กิวเมนต์ โปรดทราบว่า run-this-one จะปิดกั้นในขณะที่พยายามฆ่ากระบวนการที่ตรงกันจนกว่ากระบวนการที่ตรงกันทั้งหมดจะตาย

run- one- ทำงานตลอดเวลาเหมือนกับ run-one ยกเว้นว่ามันจะเกิด "COMMAND [ARGS]" เมื่อใดก็ได้ที่ COMMAND ออก (ศูนย์หรือไม่เป็นศูนย์)

Keep-One-Runningเป็นนามแฝงสำหรับการเรียกใช้หนึ่งอย่างต่อเนื่อง

run- one- จนกว่าจะประสบความสำเร็จทำงานเหมือนกับ run-one-always ยกเว้นว่ามัน respawns "คำสั่ง [ARGS]" จนกระทั่งคำสั่งออกจากที่ประสบความสำเร็จ (เช่นออกจากศูนย์)

run- one- จนกระทั่ง - ความล้มเหลวทำงานเหมือนกับ run-one-always ยกเว้นว่ามัน respawns "คำสั่ง [ARGS]" จนกระทั่ง COMMAND ออกด้วยความล้มเหลว (เช่นออกจากที่ไม่ใช่ศูนย์)


1

ตอนนี้ 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

0

ฉันได้สร้างขวดหนึ่งขวดเพื่อแก้ปัญหาเช่น crons ที่ซ้ำกันกำลังทำงานอาจเป็น java หรือ shell cron เพียงส่งชื่อ cron ใน Duplicates.CloseSessions ("Demo.jar") สิ่งนี้จะทำการค้นหาและกำจัดการมีอยู่ของ pron สำหรับ cron นี้ยกเว้นปัจจุบัน ฉันใช้วิธีการนี้แล้ว String proname = ManagementFactory.getRuntimeMXBean (). getName (); String 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 ด้วยคำสั่งเชลล์อีกครั้ง


ฉันไม่คิดว่านี่เป็นการตอบคำถามจริงๆ
kasperd

0

@Philip Reynolds คำตอบจะเริ่มดำเนินการรหัสหลังจาก 5s รอเวลาต่อไปโดยไม่ได้รับการล็อค การติดตามฝูงไม่ได้ทำงานฉันแก้ไข @Philip Reynolds คำตอบ

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

เพื่อให้รหัสจะไม่ถูกดำเนินการพร้อมกัน แทนที่จะรอ 5 วินาทีกระบวนการจะออกด้วย 1 หากไม่ได้รับการล็อค

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.