반응형

사내에 국내 / 해외 모니터링을 하는 중앙 서버가 하나 있고, 해당 중앙 서버를 Web 환경에서 컨트롤 할 수 있는 Spring Boot로 개발한 어플리케이션을 하나 운영중인데, 중앙 서버가 모니터링 하는 서버들의 상태를 특정 주기마다 불러와서 보여주는 역할을 하고 있다.


해당 역할을 하는 중앙 서버의 모니터링 시스템이 어플리케이션이 아니라, 그냥 데몬이기 때문에 어플리케이션 단에서는 ssh 통신을 해서 Input Stream을 통해 콘솔에 나타나는 정보를 가져와야 한다.


ssh 통신 간 사용한 라이브러리는 jsch (http://www.jcraft.com/jsch/)를 이용하였고, In / Output Stream을 편리하게 메소드 형태로 가져올 수 있게 구현이 되어 있다.


아무런 생각없이, 아래와 같이 구현하고 몇 개월동안 별 문제 없이 운영을 했었음. (ssh 연결 과정은 생략하며, Stream 처리 부분만 명시함.)

...
// stdout이 jsch에서 가져온 InputStream임..
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
StringBuffer sb = new StringBuffer();
String line;
while((line = reader.readLine()) != null) {
    sb.append(line + "\n");
}
...


정상적인 상황에서는 문제가 없지만, 가끔 중앙 서버와 통신하는 서버의 IP가 잘못 입력된 경우나, 없어진 서버가 생기면 시스템에 과부하가 오는 현상이 발견되었다.


디버깅을 하던 과정 중 위 코드의 치명적인 문제를 발견함.


1. 일단, BufferedReader의 readLine method가 blocking method이기 때문에, 읽을 수 있을 때까지 무한정 대기하며, 서버내의 자원을 점유한다. (이런 류의 요청이 쌓이면, OOM 등 시스템에 문제가 전파됨.)


2. 무한정 대기하다가 결국 못읽는다고 판단하면, Exception을 발생시키는데, 이 시점을 코드 레벨에서 컨트롤할 수 없음.

----------------------------------------------------------------------------

최종적으로 아래와 같이 해결함.


1. BufferedReader에서 제공하는 ready method를 이용해, 읽을 수 있는 지 먼저 검사하는 방식으로 시스템이 blocking되지 않도록 변경


2. Guava(https://github.com/google/guava)에서 제공하는 SimpleTimeLimitter를 이용하여, 코더가 Timeout을 컨트롤 할 수 있도록 구조를 변경

BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
StringBuffer sb = new StringBuffer();
int timeoutSec = 3;
SimpleTimeLimiter limiter = new SimpleTimeLimiter();
    try {
	limiter.callWithTimeout(() -> {
            String line;
            boolean isReady = false;
            while(!isReady) {
                isReady = reader.ready();
            }
            while((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
	}, timeoutSec, TimeUnit.Seconds, false);
    }
...


이제 중앙 서버가 IP가 잘못 등록되었거나 존재하지 않는 원격 서버와의 통신을 시도해도, 3초 이상 기다리지 않고 요청을 끊어버리므로 위에서 발견된 문제는 모두 해결이 되었다.

반응형
,