Quartz Job Scheduler

Mustafa Yıldırım
17 min readJul 21, 2022

--

En küçük bağımsız uygulamadan, en büyük e-ticaret sistemine kadar neredeyse tüm Java uygulamarına entegre edilebilen, zengin özelliklere sahip, açık kaynaklı bir iş planlama framewok’üdür.

Scheduler: Scheduler framework ile etkileşimde olan birincil Api

Job: Yürütmek istediğimiz bileşenler tarafından uygulanacak bire arayüz.

JobDetail: İş örneklerini tanımlamak için kullanılır.

Trigger: Belirli bir işin gerçekleştirileceği programı belirleyen bir bileşen.

JobBuilder: İşlerin instance’larını tanımlayan, JobDetail instance’larını oluşturmak için kullanılır.

TriggerBuilder: Trigger Instance’larını oluşturmak için kullanılır.

Quartz’ın çalışması için pom.xml dosyasında en azından quartz ve slf4j-simple dependency’lerin ekli olması gerekmektedir.

Bir scheduler’ın yaşam döngüsü, bir Scheduler ve onun shutdown() metoduna yapılan bir çağrı aracılığıyla oluşturulmasıyla sınırlıdır.

Scheduler arayüzü oluşturulduktan sonra, Jobs ve Triggers eklemek, kaldırmak ve listelemek için kullnaılabilir ve zamanlamayla ilgili diğer işlemleri (bir tetikleyiciyi duraklatmak gibi) gerçekleştirebilir. Ancak zamanlayıcı start() metoduyla başlatılana kadar herhangi bir tetikleyici (trigger)’de hareket etmeyecektir.

Quartz, Domain Specific Language (veya “fluent interface” akıcı arabirim olarak da anılan DSL) tanımlayan “builder” sınıfları sağlar.

SimpleTrigger, ‘tek seferde’ yürütmeye (belirli bir zamanda bir işin yalnızca tek bir şekilde yürütülmesine) veya belirli bir zamanda bir işi başlatmanız ve gecikmeyle N kez tekrar ettirmeniz gerekiyorsa kullanışlıdır.

CronTrigger, ‘her cuma, öğlen” veya “her ayın 10. günü saat 10:15’te” gibi takvime benzer programlara dayalı olarak tetikleme yapmak istiyorsanız kullanışlıdır.

Quartz’ı geliştirenler, çizelge ile bu çizelgede yapılacak iş arasında bir ayrım oluşturmaknın mantıklı olgduğunu düşündükelrinden dolayı diğer job scheduler’larda olmayan Jobs and Triggers kavramını kullanmaktadır.

Örneğin, işler bir tetikleyiciden bağımsız olarak Job Schedulerda oluşturulabilir ve saklanabilir ve birçok tetikleyici aynı işle ilişkilendirilebilir. Bu gevşek bağlamanın (loose-coupling) bir başka yararı da ilişkili tetikleyicilerin süresi dolduktan sonra zamanlayıcıadan kalan işleri yapılandırma yeteneğidir, böylece yeniden tanımlamaya gerek kalmadan daha sorna yeniden planlanabilir. Ayrıca, ilişkili işini yeniden tanımlamanıza gerek kalmadan bir tetikleyiciyi değiştirmenize olanak tanır.

Identites

Jobs ve Trigger, Quartz scheduer’ a kaydedildikleri için tanımlayıcı anahtarlar verilir. İşlerin ve Tetikleyicilerin anahtarları (JobKey ve TriggerKey), işlerinizi ve tetikleyicilerini “raporlama işleri” ve “bakım işleri” gibi kategoriler halinde düzenlenmek için yararlı olabilecek “gruplara” yerleştirilmelerini sağlar.

Bir işin veya tetikleyicinin anahtarının ad kısmı grup içinde benzersiz olmalıdır veya başka bir değişle, bir işin veya tetikleyicinin tam anahtarı identifier(tanımlayıcısı) ad ve grubun bileşiğidir.

More About Jobs and Jobs Detail

JobDetail örnekleri JobBuilder sınıfı kullaılarak oluşturulur. Kodunuzda DSL hissine sahip olmak için genellike tüm yöntemlerinin statik bir içe aktarımını kullanmak isteyeceksiniz.

import static org.quartz.JobBuilder.*;

Planlayıcıya bir JobDetail instance’ı verdiğimize ve JobDetail’i oluştururken yalnızca işin sınıfını sağlayarak yürütülecek işin türünü bilgiğine dikkat edin zamanlayıcı her işi (her zaman) yürüttüğünde, execute metodu(çağırman önce sınıfın yeni bir instance’ını oluşturur). Yürütme tamamlandığında, iş sınıfı instance’ına başvurular bırakılır ve instance daha sonra çöp olur.

Bu işin sonuçlarından biri işlerin bağımsız değişken olmayan bir kurucuya sahip olması gerektiğidir (varsayılan JobFactory uygulamasını kullanırken)

JobDataMap

JobDataMap, yürütüldüğünde job instance’ını kullanılabilir olmasını istediğiniz herhangi bir miktarda (seri hale getirilebilir) veri nesnesini tutmak için kullanılabilir. JobDataMap, Java Map Interface’inin bir uygulamasıdır ve ilkel türdeki verileri depolamak ve almak için bazı ek kolaylık yöntemlerine sahiptir.

JobDataMap’e ne yerleştirileceğine karar verirken biraz daha dikkatli olmak gerekiyor, çünkü içerisindeki nesne seri hale getirilecek ve bu nedenle sınıf sürümü sorunlarına yatkın hale gelecektir. Açıkcası, standart Java türleri çok güvenli olmalıdır, ancak bunun ötesinde biri serileştirilmiş instance’ları olan bir sınıfın tanımını her değiştirdiğinde, uyumluluğu bozmamaya özen göstermelidir. İsteğe bağlı olarak JDBC-JobStore ve JobDataMap’i haritada yalnızca primitive ve stringlerin depolanmasına izin verilen bir moda koyabilir, böylece daha sonraki serileştirme sorunları olasılığını ortadan kaldırabilirsiniz.

Job Instance

Tek bir iş sınıfı oluşturabilir ve birçok “instance defination”, her biri kendi özellikleri ve JobDataMap’i olan birden çok JobDetails instance’ını oluşturak ve hepsinin schedular’a ekleyerek zamanlayıcı içerisnde saklayabilirsiniz.

