زمانبند لاراول، از نزدیک
خلاصهٔ کاملتر
نویسنده تو این مقاله سراغ چیزی رفته که معمولاً پشت API تمیز زمانبند لاراول گم میشه: خود مکانیزم. حرف اصلیش سادهست — کرون فقط لاراول رو بیدار میکنه، ولی صاحب زمانبندی خود لاراوله. کرون هیچ درکی از dailyAt() یا weekdays() نداره؛ تنها کارش اینه که هر دقیقه php artisan schedule:run رو اجرا کنه. بعدش لاراول اپلیکیشن کنسول رو بالا میآره، تعریفهای زمانبندی رو لود میکنه و از زمانبند خودش میپرسه الان نوبت کدوم رویدادهاست.
به گفتهٔ نویسنده، همین جداسازی موقع دیباگ به درد میخوره: اگه تسکی اصلاً اجرا نشده، اول باید پرسید زمانبند اصلاً استارت خورده یا نه؛ اگه استارت خورده ولی تسک رد شده، باید سراغ بررسیهای خود لاراول رفت. پشت صحنه Schedule::command() دستور رو به یه رشته تبدیل میکنه و به exec() میسپره، و اونجا یه شیء Event ساخته میشه که همهچی — عبارت کرون، تایمزون، فیلترها، هوکها و تنظیمات قفل — توش نگه داشته میشه. Schedule::call() هم یه CallbackEvent میسازه که کالبک رو از دل کانتینر صدا میزنه، پس تزریق وابستگی توی کلوژرهای زمانبندیشده طبیعی کار میکنه.
متدهای فرکانس هم قابلیتهای جدا نیستن؛ بیشترشون فقط عبارت کرونِ رویداد رو دستکاری میکنن. یه رویداد با * * * * * شروع میشه و dailyAt('02:30') اونو به 30 2 * * * تبدیل میکنه. ولی when() و between() کار دیگهای میکنن: یه فیلتر کالبک اضافه میکنن که بعد از پیدا شدن رویدادهای due اجرا میشه. پس دو لایهٔ صلاحیت داریم — عبارت کرون میگه این دقیقه نوبتشه یا نه، و فیلترها میگن با وضعیت فعلی اپ اجراش درسته یا نه:
Schedule::command('billing:reconcile')
->dailyAt('02:30')
->environments(['production'])
->when(fn (): bool => config('billing.enabled'))
->skip(fn (): bool => app()->isDownForMaintenance());دو قفلی که معمولاً کنار هم زنجیر میشن هم یکی نیستن. onOneServer() یه قفل زمانبندیه: تو دیپلوی چندسروری همهٔ سرورها یه تیک کرون رو میگیرن، پس اولین سروری که قفل رو بگیره اجراش میکنه و بقیه اون رویداد رو تو اون دقیقه رد میکنن. قفل پیشفرض از نام mutex رویداد بهعلاوهٔ ساعت و دقیقه ساخته میشه و یه ساعت اعتبار داره. این کار به یه کش مشترک و اتمیک نیاز داره — دیتابیس، Redis، Memcached یا DynamoDB؛ درایور array روی هر سرور هیچی رو بین سرورها هماهنگ نمیکنه.
withoutOverlapping() اما مسئلهٔ دیگهای رو حل میکنه: یه تسک میتونه از فاصلهٔ زمانبندی خودش طولانیتر بشه. دستوری که هر دقیقه اجرا میشه و پنج دقیقه طول میکشه، بدون این قفل میتونه پنج نسخهٔ همزمان داشته باشه. نویسنده یادآوری میکنه که تایمزون یه رویداد فقط ساعتی رو که برای ارزیابی همون تسک استفاده میشه عوض میکنه، نه ساعت سرور رو؛ و چون تغییر ساعت تابستانی میتونه یه زمان محلی رو دوبار یا اصلاً نسازه، برای کاری که باید دقیقاً یکبار اجرا شه UTC انتخاب امنتریه.
نکات کلیدی:
- کرون فقط هر دقیقه schedule:run رو صدا میزنه؛ منطق زمانبندی داخل خود اپلیکیشنه
- هر تسک به یه شیء Event تبدیل میشه که عبارت کرون، تایمزون، فیلترها و قفلهاش رو نگه میداره
- متدهای فرکانس فقط عبارت کرون رو میسازن؛ when() و skip() یه لایهٔ فیلتر جداگانهن
- onOneServer() بین چند سرور یکی رو انتخاب میکنه و به کش مشترک و اتمیک نیاز داره
- withoutOverlapping() جلوی اجرای همزمان چند نسخه از یه تسک طولانی رو میگیره
- schedule:pause و schedule:continue یه سوئیچ عملیاتی روی کشان، نه جایگزین تسک idempotent




