#10. 분산 서버 구조 사례

게임 서버 프로그래밍/게임 서버 프로그래밍 교과서

2020. 2. 17. 16:08

10.분산 서버 구조 사례

분산 서버 구조 사례

1. 로그온 처리의 분산
  • 로그온 분산 처리 과정

    • 클라이언트->서버 ID,Password를 암호화 해서 보낸다
    • 로그온 담당 서버(인증서버)에서 이 메시지를 받고 복호화한다
    • 서버는 복호화된 메시지에서 ID,Password를 구한다. 그리고 DB에 질의를 던지고 결과를 기다린다
    • DB에서 질의를 수행한 후 응답을 서버에 회신한다
    • 서버는 이를 받아 로그인 성공 혹은 실패를 판단. 그리고 로그온 결과를 클라이언트에 보낸다
  • 과부하 지점

    • 동시 접속자가 많아지면 인증 서버에서 CPU 사용량이 점점 과부하에 가까워진다

      • 로그온 메시지를 복호화하는 연산 과정 때문
    • 데이터베이스 서버도 과부하에 점점 가까워진다

      • 질의 분석이나 디스크 스토리지에서 읽기 엑세스를 하는 데 걸리는 시간 때문
  • 과부하 지점 수평 확장

    • 인증 서버 수평 확장

      • 클라이언트가 서버 주소 목록을 가지는 경우

        • 수평 확장된 인증 서버 중 하나에 접속
        • ID/Password를 보내고 질의 과정을 통해 서버는 클라이언트에 로그온 결과를 보낸다
      • 클라이언트가 서버 주소 목록을 가지고 있지 않는 경우

        • 로드 밸런서

          • 클라이언트에서 들어오는 연결을 서로 다른 서버에 분배해주는 하드웨어
          • 로드 밸런서는 많은 양을 처리할 수 있어 과부하가 거의 걸리지 않는다

        • 라운드 로빈 DNS

          • 복수의 로드 밸런서를 두고 DNS가 로드 밸런스 중 주소 하나를 무작위로 리턴하는 방법
          • 클라이언트는 DNS에서 도메인 이름에 대한 IP 주소를 일단 확보 하면 로컬 디스크에 저장 해 둔다. 그 후 며칠 동안은 그것을 계속 사용하기에 DNS 과부하가 걸리는 상황은 없다.
2. 데이터베이스의 수평 확장
  • 해쉬 함수를 이용한 수평 확장

    • 클라이언트는 여러 서버 중 하나를 무작위로 골라서 로그온 요청을 한다
    • 서버는 로그온 요청을 받으면 입력받은 ID를 해시 함수에 넣어서 샤드 인덱스를 구한다
    • 서버는 해당하는 데이터베이스 샤드에 질의를 던져서 나머지를 실행한다
  • 샤드

    • 데이터베이스의 테이블을 레코드 묶음으로 나눈 집합
