Spring Boot uygulamalarında RestClient, Apache HttpClient havuzu, timeout bütçesi, retry politikası ve ölçümleme ile bağımlı servis çağrılarını nasıl kontrollü hale getireceğinizi inceler.
Spring Boot'ta RestClient ile HTTP İstemci Dayanıklılığı ve Havuzlama
Spring Boot eğitimi için kritik konu: HTTP çağrısına süre bütçesi koymak
Bir spring rest api isteğinin toplam gecikme hedefi 800 ms ise downstream çağrısına sınırsız timeout vermek doğru değildir. Örneğin controller katmanının serileştirme ve iş mantığı için 150 ms, ağ ve downstream için 500 ms, hata yanıtı üretimi için 150 ms ayırın. Bağlantı havuzundan bağlantı bekleme, TCP connect ve response timeout değerleri ayrı sorunları temsil eder; yalnızca response timeout ayarlamak, doymuş havuzda bekleyen thread'leri korumaz.
@Bean
RestClient catalogRestClient(RestClient.Builder builder) {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(Timeout.ofMilliseconds(80))
.setConnectTimeout(Timeout.ofMilliseconds(120))
.setResponseTimeout(Timeout.ofMilliseconds(450))
.build();
PoolingHttpClientConnectionManager pool =
PoolingHttpClientConnectionManagerBuilder.create()
.setMaxConnTotal(200)
.setMaxConnPerRoute(50)
.build();
CloseableHttpClient client = HttpClients.custom()
.setConnectionManager(pool)
.setDefaultRequestConfig(requestConfig)
.evictExpiredConnections()
.build();
return builder
.requestFactory(new HttpComponentsClientHttpRequestFactory(client))
.baseUrl("http://catalog-service")
.build();
}Apache HttpClient 5 metriklerini Micrometer ile izlemek için pool nesnesinden periyodik olarak pool.getTotalStats().getLeased(), getPending() ve getAvailable() değerlerini gauge olarak yayınlayın. Yük altında pending > 0 iken p99 artıyorsa, önce route başına 50 bağlantının downstream kapasitesiyle uyumunu doğrulayın. Toplam bağlantıyı körlemesine yükseltmek, sınırlı worker veya veritabanı havuzu olan bağımlı serviste kuyruk gecikmesini büyütebilir.
Bu ayrım, bir spring boot kursu laboratuvarında kolayca doğrulanabilir: k6 ile 100 sanal kullanıcı çalıştırın, downstream'e 700 ms gecikme ekleyin ve önce yalnızca 2 saniyelik socket timeout ile ölçün. Ardından yukarıdaki 450 ms response timeout ve 80 ms connection-request timeout ile aynı testi tekrarlayın. Karşılaştırmada p95, başarısız istek oranı, Tomcat aktif thread sayısı ve pool pending değerlerini aynı zaman aralığında kaydedin; amaç daha az hata görmek değil, çağrının 800 ms üst sınırını ihlal etmeden kontrollü başarısız olmasıdır.
Spring Cloud LoadBalancer ile retry fırtınasını önlemek
spring cloud LoadBalancer, servis keşfinden bir instance seçer; fakat retry aktif olduğunda her tekrarın başka bir instance'a gitmesi hata yayılımını büyütebilir. İdempotent olmayan POST çağrılarında ağ bağlantısı response gelmeden koparsa, sunucunun işlemi tamamlayıp tamamlamadığını istemci bilemez. Bu nedenle otomatik tekrar yalnızca GET, HEAD veya açık idempotency anahtarı olan operasyonlar için uygulanmalıdır.
@Configuration
class CatalogRetryConfig {
@Bean
RetryTemplate catalogRetryTemplate() {
var retry = new SimpleRetryPolicy(2, Map.of(
ResourceAccessException.class, true,
HttpServerErrorException.class, true
));
var backoff = new ExponentialBackOffPolicy();
backoff.setInitialInterval(40);
backoff.setMultiplier(2.0);
backoff.setMaxInterval(120);
var template = new RetryTemplate();
template.setRetryPolicy(retry);
template.setBackOffPolicy(backoff);
return template;
}
}Bu örnekte en fazla iki deneme vardır ve en kötü durumda ek bekleme 40 + 80 ms olur. Ancak 450 ms response timeout ile bu değerler toplam 1.470 ms'ye çıkabilir. Üst seviye istek bütçesi 800 ms ise retry çağrısından önce kalan süreyi hesaplayın ve kalan süre 450 ms'den küçükse yeniden denemeyin. Java tarafında bunu System.nanoTime() ile monotonic süre olarak ölçün; Instant.now() NTP saat düzeltmelerinden etkilenebilir.
Retry oranını http.client.requests sayacından türetilmiş bir etiketle değil, ayrı bir catalog.retry.attempts sayacıyla ölçün. Endpoint path veya müşteri kimliği gibi sınırsız cardinality üreten etiketleri eklemeyin. Operasyonel eşik olarak, 5 dakikalık pencerede retry/ilk-deneme oranı yüzde 2'yi aşıyorsa downstream latency histogramı ve instance bazlı 5xx oranı birlikte incelenmelidir.
Spring Security ile downstream token aktarımında thread güvenliği
Bir spring security resource server'ı kullanıcı JWT'sini kabul ediyor diye token'ı her downstream servise aynen geçirmek güvenli bir varsayım değildir. Token'ın aud claim'i catalog yerine gateway için düzenlenmiş olabilir; catalog bu token'ı kabul etse bile yetki sınırı gereğinden geniş kalabilir. Kullanıcı bağlamı gerekiyorsa OAuth2 token exchange veya hedef servis için audience daraltılmış client credentials token kullanın.
@Bean
RestClient authenticatedRestClient(RestClient.Builder builder,
OAuth2AuthorizedClientManager manager) {
return builder.requestInterceptor((request, body, execution) -> {
var authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId("catalog-client")
.principal("service-client")
.build();
var client = manager.authorize(authorizeRequest);
if (client == null) {
throw new IllegalStateException("Catalog token alınamadı");
}
request.getHeaders().setBearerAuth(client.getAccessToken().getTokenValue());
return execution.execute(request, body);
}).build();
}Servlet tabanlı spring mvc uygulamasında request interceptor aynı istek thread'inde çalışır. Fakat çağrıyı @Async, executor veya scheduler'a taşırsanız SecurityContextHolder otomatik taşınmaz. Kullanıcı bağlamı gerçekten gerekliyse executor'ı DelegatingSecurityContextTaskExecutor ile sarın; service-to-service token için ise yukarıdaki gibi request context'ten bağımsız bir principal kullanmak daha denetlenebilirdir.
Token alma maliyetini görmek için Spring Boot Actuator üzerinden /actuator/metrics/http.client.requests ve OAuth istemcisinin HTTP metriklerini ayrı izleyin. Çok kısa access token ömrü veya cache dışı kalan authorized client yapılandırması, her business isteğinde token endpoint çağrısı oluşturur. Bu durumda bağlantı havuzunda catalog route'u boş görünürken identity provider route'unda pending bağlantılar oluşabilir.
Java Spring eğitimi pratiği: latency profilini önce ve sonra karşılaştırmak
HTTP istemci ayarlarını değiştirmeden önce JDK Flight Recorder ile 10 dakikalık üretime yakın yük kaydı alın: jcmd <pid> JFR.start name=http-profile settings=profile duration=10m filename=http-profile.jfr. JDK Mission Control içinde socket read, thread park ve executor queue olaylarını inceleyin. Pool doygunluğunda request thread'leri genellikle bağlantı beklerken park olur; CPU profiler'ı tek başına bu beklemeyi açıklamaz.
import http from 'k6/http';
import { check } from 'k6';
export const options = {
scenarios: {
steady: { executor: 'constant-vus', vus: 80, duration: '5m' }
},
thresholds: {
http_req_duration: ['p(95)<800'],
http_req_failed: ['rate<0.01']
}
};
export default function () {
const r = http.get('http://localhost:8080/products/42');
check(r, { '200 veya kontrollü 504': x => x.status === 200 || x.status === 504 });
}Önce-sonra tablosunda en az şu beş değeri aynı k6 senaryosunda karşılaştırın: başarılı 200 oranı, kontrollü 504 oranı, p50/p95/p99, Apache pool pending maksimumu ve JVM thread sayısı. Downstream gecikmesini WireMock withFixedDelay(700) ile sabitlemek, dış ortam dalgalanmasını azaltır. Değişiklik sonrası p99 düşerken 504 artıyorsa sonucu başarısız saymayın; toplam istek süresi hedefe yaklaşıyor ve thread birikimi kayboluyorsa bu, timeout bütçesinin gerçekten devreye girdiğini gösterir.
Bu yaklaşım, java spring eğitimi içeriklerinde sık atlanan bir ayrıntıyı ortaya çıkarır: client timeout değerleri sadece istemci davranışı değildir, upstream thread kapasitesini de belirler. Örneğin Tomcat'in 200 request thread'i 2 saniye boyunca downstream beklerse teorik olarak saniyede en fazla yaklaşık 100 eşzamanlı bloke çağrı boşaltılabilir; 450 ms sınırı aynı kapasite altında kuyrukta bekleyen iş miktarını belirgin biçimde sınırlar.
Microservices mimarisi için idempotency ve hata sözleşmesi
microservices mimarisi içinde sipariş oluşturma gibi POST operasyonlarında istemci retry desteği gerekiyorsa, Idempotency-Key değerini sunucuda benzersiz indeksle saklayın. Anahtarın yalnızca request header'da bulunması yeterli değildir; aynı anahtar farklı payload ile gelirse ilk isteğin hash'i ile ikinci isteğin hash'ini karşılaştırıp 409 döndürmelisiniz. Aksi halde istemci hatası sessizce yanlış kayda bağlanır.
create table idempotency_record (
key varchar(128) primary key,
request_hash char(64) not null,
response_code integer not null,
response_body text not null,
created_at timestamp not null
);
create unique index ux_order_external_ref
on orders(external_reference);İşlemi tek veritabanı transaction'ında yürütün: önce anahtarı insert etmeyi deneyin, unique violation oluşursa kayıtlı request_hash değerini doğrulayın ve saklanan response'u dönün. Transaction rollback olursa idempotency kaydının da rollback olması gerekir; aksi halde istemci sonraki denemede hiç oluşmamış siparişin 201 yanıtını alabilir. Bu desen, Spring MVC controller'ında exception handler ile 409 veya 422 üreterek açık bir HTTP sözleşmesine bağlanmalıdır.
Bu konu spring framework eğitimi ve spring boot eğitimi kapsamında HTTP client ayarından ayrı görülmemelidir. Retry kararı, connection pool limiti, Spring Security ile taşınan kimlik ve idempotency tablosu birlikte tasarlanmadığında, kısa timeout altında dahi aynı ticari işlemin iki kez yazılması mümkündür.
İ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 RestClient timeout değerleri nasıl seçilir?
Önce endpoint için uçtan uca süre bütçesi belirleyin. Bu bütçeden controller, serileştirme ve hata işleme payını çıkarın; kalanı connection-request, connect ve response timeout'a dağıtın. Apache HttpClient 5 ile pool beklemesini ayrıca sınırlayın ve k6 p95/p99 sonuçlarını JFR thread park olaylarıyla birlikte karşılaştırın.
Spring Cloud LoadBalancer kullanırken POST isteği retry edilmeli mi?
Varsayılan cevap hayırdır. POST, idempotency garantisi vermiyorsa bağlantı kopmasının sunucuda işlem tamamlanmadan mı yoksa tamamlandıktan sonra mı oluştuğu bilinemez. Retry gerekiyorsa Idempotency-Key, payload hash kontrolü ve veritabanında unique constraint kullanın; ardından deneme sayısını kalan süre bütçesiyle sınırlandırın.
Spring Security downstream servis çağrısında kullanıcı JWT'si aktarılmalı mı?
Hedef servisin audience ve yetki modeli token ile uyumluysa aktarılabilir, ancak gateway audience'lı token'ı doğrudan paylaşmak sık yapılan hatadır. OAuth2AuthorizedClientManager ile hedef servise özel client credentials veya token exchange token'ı alın. @Async kullanılıyorsa kullanıcı context'i için DelegatingSecurityContextTaskExecutor gerektiğini ayrıca test edin.
Spring MVC uygulamasında HTTP connection pool doygunluğu nasıl bulunur?
Apache HttpClient connection manager'ın leased, pending ve available istatistiklerini Micrometer gauge olarak yayınlayın. Aynı anda JFR kaydında thread park sürelerini ve Actuator http.client.requests histogramını inceleyin. Pending yükselirken CPU düşük ve p99 yüksekse sorun çoğunlukla havuz beklemesi veya downstream kapasitesidir, GC değildir.
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.


