• 28.08.2026 09:01:44
  • Admin Admin

Spring Boot'ta dış HTTP çağrılarında havuz kuyruğunu, timeout hiyerarşisini ve ölçümü ele alır. Spring boot eğitimi kapsamında RestClient, Reactor Netty, Micrometer ve JFR ile p99 gecikme teşhisini uygulanabilir biçimde kurar.

Spring Boot'ta HTTP İstemci Havuzu ile Tail Latency Kontrolü

Spring Framework RestClient ile havuz bekleme süresini sınırlamak

Java backend geliştirme servislerinde dış bağımlılık yavaşladığında ilk belirti çoğu zaman uzak servisin yanıt süresi değil, HTTP bağlantı havuzunda bekleyen istek sayısıdır. Bir istek boş bağlantı bulamazsa TCP connect aşamasına bile geçmeden kuyrukta kalır. Bu nedenle connection-request timeout, connect timeout ve socket timeout tek bir değer olarak ayarlanmamalıdır: havuz kiralama süresi örneğin 150 ms, TCP bağlantısı 1 saniye, veri okunması 2 saniye olabilir. Havuz bekleme sınırı kısa tutulmazsa 30 saniyelik uygulama deadline'ı boyunca thread'ler ve istek context'leri birikir.

@Bean
RestClient inventoryClient(RestClient.Builder builder) {
    var connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
        .setMaxConnTotal(200)
        .setMaxConnPerRoute(40)
        .setDefaultConnectionConfig(ConnectionConfig.custom()
            .setConnectTimeout(Timeout.ofSeconds(1))
            .setSocketTimeout(Timeout.ofSeconds(2))
            .build())
        .build();

    var requestConfig = RequestConfig.custom()
        .setConnectionRequestTimeout(Timeout.ofMillis(150))
        .build();

    var httpClient = HttpClients.custom()
        .setConnectionManager(connectionManager)
        .setDefaultRequestConfig(requestConfig)
        .evictExpiredConnections()
        .build();

    var factory = new HttpComponentsClientHttpRequestFactory(httpClient);
    return builder.requestFactory(factory).build();
}

Bu örnekte Apache HttpClient 5 havuzu, Spring Framework RestClient'e açıkça bağlanır. setMaxConnPerRoute(40) tek bir inventory host'unun 200 toplam bağlantıyı tüketmesini engeller. Sık yapılan hata yalnızca maxConnTotal ayarlamaktır; aynı route'a giden yoğun trafik altında per-route varsayılanı darboğaz olur. Ayrıca socket timeout sürekli akan SSE veya büyük dosya indirme endpoint'lerine körlemesine uygulanmamalıdır; bu timeout okunacak veri gelmeyen süreyi sınırlar ve uzun sessizlik taşıyan stream'i kesebilir. Stream endpoint'i için ayrı bir RestClient ve ayrı havuz oluşturmak daha güvenlidir.

Java microservices için Reactor Netty havuz izolasyonu

Reaktif kod kullanan java microservices uygulamalarında WebClient'in varsayılan ConnectionProvider'ını tüm downstream'ler için paylaşmak, noisy-neighbor etkisi üretir: yavaşlayan ödeme servisi pending acquire kuyruğunu doldurur ve katalog çağrıları da bağlantı alamaz. Her kritik downstream için isimli bir ConnectionProvider oluşturun. Bu, sadece bağlantı sayısını değil, kuyruk kapasitesini ve kuyrukta kalınabilecek süreyi de bağımsızlaştırır.

@Bean
WebClient inventoryWebClient() {
    ConnectionProvider provider = ConnectionProvider.builder("inventory-pool")
        .maxConnections(40)
        .pendingAcquireMaxCount(80)
        .pendingAcquireTimeout(Duration.ofMillis(150))
        .maxIdleTime(Duration.ofSeconds(20))
        .maxLifeTime(Duration.ofMinutes(5))
        .evictInBackground(Duration.ofSeconds(30))
        .build();

    HttpClient client = HttpClient.create(provider)
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1_000)
        .responseTimeout(Duration.ofSeconds(2))
        .metrics(true, name -> "outbound.inventory");

    return WebClient.builder()
        .baseUrl("https://inventory.internal")
        .clientConnector(new ReactorClientHttpConnector(client))
        .build();
}

pendingAcquireMaxCount(80), sınırsız kuyruk yerine kontrollü reddetme yapar. 40 aktif bağlantı ve 80 bekleyen istek dolduğunda yeni istek hata alır; bu hata caller tarafında fallback veya 503'e çevrilebilir. Sınırsız kuyrukta ise hata daha geç görünür, fakat heap'te request state, trace context ve caller coroutine'leri taşınmaya devam eder. maxLifeTime özellikle load balancer'ın bağlantı yaş sınırından biraz kısa seçilmelidir; aksi halde load balancer'ın kapattığı idle TCP bağlantısı ilk yeniden kullanımda reset üretebilir.

