Zamanlanmış Görev Ekleme
Kendi kodunuzu zamanlanmış çalıştırın: kuyruğa bir handler sınıfı, yeniden deneme ve telemetriyle birlikte.
Genel Bakış
Zamanlanmış iş bir crontab değil, bir kuyruktur. Zamanlayıcı vakti gelenleri seçer, her biri cronjob_queue içinde bir satıra dönüşür ve bir handler o satırı işe çevirir.
Görev ikiye ayrılır: keşif hedef başına bir iş kuyruklar, execute tek hedefin işini yapar. Yeniden deneme toplu değil hedef başınadır.
Ön Koşullar
- Çalışan bir işçi; tık bu sisteme ulaşana kadar hiçbir şey çalışmaz.
- Handler'ın nerede yaşayacağı: çekirdek cron dizini ya da sahibi olan modül.
- Hedef için kararlı bir tanımlayıcı; yinelenmeyi durduran idempotency anahtarı o olur.
Yapı
Dosya başına bir sınıf, dosya adı da sınıf adı.
| Parça | Nerede | Kural |
|---|---|---|
| Handler sınıfı | coremio/cronjobs, WISECP\CronJobs | Execute sonek almaz, keşif Discover ile biter |
| Kayıt | coremio/cronjobs/registry.php | Otomatik keşif; TYPE tanımlayan her sınıf kaydedilir |
| Kuyruk satırları | cronjob_queue | İş başına bir satır: payload, deneme, log, sonuç |
| Zamanlama durumu | cronjob_runtime | Görev başına: son çalışma, sonraki çalışma, son slot, kapatma anahtarı |
| Görev ayarları | coremio/configuration/cronjobs.php | cronjobs/tasks/{task}: aşama soneki atılmış, noktaları tireye çevrilmiş tip |
Adım Adım
Handler'ı Yazın
- Cron dizininde sınıf başına bir dosya, adı sınıfla aynı.
TYPE'ıdiscoverya daexecuteile biten noktalı bir dize olarak tanımlayın.FREQUENCY'yi yalnız zamanlayıcının tetikleyeceği handler'a ekleyin.- Tek arayüz metodunu uygulayın: mantıksal değer ya da zengin dizi.
Kaydedin
- Çekirdek görevi için başka bir şey gerekmez; kayıt dosyası dizini tarar.
- Modül görevi kendini modülün kanca dosyasından kaydeder.
- Dosyayı kendiniz dahil edin; yükleyici yalnız çekirdek cron ad alanını çözer.
- Kayıtlı handler'ları listeleyin ve tipinizin göründüğünü doğrulayın.
İşi Kuyruklayın
- Keşifte hedefleri sorgulayın ve her hedef için bir execute işi kuyruklayın.
- Her kuyruklamaya bir idempotency anahtarı verin.
- Üst iş id'sini geçirin ve toplu işi sınırlayın.
- Sonradan gösterilecek ne varsa payload'a fotoğrafını çekin.
Koşuyu Doğrulayın
- Otomasyon panosunu açın ve elle çalıştırma kontrolünü kullanın.
- Kuyruk satırını okuyun: durum, deneme, hata günlüğü, sonuç payload'ı.
- Hiçbir şey görünmüyorsa önce tıka bakın: işsiz tarama satır bırakmaz.
- Sonuç payload'ı oturduktan sonra bir özet üretici ekleyin.
Referans
Handler Sözleşmesi
namespace WISECP\CronJobs;
interface CronJobHandler
{
// $payload BU iş için dispatch()'e verilmiş, çözülmüş dizi
// $job kuyruk satırının tamamı: id, type, attempts, max_attempts, parent_id,
// idempotency_key, started_at, process_logs ve gerisi
public function handle(array $payload, array $job): bool|array;
}
// ZORUNLU. Bu olmadan sınıf bir görev değildir ve kayıt dosyası onu atlar.
// Son parça aşamadır: tarama için '.discover', atomik iş için '.execute'.
public const TYPE = 'acme.sync.discover';
// İSTEĞE BAĞLI; anlamı "bunu zamanlayıcı doğrudan tetikler".
// Değerler: 'minute' | 'hour' | 'day' | 'month'. Operatör görev başına ezebilir.
public const FREQUENCY = 'hour';
// İSTEĞE BAĞLI, varsayılan 3. Yeniden deneme bütçesi; gecikme dakika cinsinden
// 2'nin deneme kuvvetidir, yani 2, 4, 8. Deneme dış bir yan etkiyi tekrarlıyorsa 1 yapın.
public const MAX_ATTEMPTS = 1;
// İSTEĞE BAĞLI, varsayılan 120. İşçinin bu işi alarmla kestiği saniye sayısı.
// Yedekleme gibi uzun işler için yükseltin; işçi tıkı değil işi öldürür.
public const MAX_RUNTIME = 300;
status. Başka bir ön koşul gerekiyorsa handler'a public static function available(): bool ekleyin.
Dönüşünüz Ne Demek
| Dönüş | Satır ne olur | Ne zaman kullanılır |
|---|---|---|
true | completed, telemetri yok | Gösterilecek bir şey olmayan başarı |
false | Geri çekilmeli deneme, sonra failed | Tekrarın düzeltebileceği hata |
Fırlatılan Throwable | false ile aynı, mesaj yakalanır | Hatayı yutmak istemiyorsanız |
['success' => bool, 'result' => array] | completed ya da failed, sonuç saklanır | Telemetri istiyorsanız |
['success' => true, 'status' => 'cancelled', 'result' => array] | cancelled, çocuksuz satır silinir | Bu tıkta iş yoktu |
no-targets, disabled, already-exists.
false'ta kuyruk error, gateway_error ya da message arar.
.execute sayar; tarama her zaman .discover.
Kuyruk Yardımcısı
// Kuyruklar. Yeni satır id'sini, idempotency anahtarı zaten alınmışsa MEVCUT
// satırın id'sini döndürür. Sıfırdan büyük dönüş, ekleme yapıldığının kanıtı DEĞİLDİR.
public static function dispatch(string $type, array $payload = [], array $opts = []): int;
// Kayıt. Çekirdek handler'ları için kayıt dosyası çağırır; modül kendisi çağırır.
public static function register(string $type, string $handlerClass): void;
// Sahiplenilmiş bir satırı çalıştırır. $job id değil, kuyruk satırının tamamıdır.
public static function process(array $job): bool;
// İç gözlem.
public static function resolve_handler(string $type): ?string;
public static function registered_handlers(): array;
public static function resolve_task_type(string $task): string;
// Bakım; ikisi de platformun kendi görevlerine zaten bağlı.
public static function recover_stale(int $minutes = 5): void;
public static function cleanup(array $retention = []): array;
Sonuç Sekmesi
Handler'ınızdaki statik bir metottan üretilir; HTML saklanmaz.
// $resultPayload, handle() metodunuzun döndürdüğü 'result' dizisinin ta kendisidir.
// Yalnız yönetici sekmeyi açtığında, kayıt dizini üzerinden çözülerek çağrılır.
public static function renderSummary(array $resultPayload): string;
Örnek
Tam bir çift: saatlik tarama ve tek hizmeti uzlaştıran işçi.
namespace WISECP\CronJobs;
class AcmeQuotaSyncDiscover implements CronJobHandler
{
public const TYPE = 'acme.quotasync.discover';
public const FREQUENCY = 'hour';
private const BATCH = 200;
public function handle(array $payload, array $job): bool|array
{
// Operatör anahtarı. İlk satırda okumak, kapalı bir görevin hiçbir şey yapmamasını
// sağlar; yıkıcı görevler için bu zorunludur.
if ((int) (\Config::get('cronjobs/tasks/acme-quotasync/enabled') ?? 0) !== 1)
return ['success' => true, 'status' => 'cancelled', 'result' => ['reason' => 'disabled']];
$stmt = \WDB::select('id,name,owner_id')->from('users_products');
$stmt->where('status', '=', 'active', '&&');
$stmt->where('module', '=', 'AcmeCloud');
$stmt->limit(self::BATCH);
$rows = $stmt->build() ? $stmt->fetch_assoc() : [];
if (!$rows)
return ['success' => true, 'status' => 'cancelled', 'result' => ['reason' => 'no-targets']];
$parentId = (int) ($job['id'] ?? 0);
$dispatched = [];
foreach ($rows as $row) {
$sid = (int) ($row['id'] ?? 0);
if ($sid <= 0) continue;
// Anahtar, hizmetin yanı sıra saat dilimini de taşır: sabit bir anahtar bir
// sonraki saatte de var olurdu (tamamlanmış satırlar, saklama süresi işleyene
// kadar kendi anahtarlarını korur) ve sonraki her dağıtım sessizce eski satırı
// döndürürdü.
$key = 'acmeq_' . $sid . '_' . date('YmdH');
$childId = \CronJobQueue::dispatch(self::stage_execute(), [
'service_id' => $sid,
'service_label' => trim((string) ($row['name'] ?? '')),
'owner_id' => (int) ($row['owner_id'] ?? 0),
], ['idempotency_key' => $key, 'parent_id' => $parentId]);
$dispatched[] = ['service_id' => $sid, 'child_job_id' => $childId ?: null];
}
return ['success' => true, 'result' => ['items' => $dispatched, 'count' => count($dispatched)]];
}
private static function stage_execute(): string
{
return AcmeQuotaSync::TYPE;
}
}
namespace WISECP\CronJobs;
class AcmeQuotaSync implements CronJobHandler
{
// FREQUENCY yok: bu hiç zamanlanmaz, yalnız yukarıdaki tarama tarafından dağıtılır.
public const TYPE = 'acme.quotasync.execute';
public const MAX_ATTEMPTS = 2;
public function handle(array $payload, array $job): bool|array
{
$sid = (int) ($payload['service_id'] ?? 0);
if ($sid <= 0) return false; // bu kadar bozuk bir yük kendini düzeltmez
$service = \Services::get($sid);
if (!$service)
return ['success' => true, 'status' => 'cancelled', 'result' => ['reason' => 'service-gone', 'service_id' => $sid]];
$remote = \Modules::getInstance('Servers', 'AcmeCloud')->quota_of($sid);
$before = (int) ($service['options']['quota'] ?? 0);
$after = (int) ($remote['quota'] ?? 0);
if ($before === $after)
return ['success' => true, 'status' => 'cancelled', 'result' => ['reason' => 'in-sync', 'service_id' => $sid]];
// Seçenekler Services::set() üzerinden gidip gelir, o da diziyi sizin için JSON'a
// çevirir. set_options() diye bir helper yoktur: okuyun, birleştirin, haritanın
// tamamını yazın.
$options = $service['options'] ?? [];
$options['quota'] = $after;
\Services::set($sid, ['options' => $options]);
// renderSummary()'nin ihtiyaç duyacağı her şey, hâlâ doğruyken burada anlık görüntüye alınır.
return ['success' => true, 'result' => [
'service_id' => $sid,
'service_label' => (string) ($payload['service_label'] ?? ''),
'quota_before' => $before,
'quota_after' => $after,
]];
}
public static function renderSummary(array $r): string
{
$esc = static fn (string $s): string => htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$L = static fn (string $k): string => (string) \Language::gc('admin/automation/' . $k);
$sid = (int) ($r['service_id'] ?? 0);
$href = (string) \LinkGenerator::admin('services-1', ['detail'], '', ['id' => $sid]);
$name = trim((string) ($r['service_label'] ?? ''));
return '<dl class="row small mb-0">'
. '<dt class="col-sm-4 text-muted fw-normal">' . $esc($L('telemetry-acme-service')) . '</dt>'
. '<dd class="col-sm-8 mb-1"><a href="' . $esc($href) . '" target="_blank" rel="noopener">'
. $esc($name !== '' ? $name : (string) $sid) . '</a></dd>'
. '<dt class="col-sm-4 text-muted fw-normal">' . $esc($L('telemetry-acme-quota')) . '</dt>'
. '<dd class="col-sm-8 mb-1">' . (int) ($r['quota_before'] ?? 0) . ' → ' . (int) ($r['quota_after'] ?? 0) . '</dd>'
. '</dl>';
}
}
Aynı çiftin modül içindeki hâli.
use WISECP\Modules\Addons\Acme\CronJobs\QuotaSyncDiscover;
use WISECP\Modules\Addons\Acme\CronJobs\QuotaSyncExecute;
// Kayıt defteri yüklenirken, çekirdek işleyicileri yerine oturduktan sonra ateşlenir.
Hook::add('register:cronjobs', 1, function () {
// Zorunlu: otomatik yükleyici WISECP\CronJobs ad alanını çözer ama bir modül
// dizinindeki iç içe ad alanını çözmez, bu yüzden dosya elle dahil edilir.
require_once __DIR__ . DS . 'cronjobs' . DS . 'QuotaSyncDiscover.php';
require_once __DIR__ . DS . 'cronjobs' . DS . 'QuotaSyncExecute.php';
CronJobQueue::register(QuotaSyncDiscover::TYPE, QuotaSyncDiscover::class);
CronJobQueue::register(QuotaSyncExecute::TYPE, QuotaSyncExecute::class);
});
Kuyruğu kendiniz sürmek.
// Tip kayıtlı mı? Kayıtlı olmayan bir tip açılışı değil, işi başarısız kılar.
$handler = CronJobQueue::resolve_handler('acme.quotasync.execute');
// Kuyruğa bir iş ekleyin. Dönüşe dikkat: var olan bir anahtar size ESKİ satırın id'sini verir.
$id = CronJobQueue::dispatch('acme.quotasync.execute',
['service_id' => 5001],
['idempotency_key' => 'acmeq_manual_5001', 'priority' => 1]);
// İşçiyi atlayarak burada ve şimdi çalıştırın. process() id'yi değil SATIRI ister.
$job = CronJobQueue::get($id);
$ok = $job ? CronJobQueue::process($job) : false;
// İşleyicinin bildirdiğini geri okuyun; Sonuçlar sekmesinin kendi kaynağı budur.
$row = CronJobQueue::get($id, 'status,attempts,result_payload');
$result = Utility::jdecode((string) ($row['result_payload'] ?? ''), true) ?: [];
Tuzaklar
Tamamlanmış satır anahtarını yedi gün korur ve aynı anahtar eski id'yi döndürür. Hedef meşru olarak iki kez işlenebiliyorsa anahtara zaman slotunu koyun.
Kayıt silen ya da dışarı veri gönderen görev kapalı gelir ve ilk satırda kendi anahtarını kontrol eder.
Çocuğu olmayan cancelled satır silinir, o yüzden kuyruk saymak boşluk gösterir. Kanıt zamanlama durumu tablosu.
İşçi bir alarm kurar ve süreyi aşan işi keser; uzun işi tercihen kuyruklamalara bölün.
Hata üç kez denenir ve handler'ın yaptığını yeniden yapar. Idempotent yapın ya da bütçeyi bire çekin.
İlgili Makaleler
Geri bildiriminiz için teşekkürler!
Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.