• 7.09.2026 13:06:17
  • Admin Admin

Spring Boot uygulamalarında dış servis çağrılarını connection pool, timeout bütçesi, kontrollü retry ve metriklerle tasarlayın. spring rest api gecikmesini ölçerek kuyruk taşmasını önleyin.

Spring Boot'ta HTTP İstemcisi Timeout ve Retry Tasarımı

Spring Framework eğitimi: Timeout bütçesini çağrı zincirinden türetmek

Bir spring rest api isteğinin 800 ms SLO'su varsa, downstream istemciye 800 ms timeout vermek hatadır: controller serileştirmesi, kimlik doğrulama, kuyrukta bekleme ve hata dönüşü için pay kalmaz. Örneğin gateway 800 ms, servis A 650 ms, servis B 450 ms bütçesi kullanıyorsa A'nin B'ye yaptığı HTTP çağrısında 400 ms response timeout, 100 ms connection acquisition timeout ve en fazla bir retry için kalan bütçe hesaplanmalıdır. Bu yaklaşım, timeout anında bekleyen thread veya Reactor subscriber sayısının sınırsız büyümesini engeller.

Bu hesap, iyi bir spring framework eğitimi veya java spring eğitimi içinde sadece timeout türlerini listelemekten daha değerlidir: her katmanın deadline'ı request context ile taşınmalıdır. Spring MVC tarafında istemcinin isteği kesmesi downstream çağrıyı otomatik iptal etmez; servlet thread'i kendi timeout'una ulaşana kadar bloklanabilir. Uygulama girişinde `X-Request-Deadline` gibi güvenilir bir internal header üretin, her hop'ta kalan süreyi `deadline - now` olarak hesaplayın ve negatifse HTTP çağrısı başlatmadan 504 üretin. İstemciden gelen bu header'ı doğrudan kabul etmeyin; aksi halde kullanıcı sahte bir deadline ile kapasite planınızı bozabilir.

public Duration remainingBudget(Instant deadline, Clock clock) {
    Duration left = Duration.between(clock.instant(), deadline);
    if (left.isNegative() || left.isZero()) {
        throw new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT,
                "downstream budget exhausted");
    }
    return left.compareTo(Duration.ofMillis(50)) < 0
            ? Duration.ofMillis(50) : left;
}

Bu fonksiyonu test ederken `Clock.fixed(...)` kullanın ve 49 ms, 50 ms, 51 ms sınırlarını ayrı test edin. Sık görülen hata, kalan süreyi `int` milisaniyeye çevirmektir; uzun süreli deadline'larda taşma ve yuvarlama, beklenmedik anlık timeout üretebilir. `Duration` ile taşıyıp yalnızca HTTP istemci API'sinin sınırında dönüştürmek daha güvenlidir.

Spring Boot eğitimi için WebClient pool, DNS ve response timeout ayarları

Reactor Netty tabanlı `WebClient` için `responseTimeout`, TCP bağlantısının kurulmasını değil, response'un ilk byte'ı dahil yanıt bekleme süresini sınırlar. Bağlantı havuzu doluysa istek önce acquisition kuyruğunda bekler. Bu nedenle `pendingAcquireTimeout` ayrı ayarlanmazsa, uygulamanın SLO'sundan uzun bir bekleme gerçekleşebilir. Aşağıdaki bean, outbound `catalog` trafiği için havuz beklemesini 100 ms, TCP connect süresini 150 ms ve yanıt süresini 400 ms ile sınırlar.

@Bean
WebClient catalogWebClient(WebClient.Builder builder) {
    ConnectionProvider pool = ConnectionProvider.builder("catalog-pool")
            .maxConnections(80)
            .pendingAcquireMaxCount(160)
            .pendingAcquireTimeout(Duration.ofMillis(100))
            .maxIdleTime(Duration.ofSeconds(20))
            .maxLifeTime(Duration.ofMinutes(5))
            .evictInBackground(Duration.ofSeconds(30))
            .build();

    HttpClient httpClient = HttpClient.create(pool)
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 150)
            .responseTimeout(Duration.ofMillis(400))
            .doOnConnected(connection -> connection
                    .addHandlerLast(new ReadTimeoutHandler(450, TimeUnit.MILLISECONDS)));

    return builder.baseUrl("https://catalog.internal")
            .clientConnector(new ReactorClientHttpConnector(httpClient))
            .build();
}

`maxConnections=80` sayısı CPU çekirdeğiyle değil, downstream'in izin verdiği eşzamanlı istek, p95 servis süresi ve hedef throughput ile seçilmelidir. Little's Law ile 1.200 RPS ve 60 ms ortalama servis süresi için yaklaşık 72 eşzamanlı bağlantı gerekir: `L = lambda * W = 1200 * 0.060`. Ancak HTTP/2 kullanılıyorsa bağlantı başına stream limiti de hesaplamaya katılır; salt socket sayısını artırmak downstream kuyruklarını büyütebilir.