int Hash(string userID, int shardCount)
{
    int x = 0;
    foreach(ch in userID)
    {
        //0에 xor 연산하면 ? ch 그대로가 x에 담긴다 
        //그 다음 부턴 절대 전의 ch와 같은 값을 가질 수 없는 조합이 된다
        x ^= ch;
    }
    //미리 정해둔 샤드 개수 만큼 리턴 (이 값이 샤드 인덱스가 된다)
    return x % shardCount;
}
  • 샤드키

    • 어떤 키로 레코드를 찾는데, 그 레코드를 찾기 전에 그것이 어느 데이터베이스 샤드에 있는지 먼저 알아내려는 목적으로 사용되는 키. 파티션 키라고도 불린다
  • 데이터베이스의 업데이트

    • 샤드 개수가 추가되는 경우

      • 위의 MOD 방식의 샤드 인덱스는 샤드가 추가 됨으로써 값이 달라져버린다. 리해시를 하여 해결한다.

      • 리해시

        • 해시 테이블 알고리즘에서 항목 개수가 달라지면 기존에 있던 모든 데이터에서 항목 재배치를 하는 것
        • 총 레코드 수가 굉장히 많을 때 서버는 점검 상태가 되어야한다
        • 문제를 개선하려면 샤드가 추가되거나 제거될 때 레코드 이동으로 발생한 정지 순간이 최소한이어야 한다
  • 리해시 문제의 해결 방법

    • 일관된 해시 알고리즘

      • 항목 개수를 고정시키고 한 항목을 해시 함수의 여러 반환 값에 대응 하는 것
      • 샤드가 추가될 때마다 기존 샤드 중 하나가 담당하던 해시 값의 범위를 나누어 준다
      • 샤드가 제거될 때는 제거 되는 샤드가 인접한 샤드에 해시 값의 범위를 나누어 준다
      • 샤드가 1개 늘어나더라도 다른 샤드 중 하나만 재배치 연산을 하면 된다.
    • 매핑 DB

      • 데이터 자체보다 데이터를 가리키는 또 다른 데이터를 다루는 방법

      • "어떤 키는 어떤 샤드에 있다." 라는 정보만을 가진다

      • 데이터베이스 샤드가 증설되더라도 인접한 샤드에서 레코드를 옮겨 올 필요가 없다.

        해당 키를 관리하는 DB가 따로 있기 때문

      • 샤드 삭제시 레코드들을 다른 샤드로 옮겨야 한다. 이때 블로킹은 어쩔 수 없이 일어난다.

        다만, 사용자수 급감으로 샤드 개수를 줄일 때는 신속하지 않아도 되기에 블로킹에 의한 지연은 큰 의미가 없다

      • 매핑 DB가 과부하에 걸린다면 ?

        • 다른 모든 것이 확장성을 대비 했어도 아무 소용이 없어진다
        • 매핑 DB도 확장성을 가져야 한다.
    • 최종적으로 로그인을 담당하는 분산 서버

      • 로그인 서버
      • 매핑 DB 서버 샤드
      • 플레이어 정보를 담은 DB 서버 샤드
    • 이에 해당하는 프로세스 시퀀스

      • 클라이언트는 인증 서버 중 하나에 접속
      • 매핑 데이터베이스 중 하나를 골라 유저 ID에 대응하는 데이터베이스 #ID를 얻는 질의를 던진다
      • 인증 서버는 해당 DB에 CURD 질의를 던진다
    • 서버를 접속할 때마다 로그인을 해야하는 문제

      • 로그인 서버와 용무를 끝낸 후 다른 서버에 접속할 때도 로그인을 해야하는 문제

      • 모든 서버가 로그인 로직을 가지고 있어햐 하므로, 데이터베이스에 부하가 걸린다

      • 해결 방법

        • 클라이언트는 로그인 서버에 ID/PassWord를 보낸다
        • 로그인 서버는 클라이언트에 로그인 성공여부를 보낸다
        • 로그인 서버는 서버B에 크리덴셜을 보낸다
        • 서버 B는 ACK를 인증 서버에 보낸다
        • 로그인 서버는 클라이언트에 크리덴셜을 보낸다
        • 클라이언트는 서버 B에 연결한다
        • 크리덴셜도 보낸다
        • 서버는 크리덴셜을 보고 유저가 누군지 알아채고 로그인 성공을 클라이언트에 회신한다
      • 크리덴셜

        • 비밀 징표란 뜻(대략 ID+Password 조합)
3. 매치메이킹 분산 처리
  • 매치 메이킹

    • 서로 다른 플레이어 간 대전이나 협력 플레이를 위해, 다른 플레이어들을 찾아 모으는 것
  • 로비 서버

    • 매치메이킹을 담당 하는 서버
  • 서버 하나가 매치메이킹 + 게임방 처리를 한다면?

    • 게임방 안의 월드 시뮬레이션을 위한 서버 CPU 과부하

    • 매치메이킹에 동시접속자가 몰릴 경우 서버 CPU, 램 사용량 과부하

    • 과부하 지점

      • 게임방 안 전투 플레이를 처리하는 곳
    • 과부하 해결 방법

      • 응집력에 따른 분산

        • 방 안에 있는 플레이어끼리는 데이터 응집력을 높임
        • 서로 다른 방에 있는 플레이어끼리는 응집력이 없음
  • 매치 메이킹은 가까운 리전에 있는 플레이어 끼리 매칭 시킴

    • 네트워크 레이전시가 줄어든다

      • 로비 서버는 레이턴시에 민감하지 않다
  • 매치 메이킹의 최소 조건

    • 나는 지금 매칭을 기다리는 중
    • 너도 지금 매칭을 기다리는 중
    • 너와 내 실력이 비슷(매칭 기준)
  • 로비 서버의 과부하

    • 매치메이킹만 담당하는 로비 서버는 한 대뿐이므로 동시접속자가 계속 증가하면 로비 서버도 결국 과부하에 걸린다
  • 로비가 분산되었을 때

    • 동기 분산 처리

      • 다른 로비 서버의 플레이어 목록을 뒤지는 데 서버 간 통신 횟수가 너무 많다
    • 비동기 분산 처리

      • 다른 서버에 명령은 보낼 수 있으나 그 명령에 대한 응답을 받을 방법이 없다
  • 매치 메이킹을 위한 의사코드( 비동기 분산 처리)