Örneğin, “SalesReportJob” adlı Job interface’ini uygulayan bir sınıf oluşturabilirsiniz iş, satış raporunun temel alması gereken satış görevlisinin adını bulertmek çin kendisine (JobDataMap) aracılığıyla gönderilen parametlerin beklenmesi için kodlanabilir. Daha sonra, ilgili işlere girdi olarak ilgili JobDataMaps’te belirtilen “joe” ve “mike” olan “SalesReportForJoe” ve “SalesReportForMike” gibi işin birden çok tanımını (JobDetails) oluşturabilir.

Bir trigger ateşlendiğinde, ilişkili olduğu JobDetail (instance defination) yüklenir ve başvurduğu iş sınıfı, Scheduler’da yapılandırılan JobFactory aracılığıyla başlatılır. Varsayılan JobFactory, iş sınıfında basitce newInstance() metodunu çağırır, ardından sınıf üzerinde JobDataMap içindeki anahtarların adlarıyla eşleşen ayarlayıcı yöntemleri çağırmaya çalışır. Uygulamanızın IoC veya DI kaysayıcısının iş örneğini üretmesi/başlatması gibi şeyleri gerçekleştirmek için kendi JobFactory uygulamanızı oluşturmak isteyebilirisniz.

Job Satate and Concurrency (İş Durumu ve Eş Zamanlık)

