[질문] preforked server에서, accept 사용시 blocking문제...
안녕하세요. 처음 글을 쓰게 됩니다.
accept 주위에 file lock을 사용하는 preforked server를 운용중에 있습니다.
client는 java applet으로 작성되어 있구요.. 서비스 하는데 별 문제는 없는데,
가끔 client에서 NoRouteHostException 이 발생해서 원인을 파악하던중 아
래와 같은 의문이 들었습니다. (네트웍이 불안해서라고 생각했지만, 상사가 문제
를 해결해라라고 해서, 서버 소스를 뒤지고 있습니다. -_-);
먼저 서버 소스의 일부분 입니다.
(server child process는 여러개가 생성되어 있습니다. 각 차일드마다 아래처럼 client의 접속을 기다리고 있습니다.)
/*lock을 이용하여 대기한다.*/ g_sc_lock_wait(fd); /*client의 접근을 기다린다.*/ datafd = accept(dsock, (struct sockaddr *)&client, &cli_len); /*lock을 해제해 준다*/ g_sc_lock_release(fd);
java applet이 위의 서버로 connect를 할 경우 accept에 문제가 발생하면(네
트웍 상황이 좋지 않다거나, 여타의 이유로..) accept()함수가 바로 리턴하지 못
하는 상황이 발생할 수 있지 않습니까? 제 생각에는, accept() 함수가 바로 리
턴되지 않고 몇 초간의 딜레이가 발생한다면 lock을 release하지 못하게 되고,
다른 클라이언트들도 서버에 접속하는데 딜레이가 발생하여서 서비스를 원할하
게 할 수 없을 것 같습니다. 혹시 이것이 java client의 NoRouteException과
관련이 있지나 않은지.. 혹은 제 추측이 맞다면 문제를 해결할 수 있는 방안에는
어떤 방법들이 있는지 고견을 듣고 싶습니다. :?
추측하신 이유로 그런 에러가 나는건지 자바경험이 적어서 잘 모르겠습니다.
추측하신 이유로 그런 에러가 나는건지 자바경험이 적어서 잘 모르겠습니다.
하지만 일단 그 accept 에서 락문제로 다른 클라이언트에서 오래 기다리게 되는 문제의 해결 법은 서버에서 소켓을 block 모드로 하고 accept 위, 아래에 있는 lock 을 제거 하시면 됩니다.
block 모드일때 accept 는 자동으로 accept 할 client 가 있을 때까지 계속 대기 하고 있다가 accept 되면 반환되니깐 별도의 lock 시스템을 만들 필요는 없습니다.
별도의 락을 제거 하게 되면 기존의 accept 를 하나의 프로세스만이 했던것을 동시에 여러 프로세스가 accept 를 하게 되죠.
헌데 이런 방법에 약간의 문제점이 있긴합니다.
저의 경우 레드햇 2.4 커널을 사용하는데요.
만약 prefork 된 프로세스가 10개가 있고 모두 accept 상태로 접속을 기다리고 있는데
짧은 시간에 접속 요청이 5개가 (거의 동시에) 발생하면 프로세스 다섯개에 각각 하나씩 분배되지 않고 한 프로세스에 몰리는 현상이 발생하더군요.
이럴 경우 가장 먼저 사용자를 제외하고 다른 사용자의 해당 프로세스의 back log 에 대기상태로 있게되서 back log 의 뒷쪽에 들어간 client 의 경우 timeout 이 발생하는 경우도 있더군요.
하지만 이런 스케줄링은 커널에서 하기때문에,
prefork 에서 block 모드로 accept 를 하고 있을 땐 이걸 어떻게 해결할 방법을 찾지 못했습니다.
하지만 웹 처럼 동시에 많이 요청하는 경우 문제가 되지만,
ftp 나 telnet 처럼 연결 지향적인 서버라면 이런 방식을 이용하시면 간단히 해결 될 듯 하군요.
저의 경우 업그레이드때 fork 에서 쓰레드로 변경하고,
accept 는 한녀석만 하고 연결되면 응답은 쓰레드로 넘기는 방식으로 구현을 했습니다.
허허.. 이거 당직이라 심심해서 답변을 몇개 썼는데 모두 길게 달았군요. ^^
도움이 되셨길...
nonblock 모드는 어떨까요...
소켓을 논블럭 모드로 만드시고 accept()
이전에 select나 epoll로 이벤트가 발생할때까지 wait하게 하면 어떨까요..
그리고 select나 epoll이전에 lock을 걸고 accept후에 바로 lock을 풀면
한쪽 프로세스에서 accept이후의 처리를 하는동안 다른 프로세스에서는
lock에서 대기하면서 계속적으로 클라이언트의 접속을 받아들일 수 있을거 같은데요...
집에나 갈까?
indie 님께서 말씀하신 방법은 많은 사람들이 생각하는 방법이지만t
indie 님께서 말씀하신 방법은 많은 사람들이 생각하는 방법이지만
thundering herd problem 이라고 불리는 문제가 있습니다.
이게 뭔고 하니, 10 개의 프로세스가 select 에서 대기중에 있다가 접속 요청이 들어오면 10개의 프로세스가 모두 깨어서나 lock 부분에서 하나의 프로세스만 통과하고 나머지는 불필요하게 깨어나는 비효율성이 발생합니다.
그리고 처음 질문하신분이 말씀하셨듯이 lock 을 너무 늦게 풀어서 다른 대기 client 에서 문제가 생기는 것 같다고 하셨으니 indie 님 말씀 처럼 구현해서는 이 문제가 해결 되지 않을 듯 하군요.
제가 얘기한 방법에서는 lock은 select이전에 걸기 때문에 모든
제가 얘기한 방법에서는
lock은 select이전에 걸기 때문에 모든 자식들이 다 깨어날 수가 없습니다.
오로지 하나의 프로세스만 select에서 대기하게 됩니다.
그리고 바로 accept를 호출하고 논블록이기 때문에 delay없이 바로 리턴할거라 생각합니다.
그리고 바로 lock을 풀기때문에 하나의 클라이언트가 접속을 하는동안에 다른
클라이언트의 접속에 문제가 있다고는 보지 않습니다.
단지 서버가 루프를 돌면서 일을 처리해야 하는 구조 라면 문제가 있다고 봅니다.
집에나 갈까?
[quote="devilhero"]indie 님께서 말씀하신 방법은 많은
thundering herd problem은 이제 없는 것으로 알고 있습니다만... ^^;; (Linux 2.2.9까지 있던 문제라고 하더군요.)
http://www.citi.umich.edu/projects/linux-scalability/reports/accept.html
FreeBSD 5.x에서 보면 수백개의 아파치 프로세스가 모두 accept 하고 있습니다. 예전에는 하나만 select 하고 나머지는 모두 lock 하고 있었죠. (아파치 코드는 두 가지 모두 가능하도록 되어있습니다. 시스템에 따라서 선택할 수 있지요.)
답변 감사합니다.일단, 서버의 다른 process가 lock을 획
답변 감사합니다.
일단, 서버의 다른 process가 lock을 획득하지 못했기 때문에 client에서 NoRouteHostException 이 발생하는지 테스트 하기 위해서 ,thundering herd problem 를 감수하고 lock부분을 빼 봤습니다.
여전히 NoRouteHostException이 발생하였습니다.
디버깅 하면서 특이한 점이 발견되었는데요.. 일단 아래 코드에서..
client 의 connect 에서는 NoRouteHostException이 발생하였지만, 서버쪽에서는 "socket accepted"라는 메세지가 출력이 되는군요..
위의 결과로 봤을때는 서버쪽에서는 TCP three-way handshake가 정상적으로 수행되었다는 말이고, 클라이언트는 성공적으로 수행하지 못했다는 이야기 인것 같은데, Java Socket쪽에 버그가 있는게 아닌지 생각해 봅니다. ㅠ.ㅠ 도대체 어떻게 결론을 내려야 되는지 원....
일단은 indie님께서 지적하신 데로 nonblock 으로 accept를 해서, lock 문제는 해결해야 겠습니다...
참.. 사용중인 서버의 OS는 HP-UX 11.10 입니다.
제가 말씀 드린 thundering herd problem 은 accep
제가 말씀 드린 thundering herd problem 은 accept 함수 말고 select 함수를 동시에 여러 프로세스가 동일한 fd에 대해 하고 있을 때를 말씀드린거였습니다.
그리고 indie 님께서 말씀하신건 select 이전에 lock 를 거는 것이었군요.
전에 select 이후에 lock 을 거는 걸로 이해했습니다. 죄송합니다.
댓글 달기