İçeriğe geç

@Scheduled ve ShedLock — Cron İşi Kaç Pod'da Çalışıyor?

Orta 8 dk Sık karşılaşılır

Önce şunu oku: Oturum Yönetimi ve Stateless Ölçekleme

30 saniyede özet

@Scheduled her sunucuda ayrı çalışır: üç kopya çalıştırırsan iş de üç kez çalışır. ShedLock veritabanına bir satır yazarak işi tek sunucuya indirir; kilidin süresini de işin süresine göre seçmek gerekir.

Servis bir replikadan üçe çıkarıldı. Ertesi sabah müşteri hizmetlerinin telefonu durmadı: herkes aynı faturayı üç kez almıştı.

Aynı alarm üç telefonda: kapıyı kaç kişi kilitler?
Adım adım oku
  1. Saat 02:00'de uygulamanın üç kopyasındaki @Scheduled zamanlayıcısı aynı anda çalar.
  2. Birbirlerinden haberleri olmadığı için üçü de fatura işini çalıştırır: herkes faturayı üç kez alır.
  3. ShedLock ile işe başlayan ilk kopya ortak bir deftere, yani veritabanındaki bir satıra adını yazar.
  4. Öteki kopyalar satırı görür ve o turu atlar. Fatura bir kez gönderilir.
  1. Bayt: Ertesi sabah müşteri hizmetlerinin telefonu susmadı!

  2. Sen: Ne oldu?

  3. Bayt: Herkes aynı faturayı üç kez almış. Kod değişmedi, sadece üç kopya çalıştırdık.

  4. Bayt: Üç vardiya amirinin telefonunda aynı alarm varsa, kapıyı kaç kişi kilitler?

@Scheduled sadece kendi sunucusunu tanır

@EnableScheduling açıkken Spring, @Scheduled metotları kendi zamanlayıcısıyla çalıştırır. Boot’un zamanlayıcısı varsayılan olarak tek thread’lidir; uzun bir iş, diğer zamanlanmış işleri de bekletir.

Zamanlayıcı yalnızca kendi JVM’ini bilir. Üç pod varsa üç zamanlayıcı vardır ve dakikası gelince üçü de işi başlatır.

Kafam karıştı, daha basit anlat

Üç sunucunun her birinin kendi çalar saati var. Saat sekizi gösterince üçü de aynı işi başlatır, çünkü birbirlerinden haberleri yoktur.

Hızlı kontrolBaşlangıç

Gece 02:00'de fatura gönderen bir @Scheduled işi olan servis 1 replikadan 3 replikaya çıkarıldı. Ertesi sabah ne görürsün?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Bir serviste iki @Scheduled iş var: her dakika çalışan hızlı bir sağlık kontrolü ve bazen 10 dakika süren bir rapor. Rapor çalışırken sağlık kontrolü de duruyor. Neden?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

@Scheduled(fixedRate = 60_000) ile @Scheduled(fixedDelay = 60_000) arasındaki fark nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

ShedLock: defterdeki satır

ShedLock, pod’lar arasında paylaşılan bir distributed lockBirden çok süreç arasında paylaşılan kilit; genellikle bir veritabanı satırı ya da Redis anahtarı. Aynı işin aynı anda yalnızca bir yerde çalışmasını sağlar.Sözlükte gör → kurar: veritabanında iş adına bir satır. Dakikası gelen pod satırı almaya çalışır; alamayan o çalıştırmayı atlar.

Kilit alındı, lockAtMostFor 1 dakika. İş bu sefer 3 dakika sürüyor. 02:01'de ne olur? Cevabı göster

Başka bir pod işi yeniden başlatır. Kilit 1 dakika sonra düşer, iş ise hâlâ sürüyor. Aynı iş artık iki pod’da çalışıyor.

lockAtMostForShedLock'ta kilidin, sahibi ölse bile en geç ne zaman düşeceği. En uzun normal çalıştırmadan uzun olmalı; kısa olursa iş iki yerde çalışır.Sözlükte gör →, kilidin sahibi ölse bile en geç ne zaman düşeceğidir. En uzun normal çalıştırmadan uzun olmalı. Bedeli: kilidi tutan pod çökerse, o süre boyunca kimse işi çalıştıramaz.

Kafam karıştı, daha basit anlat

ShedLock ortak bir deftere “bu işi ben yapıyorum” diye yazmaktır. Satırı ilk yazan işi yapar, ötekiler o turu atlar.

Hızlı kontrolOrta