@DisallowConcurrentExecution Quartz’a belirli bir iş tanımının (verilen iş sınıfına atıfta bulanan birden çok instance’ını aynı anda yürütmemesini söyleyen Job sınıfına eklenebilen bir açıklamadır.

@PersistJobDataAfterExecution: Quartz’a execute() yöntemi başarıyla tamamlandıktan sonra (bir istisna atmadan) JobDetail’in JobDataMap’in saklanan kopyasını göncellemesini söyleyen, Job sınıfına eklenebilen bir annotationdır, böylece aynı işin bir sonraki yürütülmesi (JobDetail) orifinal olarak saklanan değerler yerine güncellenen değerleri alır.

@DisallowConcurrentExecution annotaion gibi bu bir iş sınıfı örneği için değil, bir iş tanımı instance’ı için geçerlidir, ancak iş sınıfının niteliği taşıamsına karar verildi, çünkü genellikle sınıfın nasıl kodlandığı konusunda fark oluşturur (örn: yürütme yöntemi içindeki kod tarafından anlaşılması gerekir.)

@PersistJobDataAfterExecution annotation’ının kullanırsanız, aynı işin iki örneği (JobDetail) aynı anda yürütüldüğünde hangi verilerin saklandığına dair olası karışıklığı (race condition) önlemek için @DisallowConcurrenctExecution, annotaion’ını kullanmayı da kesinlikle düşünmelisiniz.

İşlerin (Jobs) diğer nitelikleri

Durability — Bir iş dayanıklı değilse, onunla ilişkili aktif tetikleyici kalmadığında otomatik olarak zamanlayıcıdan silinir. Başka bir deyişle, dayanıksız işlerin, tetikleyicilerin varlığıyla sınırlı bir ömrü vardır.

RequestRecovery — Bir iş “kurtarma talep ediyorsa” ve zamanlayıcının ‘zorla kapanması’ (hard shutdown) sırasında yürütülüyorsa (yani çalıştığı işlem çökerse veya makine kapatılırsa), zamanlayıcı yeniden başlatıldığında yeniden yürütülür. Bu durumda, JobExecution Context.isRecovery() metodunun değeri time olur.

JobExecutionException

Son olrak, Job.execute(…) metodunun birkaç detayını size bildirmemiz gerekir. Execure metodundan atmanıza izin verilen tek istisna türü (RuntimeExceptions dahil) JobExecitonExceptions’dır. Bu nedenle, yürütme yönteminin tüm içeriğini bir “try-catch” bloğu ile sarmanız gerekir.

More About Triggers

İşler gibi, triggerlarla çalışmak oldukça kolaydır ancak quartz’ı tam olarak kullanabilmeniz için fakında olmanız ve anlamanız gereken çeşitli özellitirilebilir seçenekler içerir. Ayrıca, daha önce berlitldiği gibi farklı zamanlama ihtiyaçlarını karşılamak için seçebileceğinize farklı tetikleyici türleri vardır.

Ortak Tetikleme Nitelikleri

Tüm tetikleyici türleinin, kimliklerini izlemek için TriggerKey özelliklerine sahip olamsının yanı sıra tüm tetikleyici türlei için ortak olan bir dizi başka özellik vardır. Bu ortak özellikler tetikleyici tanımını oluştururken TriggerBuilder kullanılarak ayarlanır.

Tüm trigger tiplerinin ortak özelliklerinin listesi:

— JobKey özelliği, trigger ateşlendiğinde yürütilmesi gereken işin kimliğini berliler.

— startTime özelliği, tetikleyicinin zamanlanmasının ilk kez ne zaman devreye girdiğini gösteriri. Değer belirli bir takvim tarihinde zmaan içinde bir anı tanımlayan bir Java.util.Date nesnesidir. Bazı trigger tipleri için trigger aslında başlandıç zamanında tetiklenir, diğerleri için yalnızca programın izlenmeye başlanması gereken zamanı işaret eder.

Priority

Bazen çok sayıda Tetikleyiciniz (veya Quartz iş parçacığı) olduğunda Quartz, aynı anda tetiklenmesi planlanan tüm tetikleyici hemen tetiklemek için yeterli kaynağa sahip olmayabilir. Bu durumda, Triggerlardan hangisinin ilk çatlayacağını kontrol etmek istersiniz. Bu amaçla, bir tetikleyicide priority özelliğini ayarlayabilirsiniz. N tetikleyiciler aynı anda tetiklenecekse, ancak şu anda yalnızca Z çalışan iş parçacığı mevcutsa, ilk önce en yüksek önceliğe sahip ilk Z tetikleyicileri yürütülür. Bir tetikleyiciye öncelik ayarlamazanız varsayılan olarak 5 değerini kullanır. Öncelik, pozitif veya negatif değer alabilir.

Note: Öncelikler yalnıcza aynı anda ateşlenecek olan tetikleyicilerde karşılaştırılır. 10:59'da tetiklenen bir tetikleyici 11:00'dan daha önce tetiklenecektir.

Note: Bir tetikleyicinin işinin kurtarma gerektirdği algılandığında, kurtarma işlemi orijinal tetikleyiciyle aynı önceliğe sahip olacak şekilde planlanır.

Tekleme Talimatları

Tetikleyicinin bir diğer önemli özelliği de “tekleme talimatıdır.” Kalıcı bir tetikleyici, zamanlayıcının kapanması nedeniyle veya Quartz’ın iş parçacığı havuzunda işi yürütmek için kullanılabilir iş parçacığı almadığı için ateşleme zamanında “kaybederse” bir tekleme oluşur. Farklı tetik türleri kendilerine sunulan farklı tekleme talimatlarına sahiptir. Varsayılan olarak, tetik tipine ve yapılandırmasına dayalı dinamik davranışı olan ‘akıllı politika’ talimatını kullanır. Zamanlayıcı başladığında, hatalı ateşlenen kalıcı tetikleyicileri arar ve ardından herbirini ayrı ayrı yapılandırılmış teklemek talimatlarına göre günceller.

Calendars

Quartz Calendar nesnerli (java.util.Calendar nesneleri değil), tetikleyici tanımlandığı ve zamanlayıcıda depolandığı sırada tetikleyicilerle ilişkilendirilebilir. Takvimler, tetikleyicinin tetikleme programından zaman bloklarını hariç tutumak için kullanışlıdır. Örneğin, hafta içi hargün saat 09:30'da bir işi başlatan bir tetikleyici oluşturulabilir, ancak ardından işletmenin tüm tatillerini hariç tutan bir Calendar ekleyebilirsiniz. Calendar sınıfındaki

  • isTimeIncluded (long time stamp)
  • getNextIncludedTime(long timeStamp)

Bu yöntemlerin parametlerinin long türde olduğuna dikkat edin. Tahmin edebileceğiniz gibi, bunlar milisaniye biçimindeki damgalardır. Bu, takvimlerin milisaniye kadar dar zaman bölümlerini “engelleyebileceği” anlamina gelir.

Tüm günleri “engelleme” ile engelleyecekseniz, org.quartz.impl.HolidayCalendars sınıfını kullanınız.

Takvimler, addCalendar(…) metodu aracılığıyla planlayıcıya örneklenmeli ve kaydedilmelidir. HolidayCalendar, kullanırsanız, somuşlaştırdıktan sorna, programlamadan hariç tutmak istediğiniz günlerle doldurmak için addExceludedDate(Date date) metodunu kullanmalısınız.

SingleTrigger

bir işin belili bir zamanda veya belirli bir anda tam olarak bir kez yürütülmesi ve ardından belili bir aralıkta tekrarlanması gerekiyorsa, SimpleTrigger zamanlama ihtiyaçlarınızı, karşılamalıdır. Örneğin triggerın 13 Ocak 2015 tarihinde tam olarak 11:23:54 AM’de tetiklenmesini istiyorsanız veya o anda tetiklenmesini ve ardından her on saniyede bir beş kez daha tetiklenmeisni istiyorsanız.

SimpleTrigger’ın özellikleri: Başlangıç zamanı, bitiş zamanı, tekrar sayısı, tekrar aralığıdır. Tüm bu özellikler bitiş zamanı özelliği ile ilgili yalnızca birkaç özel notla, tam olarak olmasını beklediğiniz şeydir.

Tekrar sayısı sıfır, pozitif bir tam sayı veya SimpleTrigger.REPEAT_INDEFINITELY sabit değeri olabilir. Belirli bir ana kadar her 19 saniyede bir tetiklenen bir tetikleyici oluşturmak istiyorsanız bu yarayışlı olabilir. Başlangıç ve bitiş zamanı arasındaki kaç kez tekrar edeceğini hesaplamak yerine, basitçe bitiş zamanını belirtebilir ve ardından REPEAT_INDEFINITED tekrar sayısını kullanabilir.

— Zamanda belirli bir an için tekrar olmadan bir tetikleyici oluşturun.

— Zamanda belirli bir an için bir tetikleyici oluşturun, ardından her on saniyede bir tekrarlayın.

— Beş dakika sonra bir defa ateşlenecek bir tetikleyici oluşturun

— Şimdi harekete geçecek bir trigger oluşturun, ardından 22:00'a kadar her beş dakikada bir tekrarlayın.

— Bir sonraki saatin başında ateşlenecek bir tetikleyici oluşturun, ardından her 2 saatte bir sonsuza kadar tekrarlayın.

startAt(…) metodunu çağırmazsanız, geçerli saat(hemen) varsayılır.

SimpleTrigger Misfire Instructions

SimpleTrigger’ın Tekleme Komut Sabitleri

  • MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
  • MISFIRE_INSTRUCTION_FIRE_NOW
  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_EXISTING_REPEAT_COUNT
  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_EXISTING_EXISTING_COUNT

CronTrigger

Takvim benzeri kavramlara dayalı olarak yinelenen bir iş başlatma programına ihtiyacınız varsa, cronTrigger genellikle SimpleTrigger’dan daha kullanışlıdır.

CronTrigger ile, “her cuma öğlen”, “haftaiçi hergün sabah 09:30'da”, “her pazartesi, çarşamba günü saat 09:00–10:00 arasında her 5'dakikada bir” gibi ateşleme programları tetikleyebilirsiniz. Öyle olsa bile, SimpleTrigger gibi, CronTrigger’ın da programın ne zaman yürürlükte olduğunu belirten bir startTime ve programın ne zaman durdurulacağını belirten (isteğe bağlı) bir endTime vardır.

Cron Expressions

Cron-Expressions, CronTrigger instance’larını yapılandırmak için kullanılır. Cron-Expressions, aslında programın bireysel ayrıntılarını tanımlayan yedi lat ifadeden oluşan dizelerdir. Bu alt ifadeler boşlukla ayrılır ve şunları temsil eder.

  1. Seconds
  2. Minutes
  3. Hours
  4. Day-of-Month
  5. Month
  6. Day-of-Week
  7. Year (optional field)

Tam bir cron ifadesinin bir örneği, “0 0 12 ? * WED” => Her çarşamba saat 12:00:00" anlamına gelir.

Bireysel alt ifadeler, aralıklar ve/veya listeler içerebilir. Örneğin, önceki (“WED” yazan) örnekteki haftanın günü alanı, “MON-FRI”, “MON,WED,FRI” ve hatta “MON-WED,SAT” şeklinde olabilir.

Joker karakterler (“ karakteri), bu alanın “her” olası değerini söylemek için kullnaılabilir. Bu nedenle, bir önceki örneğin “Ay” alanındaki “ karakter basitçe “her ay” anlamına gelir. Haftanın Günü alanındaki bir “*” bu nedenle açıkça “hagtanın her günü” anlamına gelir.

Ayın günü 1–31 arasında, Aylar 0–11 arasında, haftanın günleri (1=pazar) 1,7 arasında dizeleri kullanarak belirtilebilir.

Değer artışını belirtmek için ‘/’ karakteri kullanılabilir. Örneğin, Dakika alanına ‘0/15’ koyarsanız ‘saatin her 15. dakikasında, sıfırdan başlayarak, Dakika alanında ‘3/20’ kullanıldıysa ‘saatin her 20. dakikasında bir, üçincü dakikadan” başlayarak anlamına gelir.

‘?’ ayın günü ve haftanın günü alanları için karaktere izin verilir. “Belirli bir değer yok” belirtmek için kullanılır.

Ayın günü ve haftanın günü alanları için ‘L’ karakteine izin verilir. Bu karakter, “last’ın kısaltmasıdır, ancak iki alanın her birinde farklı anlamlara sahiptir. Örneğin, ayın günü alanındaki “L” değeri, ‘ayın son günü’ anlamına gelir.

Örneğin, “6L” veya “FRIL”, her ikisi de “ayın son cuması”

“W”, verilen güne en yakın haftanın gününü (Pazartesi-Cuma) belirtmek için kullanılır.

Her 5 dakikada bir tetikleyici oluşturmak için => “0 0/5 * * * ?”

TriggerListeners and JobListeners

Listener’lar, schedule içinde meydana gelen olaylara dayalı eylemleri gerçekleştirmek için oluşturduğunuz nesnelerdir. Tahmin edebileceğiniz gibi, TriggerListener’lar triggerlarla ilgili olayları ve JobListener’lar job’larla ilgili olayları alır.

Tetikleyiciyle ilgili olaylar şunları içerir: tetikleme tetiklemeleri, yanlış tetikleme tetiklemeleri (bu belgenin “Tetikleyiciler” bölümünde ele alınmıştır) ve tetikleme tamamlamaları (tetikleyici tarafından başlatılan işler biter).

org.quartz.TriggerListener Interface

public interface TriggerListener {

public String getName();

public void triggerFired(Trigger trigger, JobExecutionContext context);

public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);

public void triggerMisfired(Trigger trigger);

public void triggerComplete(Trigger trigger, JobExecutionContext context,
int triggerInstructionCode);
}

