İçeriğe geç

Dağıtık Kilit ve Fencing Token — Kilit Senin Sandığında

İleri 9 dk Sık karşılaşılır

Önce şunu oku: @Scheduled ve ShedLock — Cron İşi Kaç Pod'da Çalışıyor?

30 saniyede özet

Süreli bir dağıtık kilit, sahibi uzun süre durursa sessizce başkasına geçer. Uyanan eski sahip kilidin hâlâ kendisinde olduğunu sanır ve yazar. Fencing token ile kararı yazılan yer verir: eski numarayla gelen yazma reddedilir.

Tek kişilik bir park yeri için numaralı fiş veriliyor ve her fiş on dakika geçerli. Fişi alan kişi arabada uyuyakalıyor; fişin süresi doluyor ve yer bir başkasına veriliyor. Uyanan ilk kişi elindeki fişe bakıp park etmeye çalışıyor.

  1. Bayt: Gece faiz işi iki sunucuda çalışıyor ama dağıtık kilit var. Yalnızca biri faiz ödeyebilir!

  2. Sen: Bu sabah bir müşteriye günün faizi iki kez yatmış.

  3. Bayt: Kilit vardı... ikisi aynı anda nasıl çalışabildi?

  4. Bayt: Belki aynı anda değil, sırayla. Biri kilidin hâlâ kendisinde olduğunu sanmış olabilir.

Kilit neden süreyle verilir?

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 →, birden çok sunucudan yalnızca birinin bir işi yapmasını sağlar. Ama kilidi tutan sunucu çökerse kilidi hiç bırakmaz ve iş sonsuza kadar bekler.

Bu yüzden kilit bir süreyle verilir: bir leaseBir kilidin ya da hakkın belirli bir süre için verilmesi. Süre dolunca hak kendiliğinden düşer; sahibi çökse bile sistem takılmaz.Sözlükte gör →. Süre dolunca kilit kendiliğinden serbest kalır ve başka bir sunucu alabilir.

Kafam karıştı, daha basit anlat

Kilit, sahibi çökerse sonsuza kadar kalmasın diye bir süreyle verilir.

Hızlı kontrolBaşlangıç

Dağıtık kilit ne işe yarar?

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

Dağıtık kilit neden bir süreyle (lease) verilir?

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

Süre dolarken uyuyan iş

A kilidi 10 saniyelik bir süreyle aldı ve 12 saniyelik bir GC duraklamasına girdi. Bu sırada B kilidi alıp faizi ödedi. A uyandığında ne yapar? Cevabı göster

Faizi o da öder. A duraklamayı fark etmez; ona göre bir an önce kilidi almıştı. Kilidin süresinin dolduğunu bilmediği için yazmaya devam eder.

Fişin sahibi değil, kapıdaki görevli karar verir.
Adım adım oku
  1. A 33 numaralı fişi aldı, sonra uyuyakaldı.
  2. Fişin süresi doldu, B 34 numarayı aldı.
  3. A uyandı; görevli yalnızca en yeni numarayı kabul eder.
  4. Son sözü kapıdaki görevli söyler.

Duraklama yalnızca GC değildir: ağ gecikmesi ya da yavaşlayan bir disk de aynı şeyi yapar. Yazmadan hemen önce “kilit hâlâ bende mi?” diye sormak da yetmez, çünkü soru ile yazma arasında da durabilirsin.

Çözüm fencing tokenKilit her verilişinde artan bir numara. Yazılan yer gördüğü en büyük numaradan küçüğünü reddeder ve eski kilit sahibinin geç yazmasını durdurur.Sözlükte gör →: kilit her verilişinde artan bir numara verir. Yazılan yer, gördüğü en büyük numaradan küçük olanı reddeder; böylece eski sahibin geç yazması kapıda kalır.

Kafam karıştı, daha basit anlat

Kilidin sahibi duraklamayı fark etmez. Numaralı token ile kararı yazılan yer verir.

Hızlı kontrolOrta

A kilidi 10 saniyelik süreyle aldı ve 12 saniyelik bir GC duraklamasına girdi. Ne olabilir?

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

