해당 블로그의 내용은 게임 서버 프로그래밍 교과서의 내용을 요약, 정리한 것입니다.

로그온 처리의 분산

보통의 로그온 처리는 다음과 같이 한다.

  1. 클라이언트는 서버에 ID와 비밀번호를 암호화하여 보낸다.
  2. 로그온 담당 서버에서 메시지를 받는다. 그리고 메시지를 복호화한다.
  3. 서버는 복호화된 메시지에서 ID와 비밀번호를 구한다. 그리고 DB에 질의를 던지고 결과를 기다린다.
  4. DB에서 질의를 수행한 후 응답을 서버에 회신한다.
  5. 서버는 이를 받아 로그인 성공 혹은 실패를 판단한다. 그 결과를 클라이언트에 전달한다.

이 때, 과부하가 주로 걸리는 지점은 서버에서 복호화된 메시지를 통해 ID와 비밀번호를 구하는 작업, DB에서 질의를 수행하는 작업에서 주로 발생한다.

데이터베이스 양 자체는 과부하가 걸리지 않는다. 사용자 한 명당 1KB를 차지한다고 해도 60억개의 데이터베이스가 모여도 6TB이다.

과부하가 주로 걸리는 지점을 수평 확장 해보자.

인증 서버 수평 확장

인증 서버를 여러 대를 둔다.
클라이언트는 인증 서버 중 랜덤으로 하나에 접속하며, ID와 비밀번호를 보낸다. 이후 동일하게 진행된다.

하지만 만약 클라이언트가 인증서버 주소를 모를 경우, 클라이언트에서 들어오는 연결을 서로 다른 인증 서버에 분배해 줄 수 있다. 이러한 역할을 하는 것을 로드 밸런서 라고 한다.
로드 밸런서 자체가 과부하될 수 있지만, 로드 밸런서는 매우 많은 양을 처리할 수 있다.
로드 밸런서도 과부하에 종종 걸리는데, 이를 해결하고자 DNS서버의 작동 방식을 특이하게 하는 방식을 사용한다.

클라이언트가 서버에 접속하려면 도메인 이름을 IP주소로 변경해야 하는데, 이를 위해 클라이언트는 도메인 이름 서버에 IP주소가 무엇인지 묻는다. 이때 DNS는 로드 밸런서 중 하나를 무작위로 리턴한다.

데이터베이스의 수평 확장

플레이어 간 상호 작용은 없다고 전제했으므로 데이터베이스 자체는 확장하기 쉽다.
플레이어 정보를 서로 다른 데이터베이스 샤드에 골고루 나누어 저장한다. 클라이언트는 여러 인증 서버 중 하나에 접속하고, ID와 비밀번호를 보낸다. 인증 서버는 어느 데이터베이스 샤드에 질의를 실행해야 할지 판단해야 한다.

ID의 문자열을 이용해서 해시 함수를 실행한다. 해시 함수는 몇 번째 샤드인지를 가리키는 인덱스가 될 것이다.

하지만 데이터베이스 샤드가 추가된다면 이야기가 다르다.
샤드가 총 10개였을때 X MOD 10을 이용하여 샤드 인덱스를 얻었지만, 샤드를 하나 추가해서 X MOD 11이 되므로 다른 값을 반환한다.

따라서 해시 알고리즘에서는 항목 개수가 달라지면 기존에 있던 모든 데이터에서 항목 재배치를 한다. 이를 리해시 라고 한다.

하지만 레코드가 많다면 리해시 해야 할 개수가 많아지므로, 다른 방법을 사용해야 한다.

  1. 이동하는 레코드 개수가 최소가 되는 알고리즘을 사용한다.
  2. 이동할 일이 없게 만든다.
  3. 이동하더라도 이동하는 레코드 크기를 최소로 만든다.

첫번째 방법은 샤드를 추가할 때 기존 샤드가 맡던 해시 값 일부를 나누어 가지면 된다.
이런 방식을 사용하면, 데이터가 많아 샤드를 추가해야 하는 샤드 이외의 샤드는 그대로 유지된다.

두번째 방법이나 세번째 방법은 매핑 DB를 만들어 해결할 수 있다.

매치메이킹의 분산 처리

