Language & Framework/WebFlux

[WebFlux] WebClient Connection Reset By Peer 오류 발생

수미수 2023. 8. 18. 18:07
반응형

들어가기 전

  Spring WebFlux 를 사용하는 서비스가 운영에서 잘 동작 하고 있었는데, 어느날 오류를 모니터링 중 간헐적으로 "Connection Reset By Peer" 오류가 발생 한 것을 확인하였다. WebFlux 기반의 서비스가 잘 동작 하다가 갑자기 오류를 보니 갑자기 식은땀이 났다. 왜냐하면, 현재 운영중인 아키텍처 구조가 MSA 형태로 구성되어 있고, 오류가 발생한 WebFlux 서비스는 여러 서비스들로 부터 많은 요청을 받고 있다보니 빠르게 조치가 필요 했기 때문이다. 

  해당 원인은 WebClient 를 사용하여 Connection을 맺은 후 다시 해당 Connection 을 사용할 때 발생되는 오류라는 것을 알게 되었고, 왜 해당 오류가 발생 하였고, 어떻게 조치를 했는지에 대해서 이야기 하고자 한다.

오류 내용

  이번에 발생한 오류는 WebClient 를 이용하여, Server to Server 방식으로 다른 서버의 API 를 호출 할 때, "Connection reset by peer" 라는 오류가 발생하였다.  

  해당 오류는 WebClient 에서 다른 서버와 통신하기 위해 맺어 둔 Connection 이 Close 되었는데, 해당 Connection 을 다시 사용 할 경우 해당 오류가 나타난다고 한다. 음.. WebClient를 통해서 연결을 해둔 Connection이 닫혔다? 라고 간단히 이해를 했다.

  좀더 검색을 한 결과 클라이언트가 요청을 보냈는데 서버에서 RST 패킷을 보낼 경우 발생 된다고 하며, 이는 클라이언와 서버 연결에서 한쪽만 연결이 된 경우이다. 기본적으로 TCP 연결 종료 시 FIN 요청을 하고 FIN 요청을 받은 후 ACK 를 응답해야 하는데 네트워크 상황 등 여러가지 이유에 의해서 FIN 요청을 전달 받지 못하는 경우에 해당 된다고 한다.

"Connection reset by peer" is the TCP/IP equivalent of slamming the phone back on the hook. It's more polite than merely not replying, leaving one hanging. But it's not the FIN-ACK expected of the truly polite TCP/IP converseur.

해결

  우선 위의 원인을 파악 한 뒤, WebClient의 Connection Timeout과 Idle Timeout 을 튜닝 해야 하는 것을 알게 되었다.  Default 설정으로 동작하는 WebClient 객체를 아래 코드와 같이 Connection Timeout 과 Idle Timeout 을 설정 한 뒤 해당 오류를 해결 할 수 있었다.

fun getClient(): WebClient {

        val provider = ConnectionProvider.builder("web")
            .maxConnections(200)
            .maxIdleTime(Duration.ofSeconds(20))
            .maxLifeTime(Duration.ofSeconds(60))
            .pendingAcquireTimeout(Duration.ofSeconds(60))
            .evictInBackground(Duration.ofSeconds(120)).build()

        val httpClient = HttpClient.create(provider)
            .baseUrl(endpoint)
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
            .responseTimeout(Duration.ofMillis(90000))
            .doOnConnected { conn ->
                conn.addHandlerLast(
                    ReadTimeoutHandler(90000, TimeUnit.MILLISECONDS)
                )
                    .addHandlerLast(WriteTimeoutHandler(90000, TimeUnit.MILLISECONDS))
            }
        return WebClient.builder().clientConnector(ReactorClientHttpConnector(httpClient)).build()

    }

추가로 아래와 같이 높은 부하가 발생할 경우, 높은 maximum connection 이 생기지 않도록 조심해라고 한다고 하니 참고 해야 한다. 

Reactor Netty References
When you expect a high load, be cautious with a connection pool with a very high value for maximum connections. You might experience reactor.netty.http.client.PrematureCloseException exception with a root cause "Connect Timeout" due to too many concurrent connections opened/acquired.

참고

 

What does "connection reset by peer" mean?

What is the meaning of the "connection reset by peer" error on a TCP connection? Is it a fatal error or just a notification or related to the network failure?

stackoverflow.com

 

반응형