Fencing token nedir?

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

Kendin gör

Kilit süresi dolarken uyuyan iş

Tohum 79297
A
B
  • ▭ kilit süresi
  • ⚠ GC duraklaması
  • | şu an: 0 sn

Oynat ya da adımla: her adım dört saniye.

Defter: 0 faiz kaydı (+0)

Hız
Adım 0

Şu an ne oldu?

Uzun GC duraklaması (12 sn)

Kilit 10 saniyelik bir süreyle veriliyor. A kilidi alıp faiz ödemeye başlıyor.

Görevler0/3

  • Faizi iki kez ödetaçık

    İpucu

    Varsayılan ayarlar yeter.

  • Uzun duraklamada bile faizi bir kez ödeaçık

    İpucu

    Kararı defter versin.

  • Fencing olmadan, A işini kilit süresi içinde bitirsinaçık

    İpucu

    Duraklamayı kısalt.

Olay günlüğü (0)

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

  1. Varsayılanla oynat. Uzun duraklama, fencing yok: kilidin süresi doldu, B faizi ödedi, A uyanınca bir kez daha ödedi.
  2. “Defter fencing token kontrol etsin”i aç. A’nın 33 numaralı token’la gelen geç yazması reddedildi; faiz bir kez ödendi.
  3. Fencing’i kapat, “Kısa GC duraklaması” seç. A işini kilit süresi dolmadan bitirdi; sorun hiç çıkmadı.
Hızlı kontrolOrta

Yazmadan hemen önce "kilit hâlâ bende mi?" diye sormak neden yetmez?

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

Defter en son 34 numaralı token'ı gördü. 33 ile bir yazma geldi. Ne yapmalı?

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

Tuzaklar

Kilidi doğruluk garantisi sanmak. Kilit çoğu zaman yalnızca aynı işin iki kez yapılmasını azaltır. Para gibi önemli işlerde son kontrolü yazılan yerde yap: fencing token, benzersiz bir anahtar ya da idempotent bir yazma.

Süreyi işten kısa seçmek. İş normalde 30 saniye sürüyorsa 10 saniyelik bir süre, duraklama olmadan da kilidi kaçırır. Süreyi işin en uzun hâlinden belirgin biçimde uzun tut.

Token’ı yazılan yerde kontrol etmemek. Token’ı kilitten alıp hiçbir yerde karşılaştırmazsan hiçbir şeyi korumaz. Kontrol, verinin yazıldığı yerde ve aynı işlemde yapılmalı.

Kafam karıştı, daha basit anlat

Kilit tek başına yetmez; süreyi uzun tut ve token’ı yazılan yerde kontrol et.

Hızlı kontrolOrta

İş normalde 30 saniye sürüyor ve kilit süresi 10 saniye. Sorun nedir?

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

Aşağıdaki örnek bir bankanın gece faiz işinden: kilit token’ı bir sayaçtan alınıyor ve defter, gördüğü en büyük token’dan küçüğünü aynı işlemde reddediyor.

Derinleş · Gece faiz işi: dağıtık kilit ve fencing token 4 dosya · ~80 satır · ilk okumada atlayabilirsin
Proje dosyaları

src/main/java/com/bank/interest/ InterestLock.java Kilit: Redis'te süreli bir anahtar; her alınışta artan bir sayaçtan token veriliyor.

src/main/java/com/bank/interest/InterestLock.java
@Component
class InterestLock {
private static final Duration LEASE = Duration.ofMinutes(5);
private final StringRedisTemplate redis;
InterestLock(StringRedisTemplate redis) {
this.redis = redis;
}
/** A granted lock carries a token that only goes up, whoever holds it. */
Optional<Long> tryAcquire(String job, String owner) {
Boolean acquired = redis.opsForValue().setIfAbsent("lock:" + job, owner, LEASE);
if (!Boolean.TRUE.equals(acquired)) {
return Optional.empty();
}
return Optional.of(redis.opsForValue().increment("fence:" + job));
}
}