İşle ilgili olaylar şunları içerir: işin yürütülmek üzere olduğuna dair bir bildirim ve işin yürütülmesi tamamlandığında bir bildirim.

org.quartz.JobListener Interface

public interface JobListener {

public String getName();

public void jobToBeExecuted(JobExecutionContext context);

public void jobExecutionVetoed(JobExecutionContext context);

public void jobWasExecuted(JobExecutionContext context,
JobExecutionException jobException);

}

Kendi Listener’larınızı oluşturma

Bir listener oluşturmak için org.quartz.TriggerListener ve/veya org.quartz.JobListener interfacelerini uygulayan bir nesne oluşturmanız yeterlidir. Listenerlar daha sonra çalışma süresi boyunca schedule’a kaydedilir ve bir ad verilmelidir (veya daha doğrusu getName() yöntemiyle kendi adlarının reklamını yapmalıdırlar). Size kolaylık sağlamak için, bu interface’leri uygulamak yerine, sınıfınız JobListenerSupport veya TriggerListenerSupport sınıfını extend edebilir ve ilgilendiğiniz event’leri geçersiz kılabilir.

Listener’lar, dinleyicinin hangi Jobs/Triggers almak istediğini açıklayan bir Matcher ile birlikte scheduler’s ListenerManager’ına kaydedilir.

Listener’ların, çalışma süresi boyunca scheduler’a işler ve tetikleyicilerle birlikte kaydedilir ve JobStore’da DEPOLANMAZ. Bunun nedeni, listener’ların genellikle uygulamanızla bir entegrasyon noktası olmasıdır. Bu nedenle, uygulamanız her çalıştığında, listener’ların scheduler’a yeniden kaydedilmesi gerekir.

Adding a JobListener that is interested in a particular job:

scheduler.getListenerManager().addJobListener(myJobListener, KeyMatcher.jobKeyEquals(new JobKey("myJobName", "myJobGroup")));

Eşleştiricileri tanımlamanızı daha temiz hale getirecek olan eşleştirici ve anahtar sınıfları için statik içe aktarma kullanmak isteyebilirsiniz.

import static org.quartz.JobKey.*;
import static org.quartz.impl.matchers.KeyMatcher.*;
import static org.quartz.impl.matchers.GroupMatcher.*;
import static org.quartz.impl.matchers.AndMatcher.*;
import static org.quartz.impl.matchers.OrMatcher.*;
import static org.quartz.impl.matchers.EverythingMatcher.*;
...etc.,

Yukarıdaki örneği buna çevirir:

scheduler.getListenerManager().addJobListener(myJobListener, jobKeyEquals(jobKey("myJobName", "myJobGroup")));

Belirli bir grubun tüm işleriyle ilgilenen bir JobListener ekleme:

scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));

Belirli iki grubun tüm işleriyle ilgilenen bir JobListener ekleme:

scheduler.getListenerManager().addJobListener(myJobListener, or(jobGroupEquals("myJobGroup"), jobGroupEquals("yourGroup")));

Tüm işlerle ilgilenen bir JobListener ekleme:

scheduler.getListenerManager().addJobListener(myJobListener, allJobs());

…TriggerListeners’ı kaydettirmek aynı şekilde çalışır.