Rapor her gece iki kez gönderiliyor, ama yalnızca 02:00'daki çalıştırma 10 dakikadan uzun sürdüğünde. ShedLock kurulu. Hatalı satır hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Hatalı satıra dokun, sonra kontrol et.

NightlyReport.java
Java 21UTF-8LF

ShedLock kullanan bir işte kilidi tutan pod çalıştırmanın ortasında çöktü. lockAtMostFor 30 dakika. Ne olur?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Cron işi kaç pod'da çalışıyor?

Tohum 26760
  • Pod A

    boşta

  • Pod B

    boşta

  • Pod C

    boşta

Oynat ya da adımla.

Hız
Adım 0

Şu an ne oldu?

3 pod, kilit yok

Her pod kendi zamanlayıcısını çalıştırır ve dakikası gelince işi başlatır. Pod'ları birbirinden haberdar eden tek şey kilittir.

Görevler0/3

  • Aynı işi üç pod'da birden çalıştıraçık

    İpucu

    Kilit olmadan ölçekle.

  • Kilit varken yine de çift çalıştıraçık

    İpucu

    lockAtMostFor'a bak.

  • Üç pod, sıfır çift çalıştırma, sıfır boş dakikaaçık

    İpucu

    Doğru kilit süresi, çökme yok.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Varsayılanla oynat. Üç pod, kilit yok: her dakika üç çalıştırma.
  2. Kilidi “ShedLock” yap. Her dakika tek çalıştırma.
  3. lockAtMostFor’u “1 dk” yap. 02:01’de ikinci pod da başladı.
  4. “10 dk”ya dön ve çökmeyi aç. Dört dakika boyunca hatırlatma yok.
  5. Çökme açıkken “1 dk” seç. B hemen devraldı; ama çökme olmasaydı 3. adımdaki gibi iş iki kez çalışırdı.
Hızlı kontrolOrta

Her işi, 3 replikalı bir serviste en uygun çalıştırma biçimine göre ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Her pod'da çalışsın