Match(player)
{
    var list;
    for each(p in players)
    {
        //이 코드 구현 자체가 불가능하다 (비동기에서)
        if(p != player
          && p.waitingForGame
          && SmilarTo(player.elo , p.elo))
        {
            list.add(p);
        }
    }
    if(list.count == 9)
    {
        StartGameRoom(list);
    }
}
  • 데이터 복제에 기반을 둔 로컬 처리

  • 작동 방식

    • 로비 서버를 여러 개 둔다

      • 클라이언트는 로비 서버 중 아무것이나 하나 골라 접속한다
      • 모든 로비 서버 간 통신을 통해 플레이어 목록을 동기화 한다 (매치 메이킹을 기다리는 것들만 동기화)
    • 매칭 대기 취소 버튼을 누르면 그 정보도 다른 서버에 알린다

      • 플레이어 정보에는 플레이어가 어떤 로비 서버에 있는지를 담고 있어야 한다
  • 잠재적인 문제점1

    • 로비 서버 개수가 많은 경우 초당 보내야하는 메시지가 너무 많아 과부하가 걸린다
  • 해결 방법

    • 서로 같은 범위의 실력을 가진 플레이어들을 같은 로비 서버에 넣는다

      • 비슷한 실력의 플레이어끼리만 동기화 하므로 결과적으로 각 로비 서버가 수신하거나 송신해야 할 데이터의 양이 적어진다
  • 잠재적인 문제점2

    • 로비 서버 간 플레이어 데이터를 공유할 경우 간발의 차이로 옛날 데이터를 엑세스하는 불상사가 생긴다 = 데이터 스테일 문제
  • 해결 방법

    • 근본적인 해결 방법은 없다. 매치메이킹 재시작을 자동으로 하는 등 수습 처리를 해야 함

      • 메모리저장소에 플레이어 목록을 보관한다

        • 로비 서버 안에 연산량이 많고, 메모리 저장소 서버 내부 연산량이 적을 때 효과적
        • 메시지 통신량이 많은 경우 메모리 저장소 서버를 수평 확장(파티셔닝)하는 방법으로 해결 가능
4. 몬스터 NPC 처리의 분산 처리
  • 몬스터 처리에서 성능 문제

    • 몬스터 수는 플레이어 수보다 훨씬 많다.
    • 몬스터의 길찾기 알고리즘은 A* 알고리즘이기 때문에 연산량이 많이 필요하다
    • 연산 로직에 기획자의 아이디어가 많이 들어가기에 구조가 복잡해 진다
  • 해야 하는 연산량보다 메시지 송수신에 더 많은 처리량이 낭비됨

    • 가장 많은 연산을 차지하는 길찾기 연산 부분만 기능적으로 분산하는 방법이 좋다

      • 게임 서버와 길찾기 연산 전담 서버를 분리
      • 게임 서버가 길찾기 서버에 시작점과 끝점 정보를 보낸다
      • 길찾기 서버는 두 점을 잇는 경로 정보를 응답한다
5. 플레이어 간 상호 작용 분산 처리
  • 특징

    • 매우 정확해야 한다
    • 높은 성능 수준이 요구된다
    • 원자성이 있어야 한다
    • 간발의 지연 시간도 생각해야 한다
  • 플레이어 간 상호 작용 처리는 분산하지 않는 것이 일반적이다. 굳이 분산 한다면 지역적 분산 처리를 한다

