• 24.09.2026 13:03:35
  • Admin Admin

Spring Framework uygulamalarında WebClient bağlantı havuzu, timeout bütçesi ve kuyruk metriklerini ölçerek p99 gecikmesini kontrol edin. Bu yaklaşım, java backend geliştirme servislerinde gizli istemci darboğazlarını görünür kılar.

Spring Framework'te HTTP İstemci Kuyruklanmasını Ölçme ve Sınırlandırma

Spring Framework'te görünmeyen HTTP istemci kuyruğunu teşhis etme

Bir downstream servis yavaşladığında ilk hata çoğu zaman CPU veya uygulama thread sayısı değildir. Reactor Netty, aynı hedef için izin verilen aktif bağlantı sayısına ulaştığında yeni istekleri pending-acquire kuyruğuna koyar. Kuyrukta bekleyen süre, uygulama metriklerinde yalnızca toplam HTTP süresi olarak görünürse yanlışlıkla uzak servisin gecikmesi sanılır. Spring Boot Actuator ve Micrometer ile önce havuz metriklerini Prometheus'a açın:

management:
  endpoints:
    web:
      exposure:
        include: health,prometheus
  metrics:
    distribution:
      percentiles-histogram:
        http.client.requests: true
Ardından /actuator/prometheus çıktısında Reactor Netty sürümünüze göre yayımlanan reactor_netty_connection_provider_* metriklerini bulun. active_connections bağlantı üst sınırına yaklaşırken pending_connections yükseliyor ve http.client.requests p99 değeri artıyorsa darboğaz uygulama içi bağlantı edinimidir.

Başlangıç ölçümünü üretime benzer yükte k6 ile alın; sadece ortalama gecikmeyi değil p50, p95, p99 ve hata oranını aynı raporda kaydedin. Örneğin 200 sanal kullanıcıyla 10 dakika sabit yük çalıştırmak için

k6 run --vus 200 --duration 10m http-client-load.js
komutunu kullanın. http-client-load.js içinde hedef endpoint için her isteğin latency değerini Trend metriğine yazın. Testten önce DNS, TLS ve hedef servis kapasitesi sabit kalmalıdır; aksi halde bağlantı havuzu değişikliğinin etkisini izole edemezsiniz. Bu ölçüm disiplini, java eğitimi veya java programlama eğitimi kapsamında öğrenilen thread pool sezgisinin reaktif HTTP istemcisinde neden tek başına yeterli olmadığını pratikte gösterir.

WebClient havuzunu profil verisiyle boyutlandırma

Havuz boyutunu CPU çekirdek sayısına göre seçmek doğru bir model değildir; bu değer eşzamanlı downstream gecikmesi ve hedefin kabul edebildiği bağlantı sayısıyla ilgilidir. İlk deneyde varsayılan sağlayıcı yerine isimli ve metrikli bir ConnectionProvider kurun. pendingAcquireMaxCount için sonlu bir sınır koymak önemlidir: sınırsız kuyruk, kısa süreli bir downstream yavaşlamasını heap üzerinde birikmiş request context nesnelerine dönüştürür.

ConnectionProvider provider = ConnectionProvider.builder("catalog-pool")
    .maxConnections(80)
    .pendingAcquireMaxCount(160)
    .pendingAcquireTimeout(Duration.ofMillis(250))
    .maxIdleTime(Duration.ofSeconds(20))
    .metrics(true)
    .build();

HttpClient httpClient = HttpClient.create(provider)
    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 200)
    .responseTimeout(Duration.ofMillis(700));

WebClient catalogClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(httpClient))
    .baseUrl("https://catalog.internal")
    .build();
Bu sınırlar aşıldığında istek beklemek yerine hata alır; çağıran katman bunu kontrollü 503 veya fallback olarak ele almalıdır.

Değişiklikten önce ve sonra aynı k6 senaryosunu çalıştırın. Karşılaştırma tablosunda en az şu dört değeri tutun: http.client.requests p99, pending bağlantı tepe değeri, downstream'e giden istek sayısı ve 5xx oranı. maxConnections değerini 80'den 160'a çıkarmak p99'u düşürürken hedef veritabanındaki connection sayısını veya hedefin p99'unu yükseltiyorsa, sorunu sadece başka servise taşımış olursunuz. CPU etkisini ayırmak için Java Flight Recorder ile 60 saniyelik kayıt alın:

