Java backend geliştirme servislerinde tek bir yavaş bağımlılığın zincirleme hata üretmesini, gecikme bütçesi, HTTP istemci sınırları ve Resilience4j politikalarıyla ölçerek ve test ederek yönetin.
Spring Boot'ta Gecikme Bütçesi ve Resilience4j Zaman Aşımı Tasarımı
Java backend geliştirme için gecikme bütçesini sözleşmeye dönüştürme
Bir endpoint için yalnızca genel bir timeout değeri belirlemek yerine, istemci tarafında ölçülebilir bir gecikme bütçesi tanımlayın. Örneğin /checkout uç noktasının p99 hedefi 800 ms ise, gateway ve serileştirme için 100 ms, veritabanı için 180 ms, inventory çağrısı için 220 ms, payment çağrısı için 220 ms ve 80 ms koordinasyon payı ayırın. Bu dağılımı OpenAPI açıklamasına, Grafana dashboard'ına ve alarm eşiğine aynı değerlerle taşıyın. Böylece bir java microservices çağrı zincirinde payment servisinin 500 ms beklemesi, kullanıcıya 800 ms sonra cevap dönmek yerine üst servislerin iş parçacıklarını tüketir hale gelmeden kesilir.
- Her bağımlılık için connect timeout, connection-pool acquire timeout, response timeout ve toplam çağrı zamanını ayrı izleyin.
- Retry varsa, bütçe hesabına her denemenin üst sınırını dahil edin: 220 ms bütçeli bir çağrıda iki deneme yapacaksanız tek deneme 100 ms civarında kesilmelidir.
- Prometheus'ta http_client_requests_seconds_bucket metriğini destination etiketiyle kaydedin; yalnızca endpoint bazlı metrik, hangi uzak bağımlılığın gecikmeyi oluşturduğunu göstermez.
Bu yaklaşım, java eğitimi veya java programlama eğitimi sırasında sık görülen 'HTTP istemcisinde timeout koyduk' seviyesinden daha ileridir: connect timeout yalnızca TCP bağlantısı kurulmasını sınırlar, response timeout ise bağlandıktan sonra ilk veya sonraki baytların beklenmesini sınırlar. Havuzdan bağlantı alma süresi ayrı sınırlanmazsa, tüm socket'ler meşgul olduğunda istek response timeout'a hiç ulaşmadan kuyrukta bekleyebilir. Bu nedenle gecikme bütçesi, uygulama mantığından önce connection pool davranışını kapsamalıdır.
Spring Framework WebClient ve Reactor Netty ile dört katmanlı sınır
Spring Framework üzerinde WebClient kullanıyorsanız Reactor Netty'nin varsayımlarına güvenmek yerine bağlantı havuzunu, TCP bağlantısını ve cevap bekleme süresini açıkça yapılandırın. Aşağıdaki bean, inventory için havuzda 100 ms'den uzun beklemeyi, TCP bağlantısında 80 ms'yi ve HTTP cevabında 180 ms'yi reddeder. En dıştaki Reactor timeout ise istek gövdesi tüketimi dahil 220 ms toplam bütçeyi uygular.
@Configuration
class InventoryClientConfig {
@Bean
WebClient inventoryWebClient(WebClient.Builder builder) {
ConnectionProvider pool = ConnectionProvider.builder("inventory-pool")
.maxConnections(80)
.pendingAcquireTimeout(Duration.ofMillis(100))
.maxIdleTime(Duration.ofSeconds(20))
.build();
HttpClient httpClient = HttpClient.create(pool)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 80)
.responseTimeout(Duration.ofMillis(180));
return builder
.baseUrl("http://inventory")
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
}Çağrı noktasında timeout'u yalnızca Netty yapılandırmasına bırakmayın. JSON body decode, filtreler ve uygulama kodu responseTimeout kapsamı dışında gecikme üretebilir. Aşağıdaki kod, toplam sürenin 220 ms'yi aşmasını engeller ve timeout hatasını inventory hata sınıfına çevirir; circuit breaker'ın teknik ağ hatası ile iş alanı hatasını ayırabilmesi için bu dönüşüm önemlidir.
Mono<Inventory> findInventory(String sku) {
return inventoryWebClient.get()
.uri("/items/{sku}", sku)
.retrieve()
.bodyToMono(Inventory.class)
.timeout(Duration.ofMillis(220))
.onErrorMap(TimeoutException.class,
ex -> new InventoryTimeoutException(sku, ex));
}Kritik incelik: Reactor'daki timeout downstream aboneliği iptal eder, fakat uzak sistem isteği işlemeye başlamış olabilir. POST gibi yan etkili operasyonlarda timeout sonrasında kör retry yapmak çift rezervasyon veya çift tahsilat üretebilir. Bu nedenle retry yalnızca idempotent GET çağrılarına uygulanmalı ya da POST isteği Idempotency-Key ile uzak sistemin kalıcı deduplikasyon tablosuna bağlanmalıdır. Bu ayrım, spring boot eğitimi materyallerinde çoğu zaman tek bir retry örneğiyle geçilir; üretimde ise hata semantiğinin asıl belirleyicisidir.
Resilience4j ile timeout, retry ve circuit breaker sıralaması
Resilience4j politikasını bütün servisler için aynı sayılarla kopyalamayın. Inventory gibi hızlı okunabilir bir bağımlılıkta 220 ms TimeLimiter, iki deneme ve kısa bir backoff kabul edilebilirken, ödeme başlatma çağrısında retry sayısı sıfır olmalıdır. Aşağıdaki yapılandırmada breaker son 50 çağrıyı değerlendirir, yavaş çağrıyı 180 ms üzerinde sayar ve timeout dahil hata oranı yüzde 40'a ulaşınca trafiği keser.
resilience4j:
timelimiter:
instances:
inventory:
timeout-duration: 220ms
cancel-running-future: true
circuitbreaker:
instances:
inventory:
sliding-window-type: COUNT_BASED
sliding-window-size: 50
minimum-number-of-calls: 20
slow-call-duration-threshold: 180ms
slow-call-rate-threshold: 40
failure-rate-threshold: 40
wait-duration-in-open-state: 10s
retry:
instances:
inventory:
max-attempts: 2
wait-duration: 15ms
retry-exceptions:
- java.io.IOException
- java.util.concurrent.TimeoutExceptionAnnotation tabanlı kullanımda TimeLimiter, CompletableFuture veya reactive tür gibi asenkron bir dönüş değeri ister. Senkron bir RestClient metodunu @TimeLimiter ile işaretlemek, çağrıyı sihirli biçimde kesmez; çağrıyı çalıştıran iş parçacığı bloklanmaya devam eder. Aşağıdaki örnekte HTTP çağrısı reactive zincirden gelecekte tamamlanır ve fallback, stok miktarını uydurmak yerine istemcinin yeniden deneyebileceği açık bir hata üretir.
@Service
class InventoryGateway {
private final WebClient inventoryWebClient;
@CircuitBreaker(name = "inventory", fallbackMethod = "unavailable")
@TimeLimiter(name = "inventory")
@Retry(name = "inventory")
CompletableFuture<Inventory> get(String sku) {
return inventoryWebClient.get()
.uri("/items/{sku}", sku)
.retrieve()
.bodyToMono(Inventory.class)
.timeout(Duration.ofMillis(220))
.toFuture();
}
CompletableFuture<Inventory> unavailable(String sku, Throwable error) {
return CompletableFuture.failedFuture(
new InventoryUnavailableException(sku, error));
}
}Decorator sırası burada davranışı değiştirir. Retry, breaker'ın içinde konumlanırsa her deneme ayrı hata olarak breaker istatistiğine girebilir; dışında konumlanırsa bir iş isteği tek sonuç olarak değerlendirilir. Bunu varsaymak yerine testte `resilience4j.circuitbreaker.calls` metriğinde `kind=failed`, `kind=slow_successful` ve `kind=not_permitted` serilerini doğrulayın. Bir java kursu projesinde bu metrikleri görmeden yalnızca HTTP 500 saymak, breaker'ın half-open durumunda kaç deneme yaptığını gizler.
Java microservices gecikmesini JFR, k6 ve Prometheus ile önce-sonra doğrulama
Timeout değerlerinin etkisini kanıtlamak için aynı yük profiliyle iki koşu yapın. İlk koşuda HTTP havuz sınırları ve breaker kapalı, ikinci koşuda yukarıdaki ayarlar açık olsun. Java Flight Recorder kaydını uygulama sürecinden şu komutla alın ve JDK Mission Control içinde Java Monitor Blocked, Socket Read, Virtual Thread Pinned varsa ilgili olaylar ile Executor istatistiklerini karşılaştırın:
jcmd <pid> JFR.start name=latency settings=profile duration=5m filename=latency.jfr Amaç yalnızca p99'un düşmesi değildir; timeout sonrası request executor veya Netty event loop üzerinde biriken iş sayısının sınırlı kaldığını görmektir.k6 ile kontrollü bir yavaş bağımlılık senaryosu oluşturun. WireMock'ta inventory endpoint'ini 400 ms geciktirin, ardından aşağıdaki testle 100 sanal kullanıcı altında checkout çağırın. İkinci koşuda p99, hata oranı ve özellikle 220 ms üzerindeki çağrı yüzdesini kıyaslayın. Timeout eklemek p99'u hedefin altına çekebilir ama fallback yüzünden iş başarısını azaltabilir; bu yüzden hem teknik hem iş metriğini birlikte değerlendirin.
import http from 'k6/http';
import { check } from 'k6';
export const options = {
vus: 100,
duration: '2m',
thresholds: {
http_req_duration: ['p(99)<800'],
http_req_failed: ['rate<0.05']
}
};
export default function () {
const res = http.get('http://localhost:8080/checkout?sku=A-42');
check(res, { 'bounded response': r => r.status === 200 || r.status === 503 });
}Prometheus sorgusunda yalnızca ortalama süreyi kullanmayın. Örneğin `histogram_quantile(0.99, sum(rate(http_client_requests_seconds_bucket{clientName="inventory"}[5m])) by (le))` ile istemci p99'unu, `sum(rate(resilience4j_circuitbreaker_calls_total{kind="not_permitted"}[5m]))` ile açık breaker nedeniyle reddedilen trafik oranını izleyin. Hibernate ORM veya spring data jpa sorguları aynı endpoint bütçesini tüketiyorsa, JDBC metriğini de ayırın; uzak servis timeout'unu düşürmek, 350 ms süren yerel SQL sorgusunu çözmez.
Spring AI, Spring MCP ve model context protocol çağrılarında bütçe yönetimi
Spring AI ile model çağrısı yapan bir backend'de modelin ilk token süresi, tüm cevabın tamamlanma süresi ve tool çağrısı süresi farklı bütçelerdir. model context protocol kullanan bir araç sunucusuna 5 saniye tanıyıp kullanıcı HTTP isteğine 2 saniye timeout vermek, üst katmanın beklerken vazgeçmesine rağmen tool işleminin devam etmesine neden olur. Spring MCP entegrasyonunda kullanıcı isteği iptal edildiğinde tool çağrısına cancellation sinyali iletmeyi ve yazma işlemlerinde idempotency anahtarı kullanmayı tasarım gereksinimi yapın.
Örneğin bir spring ai akışında LLM'nin tool seçimi için 700 ms, model context protocol üzerinden müşteri araması için 300 ms ve yanıt derleme için 200 ms bütçe ayırın. Tool sunucusu bu 300 ms içinde yanıt veremiyorsa araç sonucu olarak yapılandırılmış `TOOL_TIMEOUT` dönün; modelin varsayım yaparak müşteri verisi üretmesine izin vermeyin. Bu kural, java fullstack eğitimi projelerinde arayüzün 504 yerine açıklanabilir bir hata durumu göstermesini sağlar ve java backend geliştirme katmanında hangi bağımlılığın bütçeyi aştığını trace üzerinde görünür kılar.
İlgili Eğitim
Sık Sorulan Sorular
Spring Boot eğitimi kapsamında WebClient timeout kaç milisaniye olmalı?
Tek bir evrensel değer yoktur. Önce endpoint için p99 hedefini belirleyin, ardından gateway, SQL ve her uzak çağrıya pay ayırın. Örneğin 800 ms endpoint hedefinde inventory için 220 ms toplam süre seçilebilir; bunu 80 ms connect timeout, 100 ms pool acquire timeout ve 180 ms response timeout olarak ayrı sınırlar halinde uygulayın. k6 ile yavaş bağımlılık senaryosunda p99 ve 503 oranını doğrulayın.
Spring Framework WebClient timeout ile Resilience4j TimeLimiter birlikte gerekli mi?
Evet, farklı kapsamları sınırlarlar. Reactor Netty responseTimeout HTTP cevap beklemesini sınırlar; WebClient zincirindeki `timeout(Duration.ofMillis(220))` decode ve filtreler dahil toplam reactive süreyi keser; Resilience4j TimeLimiter ise circuit breaker ve fallback politikasına timeout sonucunu standartlaştırır. Çift sınır koyarken dıştaki TimeLimiter değerini içteki HTTP sınırından biraz büyük tutun, aksi halde hangi katmanın hata ürettiği belirsizleşir.
Hibernate ORM ve Spring Data JPA gecikmesi circuit breaker ile çözülür mü?
Hayır. Circuit breaker uzak bağımlılık çağrılarını kesmek için tasarlanır; aynı süreçteki yavaş SQL'i düzeltmez. spring data jpa için Hibernate Statistics, datasource pool metriği ve PostgreSQL tarafında `EXPLAIN (ANALYZE, BUFFERS)` kullanın. Sorgu p95'i 180 ms ise önce indeks, fetch planı veya sorgu şekli düzeltilmeli; SQL timeout'u yalnızca hasarı sınırlayan son katmandır.
Java kursu projesinde Spring AI ve Spring MCP timeout hatası nasıl test edilir?
MCP tool sunucusunu WireMock veya Testcontainers ile geciktirin ve entegrasyon testinde 300 ms tool bütçesinin ardından `TOOL_TIMEOUT` döndüğünü doğrulayın. Test ayrıca iptal sonrası yazma yapan tool'un Idempotency-Key ile ikinci kez kayıt oluşturmamasını kontrol etmelidir. Bu, spring ai yanıtının gecikmiş tool sonucunu sonradan kullanıcı oturumuna yazması gibi yarış koşullarını yakalar.
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.



