AWS Elasticbeanstalk + Node.js + Socket.io + Redis를 이용한 채팅서버 (2)

The Beginner Developer
7 min readOct 28, 2018

--

저의 삽질의 기록을 남기는 두번째 시간이 돌아 왔습니다.

저번의 글에 이어서 진행되는 글이므로, 첫번째 글을 보고 오시는걸 추천합니다.

이번에 주로 다룰 내용은 ‘분산서버에서 일어나는 문제 2가지와 이를 해결하기 위한 삽질’ 입니다.

1.서버가 여러개가 되면 접속이 안되요???

코드를 완성하고 beanstalk로 배포를 하고, 서버 1개로 테스트를 진행했습니다. 네, 제가 계획한 대로 잘 되더군요. 이때까지만 해도

기분이 아주 그냥 호우!

그리고 서버를 2대로 늘였습니다. 그러더니…

ㅜㅜ

개발자 도구를 확인하면 연결을 못한다는 에러를 1초에 한번씩 뱉어내는 브라우저를 확인 할 수 있었습니다.

왜 이런 문제가 발생할까요?

Socket.io의 최초연결시 작동을 알아야한다.

socket.io는 최초 연결시 polling을 이용합니다. polling을 쉽게 말씀드리면 http로 계속해서 요청과 응답을 받는다고 생각하시면 됩니다. polling을 이용해 연결이 성공되면 그때서야 websocket방식을 사용합니다.

그리고, 로드밸런서를 거친다면 요청 할때마다 요청하는 서버는 다른곳이 될 수가 있습니다.

최초 연결된 서버에는 세션이 존재하는데, 다른 서버에는 세션이 없어서
다른 서버에는 ‘어?? 얘는 언제 요청했지?? 너 누구냐??’ 할겁니다.
그래서 통신이 실패하고 브라우저는 연결이 안된다고 에러를 뱉어댑니다.

저의 허접한 두뇌에서 나온 해결법은 두 가지가 나왔습니다.

첫번째, 모든 서버가 세션을 공유하는방법.

두번째, 세션을 고정시키는 방법. (sticky session)

첫번째 방법을 이용하면 세션저장소가 필요합니다. 그리고 손이 많이 갈거 같아서 패스했습니다. (이 방법을 쓰시려면 redis에 세션을 저장하시면 됩니다.)

두번째 방법은 AWS에서 제공하기 때문에 간단하게 해결 가능합니다.

https://docs.aws.amazon.com/ko_kr/elasticloadbalancing/latest/application/load-balancer-target-groups.html#sticky-sessions

고정세션을 활성화 하시면 최초연결시 접속한 서버로만 요청이 전송됩니다. sticky duration은 서비스의 특성에 맞게 설정하시면 될겁니다.

최초연결한 서버로만 요청이 전송되므로, 세션 문제없이 연결이 됩니다.

그리고 저의 뇌 밖에서 나온 다른 방법이 하나 더 있습니다.

처음에는 고정세션으로 문제를 해결했습니다.
하지만 이 방법이 이상하다고 생각했습니다. socket의 특성상 서버와 유저가 계속 연결되어 있습니다.
그리고, 고정세션은 http통신에서 더 적합한 해결법이라는 생각이 들었습니다.

더 알아본 결과 최초 연결시 polling을 사용하지 않고, 바로 websocket으로 연결시키는 방법을 찾았습니다.

이렇게 최초 연결시 옵션을 주면 polling을 사용하지 않고 바로 websocket으로 연결한다.

이렇게 하면 고정세션 활성화 필요없이 에러 없이 연결이 가능합니다.

2. 서버가 줄어드는 경우 기존에 연결 된 유저는 어떻게 처리 할 것인가.

이를 해결하기 위해 search를 시작했습니다. search를 하면서 haproxy를 이용한 서버구조를 보게됬고, 이 구조를 서비스에 적용시킨다면 아래처럼 될거라고 생각했습니다.

haproxy를 ec2인스턴스안에 설치해서 설정을 해주면 된다고 생각했다.

HAProxy is a free, very fast and reliable solution offering high availability, load balancing, and proxying for TCP and HTTP-based applications.

haproxy를 쉽게 말씀드리면 긴 시간동안에도 정상적인 서버 운용(고가용성)을 보장해주는 녀석 정도로 알고 계시면 됩니다.