Listener’lar çoğu Quartz kullanıcısı tarafından kullanılmaz, ancak uygulama gereksinimleri, İşin kendisinin uygulamayı açıkça bildirmesi gerekmeden, olayların bildirilmesine ihtiyaç duyduğunda kullanışlıdır.

SchedulerListeners

SchedulerListeners, TriggerListeners ve JobListeners’a çok benzer, ancak belirli bir tetikleyici veya işle ilgili olaylar değil, Scheduler’ın kendi içindeki olaylar hakkında bildirim alırlar.

Scheduler ile ilgili olaylar şunları içerir: bir job/trigger’ın eklenmesi, bir job/trigger’ın kaldırılması, scheduler’da ciddi bir hata, scheduler’ın kapatıldığının bildirilmesi ve diğerleri.

org.quartz.SchedulerListener Interface

public interface SchedulerListener {

public void jobScheduled(Trigger trigger);

public void jobUnscheduled(String triggerName, String triggerGroup);

public void triggerFinalized(Trigger trigger);

public void triggersPaused(String triggerName, String triggerGroup);

public void triggersResumed(String triggerName, String triggerGroup);

public void jobsPaused(String jobName, String jobGroup);

public void jobsResumed(String jobName, String jobGroup);

public void schedulerError(String msg, SchedulerException cause);

public void schedulerStarted();

public void schedulerInStandbyMode();

public void schedulerShutdown();

public void schedulingDataCleared();
}

SchedulerListener’lar, scheduler’ın ListenerManager’ına kaydedilir. SchedulerListeners, org.quartz.SchedulerListener interface’ini uygulayan hemen hemen her nesne olabilir.

Bir SchedulerListener Eklemek:

scheduler.getListenerManager().addSchedulerListener(mySchedListener);

Bir SchedulerListener’ı Kaldırma:

scheduler.getListenerManager().removeSchedulerListener(mySchedListener);

Job Stores

JobStore’lar, planlayıcıya verdiğiniz tüm “iş verilerinin” takibinden sorumludur: işler, tetikleyiciler, takvimler, vb. Quartz planlayıcı örneğiniz için uygun JobStore’u seçmek önemli bir adımdır. Neyse ki, aralarındaki farkları anladıktan sonra seçim çok kolay olmalı. Zamanlayıcı örneğinizi oluşturmak için kullandığınız SchedulerFactory’ye sağladığınız özellikler dosyasında (veya nesnesinde) zamanlayıcınızın hangi JobStore’u kullanması gerektiğini (ve onun yapılandırma ayarlarını) beyan edersiniz.

Bir JobStore instance’ını asla doğrudan kodunuzda kullanmayın. Nedense birçok insan bunu yapmaya çalışır. JobStore, Quartz’ın kendisinin sahne arkası kullanımı içindir. Quartz’a (yapılandırma yoluyla) hangi JobStore’un kullanılacağını söylemelisiniz, ancak o zaman sadece kodunuzdaki Scheduler interface’i ile çalışmalısınız.

RAMJobStore

RAMJobStore, kullanımı en basit JobStore’dur, aynı zamanda en performanslısıdır (CPU zamanı açısından). RAMJobStore adını bariz bir şekilde alır: tüm verilerini RAM’de tutar. Bu nedenle yıldırım hızındadır ve ayrıca yapılandırılması bu kadar basittir. Dezavantajı, uygulamanız sona erdiğinde (veya çöktüğünde) tüm zamanlama bilgilerinin kaybolmasıdır — bu, RAMJobStore’un işlerde ve tetikleyicilerde “geçici olmayan” ayarını kabul edemeyeceği anlamına gelir. Bazı uygulamalar için bu kabul edilebilir — hatta istenen davranıştır, ancak diğer uygulamalar için bu felaket olabilir.

RAMJobStore’u kullanmak için (ve StdSchedulerFactory kullandığınızı varsayarak) Quartz’ı yapılandırmak için kullandığınız JobStore sınıf özelliği olarak org.quartz.simpl.RAMJobStore sınıf adını belirtmeniz yeterlidir:

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

Endişelenmeniz gereken başka bir ayar yok.

JDBCJobStore

JDBCJobStore da uygun bir şekilde adlandırılmıştır — tüm verilerini JDBC aracılığıyla bir veritabanında tutar. Bu nedenle yapılandırması RAMJobStore’dan biraz daha karmaşıktır ve aynı zamanda o kadar hızlı değildir. Bununla birlikte, özellikle birincil anahtarlar üzerinde dizinlerle veritabanı tabloları oluşturursanız, performans dezavantajı çok kötü değildir. İyi bir LAN’a sahip (programlayıcı ve veritabanı arasında) oldukça modern makinelerde, bir ateşleme tetikleyicisini alma ve güncelleme süresi tipik olarak 10 milisaniyeden az olacaktır.

JDBCJobStore hemen hemen her veritabanıyla çalışır, Oracle, PostgreSQL, MySQL, MS SQLServer, HSQLDB ve DB2 ile yaygın olarak kullanılmaktadır. JDBCJobStore’u kullanmak için önce Quartz’ın kullanması için bir dizi veritabanı tablosu oluşturmanız gerekir. Tablo oluşturma SQL komut dosyalarını Quartz dağıtımının “docs/dbTables” dizininde bulabilirsiniz. Veritabanı türünüz için halihazırda bir komut dosyası yoksa, mevcut olanlardan birine bakın ve DB’niz için gerekli herhangi bir şekilde değiştirin. Unutulmaması gereken bir şey, bu komut dosyalarında, tüm tabloların “QRTZ_” önekiyle başlamasıdır (“QRTZ_TRIGGERS” ve “QRTZ_JOB_DETAIL” tabloları gibi). JDBCJobStore’a ön ekin ne olduğunu bildirdiğiniz sürece (Kuvars özelliklerinizde) bu önek aslında istediğiniz herhangi bir şey olabilir. Aynı veritabanı içinde birden çok zamanlayıcı örneği için birden çok tablo kümesi oluşturmak için farklı öneklerin kullanılması yararlı olabilir.

