org.springframework.web.reactive.function.client.WebClientRequestException: readAddress(..) failed: Connection reset by peer; 오류 해결

 

현재 프론트엔드로 개발하고 있는데 간헐적으로 서버를 통해 외부 API를 요청할때 오류가 발생하는것을 확인 했다.

여기저기 찾아보니 

WebClinet를 사용해서 외부에 요청을 보낼때 대상서버의 연결이 닫혔을때 나타나는 오류 라고 한다. 

일단 의심이 가는 시나리오는

우리쪽 A서버에서 외부 B서버로 API 요청을 보내고 커넥션을 맺을때 B서버가 중간에 재실행 혹은

별도의 문제로 서버다운이 발생하는 경우라고 생각하여 해당 업체에 문의해본 결과

서버가 내려간적은 없다고 한다. 

 

두번째 시나리오는 B서버가 로드밸런서를 사용해 A서버에서 최초 요청시 B-1에 커넥션을 맺고 통신을 하다

다시 요청시에는 B-2 서버로 요청을 하는 경우

세번째는 로드밸런서 자체의 타임아웃이 A서버보다 빠른 경우

 

확인을 위해 해당 업체에 문의한결과 로드밸런서를 사용중이며 타임아웃이 10분이라고 한다.

 

오류 재현을 위해 테스트해보니 최초 요청 이후 10분후 재요청을 했을 경우에만 동일한 오류가 나는것을 확인하고

세번째 문제가 이유라고 판단 하였고 

다른분들이 작성한 깃이나 블로그의 도움을 받아 수정 할수 있도록 백엔드 팀에 전달하였다.

문제 해결을 위해 참고한 블로그및 사이트들이다.

참고 사이트

https://velog.io/@youngerjesus/Connection-Reset-by-Peer-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0

 

Connection Reset by Peer 문제 해결

Client 가 요청을 보냈는데 서버쪽에서 연결이 닫혔다고 다시 연결하라는 RST (Reset) 패킷을 보내는 경우에 이 에러가 발생한다.Connection prematurely closed BEFORE response 이렇게 쓰기도 한다. Client-Server 연

velog.io

https://jskim1991.medium.com/spring-boot-how-to-solve-webclient-connection-reset-by-peer-error-b1fa38e4106a

 

[Spring Boot] How to solve WebClient Connection reset by peer error

I had a requirement to fetch user data from an external system. It was implemented using WebClient as part of declarative http client…

jskim1991.medium.com

https://github.com/reactor/reactor-netty/issues/1774

 

Connection reset by peer exception · Issue #1774 · reactor/reactor-netty

We have a micro service based spring boot architecture where we are using spring webclient (which internally uses reactor netty) for internal communication between services. The issue that we faced...

github.com

 

 

해결방법

일단 해결 방법은 B서버 로드밸런서의 타임아웃시간에 도달하기 전에 우리쪽에서 먼저 연결을 해제하고

다시 커넥트를 거는것으로 해결 하였고 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Configuration
public class HttpProxyConfiguration {
 
    @Value("${tracker.url}")
    private String trackerUrl;
 
    @Bean
    TrackerClient trackerClient(WebClient.Builder builder) {
        ConnectionProvider provider = ConnectionProvider.builder("fixed")
                .maxConnections(500)
                .maxIdleTime(Duration.ofSeconds(20))
                .maxLifeTime(Duration.ofSeconds(60))
                .pendingAcquireTimeout(Duration.ofSeconds(60))
                .evictInBackground(Duration.ofSeconds(120)).build();
 
        HttpClient httpClient = HttpClient.create(provider);
        httpClient.warmup().block();
 
        var reactorClientHttpConnector = new ReactorClientHttpConnector(httpClient);
 
        var wc = builder.baseUrl(trackerUrl)
                .clientConnector(reactorClientHttpConnector)
                .build();
 
        var wca = WebClientAdapter.forClient(wc);
        return HttpServiceProxyFactory.builder()
                .clientAdapter(wca)
                .build()
                .createClient(TrackerClient.class);
    }
}
cs

 

이 코드를 참조하여 문제를 수정한것을 확인 할 수 있었고