jcmd <pid> JFR.start name=http-pool settings=profile duration=60s filename=http-pool.jfr
JDK Mission Control'da Socket Read, Java Monitor Blocked ve allocation flame graph görünümlerini inceleyin. Event loop thread'lerinde bloklayan kod varsa havuz boyutunu artırmak kuyruk sorununu çözmez.

Java backend geliştirme için uçtan uca timeout bütçesi

Tek bir response timeout, istemci gecikme bütçesinin tamamını temsil etmez. DNS çözümleme, TCP connect, TLS handshake, havuzdan bağlantı bekleme, ilk byte ve gövde okuma ayrı aşamalardır. Örneğin çağıran API'nin p99 hedefi 900 ms ise 250 ms havuz bekleme, 200 ms connect ve 700 ms response timeout toplamda 1150 ms'lik bir bekleme yolu açar. Bunun yerine toplam bütçeyi request context'e taşıyın ve her downstream çağrıda kalan süreyi hesaplayın. Spring MVC tarafında bu bilgiyi ServerWebExchange attribute'una, WebFlux dışındaki servislerde ise açık bir Deadline nesnesine koymak ThreadLocal kullanımından daha güvenlidir.

Aşağıdaki filtre, kalan süre 0'a düştüğünde ağ çağrısı başlatmaz ve her WebClient isteğine yalnızca kalan bütçe kadar timeout uygular. Bu yaklaşım, request timeout iptal edilmiş olsa bile bağımsız çalışan istemci çağrılarının bağlantı havuzunu tüketmesini önler.

record Deadline(long expiresAtNanos) {
  Duration remaining() {
    return Duration.ofNanos(Math.max(0, expiresAtNanos - System.nanoTime()));
  }
}

Mono<String> fetch(WebClient client, Deadline deadline) {
  Duration left = deadline.remaining();
  if (left.isZero()) {
    return Mono.error(new TimeoutException("request budget exhausted"));
  }
  return client.get().uri("/v1/items")
      .retrieve()
      .bodyToMono(String.class)
      .timeout(left);
}
Kritik incelik şudur: Reactor timeout aboneliği iptal eder, fakat özel bir SDK veya blocking adapter kullanıyorsanız socket'in gerçekten kapandığını ayrıca doğrulamalısınız. Netty wire logları veya tcpdump ile FIN/RST akışını kontrol etmek, sızan bağlantıları erken yakalar.

Java microservices ortamında DNS, keep-alive ve HTTP/2 tuzakları

Kubernetes veya dinamik servis keşfi bulunan java microservices sistemlerinde uzun yaşayan keep-alive bağlantıları, endpoint listesi değişse bile eski pod'a trafik gönderebilir. maxLifeTime değerini yük dengeleyicinin connection draining süresinden kısa, ancak TLS handshake maliyetini gereksiz artırmayacak kadar uzun seçin. Örneğin load balancer 60 saniye sonra eski bağlantıları kapatıyorsa 45 saniyelik yaşam süresi deneyin:

ConnectionProvider provider = ConnectionProvider.builder("payments-pool")
    .maxConnections(100)
    .maxIdleTime(Duration.ofSeconds(15))
    .maxLifeTime(Duration.ofSeconds(45))
    .evictInBackground(Duration.ofSeconds(30))
    .metrics(true)
    .build();
Değişiklikten sonra handshake sayısını, bağlantı reset hatalarını ve hedef pod dağılımını birlikte ölçün. Yalnızca reset sayısını azaltmak için sınırsız connection lifetime seçmek, rollout sırasında eski replica'lara yapışan trafik üretir.

HTTP/2 kullanılıyorsa maxConnections doğrudan eşzamanlı istek sınırı değildir; tek TCP bağlantısı üzerinde birden çok stream açılabilir ve uzak sunucu SETTINGS_MAX_CONCURRENT_STREAMS ile sınır koyabilir. Bu nedenle pool active_connections düşükken istek gecikmesi yüksek olabilir. Netty DEBUG loglarını kısa süreli açıp SETTINGS frame'lerini gözlemleyin veya Envoy access loglarındaki upstream_cx_active ve upstream_rq_pending değerlerini korele edin. DNS için JVM'nin ağ adresi önbellek ayarını da denetleyin:

java -XshowSettings:properties -version 2>&1 | grep networkaddress.cache
Güvenlik politikasıyla ayarlanmış uzun TTL, servis keşfi güncellense bile istemcinin eski IP'yi kullanmasına neden olabilir.