Tabloları oluşturduktan sonra, JDBCJobStore’u yapılandırmadan ve çalıştırmadan önce vermeniz gereken önemli bir karar daha var. Uygulamanızın ne tür işlemlere ihtiyaç duyduğuna karar vermeniz gerekir. Scheduler komutlarınızı (tetikleyici ekleme ve çıkarma gibi) diğer işlemlere bağlamanız gerekmiyorsa, o zaman Quartz’ın JobStoreTX’i JobStore’unuz olarak kullanarak işlemi yönetmesine izin verebilirsiniz (bu en yaygın seçimdir).

Diğer işlemlerle (yani bir J2EE uygulama sunucusu içinde) birlikte çalışmak için Quartz’a ihtiyacınız varsa, o zaman JobStoreCMT’yi kullanmalısınız — bu durumda Quartz, uygulama sunucusu konteynerinin işlemleri yönetmesine izin verecektir.

Bulmacanın son parçası, JDBCJobStore’un veritabanınıza bağlantı alabileceği bir DataSource kurmaktır. DataSources, Quartz özelliklerinizde birkaç farklı yaklaşımdan biri kullanılarak tanımlanır. Bir yaklaşım, Quartz’ın DataSource’un kendisini oluşturmasını ve yönetmesini sağlamaktır — veritabanı için tüm bağlantı bilgilerini sağlayarak. Başka bir yaklaşım, Quartz’ın, JDBCJobStore’a DataSource’un JNDI adını sağlayarak, Quartz’ın içinde çalıştığı bir uygulama sunucusu tarafından yönetilen bir DataSource kullanmasını sağlamaktır. Özelliklerle ilgili ayrıntılar için “docs/config” klasöründeki örnek yapılandırma dosyalarına bakın.

JDBCJobStore’u kullanmak için (ve StdSchedulerFactory kullandığınızı varsayarsak) önce Quartz yapılandırmanızın JobStore sınıf özelliğini org.quartz.impl.jdbcjobstore.JobStoreTX veya org.quartz.impl.jdbcjobstore.JobStoreCMT olarak ayarlamanız gerekir. yukarıdaki birkaç paragraftaki açıklamalara göre yaptığınız seçim.

Quartz’ı JobStoreTx’i kullanacak şekilde yapılandırma

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

Ardından, JobStore’un kullanması için bir DriverDelegate seçmeniz gerekir. DriverDelegate, özel veritabanınız için gerekebilecek herhangi bir JDBC çalışmasını yapmaktan sorumludur. StdJDBCDelegate, işini yapmak için “vanilla” JDBC kodunu (ve SQL deyimlerini) kullanan bir temsilcidir. Veritabanınız için özel olarak yapılmış başka bir temsilci yoksa, bu temsilciyi kullanmayı deneyin — yalnızca StdJDBCDelegate’i kullanırken sorun bulduğumuz veritabanları için veritabanına özel temsilciler yaptık (en çok görünen bu!). Diğer delegeler “org.quartz.impl.jdbcjobstore” paketinde veya alt paketlerinde bulunabilir. Diğer temsilciler arasında DB2v6Delegate (DB2 sürüm 6 ve öncesi için), HSQLDBDelegate (HSQLDB için), MSSQLDelegate (Microsoft SQLServer için), PostgreSQLDelegate (PostgreSQL için), WeblogicDelegate (Weblogic tarafından yapılan JDBC sürücülerini kullanmak için), OracleDelegate (Oracle kullanmak için), ve diğerleri.

Temsilcinizi seçtikten sonra sınıf adını JDBCJobStore’un kullanması için temsilci olarak ayarlayın.

JDBCJobStore’u DriverDelegate kullanacak şekilde yapılandırma

org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

Ardından, hangi tablo önekini (yukarıda tartışılmıştır) kullandığınızı JobStore’a bildirmeniz gerekir.

JDBCJobStore’u Tablo Öneki ile Yapılandırma

org.quartz.jobStore.tablePrefix = QRTZ_

Son olarak, JobStore tarafından hangi DataSource’un kullanılacağını belirlemeniz gerekir. Adlandırılmış DataSource, Quartz özelliklerinizde de tanımlanmalıdır. Bu durumda, Quartz’ın DataSource adı “myDS” (konfigürasyon özelliklerinde başka bir yerde tanımlanmıştır) kullanması gerektiğini belirtiyoruz.

JDBCJobStore’u kullanılacak DataSource adıyla yapılandırma

org.quartz.jobStore.dataSource = myDS