위 코드의 각 부분의 설명을 보자면

  1. @Value("${tracker.url}"): 이 어노테이션은 tracker.url이라는 이름의 프로퍼티 값을 trackerUrl 변수에 주입(inject)합니다. 이 프로퍼티 값은 외부 트래커 서비스의 기본 URL을 포함하며, 애플리케이션 구성 파일(예: application.properties 또는 application.yml)에서 정의됩니다.
  2. TrackerClient trackerClient(WebClient.Builder builder) 메서드: 이 메서드는 TrackerClient 인터페이스의 구현체를 생성하고 구성합니다. 이 인터페이스는 외부 트래커 서비스와의 통신을 위한 메서드를 정의합니다.
  3. ConnectionProvider 설정: ConnectionProvider는 WebClient의 네트워크 연결을 관리하는 데 사용됩니다. 여기서는 최대 연결 수, 최대 유휴 시간, 연결의 최대 수명, 대기 중인 연결 획득 타임아웃 등을 설정합니다. 이 설정은 서비스와의 통신 중 발생할 수 있는 다양한 시나리오를 관리하기 위한 것입니다.
  4. HttpClient 생성 및 설정: HttpClient.create(provider)를 사용하여 ConnectionProvider를 사용하는 HttpClient 인스턴스를 생성합니다. httpClient.warmup().block() 호출은 HttpClient를 "온기"시키며, 네트워크 연결을 미리 설정하여 첫 번째 요청의 지연 시간을 줄이는 데 도움이 됩니다.
  5. WebClient 구성 및 생성: WebClient.Builder 인스턴스에 기본 URL, 클라이언트 커넥터 등을 설정하여 WebClient 인스턴스를 생성합니다. 이 WebClient 인스턴스는 TrackerClient의 HTTP 요청을 실행하는 데 사용됩니다.
  6. HttpServiceProxyFactory를 사용한 TrackerClient 생성: 마지막으로, HttpServiceProxyFactory와 WebClientAdapter를 사용하여 WebClient를 기반으로 하는 TrackerClient 인스턴스를 생성합니다. 이를 통해 외부 트래커 서비스와의 통신을 위한 프록시 클라이언트를 얻게 됩니다.

 

이렇게 정리될수 있으며 ConnectionProvider의 옵션은

  • builder("fixed"): 연결 풀의 이름을 "fixed"로 설정합니다. 이 이름은 로깅이나 디버깅 시 해당 연결 풀을 식별하는 데 사용될 수 있습니다.
  • maxConnections(500): 연결 풀이 동시에 유지할 수 있는 최대 연결 수를 500개로 설정합니다. 이는 애플리케이션이 동시에 열 수 있는 최대 연결 수를 의미하며, 이 한도를 초과하는 연결 요청은 대기 상태가 될 수 있습니다.
  • maxIdleTime(Duration.ofSeconds(20)): 연결이 유휴 상태(즉, 데이터를 전송하지 않는 상태)로 있을 수 있는 최대 시간을 20초로 설정합니다. 유휴 시간이 이 값을 초과하면 연결이 자동으로 종료됩니다.
  • maxLifeTime(Duration.ofSeconds(60)): 연결이 생성된 후 유지될 수 있는 최대 시간을 60초로 설정합니다. 이 시간이 지나면, 연결은 사용 여부에 관계없이 종료됩니다.
  • pendingAcquireTimeout(Duration.ofSeconds(60)): 연결을 획득하기 위해 대기하는 최대 시간을 60초로 설정합니다. 연결 풀에서 사용 가능한 연결을 얻기 위해 이 시간을 초과하여 대기하는 요청은 실패하게 됩니다.
  • evictInBackground(Duration.ofSeconds(120)): 비활성 연결을 정리하는 배경 작업의 실행 간격을 120초로 설정합니다. 이 설정은 연결 풀에서 오래되거나 더 이상 필요하지 않은 연결을 주기적으로 제거하는 데 사용됩니다.

위와 같다.

기존코드는 위의 옵션에 대한 별도의 설정이 없었기에 해당 오류가 발생하였었다.

+ Recent posts