Tail latency ölçümü: Micrometer, k6 ve JFR ile önce-sonra karşılaştırması

Timeout ve havuz değeri seçmek için önce sentetik yük altında bir baseline alın. k6 senaryosunda downstream'in gecikmesini Toxiproxy ile sabit biçimde artırın; sadece normal durumdaki RPS ölçümü havuz taşmasını göstermez. Aynı commit, aynı pod limiti ve aynı downstream gecikmesiyle önce varsayılan istemciyi, sonra sınırlandırılmış havuzu çalıştırın. Karşılaştırılacak değerler p50 değil p95, p99, başarısız istek oranı, pending acquire sayısı ve uygulamanın heap kullanım eğrisidir.

toxiproxy-cli create inventory -l 0.0.0.0:8666 -u inventory.internal:443
toxiproxy-cli toxic add inventory -t latency -a latency=300 -a jitter=100
k6 run --vus 120 --duration 3m load.js
jcmd $PID JFR.start name=http-pool settings=profile duration=180s filename=http-pool.jfr

JFR kaydında jdk.SocketRead, jdk.SocketWrite, jdk.ThreadPark ve jdk.JavaMonitorEnter olaylarını inceleyin. JFR, HTTP havuzundaki lease beklemesini doğrudan semantik bir olay olarak göstermeyebilir; bu nedenle Reactor Netty metriğini veya kendi Micrometer timer'ınızı aynı zaman aralığında korele edin. Örneğin outbound.inventory pending acquire artarken SocketRead süreleri sabitse sorun uzak servisin işlem süresi değil yerel havuz kapasitesi veya kuyruk politikasıdır.

@Bean
MeterFilter outboundSloHistogram() {
    return new MeterFilter() {
        @Override
        public DistributionStatisticConfig configure(Meter.Id id,
                DistributionStatisticConfig config) {
            if (id.getName().equals("outbound.inventory")) {
                return DistributionStatisticConfig.builder()
                    .serviceLevelObjectives(0.05, 0.15, 0.5, 1.0)
                    .percentilesHistogram(true)
                    .build()
                    .merge(config);
            }
            return config;
        }
    };
}

Sonuçları 'p99 2 saniyenin altında, pending acquire 150 ms üstünde değil, hata oranı yüzde 1'in altında' gibi yayın öncesi kabul kriterleriyle kaydedin. Bir ayarın doğru olduğuna ancak aynı yükte önce-sonra grafiğiyle karar verin. Özellikle p99 düşerken hata oranı yükseliyorsa, timeout yalnızca gecikmeyi kullanıcıya daha erken hata olarak taşımış olabilir; bu durumda fallback davranışı ve çağıran endpoint'in hata bütçesi ayrıca değerlendirilmelidir.

Hibernate ORM ve Spring Data JPA transaction sınırında uzak çağrı hatası

Hibernate ORM kullanan bir endpoint içinde dış HTTP çağrısı yapıp transaction'ı açık tutmak iki havuzu birbirine bağlar: istek HTTP bağlantısı beklerken HikariCP JDBC bağlantısını da işgal eder. Yük altında küçük bir HTTP kuyruğu, hızla veritabanı bağlantı kıtlığına dönüşür. Spring Data JPA ile yazma akışını iki aşamaya ayırın: uzak veriyi transaction dışında alın, kısa transaction içinde sürüm kontrollü olarak kaydedin.

@Service
class ReservationService {
    private final InventoryClient inventoryClient;
    private final ReservationWriter writer;

    void reserve(UUID reservationId) {
        InventoryQuote quote = inventoryClient.quote(reservationId); // JDBC yok
        writer.applyQuote(reservationId, quote);
    }
}

@Service
class ReservationWriter {
    private final ReservationRepository repository;

    @Transactional
    void applyQuote(UUID id, InventoryQuote quote) {
        Reservation reservation = repository.findById(id).orElseThrow();
        reservation.apply(quote); // Entity'de @Version alanı bulunur
    }
}

ReservationWriter'ın ayrı Spring bean olması kasıtlıdır. Aynı sınıftaki bir metoda doğrudan çağrı yapıldığında Spring proxy devreye girmez ve @Transactional beklenen transaction'ı açmaz. Uzak çağrı ile yazma arasında veri değişebileceği için @Version ile optimistic locking kullanın ve OptimisticLockException için kontrollü yeniden deneme uygulayın. HikariCP'nin hikaricp.connections.active ve hikaricp.connections.pending metriklerini outbound havuz metrikleriyle birlikte izleyin; ikisinin aynı anda yükselmesi transaction içinde bloklayan I/O olduğuna dair güçlü bir işarettir.

