Spring boot 2.6.x 기준 포스팅입니다
- Spring boot 3.0.x부터는
HttpClientFeignConfiguration
클래스가 사라진 것으로 보입니다. - 따라서
아무 설정 하지 않은 경우
와ApacheHttpClient로 변경
에 대한 내용은 추후 살펴보겠습니다.
아무 설정 하지 않은 경우
특별한 설정을 하지 않는 이상 FeignAutoConfiguration
에 의해 httpClient와 feignClient를 ApacheHttpClient
로 등록한다.
HttpClientFeignConfiguration
클래스의 connectionManager()
-> httpClient()
-> createClient()
순서로 CloseableHttpClient
가 디폴트로 생성된다.
이 과정에서 매개변수로 주입되는 FeignHttpClientProperties
을 추가로 설정해준다면, 디폴트로 생성되는 httpclient에 대해서도 약간의 설정 변경은 가능할 것으로 보인다.
httpClient()
메서드가 호출되었던 이유는 @ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "false", matchIfMissing = true)
구문 때문인데, feign.compression.response.enabled
설정을 해주지 않았기 때문에 빈으로 뜰 수 있게 되는 것이다.
따라서 feign.compression.response.enabled=true
설정을 해주면 customHttpClient()
를 대신 호출하게 되고,
HttpClientBuilder builder =
HttpClientBuilder.create().disableCookieManagement().useSystemProperties();
라인에 의해서 System Properties를 활용할 수 있을 것으로 보인다.
ApacheHttpClient로 변경
feign.httpclient.enabled=true
설정 추가io.github.openfeign:feign-httpclient
의존성 추가implementation 'io.github.openfeign:feign-httpclient
의존성 추가와는 별개로 위쪽의 설정을 명시적으로 설정해줄 필요는 없다.
HttpClientFeignLoadBalancerConfiguration
클래스에 @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
설정이 붙어있어서 설정이 없더라도 해당 클래스 내의 feignClient()
가 빈으로 생성되면서 위에서 빈으로 등록했던 CloseableHttpClient
를 ApacheHttpClient
로 감싸고 이를 이용해서 feignClient를 생성한다.
ApacheHttp5Client로 변경
feign.httpclient.enabled=true
설정 추가feign.httpclient.hc5.enabled=true
설정 추가io.github.openfeign:feign-hc5
의존성 추가implementation 'io.github.openfeign:feign-hc5'
feign.httpclient.hc5.enabled=true
설정을 하지 않으면, DefaultFeignLoadBalancerConfiguration
클래스의 feignClient()
에 의해 Client 빈이 등록되는데, ApacheHttpClient
로 변경할 때는 httpClient를 주입받아 delegate로 등록해줬던 것과는 다르게 new Client.Default(..)
를 호출해버린다.
client를 커스텀하기 위해서는 DefaultFeignLoadBalancerConfiguration
가 아니라 HttpClient5FeignLoadBalancerConfiguration
를 설정 클래스로 등록해야 하므로 feign.httpclient.hc5.enabled=true
설정은 하는 것이 낫다고 판단된다.
HttpClient5FeignConfiguration
클래스 내에서 hc5ConnectionManager()
-> httpClient5()
이 연속되어 호출되면서 .useSystemProperties()
를 통해 시스템 설정을 반영할 수 있게 된다.
OkHttpClient로 변경
feign.okhttp.enabled=true
설정 추가io.github.openfeign:feign-okhttp
의존성 추가implementation 'io.github.openfeign:feign-okhttp'
OkHttpFeignConfiguration
클래스의 httpClientConnectionPool90
-> client()
가 호출된다.
client()
에서 매개변수로 FeignHttpClientProperties
를 주입받으므로 어느정도의 설정이 가능할 것으로 보인다.
이후 OkHttpFeignLoadBalancerConfiguration
클래스의 feignClient()
에서 매개변수로 okhttp3.OkHttpClient
가 주입되어 Client
빈이 등록된다.
3가지 상황에서 커스텀 HttpClient 사용하기
ApacheHttpClient
feign.compression.response.enabled=true
설정하고,customHttpClient()
메서드 내에서.useSystemProperties()
호출CloseableHttpClient
빈 재정의 하기@ConditionalOnMissingBean(CloseableHttpClient.class)
때문에 가능하다
ApacheHttp5Client
(ApacheHttpClient와 동일한 방식이다)
httpClient5()
에서 호출되는.useSystemProperties()
이용CloseableHttpClient
빈 재정의 하기@ConditionalOnMissingBean(CloseableHttpClient.class)
때문에 가능하다
OkHttpClient
okhttp3.OkHttpClient
빈 재정의 하기@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
때문에 가능하다
세가지 HttpClient 비교
Apache HttpClient vs Apache Http5Client
Apache Http5Client의 업데이트 내역을 확인해보면 차이점을 알 수 있을 듯 하다.
- 비동기 처리 가능
- 전체적인 처리 Pipeline 방식이 Netty와 유사한 형태로 수정
- Selector에게 I/O 처리 위임하여 이벤트에 대한 처리를 위임
IOReactor
에 의해서 Connection에 대한 관리 스레드와 요청이 처리되는 스레드를 분리
Apache vs Ok
ApacheHttpClient
- 요청수에 비례하여 메모리 사용량 증가 (정비례 아님)
- 힙 사용
- GC
- 프로파일링
- 대용량 처리 가능
- 실행하고 오래 떠있는 경우에 적합
OkHttp
- 초당 요청수에 비해 적은 메모리 사용
- 한번 생성하고 나면 매우 빠르다
- 어느정도의 요청수에 적합
- 프로세스가 실행했다 죽었다 하는 클라이언트에 적합
- 다이렉트 버퍼를 쓰는 부분은 프로파일링 불가능
성능은 OkHttp가 빠르지만, 기능의 안정성은 Apache가 더 뛰어난 듯하다.
OkHttp는 자잘한 버그가 존재하는 듯 하다.
비동기 Client
위의 세가지 client는 대부분 동기로 작용한다.
전형적인 Spring MVC 애플리케이션 구성은 아래와 같다.
- 서버에서의 네트워크는 모두 Tomcat으로 처리
- 업스트림과의 통신은 Apache HTTPClient를 사용해서 동기 방식으로 통신
따라서 어떤 하나의 업스트림에 장애가 발생해서 응답 지연이 길어지는 상황이 발생하면, 그로부터 파생된 장애가 전체 서버 시스템을 마비시킬 가능성이 존재한다.
장애가 발생한 업스트림의 응답을 Tomcat 스레드가 계속 기다리면서 대기하다가, 종국에는 Tomcat 스레드 풀의 모든 스레드가 해당 업스트림의 응답을 기다리게 되면서 더 이상 요청을 받을 수 없는 상태가 되는 것이다.
따라서 비동기로 적용되어도 되는 internal call에 대해서는 비동기 client를 적극 활용하는 것이 성능상 이점이 많을 것으로 보인다.
client 자체는 아래 두가지 중에서 사용하면 될 듯 하다.
- Spring의 WebClient
- line corps의 Armeria
Feign-Reactive
OpenFeign에서 관리하는 feign-reactive-wrappers
와 PlaytikaOSS에서 관리하는 feign-reactive
중에 feign-reactive-wrappers
를 사용해보겠습니다.
Spring WebClient를 Feign 구현체로서 사용하므로, 아래 두가지 장점이 혼합된다.
- concise syntax of Feign to write client side API
- fast, asynchronous and non-blocking HTTP client of Spring WebClient.
사용 방식은 아래와 같다.
@RequestLine("GET /v1/mapping/property/{propertyId}/channels")
Mono<TestResponse> getChannels(@Param("propertyId") String propertyId);
ExampleApiClient exampleApiClient = ReactorFeign.builder()
.logLevel(Level.FULL)
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.errorDecoder(new FeignErrorDecoder())
.requestInterceptor(requestInterceptor())
.target(ExampleApiClient.class, "http://localhost:8080");
Mono<TestResponse> channels = exampleApiClient.getChannels("1");
ReactorFeign
은 빈으로 등록해서 사용하면 될 것이다.
Armeria
단계적 마이그레이션
라인 기술 블로그의 아티클에서는 아래 세가지 단계로 Spring MVC -> Armeria로의 마이그레이션 과정을 나누고 있습니다.
- Quick-win 단계
- Must-have 단계
- Final-Goal 단계
Quick-win 단계
Spring boot Application + Armeria Client
implementation 'com.linecorp.armeria:armeria'
의존성 추가Armeria
의WebClient
를 Bean으로 등록
@Bean
public WebClient armeriaClient() {
return WebClient.builder("https://www.xxx.com")
.decorator(LoggingClient.newDecorator())
.decorator(RetryingClient.newDecorator(RetryRule.failsafe()))
.decorator(CircuitBreakerClient.newDecorator(..))
.build();
}
final AggregateHttpResponse res = armeriaClient.get("/v1/info").aggregate().join();
이 단계로의 마이그레이션을 통해 얻을 수 있는 이점
- 클라이언트의 기능 추가
- failsafe 재시도
- 서킷 브레이커
- 헬스 체크가 동반된 클라이언트 측 로드 밸런싱
Must-have 단계
기존 Tomcat이 담당하는 네트워크 레이어를 모두 Armeria로 대체
해당 단계까지는 마이그레이션에 필요한 노력이 적기에 추천
implementation 'com.linecorp.armeria:armeria-tomcat9'
의존성 추가ArmeriaServerConfigurator serverConfigurator
빈 등록
@Bean
public ArmeriaServerConfigurator serverConfigurator(ServletWebServerApplicationContext context) {
TomcatWebServer container = (TomcatWebServer) context.getWebServer();
container.start();
return sb -> {
sb.serviceUnder("/", TomcatService.of(container.getTomcat()));
}
}
이 단계로의 마이그레이션을 통해 얻을 수 있는 이점
- Spring MVC에서 서빙하던 REST 서비스와 동일한 포트에 Tomcat의 오버헤드 없이 gRPC나 Thrift 같은 RPC 프로토콜을 지원하는 서비스를 완벽한 비동기 방식으로 추가
- 서버 프로토콜 측면에서의 강점
- 평문 TCP 연결에서도 HTTP/2로 통신할 수 있는 서버를 만들 수 있어서 튜닝해야 하는 지점이 다소 줄어든다
- 레거시 코드 변경 없이 가능하다
Final Goal 단계
Spring은 DI 컨테이너로서의 역할만 남겨둔다.
라우터 레벨 (인터셉터 혹은 필터)와 같은 것 -> Armeria 데코레이터로 대체
컨트롤러 -> Armeria의 AnnotatedService
로 대체
자세한 내용은 아래 참조의 라인 블로그에서 확인 가능하다.
고민해볼 지점
Q. 비동기 client가 반드시 필요할까?
- 비동기 처리 등에 대한 수요가 크지 않고, 기존의 MVC 구조를 가진 프로젝트에 이질적일 수 있다.
- 로깅이나 테스트 등이 까다롭다.
- 러닝 커브가 존재하고, 이벤트 루프의 스레드를 블로킹하는 등의 실수를 저지를 가능성도 존재한다.
Q. 비동기 client를 도입한다면 어느 부분에서 어느 정도의 성능 향상을 거둘 수 있을 것인가?
- 내부 call에서 비동기 처리로 대체할 수 있는 부분이 얼마나 있는가?
- 추후 client로의 응답도 비동기 처리로 대체할 수 있는가?
스프링 부트 업데이트 이후 살펴볼 사항
OpenFeign to HttpExchange
아직까지는 HttpExchange에 대한 레퍼런스가 크게 있지는 않은 것으로 보인다.
어느 시기에 갈아타는 것이 적절할 것인가?
레퍼런스
Apache vs Ok
https://mindule.tistory.com/27
Armeria
https://engineering.linecorp.com/ko/blog/hello-armeria-bye-spring
'개발' 카테고리의 다른 글
단위 테스트에서 Mockito 잘 활용하기 (0) | 2023.09.22 |
---|---|
Spring Boot 3.x 기반 actuator 설정 및 custom metrics 추가 (0) | 2023.07.11 |
Kafka를 이용해 '실시간' 데이터 처리가 가능한가? (0) | 2022.12.11 |
2. Apis - 요약 (0) | 2022.12.04 |
1. Getting Started - 요약 (0) | 2022.12.04 |