Pod'un kendi belleğiyle ilgili.

    Tek pod'da, kilitle

    Paylaşılan veriye dokunan, kısa ve sık.

      Ayrı bir iş (Kubernetes CronJob)

      Uzun, ağır, kendi kaynağını isteyen.

        Tuzaklar

        Kilide tek başına güvenmek. Kilit düşebilir, saatler farklı olabilir. İşin kendisini idempotent yap: zaten gönderilmiş faturayı atla.

        Sessiz başarısızlık. Tekrarlayan bir işin exception’ı yalnızca log’a yazılır ve iş bir sonraki tetiklemede yine denenir. Son başarılı çalıştırma zamanını metrik olarak yayınla ve alarm kur.

        Saat dilimi. cron ifadesi sunucunun saat dilimiyle yorumlanır. zone = "Europe/Istanbul" gibi açıkça yaz.

        Uzun iş web pod’unda. Bir saat süren dışa aktarma, deploy ve ölçeklemeyle kesilir. Onu bir Kubernetes CronJob’a taşımak çoğu zaman daha basittir.

        Kafam karıştı, daha basit anlat

        Kilit düşebilir. İşi, iki kez çalışsa da zarar vermeyecek şekilde yaz: gönderilmiş faturayı yeniden gönderme.

        Hızlı kontrolİleri

        ShedLock'lu, her dakika çalışan ve 200 ms süren bir iş var. Pod saatleri birkaç saniye farklı. Bazen aynı dakikada iki çalıştırma görülüyor. Hangi ayar bunu önler?

        Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

        Aşağıdaki örnek bir bankanın kredi servisinden: vadesi yaklaşan taksitler için SMS gönderen iş üç pod’da güvenle çalışıyor. Kilit, idempotent iş, izlenebilir son başarı ve açık saat dilimi bir arada.

        Derinleş · Kredi taksidi hatırlatma işi: uçtan uca 5 dosya · ~75 satır · ilk okumada atlayabilirsin
        Proje dosyaları

        src/main/java/com/bank/loan/reminders/ SchedulingConfig.java Zamanlayıcı ve ShedLock'u açar. Kilit süresinin veritabanı saatine göre hesaplanması pod saat farklarını etkisiz kılar.

        src/main/java/com/bank/loan/reminders/SchedulingConfig.java
        @Configuration
        @EnableScheduling
        @EnableSchedulerLock(defaultLockAtMostFor = "PT10M")
        class SchedulingConfig {
        @Bean
        LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(
        JdbcTemplateLockProvider.Configuration.builder()
        .withJdbcTemplate(new JdbcTemplate(dataSource))
        .usingDbTime() // lock_until computed by the database, not by pod clocks
        .build());
        }
        }

        src/main/java/com/bank/loan/reminders/ InstallmentReminderJob.java Tetikleyici ince: kilidi alır, işi servise bırakır ve son başarı zamanını metrik olarak yayınlar.

        src/main/java/com/bank/loan/reminders/InstallmentReminderJob.java
        @Component
        class InstallmentReminderJob {
        private final ReminderService reminders;
        private final AtomicLong lastSuccess = new AtomicLong();
        InstallmentReminderJob(ReminderService reminders, MeterRegistry meters) {
        this.reminders = reminders;
        // Alert when this stops moving: silent failures are the real risk of scheduled jobs.
        meters.gauge("loan.reminders.last.success.epoch", lastSuccess);
        }
        @Scheduled(cron = "0 * * * * *", zone = "Europe/Istanbul")
        @SchedulerLock(name = "installmentReminders", lockAtMostFor = "PT10M", lockAtLeastFor = "PT30S")
        public void run() {
        LockAssert.assertLocked(); // fails loudly if someone calls run() without the lock
        reminders.sendDue(Instant.now(), 200);
        lastSuccess.set(Instant.now().getEpochSecond());
        // A full batch of 200 means more are waiting: the next minute picks them up.
        // No unbounded loop inside the lock, so a run never outlives lockAtMostFor.
        }
        }

        src/main/java/com/bank/loan/reminders/ ReminderService.java Asıl iş idempotent: müşteriye aynı taksit için ikinci SMS gitmez, kilit düşse bile.

        src/main/java/com/bank/loan/reminders/ReminderService.java
        @Service
        class ReminderService {
        private final InstallmentRepository installments;
        private final SmsGateway sms;
        ReminderService(InstallmentRepository installments, SmsGateway sms) {
        this.installments = installments;
        this.sms = sms;
        }
        @Transactional
        public int sendDue(Instant now, int batchSize) {
        // Installments due within three days whose reminder_sent_at IS NULL.
        // That condition makes the job idempotent: a second run finds nothing to do.
        List<Installment> due = installments.findDueSoonWithoutReminder(now, Duration.ofDays(3), Limit.of(batchSize));
        for (Installment installment : due) {
        sms.send(installment.customerPhone(), Templates.INSTALLMENT_REMINDER,
        installment.dueDate(), installment.amount());
        installment.markReminderSent(now);
        }
        return due.size();
        }
        }

        src/main/resources/db/migration/ V12__shedlock.sql ShedLock'un tablosu; Flyway migration'ıyla oluşturulur.

        src/main/resources/db/migration/V12__shedlock.sql
        CREATE TABLE shedlock (
        name VARCHAR(64) NOT NULL PRIMARY KEY,
        lock_until TIMESTAMP NOT NULL,
        locked_at TIMESTAMP NOT NULL,
        locked_by VARCHAR(255) NOT NULL
        );

        src/main/resources/ application.yml Zamanlayıcı havuzu ve saat dilimi ayarları.

        src/main/resources/application.yml
        spring:
        task:
        scheduling:
        pool:
        size: 4 # default is 1: one slow job would delay every other job
        thread-name-prefix: sched-
        shutdown:
        await-termination: true
        await-termination-period: 30s

        Kendini sına

        Şimşek turu1/5

        @Scheduled bir iş, uygulamanın çalışan her kopyasında ayrı ayrı çalışır.

        Soru 1/2İleri

        @Scheduled bir metot her çalıştırmada exception fırlatıyor. Ne olur?

        Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

        Aklında kalacak üç şey

        1. 1 @Scheduled sunucular arasında haberleşmez. Kopya sayısı arttıkça kilitsiz her zamanlanmış iş de çoğalır.
        2. 2 lockAtMostFor, en uzun normal çalıştırmadan uzun olmalıdır. Bedeli: kilidi tutan sunucu çökerse iş o süre boyunca bekler.
        3. 3 Kilit de şaşabilir; bu yüzden iş iki kez çalışsa bile zarar vermemeli ve son başarılı çalışma zamanı izlenmelidir.
        Sonraki kapı Birkaç işçi binlerce isteği nasıl taşır? Ta ki biri bir yerde beklemeye başlayana kadar. WebFlux ve Event Loop — Bloklayan Tek Satır Neyi Durdurur? · 9 dk

        5 kart sonraki derste seni bekliyor

        0/5 kart bu dersten toplandı

        Bu dersin üstüne kurulanlar

        Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.