tonykang22 / hello-world-auto-store

3 stars 1 forks source link

[product-manager] Amazon 상품 Sync #122

Open tonykang22 opened 1 year ago

tonykang22 commented 1 year ago

Amazon 상품 Sync

개요



Thread Pool 선택


결론은...?

image



RestTemplate 동시성 문제


RestTemplate ?



RestTemplate의 동작 원리

image
  1. 애플리케이션이 RestTemplate 생성하고, URI, HTTP 메소드 등의 헤더를 담아 요청한다.
  2. RestTemplate는 MessageHttpMessageConverter 를 사용하여 requestEntity 를 요청메세지로 변환한다.
  3. RestTemplate 는 ClientHttpRequestFactory 로 부터 ClientHttpRequest 를 가져와서 요청을 보낸다.
  4. ClientHttpRequest 는 요청메세지를 만들어 HTTP 프로토콜을 통해 서버와 통신한다.
  5. RestTemplate 는 ResponseErrorHandler 로 오류를 확인하고 있다면 처리로직을 태운다.
  6. ResponseErrorHandler 는 오류가 있다면 ClientHttpResponse 에서 응답데이터를 가져와서 처리한다.
  7. RestTemplate 는 HttpMessageConverter 를 이용해서 응답메세지를 java object(Class responseType) 로 변환한다.
  8. 어플리케이션에 반환된다.



그렇다면...


현재 HWAS project에서는 어떻게 사용되고 있는가?

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplateBuilder()
                .setConnectTimeout(Duration.ofMinutes(3))
                .setReadTimeout(Duration.ofMinutes(3))
                .build();
    }
...
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;

public class RestTemplateBuilder {

...

    public RestTemplateBuilder(RestTemplateCustomizer... customizers) {
        Assert.notNull(customizers, "Customizers must not be null");
        this.requestFactoryCustomizer = new RequestFactoryCustomizer();
        this.detectRequestFactory = true;
        this.rootUri = null;
        this.messageConverters = null;
        this.interceptors = Collections.emptySet();
        this.requestFactory = null;
        this.uriTemplateHandler = null;
        this.errorHandler = null;
        this.basicAuthentication = null;
        this.defaultHeaders = Collections.emptyMap();
        this.customizers = copiedSetOf(customizers);
        this.requestCustomizers = Collections.emptySet();
    }

...

        public RestTemplate build() {
        return configure(new RestTemplate());
    }

        public <T extends RestTemplate> T configure(T restTemplate) {
        ClientHttpRequestFactory requestFactory = buildRequestFactory();
        if (requestFactory != null) {
            restTemplate.setRequestFactory(requestFactory);
        }
        addClientHttpRequestInitializer(restTemplate);
        if (!CollectionUtils.isEmpty(this.messageConverters)) {
            restTemplate.setMessageConverters(new ArrayList<>(this.messageConverters));
        }
        if (this.uriTemplateHandler != null) {
            restTemplate.setUriTemplateHandler(this.uriTemplateHandler);
        }
        if (this.errorHandler != null) {
            restTemplate.setErrorHandler(this.errorHandler);
        }
        if (this.rootUri != null) {
            RootUriTemplateHandler.addTo(restTemplate, this.rootUri);
        }
        restTemplate.getInterceptors().addAll(this.interceptors);
        if (!CollectionUtils.isEmpty(this.customizers)) {
            for (RestTemplateCustomizer customizer : this.customizers) {
                customizer.customize(restTemplate);
            }
        }
        return restTemplate;
    }

....

        /**
     * Build a new {@link ClientHttpRequestFactory} instance using the settings of this
     * builder.
     * @return a {@link ClientHttpRequestFactory} or {@code null}
     * @since 2.2.0
     */
    public ClientHttpRequestFactory buildRequestFactory() {
        ClientHttpRequestFactory requestFactory = null;
        if (this.requestFactory != null) {
            requestFactory = this.requestFactory.get();
        }
        else if (this.detectRequestFactory) {
            requestFactory = new ClientHttpRequestFactorySupplier().get();
        }
        if (requestFactory != null) {
            if (this.requestFactoryCustomizer != null) {
                this.requestFactoryCustomizer.accept(requestFactory);
            }
        }
        return requestFactory;
    }

...
}
public class ClientHttpRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> {

...

    @Override
    public ClientHttpRequestFactory get() {
        for (Map.Entry<String, String> candidate : REQUEST_FACTORY_CANDIDATES.entrySet()) {
            ClassLoader classLoader = getClass().getClassLoader();
            if (ClassUtils.isPresent(candidate.getKey(), classLoader)) {
                Class<?> factoryClass = ClassUtils.resolveClassName(candidate.getValue(), classLoader);
                return (ClientHttpRequestFactory) BeanUtils.instantiateClass(factoryClass);
            }
        }
        return new SimpleClientHttpRequestFactory();
    }

}


SimpleClientHttpRequestFactory ?

RestTemplate restTemplate = new RestTemplate();


중간 결론



tonykang22 commented 1 year ago

코멘트 작성자: kingwaggs



@tonykang22 상웅님, 잘 정리해주시고 리서치 해주셨네요.

제가 이해한 바로는

시스템 프로퍼티 http.maxConnections를 할당해주면 늘릴 수 있다.

저희 배포 서버의 시스템 프로퍼티 http.maxConnections 값을 5 이상으로 잡는다면 더 많은수의 Http connection 을 확보하여 forkjoin pool 을 사용할 때 병렬로 처리 가능한 작업의 수가 더 늘어날 것이다 라고 볼 수 있을까요?

만약 그렇다면, 단순히 저희 서버에서 http.maxConnections 값을 상향 조정한다고 해서 바로 그 값이 적용되진 않을것 같다는 생각이 듭니다. TCP connection 은 양방향에 hand shake 를 거쳐 만들어 지게 되는데 상대방쪽 그러니깐 저희로 치면 Rainforest 가 되겠죠. Rainforest 에서 요청을 받아주는 시스템의 설정값 또한 영향을 미치지 않을까 생각되네요.

이건 우선 저희쪽 값을 상향조정해보고 정말 차이가 발생하는지 구체적인 테스트가 필요할 것 같습니다.

tonykang22 commented 1 year ago

@kingwaggs 아..! 해당 이슈 작성 중 호흡이 너무 길어 중간 저장 후 이어 작성하려 했었습니다.


저희 배포 서버의 시스템 프로퍼티 http.maxConnections 값을 5 이상으로 잡는다면 더 많은수의 Http connection 을 확보하여 forkjoin pool 을 사용할 때 병렬로 처리 가능한 작업의 수가 더 늘어날 것이다 라고 볼 수 있을까요?

네 사실 이슈를 작성하며 하나씩 확인해보는게 아니라, 다 확인해본 후에 이슈를 작성 중입니다. 추후 이슈는 그럼 새로운 이슈로 작성해두겠습니다. 참고 및 코멘트 부탁드리겠습니다 ㅎㅎ