6. 로그 및 통계 분석의 분산 처리
  • 게임 서버에서는 플레이어들의 행적, 즉 로그를 모집하고 통계 분석을 한다

    • 이러한 처리는 순간적으로 많은 부하를 일으킨다
    • DB에 기록하는 시간은 파일에 직접 기록하는 시간보다 훨씬 많이 걸린다
    • DB에 쌓인 데이터를 분석하는 경우는 굉장히 오랜 시간이 소요된다
    • 로그를 담는 DB에 순간적으로 디스크 I/O가 많이 발생한다
  • 게임 서버와 로그 서버는 분리 해야한다

    • 로그 DB는 신뢰성이 보장되어야 한다

      • 로그 DB가 죽더라도 신뢰성을 가지는 방법

        • 게임 서버에서 로그를 남기라고 지시한다
        • 그 지시를 로컬 파일에 일단 저장
        • 로그 DB에 질의를 던진다
        • 질의 실행 후 로컬 파일에 있는 해당 내용을 삭제
        • 로그 서버가 죽었다 켜지면 로컬 파일에서 아직 기록하지 않은 로그를 로그 DB에 채워 넣음
  • 역할에 따른 다양한 서버와 종류

    • 순위 서버

      • 순위 연산은 많은 연산량을 차지하므로 순위만 담당하는 서버를 분리하는 것이 일반적
    • 메신저 채팅 서버

      • 게임 서비스가 불안정한 상황에서도 채팅이 잘 되게 만들고 싶을 때는 메신저와 채팅 서버를 별도로 분리 함
7. 게임 장르별 분산 서버 형태
  • 게임 장르별로 어느 정도는 유사한 패턴이 있으므로 게임 서버 분산 모델을 설정할 때 도움이 된다

  • MMORPG

    • 게임 월드를 구성하는 소규모 지역별로 담당 서버를 둔다

    • 경매장 같은 특수한 목적의 서버를 별도로 분리하기도 한다

    • NPC 관련 로직에 과부하가 걸리면 분산. 플레이어 이동도 마찬가지

    • 소켓 I/O에서 과부하가 걸리면 게임서버와 클라이언트 사이에 패킷 릴레이 서버를 두어 릴레이 서버가 멀티캐스트를 분담

    • 무제한의 동시접속자 처리 방법

      • 고전적 분산 처리 방식

        • 게임 월드 하나를 담당하는 서버 세트와 똑같은 것들을 계속해서 증설하는 방식
      • 클라이언트 메시지를 적절한 서버로 전달해 주는 릴레이 서버와 넓은 지역을 작은 단위로 나누어 처리하는 로직 서버로 구별

        • 플레이어 수만 명에서 수백만 명이 매우 넓은 하나의 지역에서 플레이할 때 이용
    • 심리스 MMO 서버 구조

      • 로직 서버가 담당하는 캐릭터 수가 지나치게 많아지면, 로직 서버가 담당하는 캐릭터의 소유권을 인접한 다른 로직 서버에 넘겨준다. 그리고 로직 서버가 담당하는 지역의 넓이를 줄이는 방식.
      • 서버당 통신량과 OS 레벨 처리량이 많이 증가한다. 서버 운영 효율성이 떨어진다
      • 스테일 , 블로킹 문제가 발생하기도 한다
      • 이음새 없는 거대한 월드 안에 동시접속자가 수만 명 이상 있어야 하는 온라인 게임이라면 심리스 MMO 서버 구조가 유일한 해결책
  • 방을 만드는 게임 (MOBA, FPS, MMORPG)

    • 수평 확장된 로비 서버가 있고, 여기서 매치메이킹을 담당한다
    • 다수의 게임 플레이방을 담당하는 배틀 서버를 구성한다
  • SNG(소셜 게임)

    • 게임 플레이 로직을 담당하는 서버와 게임 플레이어의 메모리 내부 정보를 가진 서버가 분리된다
    • 플레이어 간 상호 작용 횟수가 MMORPG,FPS 게임에 비해서 훨씬 적다
    • 플레이어 간 상호 작용에서 발생하는 지연 시간이 어느정도 길어도 된다
    • 상호작용 해야 되는 플레이어 범위가 훨씬 넓다
    • 비동기 멀티플레이를 하는 모바일 RPG 게임에서도 종종 이렇게 구성한다

'게임 서버 프로그래밍 > 게임 서버 프로그래밍 교과서' 카테고리의 다른 글

#9. 분산 서버 구조  (0) 2020.02.17
#8. NOSQL  (1) 2020.02.17
#7. 데이터베이스의 기초  (0) 2020.02.17
#6. 프라우드넷  (0) 2020.02.17
#5. 게임 네트워크  (0) 2020.02.17