Scheduler’ınız meşgulse (yani neredeyse her zaman iş parçacığı havuzunun boyutuyla aynı sayıda işi yürütüyorsa, muhtemelen DataSource’taki bağlantı sayısını iş parçacığı havuzunun boyutu + 2 olacak şekilde ayarlamalısınız.

“org.quartz.jobStore.useProperties” yapılandırma parametresi, JDBCJobStore’a JobDataMaps içindeki tüm değerlerin String’ler olacağını ve dolayısıyla ad-değer çiftleri olarak depolanabileceğini bildirmek için “true” (varsayılanı false olarak) olarak ayarlanabilir. daha karmaşık nesneleri BLOB sütununda serileştirilmiş formlarında depolamaktan daha iyidir. Bu, uzun vadede çok daha güvenlidir, çünkü String olmayan sınıflarınızı bir BLOB’a serileştirmeyle ilgili sınıf versiyonlama sorunlarından kaçınırsınız.

TerracottaJobStore

TerracottaJobStore, bir veritabanı kullanmadan ölçeklendirme ve sağlamlık için bir araç sağlar. Bu, veritabanınızın Quartz’dan yüksüz tutulabileceği ve bunun yerine tüm kaynaklarının uygulamanızın geri kalanı için kaydedilebileceği anlamına gelir.

TerracottaJobStore kümelenmiş(clustered) veya kümelenmemiş(non-clustered) olarak çalıştırılabilir ve her iki durumda da, veriler Terracotta sunucusunda depolandığından, iş verileriniz için uygulama yeniden başlatmaları arasında kalıcı olan bir depolama ortamı sağlar. Performansı, JDBCJobStore aracılığıyla bir veritabanı kullanmaktan çok daha iyidir (yaklaşık olarak daha iyi), ancak RAMJobStore’dan oldukça yavaştır.

TerracottaJobStore’u kullanmak için (ve StdSchedulerFactory kullandığınızı varsayarsak), kuvarsı yapılandırmak için kullandığınız JobStore sınıf özelliği olarak org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore sınıf adını belirtmeniz ve fazladan bir satır eklemeniz yeterlidir. Terracotta sunucusunun konumunu belirtmek için yapılandırma:

Configuring Quartz to use TerracottaJobStore

org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore
org.quartz.jobStore.tcConfigUrl = localhost:9510

Bu JobStore ve Terracotta hakkında daha fazla bilgiyi http://www.terracotta.org/quartz adresinde bulabilirsiniz.

Configuration, Resource Usage and SchedulerFactory

Quartz’ın mimarisi modülerdir ve bu nedenle onu çalıştırmak için birkaç bileşenin birbirine “yakalanması (snapped)” gerekir. Neyse ki, bunun gerçekleşmesi için bazı yardımcılar var.

Quartz’ın işini yapabilmesi için yapılandırılması gereken ana bileşenler şunlardır:

  • ThreadPool
  • JobStore
  • DataSources (if necessary)
  • The Scheduler itself

ThreadPool, Quartz’ın Jobs yürütürken kullanması için bir dizi Job parçacığı sağlar. Havuzda ne kadar çok iş parçacığı varsa, aynı anda çalıştırılabilen İş sayısı da o kadar fazla olur. Ancak, çok fazla iş parçacığı sisteminizi tıkayabilir. Çoğu Quartz kullanıcısı, 5 veya daha fazla iş parçacığının bol olduğunu bulur — çünkü herhangi bir zamanda 100'den daha az işleri vardır, işler genellikle aynı anda çalışacak şekilde programlanmaz ve işler kısa ömürlüdür (hızlı bir şekilde tamamlanır). Diğer kullanıcılar: 10, 15, 50 ve hatta 100 iş parçacığına ihtiyaç duyduklarını bulurlar — çünkü çeşitli zamanlamalara sahip on binlerce tetikleyicileri vardır — bu da herhangi bir anda yürütmeye çalışan ortalama 10 ila 100 işe sahip olur. Scheduler’ın havuzu için doğru boyutu bulmak, tamamen scheduler’ı ne için kullandığınıza bağlıdır. İş parçacığı sayısını mümkün olduğu kadar küçük tutmaktan (makinenizin kaynakları uğruna) başka gerçek kurallar yoktur — ancak Job’larınızın zamanında başlaması için yeterli sayıda olduğundan emin olun. Bir tetikleyicinin ateşleme zamanı geldiğinde ve uygun bir iş parçacığı yoksa, Quartz’ın bir iş parçacığı uygun olana kadar bloke edeceğini (duraklatacağını), ardından Job’ın yürütüleceğini unutmayın — olması gerekenden birkaç milisaniye sonra. Bu, iş parçacığının tekleme yapmasına bile neden olabilir — eğer zamanlayıcının yapılandırılmış “tekleme eşiği” süresince kullanılabilir bir iş parçacığı yoksa.

org.quartz.spi paketinde bir ThreadPool arayüzü tanımlanmıştır ve istediğiniz şekilde bir ThreadPool uygulaması oluşturabilirsiniz. Quartz, org.quartz.simpl.SimpleThreadPool adlı basit (ama çok tatmin edici) bir iş parçacığı havuzuyla birlikte gönderilir. Bu ThreadPool, havuzunda sabit bir dizi iş parçacığı tutar — asla büyümez, asla küçülmez. Ancak bunun dışında oldukça sağlamdır ve çok iyi test edilmiştir — Quartz kullanan hemen hemen herkes bu havuzu kullanır. JobStore’lar ve DataSources da dikkat edilmesi gereken husus, tüm JobStore’ların org.quartz.spi.JobStore arayüzünü uygulamasıdır — ve birlikte verilen JobStore’lardan biri ihtiyaçlarınızı karşılamıyorsa, kendinizinkini yapabilirsiniz.

Son olarak, Scheduler örneğinizi oluşturmanız gerekir. Scheduler’ın kendisine bir ad verilmesi, RMI ayarlarının söylenmesi ve bir JobStore ve ThreadPool örneklerinin verilmesi gerekir. RMI ayarları, Scheduler’ın kendisini RMI için bir sunucu nesnesi olarak oluşturması gerekip gerekmediğini (kendini remote connection’a açık hale getirme), RMI saplamaları hangi host ve port’un kullanılacağını vb. içerir.

StdSchedulerFactory

StdSchedulerFactory, org.quartz.SchedulerFactory arabiriminin bir uygulamasıdır. Bir Quartz Scheduler’ı oluşturmak ve başlatmak için bir dizi özellik (java.util.Properties) kullanır. Özellikler genellikle bir dosyada saklanır ve bir dosyadan yüklenir, ancak aynı zamanda programınız tarafından oluşturulabilir ve doğrudan factory’e teslim edilebilir. Factory’de basitçe getScheduler() öğesinin çağrılması, scheduler’ı üretecek, onu (ve ThreadPool, JobStore ve DataSources’ını) başlatacak ve genel arayüzüne bir tanıtıcı döndürecektir.

Quartz dağıtımının “docs/config” dizininde bazı örnek konfigürasyonlar (özelliklerin açıklamaları dahil) vardır. Tüm belgeleri Quartz belgelerinin “Reference” bölümünün altındaki “Configuration” kılavuzunda bulabilirsiniz.

DirectSchedulerFactory

DirectSchedulerFactory, başka bir SchedulerFactory uygulamasıdır. Scheduler örneğini daha programlı bir şekilde oluşturmak isteyenler için yararlıdır. Kullanımı genellikle aşağıdaki nedenlerden dolayı önerilmez: (1) kullanıcının ne yaptığını daha iyi anlamasını gerektirir ve (2) bildirimsel yapılandırmaya izin vermez — veya başka bir deyişle, sonunda zorlanırsınız — tüm zamanlayıcı ayarlarının kodlanması.

Logging

Quartz, tüm log ihtiyaçları için SLF4J framework’ünü kullanır. Log ayarlarını (çıktı miktarı ve çıktının nereye gittiği gibi) “tune” için bu belgenin kapsamı dışında kalan SLF4J çerçevesini anlamanız gerekir.

Tetik tetiklemeleri ve iş yürütmeleri hakkında ek bilgi almak istiyorsanız, org.quartz.plugins.history.LoggingJobHistoryPlugin ve/veya org.quartz.plugins.history.LoggingTriggerHistoryPlugin’i etkinleştirmek ilginizi çekebilir.

Gelişmiş (Kurumsal) Özellikler

Clustering

Kümeleme şu anda JDBC-Jobstore (JobStoreTX veya JobStoreCMT) ve TerracottaJobStore ile çalışır. Özellikler, yük dengeleme ve iş yük devretmeyi içerir (JobDetail’in “request recovery” bayrağı true olarak ayarlanmışsa).

####JobStoreTX veya JobStoreCMT ile Kümeleme “org.quartz.jobStore.isClustered” özelliğini “true” olarak ayarlayarak kümelemeyi etkinleştirin. Kümedeki her örnek, quartz.properties dosyasının aynı kopyasını kullanmalıdır. Bunun istisnaları, aşağıdaki izin verilen istisnalar dışında aynı özellik dosyalarını kullanmak olabilir: Farklı iş parçacığı havuzu boyutu ve “org.quartz.scheduler.instanceId” özelliği için farklı değer. Kümedeki her düğümün benzersiz bir instanceId’si OLMALIDIR; bu, bu özelliğin değeri olarak “AUTO” yerleştirerek (farklı özellik dosyalarına ihtiyaç duymadan) kolayca yapılabilir.

Saatleri çok düzenli çalışan bir tür zaman eşitleme hizmeti (arka plan programı) kullanılarak eşitlenmedikçe, kümelemeyi asla ayrı makinelerde çalıştırmayın (saatler birbirinden bir saniye içinde olmalıdır). Bunu nasıl yapacağınızı bilmiyorsanız http://www.boulder.nist.gov/timefreq/service/its.htm adresine bakın.

Kümelenmemiş bir örneği hiçbir zaman başka bir örneğin çalıştığı aynı tablo kümesine karşı çalıştırmayın. Ciddi veri bozulması yaşayabilirsiniz ve kesinlikle düzensiz davranışlarla karşılaşacaksınız.

Her ateşleme için işi yalnızca bir düğüm başlatır. Bununla demek istediğim, işin her 10 saniyede bir tetiklenmesini söyleyen yinelenen bir tetikleyici varsa, o zaman 12:00:00'da tam olarak bir düğüm işi çalıştıracak ve 12:00:10'da tam olarak bir düğüm çalışacak iş, vb. Her seferinde aynı düğüm olmayacak — onu hangi düğümün çalıştıracağı az çok rastgele olacak. Yük dengeleme mekanizması meşgul zamanlayıcılar için neredeyse rastgeledir (birçok tetikleyici) ancak meşgul olmayan zamanlayıcılar için (örneğin bir veya iki tetikleyici) az önce aktif olan aynı düğümü tercih eder.

####TerracottaJobStore ile Clustering Planlayıcıyı TerracottaJobStore’u kullanacak şekilde yapılandırmanız yeterlidir; zamanlayıcınız kümeleme için tamamen hazır olacaktır.

Ayrıca Terracotta sunucunuzu nasıl kurduğunuza, özellikle kalıcılık ve HA için bir dizi Terracotta sunucusu çalıştırma gibi özellikleri açan yapılandırma seçeneklerine ilişkin çıkarımları da düşünmek isteyebilirsiniz.

JTA Transactions

JobStores, JobStoreCMT, Quartz zamanlama işlemlerinin daha büyük JTA işlemleri içinde gerçekleştirilmesine izin verir.

İşler, “org.quartz.scheduler.wrapJobExecutionInUserTransaction” özelliğini “true” olarak ayarlayarak bir JTA işlemi (UserTransaction) içinde de yürütülebilir. Bu seçenek ayarlandığında, bir JTA işlemi, İşin yürütme yöntemi çağrılmadan hemen önce begin() ve yürütme çağrısı sona erdikten hemen sonra commit(). Bu tüm işler için geçerlidir.

Bir JTA işleminin yürütmesini sarması gerekip gerekmediğini iş başına belirtmek isterseniz, iş sınıfında @ExecuteInJTATransaction ek açıklamasını kullanmalısınız.

JTA işlemlerindeki İş yürütmelerini otomatik olarak sarmalayan Quartz’ın yanı sıra, Zamanlayıcı arabiriminde yaptığınız aramalar da JobStoreCMT kullanırken işlemlere katılır. Zamanlayıcıda bir yöntemi çağırmadan önce bir işlem başlattığınızdan emin olun. Bunu, doğrudan UserTransaction kullanarak veya zamanlayıcıyı kullanan kodunuzu kapsayıcı tarafından yönetilen işlemleri kullanan bir SessionBean içine koyarak yapabilirsiniz.

Quartz’ın Çeşitli Özellikleri

Plug-Ins

Quartz, ek işlevsellik eklemek için bir arabirim (org.quartz.spi.SchedulerPlugin) sağlar. Çeşitli yardımcı özellikler sağlamak için Quartz ile birlikte gelen eklentiler, org.quartz.plugins paketinde belgelenmiş olarak bulunabilir. Scheduler başlatıldığında işlerin otomatik olarak zamanlanması, iş geçmişinin günlüğe kaydedilmesi ve olayları tetikleme ve JVM çıktığında zamanlayıcının temiz bir şekilde kapanmasını sağlama gibi işlevler sağlarlar.

JobFactory

Bir tetikleyici tetiklendiğinde, ilişkili olduğu İş, Scheduler’da yapılandırılan JobFactory aracılığıyla başlatılır. Varsayılan JobFactory, iş sınıfında basitçe newInstance()’ı çağırır. Uygulamanızın IoC veya DI kapsayıcısının iş örneğini produce/initialize gibi şeyleri gerçekleştirmek için kendi JobFactory uygulamanızı oluşturmak isteyebilirsiniz.

org.quartz.spi.JobFactory arabirimine ve ilişkili Scheduler.setJobFactory(fact) yöntemine bakın.

‘Factory-Shipped’ Jobs

Quartz ayrıca e-posta göndermek ve EJB’leri çağırmak gibi şeyler yapmak için uygulamanızda kullanabileceğiniz bir dizi yardımcı Job sağlar. Bu kullanıma hazır Jobs, org.quartz.jobs paketinde belgelenmiş olarak bulunabilir.

--

--