Spring AI, Spring MCP ve model context protocol çağrılarında ayrı bütçe

Spring AI ile model sağlayıcısına, Spring MCP ile model context protocol araçlarına yapılan çağrılar klasik REST bağımlılıklarından daha değişken süreli olabilir. Bir aracın yanıtı modelin tool-call turunu beklerken gecikirse aynı genel WebClient havuzunu kullanan kullanıcı trafiğini tüketmemelidir. AI sağlayıcısı, MCP araç sunucusu ve iş kritik inventory gibi hedeflere ayrı ConnectionProvider, ayrı pending acquire limiti ve ayrı Micrometer meter adı verin.

Bir spring ai veya spring mcp entegrasyonunda araç çağrısı için 5 saniye beklemek kabul edilebilirken sipariş fiyatı için 150 ms havuz beklemesi kabul edilemez. Bu farkı kodda ayrı client bean'leriyle ifade edin ve Actuator'da `/actuator/metrics/outbound.inventory` ile `/actuator/metrics/outbound.mcp` değerlerini bağımsız sorgulayın. MCP aracının URL'si model çıktısından türetiliyorsa HTTP client katmanında allowlist uygulayın; bağlantı havuzu sınırı SSRF'i engellemez, yalnızca kaynak tüketimini sınırlar.

Bu konu bir java eğitimi veya java programlama eğitimi içinde ele alınırken yalnızca HTTP API çağrısı yazmak yeterli değildir; havuz kuyruğu, transaction süresi ve yük testi birlikte incelenmelidir. Bir java kursu, spring boot eğitimi ya da java fullstack eğitimi projesinde en iyi pratik, her downstream için sahiplik tablosu tutmaktır: host, maksimum bağlantı, acquire timeout, uçtan uca deadline, fallback ve dashboard metriği. Böylece spring framework katmanındaki client seçimi üretim kapasite varsayımlarına bağlanır.

Sık Sorulan Sorular

Spring Boot eğitiminde RestClient connection-request timeout kaç olmalı?

Sabit evrensel bir sayı yoktur. Önce caller endpoint için kalan deadline'ı belirleyin, ardından connection-request timeout'u bunun küçük bir parçası yapın. Örneğin endpoint'in 800 ms bütçesi varsa 150 ms acquire, 1000 ms connect gibi bir ayar mantıksızdır; connect timeout da kalan bütçeyi aşmamalıdır. k6 ve Toxiproxy ile p99, pending acquire ve hata oranını aynı senaryoda ölçerek değeri doğrulayın.

Java microservices uygulamasında WebClient için tek bağlantı havuzu kullanılabilir mi?

Tek havuz ancak tüm downstream'lerin gecikme, hata bütçesi ve trafik profili benzerse düşünülebilir. Ödeme, katalog ve AI araç çağrılarının farklı davranması yaygındır. ConnectionProvider.builder ile downstream başına maxConnections, pendingAcquireMaxCount ve pendingAcquireTimeout ayarlayın; Reactor Netty metriklerini isimli prefix'lerle yayımlayıp kuyruk artışını servis bazında inceleyin.

Hibernate ORM transaction içinde HTTP çağrısı neden JDBC havuzunu tüketir?

Transaction bir entity yüklediğinde veya flush gerektirdiğinde JDBC connection HikariCP'den alınabilir ve transaction bitene kadar elde kalabilir. Bu sırada HTTP istemcisi havuzunda beklemek, aynı request'in hem JDBC hem HTTP kapasitesini tutmasına neden olur. Uzak çağrıyı transaction dışına çıkarın, sonucu ayrı bean üzerindeki kısa @Transactional metoda aktarın ve @Version ile yarış durumunu yakalayın.

Spring AI ve Spring MCP için HTTP timeout ayarları nasıl izlenmeli?

AI sağlayıcısı ve model context protocol araçları için ayrı WebClient veya RestClient bean'i oluşturun. Her bean'e ayrı havuz adı ve Micrometer metric adı verin. `/actuator/metrics` üzerinden p95, p99, hata sayısı ve pending acquire değerlerini ayrı değerlendirin; inventory havuzu sağlıklıyken yalnızca outbound.mcp kuyruğu artıyorsa genel bağlantı sayısını artırmak yerine MCP bütçesini ve araç davranışını inceleyin.

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.

Opendart Akademi llms.txt