Kurumsal Java ve Spring Boot ekosisteminde periyodik veri senkronizasyonu, önbellek (cache) tazeleme, gece saatlerinde finansal mutabakatların hesaplanması ve süresi dolmuş verilerin temizlenmesi gibi işlemler için yerleşik @Scheduled anotasyonu kullanılır. Harici bir işletim sistemi yapılandırmasına veya Quartz gibi ağır kütüphanelere ihtiyaç duymadan, doğrudan Spring IoC konteyneri içinde cron job tanımlamak bu anotasyon ile son derece pratiktir.
Ancak @Scheduled anotasyonunu üretim ortamında doğru kullanabilmek için Spring'in iç çalışma dinamiklerini bilmek kritik önem taşır. Spring cron ifadeleri klasik Linux crontab yapısından farklı olarak 6 alandan (saniye dahil) oluşur. Ayrıca varsayılan ThreadPoolTaskScheduler tek bir iş parçacığı (single-thread) üzerinde koşar; bu durum uzun süren bir görevin diğer tüm zamanlanmış işleri kilitlemesine neden olur. Çoklu pod mimarisine sahip Kubernetes veya Docker ortamlarında ise aynı görevin her sunucuda mükerrer çalışmasını engellemek için ShedLock gibi dağıtık kilit mekanizmaları gereklidir.
Bu kapsamlı kılavuzda; @EnableScheduling kurulumundan 6 alanlı Spring cron sözdizimine, fixedRate ile fixedDelay arasındaki davranış farklarından ThreadPoolTaskScheduler havuz yönetimine ve ShedLock kümeleme entegrasyonuna kadar tüm aşamaları adım adım inceleyeceğiz. Ayrıca mikroservislerde zamanlama karmaşasını ortadan kaldıran HTTP webhook tabanlı dış tetikleme modelini keşfedeceğiz.
Başka bir ekosistemde aynı gereksinimi çözüyorsanız .NET için Hangfire ve Quartz.NET karşılaştırması, kalıcı depolama ile kümeleme seçeneklerini ölçmek için iyi bir başlangıçtır. Daha küçük bir servis sürecinde zamanlama kuracaksanız Go cron paketleri ve goroutine kullanımı, Spring'in bean ve scheduler modelinden farklı bir yaklaşım sunar.
Spring Framework'te @Scheduled Anotasyonu ve Çalışma Mantığı
Spring Framework, zamanlanmış görevleri yönetmek için TaskScheduler arayüzünü (interface) kullanır. Bir Spring Bean'i içerisindeki metoda @Scheduled anotasyonu eklendiğinde, uygulama ayağa kalkarken Spring bu metodları tespit eder ve arka planda bir zamanlayıcı havuzuna kaydeder.
Spring'in resmi dokümantasyonuna göre zamanlanmış bir metodun karşılaması gereken parametre ve dönüş değeri davranışı şöyledir:
- Parametresiz Olmalıdır: Anotasyonlu metod herhangi bir giriş parametresi (argument) alamaz.
- Dönüş Değeri: Sıradan senkron metodlar genellikle
voiddöndürür; farklı bir dönüş değeri varsa zamanlayıcı bunu yok sayar.
Adım Adım Kurulum: @EnableScheduling ve İlk Görevin Tanımlanması
Spring Boot projenizde zamanlayıcı mekanizmasını devreye almak için ek bir bağımlılık kurmanıza gerek yoktur; spring-boot-starter paketi bu özelliği varsayılan olarak içerir. Kurulum adımları şunlardır:
- Zamanlamayı Etkinleştirme: Ana konfigürasyon sınıfınıza veya
@SpringBootApplicationsınıfınıza@EnableSchedulinganotasyonunu ekleyin:package com.example.cron; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling public class CronApplication { public static void main(String[] args) { SpringApplication.run(CronApplication.class, args); } } - Servis veya Bileşen Tanımlama: Zamanlanmış görevi barındıran sınıfı
@Componentveya@Serviceolarak işaretleyerek Spring IoC konteynerine tanıtın. - Görevi Anote Etme: İlgili iş kuralını içeren metodun üzerine
@Scheduledanotasyonunu ve zamanlama parametresini yerleştirin.
Spring Cron İfadesi Yapısı (6 Alanlı Sözdizimi ve Linux Cron Farkı)
Spring Framework'ün benimsediği cron sözdizimi, standart Linux crontab sözdiziminden farklıdır. Standart Linux crontab 5 alandan oluşurken, Spring cron ifadeleri zorunlu olarak 6 alandan oluşur. İlk alan her zaman saniye değerini temsil eder.
| Alan Sırası | Zaman Birimi | Geçerli Değerler | Özel Karakterler |
|---|---|---|---|
| 1 | Saniye (Second) | 0 - 59 | * , - / |
| 2 | Dakika (Minute) | 0 - 59 | * , - / |
| 3 | Saat (Hour) | 0 - 23 | * , - / |
| 4 | Ayın Günü (Day of Month) | 1 - 31 | * , - / ? L W |
| 5 | Ay (Month) | 1 - 12 veya JAN - DEC | * , - / |
| 6 | Haftanın Günü (Day of Week) | 0 - 7 (0 ve 7 Pazar) veya SUN - SAT | * , - / ? L # |
Spring cron ifadelerinde ? karakteri "belirli bir değer yok" anlamına gelir ve gün çakışmalarını önlemek için kullanılır. İfadelerin genel matematiksel mantığı için crontab sözdizimi sayfamıza başvurabilirsiniz.
// Örnek Spring Cron Desenleri
"0 0 3 * * *" // Her gece saat 03:00:00'da
"0 */15 * * * *" // Her 15 dakikada bir (dakika başlarında)
"0 0 9 * * MON-FRI" // Hafta içi her sabah 09:00:00'da
"0 0 0 1 * *" // Her ayın ilk günü gece yarısı
fixedRate, fixedDelay ve cron Arasındaki Davranışsal Farklar
Spring Boot içerisinde periyodik zamanlama için üç farklı parametre bulunur. Projenizin iş kuralına göre doğru parametreyi seçmek yarış durumlarını (race condition) engeller:
- fixedRate: Önceki görevin ne zaman bittiğine bakılmaksızın, her periyot başlangıcı arasındaki süreyi sabit tutar. Örneğin
@Scheduled(fixedRate = 10000)her 10 saniyede bir yeni çalıştırma planlar. ThreadPoolTaskScheduler kullanıldığında görev 12 saniye sürerse aynı görevin sonraki çalışması gecikir; havuzda boş thread bulunması bu görevi kendiliğinden paralelleştirmez. - fixedDelay: Bir önceki görevin tamamlandığı an ile bir sonraki görevin başlayacağı an arasındaki bekleme süresini garanti eder. Örneğin
@Scheduled(fixedDelay = 10000)tanımlandığında, görev 12 saniye sürerse bir sonraki görev 12 + 10 = 22. saniyede başlar.fixedDelaybu bekleme davranışı istendiğinde tercih edilir; tek başına dağıtık veritabanı tutarlılığı sağlamaz. - cron: Takvim günlerine, belirli saatlere veya haftanın günlerine bağlı kurallar tanımlar. Ayrıca
zone = "Europe/Istanbul"parametresi eklenerek sunucunun UTC saatinden bağımsız olarak yerel saat diliminde çalışması sağlanabilir.
Thread Pool Yapılandırması: Görevlerin Birbirini Kilitlemesini Önleme
Spring Boot'un ThreadPoolTaskScheduler için varsayılan ayarı, tek bir thread (boyutu 1 olan tekil iş parçacığı havuzu) tahsis etmektir; sanal thread yapılandırmasında farklı bir zamanlayıcı kullanılabilir. Eğer sisteminizde her saat başı çalışan ve 20 dakika süren ağır bir raporlama görevi varsa, o süre zarfında her 1 dakikada bir çalışması gereken sağlık kontrolü görevi bloke olur ve 20 dakika boyunca hiç çalışamaz!
Bu sorunu çözmek için uygulamanıza bir ThreadPoolTaskScheduler bean'i ekleyerek havuz boyutunu artırmalısınız:
package com.example.cron.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
@EnableSchedulerLock(defaultLockAtMostFor = "15m")
public class SchedulerConfig {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withJdbcTemplate(new JdbcTemplate(dataSource))
.usingDbTime()
.build());
}
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // Aynı anda 10 zamanlanmış görev paralel koşabilir
scheduler.setThreadNamePrefix("spring-cron-thread-");
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(30);
scheduler.initialize();
return scheduler;
}
}
Dağıtık Sistemlerde Tekil Yürütme (ShedLock Entegrasyonu)
Modern bulut mimarilerinde uygulamalar Kubernetes pod'ları veya Docker swarm üzerinde birden fazla kopya (replica) olarak yatayda ölçeklenir. Uygulamanız 4 pod olarak çalışıyorsa ve kodunuzda @Scheduled(cron = "0 0 3 * * *") varsa, gece saat 03:00'te 4 pod aynı anda aynı görevi koşturur! Bu durum müşterilere 4 kere fatura kesilmesine veya aynı e-postanın 4 kez gitmesine yol açar. Konteyner mimarilerinde zamanlama detayları için Docker cron job kılavuzumuza göz atabilirsiniz.
Bu problemi çözmenin sektör standardı ShedLock kütüphanesidir. ShedLock, ortak bir kilit deposunda (JDBC örneğinde veritabanı tablosunda) kilit kaydı açarak, kilit geçerli kaldığı sürece görevin tek bir pod tarafından çalıştırılmasını sağlar.
<!-- pom.xml Bağımlılıkları -->
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>5.10.0</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>5.10.0</version>
</dependency>
Veritabanınızda ShedLock için gerekli kilit tablosunu oluşturun:
-- PostgreSQL / MySQL Kilit Tablosu
CREATE TABLE shedlock (
name VARCHAR(64) NOT NULL,
lock_until TIMESTAMP NOT NULL,
locked_at TIMESTAMP NOT NULL,
locked_by VARCHAR(255) NOT NULL,
PRIMARY KEY (name)
);
Gerçek Kod Örnekleri
Aşağıda ShedLock ile korunan, Türkiye saat diliminde hafta içi her sabah 09:00'da çalışan profesyonel bir Spring Boot servis örneği yer almaktadır:
package com.example.cron.service;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class FinansalMutabakatServisi {
private static final Logger log = LoggerFactory.getLogger(FinansalMutabakatServisi.class);
// Hafta içi her sabah 09:00'da Türkiye saatiyle çalışır
@Scheduled(cron = "0 0 9 * * MON-FRI", zone = "Europe/Istanbul")
@SchedulerLock(
name = "FinansalMutabakatLock",
lockAtMostFor = "15m", // Sunucu çökerse kilit en fazla 15 dk sonra düşer
lockAtLeastFor = "5m" // Görev 10 saniyede bitse bile en az 5 dk kilitli tutulur
)
public void gunlukMutabakatRaporu() {
log.info("Finansal mutabakat başladı: {}", LocalDateTime.now());
// Banka API'leri ile bakiye doğrulama mantığı
log.info("Finansal mutabakat başarıyla tamamlandı.");
}
}
Sık Karşılaşılan Hatalar ve Çözümleri
Spring Boot projelerinde zamanlanmış görevlerle çalışırken en çok karşılaşılan dört teknik hata ve çözüm yolları:
Only no-arg methods may be annotated with @Scheduled
Anotasyon eklenen metodun herhangi bir parametre kabul etmesi durumunda Spring başlatma anında bu hatayı fırlatır ve uygulama çökertilir. @Scheduled metodları parametre alamaz. İhtiyaç duyulan veriler sınıf alanları (fields) üzerinden veya @Autowired servislerinden temin edilmelidir.
Tek Thread Nedeniyle Görevlerin Birbirini Engellemesi
Uzun süren bir zamanlanmış görev başladığında diğer tüm görevlerin zamanı gelmesine rağmen çalışmaması durumudur. Varsayılan TaskScheduler havuzunun tek iş parçacıklı olmasından kaynaklanır. Çözüm yukarıda gösterilen ThreadPoolTaskScheduler bean'inin tanımlanmasıdır.
Spring Cron Parsing Error (5 Haneli Cron Girilmesi)
Geliştiricilerin Linux crontab alışkanlığıyla @Scheduled(cron = "0 3 * * *") şeklinde 5 alan tanımlaması sonucu ortaya çıkar. Spring cron 6 alan beklediği için hata verir. İfadenin başına saniye alanı eklenerek 0 0 3 * * * şeklinde düzeltilmelidir.
Kubernetes Pod'larında Mükerrer Veri Üretimi
Yatayda ölçeklenen konteynerlerde her pod'un bağımsız tetiklenmesi sonucu mükerrer kayıt oluşmasıdır. Çözüm ShedLock kütüphanesinin kurulması veya görevlerin harici bir web tetikleyicisiyle tekil çağrılmasıdır. Görevlerin hiç çalışmaması durumunda genel hata ayıklama için cron job çalışmıyor rehberimizi inceleyebilirsiniz.
cronjob.com.tr ile HTTP Tabanlı Alternatif Kurulum (Kubernetes Dostu Dış Tetikleme)
Kubernetes kümelerinde ShedLock için veritabanı kilit tabloları yönetmek, kilit sürelerini (lockAtMostFor / lockAtLeastFor) ince hesaplamak ve pod lifecycle problemlerini takip etmek operasyonel yük oluşturur. En temiz alternatif, Spring uygulamanızda güvenli bir REST uç noktası açıp dışarıdan periyodik HTTP POST çağrısı almaktır. Bu sayede uygulamanız kaç pod'a ölçeklenirse ölçeklensin, Ingress/Load Balancer her isteği tek bir pod'a yönlendirir; ayrı veya çakışan isteklerde mükerrer çalışmayı önlemek için yine kilit ve idempotent işlem tasarımı gerekir.
Spring Boot uygulamanızda gizli token korumalı bir controller oluşturun:
package com.example.cron.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/internal/cron")
public class CronWebhookController {
@Value("${cron.secret.token}")
private String beklenenToken;
@PostMapping("/veri-temizligi")
public ResponseEntity<String> veriTemizligiTetikle(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
if (authHeader == null || !authHeader.equals("Bearer " + beklenenToken)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Yetkisiz erisim");
}
// Görevi asenkron olarak tetikleyin veya senkron yürütün
return ResponseEntity.ok("Temizlik gorevi basariyla yurutuldu");
}
}
cronjob.com.tr yönetim panelinde dakikalar içinde görevinizi yapılandırın:
- Hedef URL:
https://api.siteniz.com/internal/cron/veri-temizligi - HTTP Metodu:
POST - Özel Header:
Authorization: Bearer varsayilan-guvenli-token - Zamanlama: Hazır aralıklar veya 5 alanlı standart cron ifadesi (örneğin
0 3 * * *). Saat dilimiEurope/Istanbulolarak seçilebilir. - Yanıt Bekleme Süresi (Timeout): Cron ifadeli görevlerde sistem yanıt için 290 saniyeye kadar bekler.
- İzleme ve Bildirim: Sunucunuz 5xx hatası dönerse veya ağ kesintisi yaşanırsa, 3 ardışık başarısızlık sonrasında e-posta ile bildirim alırsınız. Çalışma günlükleri (yanıt kodu, yanıt süresi, ilk 64 KB gövde) 7 gün boyunca saklanır.
ShedLock veya uygulama içi scheduler bağımlılıkları olmadan periyodik işleri dışarıdan yönetmenin avantajlarını cron alternatifleri rehberimizden inceleyebilirsiniz.
Sıkça Sorulan Sorular (SSS)
Spring @Scheduled metodları geri dönüş değeri döndürebilir mi?
Evet, parametresiz senkron metodlar bir değer döndürebilir; ancak zamanlayıcı bu dönüş değerini yok sayar. Dönüş tipinin void olması zorunlu değildir.
fixedRate ile fixedDelay farkı nedir?
fixedRate görevin başlangıç anları arasındaki süreyi temel alırken, fixedDelay bir önceki görevin tamamlanma anı ile bir sonraki görevin başlama anı arasındaki bekleme süresini belirtir.
Spring cron ifadesi Linux cron'dan nasıl ayrılır?
Standart Linux cron 5 alandan oluşurken, Spring cron ifadesi en başta zorunlu bir saniye alanı barındırır ve toplam 6 alandan (saniye, dakika, saat, gün, ay, haftanın günü) meydana gelir.
ShedLock ne işe yarar?
ShedLock, Kubernetes pod'ları veya çoklu sunucu kümelerinde ortak kilit deposunu kullanan görevlerin eşzamanlı çalışmasını, kilit süresi aşılmadığı sürece engeller.
Hemen Ücretsiz Cron Job Oluşturun
Spring Boot uygulamanızdaki token korumalı REST uç noktasını cronjob.com.tr ile beş alanlı ifade ve seçtiğiniz saat diliminde çağırın; yanıt kodunu ve yanıt süresini 7 gün boyunca inceleyin.
Ücretsiz Başla →