Java backend geliştirme servislerinde kapanış anındaki 502 ve yarım kalan işlemleri azaltmak için Spring Boot graceful shutdown, Kubernetes readiness ve bağlantı drenajını ölçülebilir bir akışla ele alın.
Spring Boot'ta Graceful Shutdown ve Trafik Drenajı Tasarımı
Java backend geliştirme için kapanış zaman çizelgesini modellemek
Bir pod kapanırken Kubernetes önce EndpointSlice kaydını değiştirmez; uygulamaya SIGTERM gönderilmesi ile yük dengeleyicinin yeni endpoint bilgisini kullanması arasında istek gelebilir. Bu nedenle yalnızca server.shutdown=graceful yeterli değildir: Spring Boot yeni TCP bağlantılarını kabul etmeyi bıraksa bile, pod halen ready görünürken reverse proxy yeni istek yönlendirebilir. Önce readiness durumunu REFUSING_TRAFFIC yapın, endpoint yayılımı için ölçülmüş bir süre bekleyin, sonra süreci sonlandırın. spring framework içindeki ApplicationAvailability mekanizması bu sinyali Actuator readiness probe'una taşır.
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class DrainService {
private final ApplicationEventPublisher events;
public DrainService(ApplicationEventPublisher events) {
this.events = events;
}
public void startDrain() {
AvailabilityChangeEvent.publish(events, this,
ReadinessState.REFUSING_TRAFFIC);
}
}Bu servis için erişimi yalnızca cluster içi yönetim ağıyla sınırlı bir endpoint üzerinden verin. Uygulama herkesin çağırabildiği bir /drain sunarsa, saldırgan tek HTTP isteğiyle pod'u load balancer havuzundan çıkarabilir. Uygulanabilir sınır olarak management portunu ayrı tutun, Kubernetes NetworkPolicy ile yalnızca namespace içinden erişime izin verin ve endpoint'i Spring Security'de mTLS veya ayrı bir service account doğrulamasıyla koruyun.
Spring Boot graceful shutdown yapılandırması ve işlem sınırları
HTTP sunucusunun in-flight istekleri bitirmesi için kapanış periyodunu, uygulamanın gerçek en uzun işlem süresinden türetin. Örneğin ödeme isteği en fazla 20 saniye sürüyor, proxy timeout 30 saniye ise 35 saniyelik Spring kapanış periyodu ve 45 saniyelik Kubernetes termination süresi makul başlangıç noktasıdır. 5 saniyelik varsayımsal değer seçmek, aktif bir hibernate orm flush işlemi veya uzak servis çağrısı sırasında JVM'nin SIGKILL almasına yol açabilir.
server:
shutdown: graceful
tomcat:
connection-timeout: 5s
spring:
lifecycle:
timeout-per-shutdown-phase: 35s
management:
endpoint:
health:
probes:
enabled: true
health:
livenessstate:
enabled: true
readinessstate:
enabled: truespring.lifecycle.timeout-per-shutdown-phase yalnızca Spring lifecycle callback'leri için üst sınırdır; Kubernetes'teki terminationGracePeriodSeconds daha kısa ise işletim sistemi süreci önce öldürür. Ayrıca bir isteğin HTTP cevabını tamamlaması, verinin kalıcı olduğu anlamına gelmez. spring data jpa ile yapılan yazmalarda transaction sınırını controller yerine servis metodunda tutun; kapanış sırasında transaction yarım kalırsa veritabanı rollback yapar, fakat dış sisteme önceden gönderilmiş bir mesajı geri alamaz. Bu tip iş akışlarında outbox veya sağlayıcının idempotency anahtarı gerekir.
Yaygın hata, drain modunda sadece yeni REST isteklerini reddedip mevcut async executor, scheduler veya mesaj tüketicilerini çalıştırmaya devam etmektir. TaskExecutor için bekleme davranışını açıkça ayarlayın; aksi halde executor thread'leri aniden kesilir ve kuyruktan alınmış fakat ack edilmemiş mesajlar tekrar işlenebilir. Kafka ve benzeri tüketicilerde drain başlangıcında partition atamasını bırakmak için container durdurma sırasını HTTP readiness değişiminden sonra, JVM kapanışından önce test edin.
Java microservices ortamında Kubernetes ile kontrollü drenaj
Kubernetes preStop hook'u SIGTERM'den önce çalışır. Hook içinde uygulamanın yönetim endpoint'ine drain çağrısı yapıp endpoint yayılımı kadar beklemek, SIGTERM'i doğrudan beklemekten daha deterministiktir. Bekleme süresini tahmin ederek değil, ingress controller access log'larında readiness değişiminden sonra eski pod'a gelen son isteğin zaman damgasını inceleyerek belirleyin. Çok bölgeli veya harici load balancer kullanan yapılarda bu süre node içi Service yönlendirmesinden belirgin biçimde uzun olabilir.
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-api
spec:
template:
spec:
terminationGracePeriodSeconds: 45
containers:
- name: app
image: registry.example/orders-api:sha-abc123
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- >-
wget -qO- --post-data='' http://127.0.0.1:8081/internal/drain && sleep 8
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8081
periodSeconds: 2
failureThreshold: 1
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8081
periodSeconds: 5Buradaki kritik ayrım şudur: liveness probe'u drain nedeniyle başarısız yapmayın. Liveness başarısızlığı kubelet'in çalışan pod'u yeniden başlatmasına neden olur; readiness başarısızlığı ise yalnızca yeni trafik akışını keser. Ayrıca preStop komutunun kullandığı wget imajda yoksa hook başarısız olabilir. Distroless imajlarda küçük bir yönetim sidecar'ı kullanın veya Spring uygulamasının SIGTERM yakalayıp aynı readiness geçişini yapan SmartLifecycle bileşenini doğrulayın.
Spring AI ve Spring MCP bağlantılarında drain davranışı
spring ai kullanan bir endpoint, model sağlayıcısına açık streaming HTTP bağlantısı veya uzun süren tool çağrısı taşıyabilir. spring mcp ile kurulan model context protocol akışlarında da istemci, sunucu kapanırken yarım kalan tool sonucunu yeniden deneyebilir. Bu yüzden drain anında yeni sohbet oturumlarını readiness ile kesin, mevcut stream'lere ise uygulama seviyesinde sonlandırma olayı gönderin ve istemcinin reconnect politikasını tanımlayın. Sadece socket'i kapatmak, istemcinin aynı tool çağrısını iki kez çalıştırmasına neden olabilir.
Her uzun istek için başlama ve bitiş zamanını Micrometer timer ile kaydedin; drain süresi kararını p95 yerine p99 ve maksimum açık istek sayısıyla birlikte verin. Örneğin http.server.requests metriğini URI etiketiyle sınırsız cardinality üretmeyecek şekilde yapılandırın; sohbet kimliği veya MCP session id'yi tag olarak eklemeyin. Bu id'leri log alanında tutmak Prometheus bellek tüketimini kontrol altında tutar.
Timer.Sample sample = Timer.start(meterRegistry);
try {
return chatClient.prompt(prompt).call().content();
} finally {
sample.stop(Timer.builder("ai.request.duration")
.tag("provider", "internal")
.publishPercentileHistogram()
.register(meterRegistry));
}Bu ayrıntı, bir java eğitimi veya java programlama eğitimi bağlamında genellikle atlanır: graceful shutdown, yalnızca Java thread'lerinin bitmesi değildir. Uygulamanın kabul ettiği işin HTTP, veritabanı transaction'ı, mesaj tüketimi ve model aracı yan etkileri boyunca hangi noktada iptal edilebilir olduğunu ayrı ayrı tasarlamak gerekir.
Önce-sonra yük testi ile drenaj regresyonunu yakalamak
Drenaj tasarımını doğrulamak için Gatling ile sabit trafik altında tek bir pod'u sonlandırın. İlk senaryoda yalnızca SIGTERM gönderin, ikinci senaryoda drain endpoint'i ve preStop akışını etkinleştirin. Her iki çalıştırmada aynı istek hızını, aynı replica sayısını ve aynı ingress ayarlarını koruyun. Karşılaştırılacak somut çıktılar: termination penceresindeki HTTP 499, 502, 503 ve 5xx sayıları; p99 gecikme; tamamlanan transaction sayısı; SIGTERM anındaki aktif bağlantı sayısıdır.
setUp(
scenario("checkout")
.exec(http("create order")
.post("/orders")
.body(StringBody("{\"sku\":\"A-42\",\"quantity\":1}"))
.asJson
.check(status.in(201, 409)))
.constantUsersPerSec(80).during(2.minutes)
).protocols(http.baseUrl("https://api.example.internal"))Test sırasında Prometheus'tan sum(rate(http_server_requests_seconds_count{status=~"5.."}[30s])) sorgusunu ve ingress'in upstream hata sayısını alın. JVM tarafında jcmd <pid> JFR.start name=drain settings=profile duration=60s filename=/tmp/drain.jfr ile kısa bir JFR kaydı başlatın; Java Mission Control'de socket read, thread park ve GC pause olaylarını SIGTERM zamanıyla eşleştirin. Amaç genel bir performans iddiası değil, kapanış süresinde hangi thread'in 35 saniyelik bütçeyi tükettiğini bulmaktır.
Bir java kursu veya java fullstack eğitimi projesinde bu testi CI'a koymak için deployment sonrası kontrollü rollout çalıştırın: canary pod'u drain edin, Gatling raporunda termination penceresinde beklenmeyen 5xx varsa pipeline'ı başarısız yapın. Bu kontrol, kod değişikliğiyle eklenen yavaş bir interceptor, bloklayan audit çağrısı veya uzayan JPA flush işleminin üretimde ilk kez rollout sırasında görünmesini engeller.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Java Spring Boot ReactJS FullStack Eğitimi (Yıldız Teknik Üniversitesi SEM)
Sık Sorulan Sorular
Spring Boot graceful shutdown neden Kubernetes readiness probe ile birlikte kurulmalı?
Graceful shutdown uygulamanın aktif HTTP isteklerini bitirmesine yardım eder, fakat tek başına pod'u Service endpoint listesinden çıkarmaz. Drain başlangıcında ReadinessState.REFUSING_TRAFFIC yayınlayın, readiness probe'un 200 yerine başarısız dönmesini doğrulayın, sonra EndpointSlice ve ingress log'larında yeni isteğin kesildiğini gözlemleyin.
Java microservices için terminationGracePeriodSeconds nasıl hesaplanır?
Değeri preStop bekleme süresi + Spring lifecycle timeout + küçük bir işletim sistemi payı olarak belirleyin. Örneğin endpoint yayılımı ölçümünüz 8 saniye, en uzun in-flight işiniz 35 saniye ise 45 saniye başlangıç değeridir. Gatling testi altında SIGKILL ile kesilen istek veya rollback edilen transaction görürseniz ölçümü ve bütçeyi yeniden değerlendirin.
Spring Data JPA ve Hibernate ORM kapanış sırasında veri kaybedebilir mi?
Açık transaction JVM zorla öldürülmeden önce tamamlanmazsa veritabanı bağlantı kopuşunda rollback yapar. Ancak transaction içinden dış HTTP çağrısı, e-posta veya mesaj gönderildi ise bu yan etki rollback olmaz. Veritabanı kaydı ve mesajı atomik görünür yapmak için outbox tablosuna aynı transaction içinde yazın; yayınlayıcıyı ayrı olarak idempotent tüketimle çalıştırın.
Spring AI ve Spring MCP stream istekleri drain sırasında nasıl ele alınmalı?
Yeni oturumları readiness ile engelleyin, açık stream'ler için bir maksimum kapanış süresi tanımlayın ve istemciye yeniden bağlanma veya iptal sinyali gönderin. Model context protocol tool çağrılarında çağrı kimliğini idempotency kaydıyla saklayın; bağlantı kopunca istemci aynı yan etkili aracı tekrar çağırdığında sonucu tekrar üretmek yerine önceki sonucu döndürün.
AI / LLM Discovery
Bu makale Opendart Akademi Java eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.


