쉼터

HTTP 통신할때 주로 httpclient를 이용해서 처리를 한다.

기본적인 httpclient 사용 방법은 다음과 같다.
여기서 주의할 점은
method.releaseConnection();
이렇게 connection을 끊어줘야한다.

근데 간혹 CLOSE_WAIT, TIME_WAIT이 누적되어서 FD가 빠지지 않는 경우가 있다.
(ESTABLISHED, CLOSE_WAIT, TIME_WAIT이 뭔지 모른다면 TCP 3-Way handshake부터 공부하면 된다.)
대충쓸때는 상관없지만 문제는 지속적으로 연결할때 CLOSE_WAIT, TIME_WAIT이 없어지지않고 계속해서 쌓이게될때가 있다.

일단 CLOSE_WAIT이 계속 누적해서 안빠진다면 httpclient를 잘못쓴 경우가 많다.
연결할때마다 계속해서 httpclient를 new해서 생성을 하면서 어느수준이상으로 연결을 시도할때 CLOSE_WAIT이 누적되어 안빠지게된다.
왜냐하면 httpclient는 default가 HTTP/1.1 방식이다.
어느정도 keepalive를 유지하기때문에
서버에서는 connectionTimeout이 지나면 연결을 종료시키지만
서버에서 연결종료된 상황을 클라이언트에서 서버로 다시 물어보기전에는 알 수가 없다.
그래서 완전히 종료가 안되고 CLOSE_WAIT 상태가 된다.
근데 이 상황에서 매번 httpclient를 생성하게되면 제대로 연결을 종료안되고
클라이언트 쪽에는 CLOSE_WAIT이 쌓이게된다.

해결책은 여러방법이 있다.
1. 클라이언트쪽에서 HTTP/1.0으로 연결하면된다.

method.getParams().setParameter(HttpMethodParams.PROTOCOL_VERSION, HttpVersion.HTTP_1_0);

1.0으로 연결을 한다는거는 keepalive를 유지안하고 매번 연결하고 끊고한다는거다.
이렇게 하면 CLOSE_WAIT이 누적은 안된다.
하지만 CLOSE_WAIT대신에 TIME_WAIT이 누적된다.
그리고 매번 연결을 끊고 연결하기때문에 throughput이 제대로 안나온다.

2. httpclient를 제한적으로 사용하는 방법이다.
httpclient를 wrapping하는 하나의 클래스를 만든다.
그리고 그 클래스를 싱글톤패턴을 이용해서 필요한곳에서 사용한다.
근데 문제는 스레드환경에서 한번에 여러군데 접속하고 싶을때는 문제가 발생한다.
그럴때는 MultiThreadedHttpConnectionManager를 이용하면된다.

MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);

이렇게만 해주면 알아서 pooling을 해준다.
MultiThreadedHttpConnectionManager에는 몇가지 옵션이 있는데
필요에 따라서 적절히 조절해주면된다.

이렇게 하면 CLOSE_WAIT 상태가 계속해서 누적되어 쌓이지 않는다.

3. IdleConnectionTimeoutThread를 이용해서 해결 할 수도 있다.

MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
IdleConnectionTimeoutThread idleConnectionTimeoutThread = new IdleConnectionTimeoutThread();
idleConnectionTimeoutThread.addConnectionManager(connectionManager);
HttpClient client = new HttpClient(connectionManager);
idleConnectionTimeoutThread.start();

이렇게 별도의 스레드를 생성해서 idle상태를 주기적으로 지워준다.

IdleConnectionTimeoutThread를 이용하지않고
connectionManager.closeIdleConnections(1000*10);
이걸 이용해서 주기적으로 지워줄 수 있다.

근데 3번째 방법은 CLOSE_WAIT를 해결하는 방법이라기보단
CLOSE_WAIT가 발생할때 더 이상 안늘어나게하는 사후처리 방법이다.

웬만하면 2번째처럼 통신할 수 있는 채널에 제약을 걸어서 많이 생성못하도록하는게 제일 좋은거 같다.

그리고 TIME_WAIT 상태가 누적되는거는 연결이 종료되는데 시간이 걸리기때문에
어쩔 수 없이 발생을 하게된다.
그래서 웬만하면 HTTP/1.1로 통신을 해야한다.
서버도 HTTP/1.1로 받을 수 있도록하고
클라이언트도 HTTP/1.1로 보내면서 keepalvie를 유지해서
나름 서로간에 전용통로를 만들어줘서 연결이 종료안되게 만들면된다.

여기서 중요한것은 MultiThreadedHttpConnectionManager 여기에
maxTotalConnections 이 값을 상황에 맞게 유지시키는거다.
내가 연결해야하는 connection수가 maxTotalConnections수를 넘어가게된다면
어쩔수 없이 기존 연결된 소켓을 끊고 새롭게 연결해야하기때문에
keepalive 상태를 유지못한다.

즉, TIME_WAIT이 안일어나게 하기 위해서는 연결 종료를 안하고
계속 그 통로를 통해서 통신을 하는건데
maxTotalConnections수를 적절히 조절해서 연결이 안끊기게
keepalive 상태를 유지하게하면된다.

자세한 사항은 httpclient wiki에 나와있다.
Posted by pchun