Laravel Scheduler คืออะไร และวิธีตั้งงานตามเวลาด้วย cron บรรทัดเดียว

Laravel Scheduler ให้เราประกาศงานตามเวลาทั้งหมดไว้ในโค้ด แล้วให้ cron เรียก schedule:run ทุกนาทีบรรทัดเดียว Laravel จะเช็คเองว่านาทีนี้มีงานไหนถึงเวลา และมีตัวช่วยกันงานรันซ้อน รันเครื่องเดียว และตั้ง timezone

เผยแพร่
เวลาอ่าน
3 นาที
แท็ก
#Laravel#Queue
ในโพสต์นี้ 5 หัวข้อ
  1. งานตามเวลาที่ระบบส่วนใหญ่ต้องมี
  2. cron บรรทัดเดียว แล้ว Laravel ตัดสินใจเอง
  3. ประกาศงานไว้ใน routes/console.php
  4. ตัวช่วยที่ควรรู้จัก
  5. สิ่งที่ scheduler ไม่ได้ทำให้

งานตามเวลาที่ระบบส่วนใหญ่ต้องมี

แทบทุกระบบที่ใช้งานจริงมีงานที่ไม่มีใครกดปุ่มสั่ง แต่ต้องเกิดตามเวลา

  • ส่งสรุปยอดขายเมื่อวานเข้า LINE กลุ่มผู้บริหารทุกเช้า
  • ยกเลิกคำสั่งซื้อที่ไม่โอนเงินภายใน 24 ชั่วโมง
  • ส่งไฟล์โอนเงินให้ธนาคารตามรอบ ซึ่งต้องรู้วันทำการของธนาคารด้วย
  • สำรองฐานข้อมูลตอนตีสอง และลบไฟล์สำรองที่เก่ากว่า 30 วัน

ทางที่ใช้กันมาตลอดคือตั้ง cron บนเซิร์ฟเวอร์ทีละงาน หนึ่งบรรทัดต่อหนึ่งงาน ปัญหาคือรายการงานไปอยู่นอกโค้ด ไม่มีใน git ย้ายเซิร์ฟเวอร์ทีก็ต้องจำมาตั้งใหม่ และไม่มีใครรู้ว่าบนเครื่องตั้งอะไรไว้บ้างจนกว่าจะ ssh เข้าไปดู

cron บรรทัดเดียว แล้ว Laravel ตัดสินใจเอง

cron คือตัวตั้งเวลาของ Linux ที่รันคำสั่งตามรูปแบบเวลา เช่น 0 8 * * * คือทุกวันตอน 8:00 Laravel ใช้ cron แค่บรรทัดเดียวให้เรียกตัวเองทุกนาที

* * * * * cd /var/www/shop && php artisan schedule:run >> /dev/null 2>&1

ทุกนาที schedule:run จะโหลดรายการงานจากโค้ด เช็คว่างานไหนถึงเวลาในนาทีนั้น แล้วรันเฉพาะงานนั้น นาทีที่ไม่มีงานก็จบไปเฉย ๆ รายการงานจริงจึงอยู่ใน git ตรวจใน pull request ได้ และย้ายเซิร์ฟเวอร์ก็แค่ตั้ง cron บรรทัดเดิม

ใครที่ใช้ shared hosting กับ cPanel หรือ DirectAdmin ก็ใช้ได้เหมือนกัน เข้าเมนู Cron Jobs แล้วใส่คำสั่งเดียวกัน โดยใช้ path เต็มของ PHP ตามที่โฮสต์บอก บางโฮสต์ให้ตั้งถี่สุดแค่ทุก 5 หรือ 15 นาที งานที่ตั้งไว้ระหว่างนั้นจะไม่ได้รัน เพราะ schedule:run เช็คเฉพาะนาทีที่ cron เรียกเท่านั้น ตอนพัฒนาบนเครื่องตัวเองไม่ต้องตั้ง cron ใช้ php artisan schedule:work ค้างไว้ใน terminal แทนได้

ประกาศงานไว้ใน routes/console.php

ตั้งแต่ Laravel 11 รายการงานอยู่ใน routes/console.php ส่วนเวอร์ชันก่อนหน้าอยู่ในเมธอด schedule() ของ app/Console/Kernel.php