일반적으로 매치메이킹을 담당하는 서버를 로비 라고 한다.
10명이 게임방에 들어가는 매치메이킹 처리는 다음과 같이 진행된다.

  1. 게임을 플레이하려는 플레이어는 게임 시작하기를 누르고 대기한다.
  2. 플레이어와 실력이 비슷한 다른 플레이어 아홉 명이 게임 시작하기를 누르면 매칭 성공이다.
  3. 매칭 성공 후 플레이어 열 명이 플레이가 가능한 방을 만든다.
  4. 게임방에서 플레이어들이 채팅으로 작전 회의를 한다. 그리고 게임을 시작한다.

이러한 상황에서 과부하 지점을 찾고 분산 서버를 설계해보자.
먼저, 게임방 안의 월드 시뮬레이션을 위한 서버 CPU과부하
메치메이킹에 동시접속자가 몰릴 경우 서버 CPU, 램 사용 과부하가 있다.

성능을 분석해 보면 전투 플레이를 처리하는 곳이 가장 큰 비중을 차지한다.
매치메이킹을 위한 연산량보다 게임방 안 월드 시뮬레이션과 대량의 네트워크 메시지를 처리하는 연산량이 훨씬 많기 때문이다.

응집도 관점에서 생각해 보면, 한 게임방 안에서 플레이어들은 잦은 상호작용을 한다. 즉, 방 안의 플레이어들은 데이터 응집력이 높으며, 서로 다른 방에 있는 플레이어끼리는 응집력이 없다.
따라서 게임 서버에서 게임방 처리를 여러 서버를 분산시켜야 하며, 게임방을 처리하는 서버를 배틀서버라 한다.

이러한 방식의 장점은 매치메이킹 이후 레이턴시를 줄일 수 있다.
로비 서버 클러스터는 한 지역에 두고, 배틀 서버들은 여러 지역에 흩어 놓을 수 있다.
로비 서버는 레이턴시에 민감하지 않기 때문이다.

하지만 사용자가 점점 많아지다 보면 로비 서버도 과부하에 걸린다.
따라서 로비 서버도 분산시켜야 하며,

  • 플레이어 실력 정보
  • 매칭하기 버튼을 눌러서 대기 중인지 여부

가 필요하다.
이를 위해서는 데이터 복제에 기반을 둔 로컬 처리를 이용해야 한다.

  1. 플레이어 정보 목록을 서로 동기화한다. 플레이어 정보에는 플레이어 실력에 대한 정보와 매칭을 기다리는지에 대한 불형 값이 있다.
  2. 매치메이킹을 할 때 조건을 만족하는 플레이어를 이미 동기화받은 플레이어 정보 목록에서 찾아낸다.
  3. 매치메이킹이 끝난 플레이어를 배틀 서버로 이동시킨다.

하지만 이러한 방법은 하나의 메시지를 다른 모든 서버에 보내야 하므로,
로비 서버당 5000명이 접속해 있고, 로비 서버가 1000대 있을 때, 한 플레이어가 자신의 상태를 변경하면 다른 999대의 서버에게 같은 메시지를 보내야 한다.
또한 다른 999대의 서버에 플레이어 상태의 변화가 있을 경우 메시지를 모두 받아 처리해야 한다.
때문에 많은 양의 메시지가 생성되면 과부하로 인한 서비스 장애가 발생할 수 있다.

이는 데이터 응집력을 통해 해결할 수 있다.
실력이 비슷한 플레이어끼리 묶으면 높은 응집력을 가진다. 실력 차이가 크게 나는 플레이어들은 그들끼리 매칭할 일이 거의 없다. 따라서 각 로비 서버는 서로 다른 범위의 플레이어들을 가지면 된다.

다른 문제는 로비 서버 간 플레이어 목록 동기화 과정에서 데이터 불일치 문제가 발생할 수 있다.
서로 다른 서버에 있는 플레이어들이 매칭이 되어 게임이 시작될 때, 플레이어1은 배틀 서버로 이동했지만 매칭이 되었다는 메시지를 받기 전에 플레이어2가 매칭을 취소한다면 문제가 발생한다.
이는 매치메이킹 재시작을 자동으로 하는 등 수습 처리를 해야 한다.

profile
코린이

0개의 댓글