DNS gecikmesi ve JVM DNS cache davranışı ayrı bir risk alanıdır. Kubernetes Service adı kullanılıyorsa JDK DNS TTL değerini altyapınızın endpoint yenilenme süresiyle doğrulayın; `networkaddress.cache.ttl` değerini rastgele sonsuz yapmak pod IP değişimlerinden sonra uzun süreli hata yaratır. Ayrıca Netty pool'daki bağlantılar DNS tekrar çözülmeden yaşamaya devam eder. Bu yüzden `maxLifeTime` değerini, load balancer veya endpoint yenileme politikanızla uyumlu sınırlayın.

Microservices mimarisi içinde retry: sadece güvenli hata sınıfları

microservices mimarisi içinde retry, başarısız çağrıyı sihirli biçimde güvenli hale getirmez; geçici ağ hatasını downstream'e ek yük olarak geri gönderir. POST ile sipariş oluşturma gibi yan etkili çağrılarda bağlantı reseti, sunucunun isteği işlemediğini kanıtlamaz. Bu nedenle retry'ı yalnızca idempotent GET ve PUT istekleriyle veya sunucu tarafında kalıcı idempotency kaydı bulunan komutlarla kullanın. 429, 503 ve bağlantı hataları adaydır; 400, 401, 403, 404 ve çoğu 409 için otomatik retry yapmayın.

Retry retry = Retry.backoff(1, Duration.ofMillis(80))
        .maxBackoff(Duration.ofMillis(120))
        .jitter(0.35)
        .filter(error -> error instanceof WebClientRequestException
                || error instanceof TimeoutException)
        .onRetryExhaustedThrow((spec, signal) -> signal.failure());

Mono<CatalogItem> result = catalogWebClient.get()
        .uri("/items/{id}", itemId)
        .retrieve()
        .onStatus(status -> status.value() == 503,
                response -> Mono.error(new ServiceUnavailableException()))
        .bodyToMono(CatalogItem.class)
        .timeout(remainingBudget(deadline, clock))
        .retryWhen(retry);

Buradaki kritik ayrıntı, dıştaki `.timeout(...)` operatörünün toplam çağrı bütçesini, `Retry.backoff(...)` ise deneme aralığını yönetmesidir. Sadece deneme başına 400 ms timeout kullanıp toplam deadline koymazsanız 80 ms backoff ve scheduler gecikmesi ile isteğiniz üst katmanın bütçesini aşar. Ayrıca `Retry` nesnesini her istek için dinamik oluşturmak yerine, politika sabitse bean olarak paylaşın; ancak deadline'a göre deneme sayısı değişecekse kalan bütçeyi kontrol eden özel bir retry predicate yazın.

spring cloud LoadBalancer kullanıyorsanız, istemci retry'ı ile load balancer retry'ını aynı anda etkinleştirmeyin. İki katmanda ikişer deneme, tek kullanıcı isteğini dört downstream çağrısına çıkarır. Resilience4j kullanıyorsanız circuit breaker'ın başarısızlık penceresini de gerçek timeout süresinden küçük seçmeyin; 400 ms süren çağrıların 200 ms pencerede başarısız sayılması, sağlıklı ama yavaş bir endpoint'i gereksiz açık devreye alabilir.

Spring MVC ve Spring Security ile outbound context sınırları

spring mvc tabanlı bir controller'dan `WebClient` çağırırken, inbound `Authorization` header'ını her hedefe körlemesine taşımayın. spring security erişim token'ı, yalnızca token audience değeri hedef servisle uyuşuyorsa iletilmelidir. Farklı hedefler için client credentials token'ı almak, kullanıcı token'ını loglama ve üçüncü taraf servise sızdırma riskini azaltır. `ClientRequest` filter'ında sadece izinli internal hostlara correlation ID ve gerekli bearer token ekleyin.

@Bean
ExchangeFilterFunction outboundHeaders(TokenSupplier tokens) {
    Set<String> trustedHosts = Set.of("catalog.internal", "price.internal");
    return (request, next) -> {
        String host = request.url().getHost();
        ClientRequest.Builder copy = ClientRequest.from(request)
                .header("X-Correlation-Id", MDC.get("correlationId"));
        if (trustedHosts.contains(host)) {
            copy.headers(headers -> headers.setBearerAuth(tokens.forAudience(host)));
        }
        return next.exchange(copy.build());
    };
}