참고로 haproxy도 load balancer의 역할을 합니다. load balancer와 haproxy를 비교하는 글도 찾아보시면 많습니다.

haproxy의 기능 중 하나로 클라이언트가 보낸 요청을 서버로 보내기 전에 해당 서버가 죽었는지 살았는지 확인하고 서버가 죽어있다면 다른 서버로 요청을 보내준다고 합니다.

이 특징을 알게된 순간 저걸 쓰면 끝이다. 고민 끝!! 이렇게 생각했습니다…

서버가 죽으면 알아서 다른서버로 요청을 보내준다니 정말 좋은 녀석 아닙니까??
이를 적용하기 위해 config파일 수정을 하는중… 갑자기 떠올랐습니다.

아니 그럼서버가 동적으로 생성되고 줄어드는데 config파일을 그러면 계속해서 바꿔줘야 하는건가?

물론 AWS를 이용하기 때문에 서버가 생성되고 줄어들때 설정 할 수 있도록하는 방법이 있을거라고는 생각했지만, 생성된 ec2의 프라이빗 ip를 어떻게 가져와서 파일을 수정하고, auto-scaling 시 발생시킬 이벤트 등록하고 등등…

배보다 배꼽이 크다. 그리고 다른 방법을 찾았다.

문제를 해결하기 위해 질문글을 올렸습니다. 그리고 친절하신 분들이 댓글을 달아주셨고, 그리고 댓글을 통해 깨달음을 얻게됩니다.

서버가 auto-scaling에 의해 줄어들게 되면, 기존에 연결됬던 유저들은 연결이 강제로 끊기게 됩니다. 그리고 socket의 특성때문에 자동으로 다른서버에 연결됩니다. 즉, 잠깐 끊기고 다시 연결됩니다.(reconnect)

연결된다고 끝은 아닙니다. 재연결 된 후 처리로직을 구현하셔야 됩니다. 아마 대부분의 경우 최초연결시 로직에서 조금 변경시키면 될겁니다.

저의 경우는, 최초 접속성공시 DB에 저장해 놓았던 유저가 채팅중이던 채팅방 목록을 가져와 해당 채팅방에 접속시키고, 채팅방 목록을 브라우저에서 볼 수있도록 DB에서 가져온 목록들을 보내줍니다.

재연결 경우에는 DB를 조회해서 목록을 갖고와 채팅방에 접속 하고 잠깐 끊긴동안 들어온 채팅목록이 있을수도 있으므로 브라우저에 남아있는 채팅방목록을 DB에서 가져온 데이터로 업데이트 시켜줍니다.

이제는 서버가 늘어나고 줄어들어도 채팅이 된다!!!!
물론 예외처리 할거 아직 남았다…

드디어 분산서버 구조에서도 채팅이 가능합니다. 서버가 늘어나던 줄어들던!! 하지만 아직 해결 하지 못한 부분은 존재합니다.

저의 경우는 현재 접속자를 구분할수 있는 값(유저 id)과 해당 유저의 socket id를 redis에 저장하고 유저가 로그아웃하면 삭제하는 식으로 관리중입니다. 모든 서버가 동일한 접속유저 리스트를 갖도록 동기화 하는게 옳지 않다고 판단했기 때문입니다. 동기화가 가능한지도 모르겠습니다.

결국 현재 접속 유저 리스트는 redis에 저장되있는데, 서버가 줄어드는 경우 그냥 연결이 끊겨서 결론적으로는 나간 상황이되므로 redis에서 접속유저 리스트에서 지워줘야 하는데, key값을 받을 수가 없어서 접속 유저리스트가 실제 접속유저와 안맞는 경우가 있다는 것.

그리고 서버가 줄어들고 재접속을 하는 경우, 채팅이 되긴 하는데 전송이 지연되는 경우가 발생합니다. 원래 메세지가 1초컷인데, 6초정도 걸리는 경우가 발생합니다…

서버 모니터링 결과 redis에서 메세지를 보내는 작동이 늦게 실행되는 걸 확인했고, 이 원인과 해결법을 찾고있습니다.

오늘의 글은 여기까지 입니다. 아마 다음주는 결제 기능 구현 관련 글을 쓸듯 합니다. 부족한 글 읽어주셔서 감사합니다.

글에 이상한 점이나, 이건 무슨 말도안되는 소리냐 싶은 부분은 바로 피드백 해주세요!! 저도 허접한 개발자니깐요…

--

--