src/main/java/com/bank/interest/ NightlyInterestJob.java İş: token'ı kilitten alıyor ve yazarken deftere taşıyor; kilidin hâlâ kendisinde olduğuna güvenmiyor.

src/main/java/com/bank/interest/NightlyInterestJob.java
@Component
class NightlyInterestJob {
private final InterestLock lock;
private final InterestLedger ledger;
private final AccountQueries accounts;
private final String owner = UUID.randomUUID().toString();
NightlyInterestJob(InterestLock lock, InterestLedger ledger, AccountQueries accounts) {
this.lock = lock;
this.ledger = ledger;
this.accounts = accounts;
}
@Scheduled(cron = "0 5 0 * * *")
void run() {
lock.tryAcquire("nightly-interest", owner).ifPresent(token -> {
LocalDate day = LocalDate.now(ZoneId.of("Europe/Istanbul")).minusDays(1);
for (AccountInterest due : accounts.interestDueFor(day)) {
// The token travels with every write; the ledger decides, not this process.
ledger.pay(due, day, token);
}
});
}
}

src/main/java/com/bank/interest/ InterestLedger.java Defter: token'ı tek bir koşullu UPDATE ile kontrol ediyor; eski token'la gelen yazma etkisiz kalıyor.

src/main/java/com/bank/interest/InterestLedger.java
@Repository
class InterestLedger {
private final JdbcClient jdbc;
InterestLedger(JdbcClient jdbc) {
this.jdbc = jdbc;
}
@Transactional
void pay(AccountInterest due, LocalDate day, long token) {
// Fencing: only a token at least as new as the highest seen may write.
int fenced = jdbc.sql("""
UPDATE job_fence SET highest_token = :token
WHERE job = 'nightly-interest' AND highest_token <= :token
""").param("token", token).update();
if (fenced == 0) {
throw new StaleLockHolderException(token); // a newer holder already wrote
}
jdbc.sql("""
INSERT INTO interest_entry (account_id, business_day, amount, fence_token)
VALUES (:account, :day, :amount, :token)
""")
.param("account", due.accountId()).param("day", day)
.param("amount", due.amount()).param("token", token)
.update(); // unique (account_id, business_day) is the last line of defence
}
}

src/main/resources/db/migration/ V32__interest_fencing.sql Benzersiz kısıt: aynı hesap ve gün için ikinci faiz kaydı veritabanı tarafından da engelleniyor.

src/main/resources/db/migration/V32__interest_fencing.sql
CREATE TABLE job_fence (
job VARCHAR(64) PRIMARY KEY,
highest_token BIGINT NOT NULL
);
INSERT INTO job_fence (job, highest_token) VALUES ('nightly-interest', 0);
ALTER TABLE interest_entry
ADD CONSTRAINT uq_interest_once_per_day UNIQUE (account_id, business_day);

Kendini sına

Şimşek turu1/4

Süreli bir kilidi alan sunucu, kilidin hâlâ kendisinde olduğunu her zaman bilir.

Soru 1/3İleri

Fencing token kilitten alınıyor ama hiçbir yerde karşılaştırılmıyor. Ne olur?

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

Aklında kalacak üç şey

  1. 1 Dağıtık kilitler bir süreyle (lease) verilir, çünkü sahibi çökebilir. Bedeli şu: sahibi uzun bir duraklamada kalırsa kilit, o fark etmeden başkasına geçer.
  2. 2 Uyanan eski sahip kilidin hâlâ kendisinde olduğunu sanır. Yazmadan hemen önce kontrol etmek de yetmez; kontrol ile yazma arasında yine durabilir.
  3. 3 Fencing token, her kilit verilişinde artan bir numaradır. Yazılan yer gördüğü en büyük numaradan küçüğünü reddederse, eski sahibin geç yazması zarar veremez.
Sonraki kapı Ağ beş saniyeliğine ikiye bölündü. İki taraf da 'havale onaylandı' dedi. Birleşince havalelerden biri yoktu. Nasıl? Lider Seçimi ve Split-Brain — Ağ Bölününce Kim Karar Verir? · 10 dk

4 kart sonraki derste seni bekliyor

0/4 kart bu dersten toplandı