ThreadLocal tabanlı MDC, reactive zincirde otomatik taşınmaz. Reactor Context ile correlation ID geçirmek ve Micrometer Tracing kullanmak daha tutarlıdır. Özellikle `.block()` çağrısı servlet thread'inde kabul edilebilir görünse bile, aynı çağrı daha sonra reactive endpoint'e taşındığında event-loop bloklamasına dönüşebilir. spring boot kursu laboratuvarlarında bunu yakalamak için BlockHound'u test profiline ekleyin; `reactor-http-nio-*` thread'inde JDBC veya dosya I/O çalıştığında testin hata vermesini sağlayın.

Spring Boot istemci gecikmesini ölçmek: önce-sonra deney tasarımı

Bir ayar değişikliğinin etkisini görmek için önce mevcut davranışı üretime benzer bir ortamda ölçün. Gatling ile sabit 300 RPS, yüzde 2 oranında 700 ms geciken downstream ve 10 dakika çalışma süresi kullanın. İlk koşulda sınırsız varsayılan acquisition beklemesini, ikinci koşulda `maxConnections=80`, `pendingAcquireMaxCount=160` ve 100 ms acquisition timeout ayarını karşılaştırın. Sadece ortalama gecikmeyi değil, p95, p99, hata oranı ve pool bekleme sayısını aynı test verisinde raporlayın.

curl -s http://localhost:8080/actuator/metrics/reactor.netty.connection.provider.pending.connections   | jq '.measurements'

curl -s http://localhost:8080/actuator/metrics/http.client.requests   | jq '.availableTags'

Micrometer registry'de metric adı kullanılan istemci ve sürüme göre farklı tag'ler taşıyabilir; bu nedenle dashboard sorgusunu yazmadan önce `/actuator/metrics` çıktısını inceleyin. Prometheus tarafında örnek p99 sorgusu şöyledir: `histogram_quantile(0.99, sum(rate(http_client_requests_seconds_bucket[5m])) by (le, uri, status))`. URI tag'inde gerçek ürün ID'si varsa cardinality patlar; `uri` değerini `/items/{id}` şablonunda tutun. Aksi halde Prometheus belleği, uygulama gecikmesinden önce sorun olur.

Önce-sonra yorumunda beklenen sonuç, her isteğin daha hızlı olması değildir. Havuz kuyruğunu 100 ms'de kesmek, başarısız istek oranını artırabilir; buna karşılık p99'u ve servlet thread tüketimini sınırlayarak sistemi geri kazanılabilir tutar. Kararı `jvm_threads_live_threads`, HTTP 5xx oranı, pending acquisition ve downstream saturation metriğini birlikte okuyarak verin. Bu ölçüm disiplini, spring boot eğitimi içeriğinde kopyalanacak timeout değerlerinden daha taşınabilir bir pratiktir.

Sık Sorulan Sorular

spring boot eğitimi kapsamında WebClient timeout kaç milisaniye olmalı?

Sabit bir sayı seçmeyin. Önce endpoint SLO'sundan gateway, serialization ve retry payını çıkarın. Örneğin 650 ms servis bütçesinde downstream için 400 ms response timeout, 100 ms pool acquisition timeout ve en fazla bir 80 ms backoff kullanılabilir. Gatling ile p99 ve `pending.connections` metriğini ölçerek bu sınırları doğrulayın.

spring cloud LoadBalancer ile retry aynı anda kullanılmalı mı?

Genellikle hayır. LoadBalancer iki deneme, Reactor Retry iki deneme yaparsa tek logical request dört fiziksel çağrı üretebilir. Retry politikasını tek katmanda tutun, yalnızca `WebClientRequestException`, 429 veya 503 gibi geçici hata sınıflarını seçin ve POST çağrılarında kalıcı idempotency garantisi olmadan retry yapmayın.

spring security token'ı spring rest api çağrılarına nasıl taşınmalı?

Authorization header'ını global filter ile tüm hostlara eklemeyin. Hedef host allowlist'i ve audience kontrolü uygulayın; internal servisler için gerekli kullanıcı token'ını, üçüncü taraflar için ayrı client credentials token'ını kullanın. Filter testinde allowlist dışındaki bir URL'ye Authorization header'ının eklenmediğini `MockWebServer` ile doğrulayın.

java spring eğitimi için WebClient pool metriklerinde hangi değerler izlenmeli?

Pool aktif bağlantı, idle bağlantı, pending acquisition, acquisition timeout sayısı, `http.client.requests` p95-p99 ve downstream 429-503 oranını birlikte izleyin. Yalnız p50 düşerken pending acquisition yükseliyorsa pool kapasitesi değil, downstream servis süresi veya concurrency limiti darboğaz olabilir.

AI / LLM Discovery

Bu makale Opendart Akademi Spring Framework 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.

Opendart Akademi llms.txt