Spring AI, Spring MCP ve model context protocol çağrılarına kota uygulama

Spring AI kullanan bir endpoint, tek kullanıcı isteğinde LLM sağlayıcısına, vektör aramasına ve birden fazla araç sunucusuna çağrı yapabilir. Spring MCP entegrasyonunda model context protocol araç çağrıları için genel WebClient havuzunu iş kritik ödeme veya sipariş çağrılarıyla paylaşmayın. Ayrı havuz, ayrı timeout ve eşzamanlılık sınırı tanımlayın; aksi halde uzun süren araç sonucu, aynı JVM'deki klasik REST trafiğinin pending-acquire kuyruğunu büyütür. Resilience4j Bulkhead ile araç sınıfı başına sert limit koymak için

BulkheadConfig config = BulkheadConfig.custom()
    .maxConcurrentCalls(12)
    .maxWaitDuration(Duration.ZERO)
    .build();
Bulkhead mcpBulkhead = Bulkhead.of("mcp-tools", config);

Mono<ToolResult> guarded = Mono.defer(() -> toolClient.execute(request))
    .transformDeferred(BulkheadOperator.of(mcpBulkhead));
maxWaitDuration değerinin sıfır olması kasıtlıdır: kuyrukta bekleyen agent adımları kullanıcı isteğinin kalan zaman bütçesini tüketmek yerine hızlıca reddedilir.

Bu ayrım java fullstack eğitimi veya bir java kursu materyalinde yalnızca backend konusu gibi görünmeyebilir; ancak kullanıcı arayüzünde iptal edilen bir sohbet isteğinin sunucuda hangi çağrıları sürdürdüğünü göstermek gerekir. Tarayıcı AbortController ile isteği iptal edip sunucuda cancellation logunu, MCP araç çağrısı sayısını ve bağlantı havuzu pending metriğini aynı traceId altında inceleyin. spring boot eğitimi bağlamında uygulanabilir hedef şudur: kullanıcı iptalinden sonra 1 saniye içinde aktif araç çağrısı sayısının taban seviyeye dönmesi. Bu test, spring data jpa veya hibernate orm kullanan araçların uzun veritabanı sorgularını ayrıca query timeout ile kesmesini de zorunlu kılar.

Sık Sorulan Sorular

Spring Framework WebClient connection pool boyutu nasıl belirlenir?

Önce sabit yük altında reactor_netty_connection_provider_pending_connections, active_connections ve http.client.requests p99 metriklerini alın. maxConnections değerini kademeli değiştirin ve her adımda hedef servisin p99'u ile hata oranını da ölçün. pending değeri artıyorsa sınır yetersiz olabilir; hedefin gecikmesi artıyorsa daha fazla bağlantı yerine istek eşzamanlılığını düşürün.

java microservices servislerinde WebClient timeout neden responseTimeout ile bitmez?

responseTimeout, bağlantı havuzu beklemesi, DNS, TCP connect ve TLS handshake için toplam üst sınır değildir. ConnectionProvider.pendingAcquireTimeout, ChannelOption.CONNECT_TIMEOUT_MILLIS ve uygulama seviyesinde kalan süre timeout'unu birlikte tanımlayın. Bu üç değerin toplamı çağıran endpoint'in gecikme bütçesini aşmamalıdır.

spring ai ve spring mcp araç çağrıları için ayrı HTTP havuzu gerekli mi?

Araç çağrıları uzun sürebiliyor veya burst üretiyorsa gereklidir. Model context protocol trafiği için ayrı ConnectionProvider, düşük pendingAcquireMaxCount ve Resilience4j Bulkhead kullanın. Prometheus'ta bu havuzun pending bağlantılarını iş kritik REST havuzundan ayrı etiketlerle izleyin.

spring data jpa ve hibernate orm kullanan bir araç çağrısında iptal nasıl yayılır?

WebClient veya Reactor iptali tek başına JDBC sorgusunu her sürücüde anında durdurmaz. Transaction timeout ve sorgu timeout'unu birlikte ayarlayın; örneğin @Transactional(timeout = 2) ile uygun JPA query timeout hint'i kullanın. Üretim sürücünüzde iptalin gerçekten database cancel paketine dönüştüğünü pg_stat_activity, MySQL processlist veya sürücü loglarıyla doğrulayı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.

Opendart Akademi llms.txt