use Illuminate\Support\Facades\Schedule;

Schedule::command('reports:daily-sales')->dailyAt('08:00');

Schedule::command('orders:cancel-unpaid')->hourly();

Schedule::command('backup:run')->dailyAt('02:00')->withoutOverlapping();

นอกจาก command() ยังสั่ง job() เพื่อโยน job เข้า queue หรือ call() กับ closure ได้ เวลามีให้เลือกเยอะ ตั้งแต่ everyMinute(), everyFiveMinutes(), weekdays() ไปจนถึง cron('0 8 * * 1-5') ถ้าอยากเขียนเอง

ดูว่ามีงานอะไรบ้างและจะรันครั้งถัดไปเมื่อไหร่ได้ด้วย php artisan schedule:list ซึ่งช่วยมากตอนรับระบบต่อจากคนอื่น

ตัวช่วยที่ควรรู้จัก

ตัวช่วย ใช้ตอนไหน
withoutOverlapping() งานยาวที่ห้ามมีสองรอบรันพร้อมกัน เช่น นำเข้าข้อมูลทุก 5 นาทีแต่บางรอบใช้ 7 นาที
onOneServer() มีเว็บหลายเครื่องที่ตั้ง cron เหมือนกัน แต่อยากให้งานรันแค่เครื่องเดียว ต้องใช้ cache กลางอย่าง Redis หรือ database
runInBackground() งานยาวที่ไม่อยากให้งานอื่นในนาทีเดียวกันต้องรอ ปกติ Laravel รันงานในนาทีเดียวกันทีละตัว
timezone('Asia/Bangkok') งานที่ต้องอิงเวลาไทย ถ้าแอปตั้ง timezone เป็น UTC ไว้
when() / skip() รันเฉพาะเมื่อเงื่อนไขผ่าน เช่น ข้ามวันหยุดของบริษัท

เรื่อง timezone สำคัญกว่าที่คิด ถ้า config/app.php ยังเป็น UTC ตามค่าเริ่มต้น dailyAt('08:00') จะรันตอน 15:00 เวลาไทย ตั้งที่ APP_TIMEZONE ทั้งแอป หรือใส่ timezone() เฉพาะงานก็ได้

ส่วนงานที่ใช้เวลานานหรือเรียก API ภายนอก ให้ scheduler แค่โยน job เข้า queue แล้วให้ worker รันต่อ งานจะได้ retry ได้และไม่ทำให้งานอื่นในนาทีนั้นรอ

สิ่งที่ scheduler ไม่ได้ทำให้

scheduler รันงานได้เฉพาะนาทีที่ cron เรียก ถ้าเซิร์ฟเวอร์ล่ม รีบูตนาน หรือ cron หายไปหลังย้ายเครื่อง งานในช่วงนั้นจะหายไปเงียบ ๆ Laravel ไม่ย้อนกลับไปรันให้ตอนเครื่องกลับมา รายงานเช้าวันนั้นจึงไม่ออก และอาจไม่มีใครรู้จนผู้บริหารถาม

ทางป้องกันมีสองชั้น ชั้นแรกคือรู้ตัวให้เร็ว ตั้งให้งานสำคัญ ping บริการเฝ้าระวังอย่าง Healthchecks.io ทุกครั้งที่รันเสร็จ ถ้าไม่มี ping เข้ามาตามเวลาก็แจ้งเตือน ชั้นที่สองคือออกแบบให้งานรันย้อนหลังได้ เช่นคำสั่งรับวันที่เข้ามา และรันซ้ำแล้วไม่เกิดผลซ้ำ (idempotent) ถ้าอยากให้ระบบรันงานที่พลาดไปให้อัตโนมัติ มีแพ็กเกจที่ทำเรื่องนี้ ลองอ่านต่อที่โพสต์เรื่องรันงานที่พลาดไปตอนเซิร์ฟเวอร์ล่มได้ครับ

รายละเอียดทุกตัวเลือกอยู่ในเอกสาร Task Scheduling ของ Laravel

อ่านต่อ

ถูกอ้างถึงใน

ล่าสุด

ดูทั้งหมด →