분산 서버 구조
1. 수직 확장과 수평 확장
확장성
사용자 수가 늘어나더라도 쉽게 대응할 수 있어야 한다는 의미
확장성의 목표
최대로 처리할 수 있는 사용자 수가 무제한이어도 가능해야 한다
스케일 업 = 수직 확장
- 서버의 하드웨어를 더 좋은 것으로 교체하여 처리량을 늘린다
스케일 아웃 = 수평 확장
- 서버 대수를 늘려서 더 많은 처리를 하는 것
- 더 현실적인 방법
- 소프트웨어 설계가 복잡하다
- 수직 확장으로 향상되는 서버보다 단위 처리 속도가 더 느리다
- 수평 확장 하기 전 보다 더 느려질 수 있다
2. 서버 분산이 없는 경우
모든 게임 서버 로직은 서버 프로세스 하나에서 수행한다
모든 플레이어 정보는 데이터베이스 하나에 저장한다
동시 접속사 수가 무제한으로 증가한 경우
서버로 보낸 메시지에 대한 처리 응답이 늦게 도착
서버 접속 과정이 매우 오래 걸린다
서버와 연결이 돌발 해제 된다
서버 접속 자체가 실패하여 타임아웃 현상이 발생
서버에서 발생하는 현상
- CPU 사용량이 증가
- 클라이언트에서 메시지를 받는 속도보다 메시지를 처리하는 속도가 느릴 때 메모리 사용량 증가
- 클라이언트에 보낼 메시지의 발생 속도보다 실제로 메시지를 보내는 속도가 느릴때 메모리 사용량 증가
- CPU 과부하는 램 사용량 증가로 이어진다
데이터베이스에서는 디스크의 최대 처리 속도를 웃도는 I/O를 요구하는 명령이 쌓임
서버에 물려 있는 네트워크 기기에 과부하가 걸림
- 라우터 과부하로 패킷 유실 발생
- TCP 재전송 타임아웃으로 TCP 연결 해제 발생
- TCP 소켓에서 ECONNABORTED 오류 발생
3. 고전적인 서버 분산 방법
서버 클러스터
인증 서버
- 사용자가 ID/PassWord 입력했을 때 그것을 인증 처리하는 역할을 함
- 하는 일이 적다 보니 굳이 수평 확장을 하지 않아도 됨
채널 서버
스위치/라우터
방화벽
- 악성 해커에게서 서버 클러스터를 보호
장점/단점
간단한 분산 서버 구성은 즉시 할 수 있다
게임 기획과 관련된 문제가 있다
- 같은 계정이라도 플레이어 정보가 서로 다른 채널 서버에 있으므로 플레이어가 열심히 키워놓은 캐릭터를 다른 채널 서버에서 쉽게 사용할 수 없다
- 플레이어는 자기가 플레이했던 채널 서버에서만 계속 게임을 해야 한다
- 모바일 게임과 글로벌 서비스 게임에서는 채널 서버를 선택하는 과정이 없을 때가 많다
논리적으로 단일서버이지만, 실제로 서버 클러스터 형태인 서버를 개발할 필요가 있다
4. 논리적 단일 서버 분산
게임 서버 분산 절차
단일 서버 기준에서 과부하가 걸리는 지점을 분석해 파악
- 라우터,스위치 / 방화벽 / CPU / 스토리지중 하나가 한계에 부딪히면 분산처리
- 엄선한 지점에서만 분산 처리를 하도록 추가 개발하는 것은 경제적이다
- 성능 분석 도구를 사용해서 어떤 함수가 가장 많은 처리량을 차지하는지 확인후 최적화
과부하가 걸리는 지점을 여러가지 분산 처리 방식으로 분산
데이터 단위 분산
기능 단위 분산
게임 로직의 분산 처리 방식
- 동기 분산 처리
- 비동기 분산 처리
- 데이터 복제 및 로컬 처리
5. 데이터 분산 vs 기능적 분산
데이터 분산
한 머신이 처리해야 하는 데이터를 같은 역할을 하는 여러 머신이 나누어서 처리하는 것
기능적 분산
한 머신이 처리해야 하는 데이터의 처리 단계를 세분화해서 여러 머신이 나누어 처리하는 것
데이터 단위 분산 (데이터베이스 개념)
테이블 1개를 테이블 안의 키 필드 단위로 분배하는 것
기능 단위 분산 (데이터베이스 개념)
서로 다른 테이블을 서로 다른 서버에 배치하는 것
6. 로직 처리의 분산 방식들
- 상호작용 의사코드
// 분산 처리가 안된 코드
Player_Att(player,Monster)
{
player.bullet--;
monster.hitpoint -=10;
if(monster.hitpoint <0)
{
player.item.add(gold,30);
DeleteEntity(monster, 10sec);
}
}
동기 분산 처리
- 어떤 연산을 다른 서버에 던져 놓고 그 결과가 올 때까지 대기
- 그 연산과 관계된 데이터가 도중에 변경되지 않게 잠금(lock)을 해야 한다.
동기식 명령 처리법
Player_Att(player, monster)
{
lock(player)
{
player.bullet--;
e = otherServer.DamagerCharacter(player.id,monster.id,10);
waitForResult(e);
if(e.hitPoint < 0)
{
player.item.Add(gold,30);
}
}
DamageCharacter(attacker, character, damage)
{
character.hitPoint -= damage;
result.hitPoint = character.hitPoint;
Reply(result);
}
}
- 분산 락 기법
Player_Attack(player, monster)
{
lock(player)
{
player.bullet--;
//몬스터 정보를 엑세스하기 위해 분산 락을 요청
otherServer.RemoteLock(monster);
//서버2에서 몬스터 정보를 얻어옴
m = otherServer.RemoteGet(monster.id);
m.hitPoint -= damage;
if(m.hitPoint <0)
{
player.item.Add(gold,30);
//서버2의 몬스터 정보를 업데이트 함
otherServer.RemoteDelete(monster.id);
}else
{
//서버2의 몬스터 정보를 업데이트 함
otherServer.RemoteSet(m);
}
//서버 2에 분산 락 해제를 요청하고 응답을 받음
otherServer.RemoteUnlock(m);
}
}
동기식 데이터 변경법 특징
서버 1에서는 서버 2에 명령을 보낸 후 이에 대한 응답이 올 때까지 기다려야 한다
동기식 데이터 변경법에서는 메시지 왕복이 총 세 번 오간다. 따라서 최소 120 마이크로초가 걸린다
서버 1에서 플레이어 정보를 보호하는 임계 영역이나 뮤텍스가 지나치게 광범위하게 보호하는 경우
40~120마이크로초만큼 시간이 지연될 가능성이 높아진다
멀티스레드 혹은 멀티 프로세스로 작동하거나 뮤텍스 잠금 범위를 좁혀야한다
비동기 분산 처리
Player_Att(player, monster)
{
player.bullet--;
otherServer.DamageCharacter(player.id, monster.id,10);
}
DamageCharacter(callerServer, attacker, character, damage)
{
character.hitPoint -= damage;
if(character.hitPoint < 0)
{
callerServer.GiveItem(attacker.id, gold, 30);
DeleteEntity(character, 10sec);
}
}
GiveItem(character, item, amount)
{
character.item.Add(item, amount);
}
비동기 분산 처리의 특징
- 동기 분산 처리와 달리 잠금으로 인한 병목이 없음
- 모든 로직을 이 방식으로 구현하기 어렵거나 불가능함
- 요청에 대한 응답을 기다리는 과정이 없기 때문에 반환 값을 주고받을 수 없음
- 서로 일방적인 통보를 주고받아야 함
동기식, 비동기식,데이터 복제 로컬 분산 처리의 단점
- 중요한 핵심 처리는 기계어 명령어 수십~수백 개인데, 분산된 서버 간 대화에 기계어 명령어가 수천 개 사용되므로 과도하게 분산 처리를 하면 비효율적인 상황이 발생
데이터 복제에 기반을 둔 로컬 처리
Player_Att(player, monster)
{
player_bullet;
monster.hitPoint -=10;
if(monster.hitPoint < 0)
{
player.item.Add(gold,30);
DeleteEntity(monster, 10sec);
}
}
데이터 복제의 특징
- 다른 서버 데이터를 들고 있으며 로직에 따른 데이터 변경 후 그것을 다른 서버에 알림
- 한쪽 서버에서 데이터 변화가 발생하면 나머지 서버에도 전파된다. 즉 복제가 일어난다.
- 분산 처리에서 발생하는 병목 현상이 없을 뿐만 아니라 여러 머신에 걸쳐 연산하지도 않으므로 응답 속도도 분산하기 전과 같이 빠르다
- 사본 데이터는 원본 데이터와 간발의 차이로 생기는 스테일 데이터 문제가 있을 수 있다.
- 하이젠버그 (찾아 보려고 하면 바로 사라지는 버그)가 생김
7. 데이터 응집도
응집도
특정 영역 안에 얼마나 많은 데이터가 관련되고 뭉쳐 있는지에 대한 의미
어떤 데이터가 있을 때 그 데이터와 자주 상관되는 다른 데이터가 얼마나 많은지에 대한 의미
응집도가 높은 데이터끼리는 가급적 분산 처리를 하지 말고, 응집도가 낮은 데이터에 대해 분산 처리를 하라
좁은 지역에 많은 캐릭터가 있을 경우 서버 한 대가 이것을 모두 처리하는게 낫다. 캐릭터의 지리적 위치를 응집도를 기준으로 하여 가까운 거리에 있는 캐릭터끼리는 같은 서버에 두는 것
매치메이킹
- 가급적 실력이 비슷한 플레이어끼리 매칭
- 데이터 응집도의 기준이 '플레이어 실력'으로 잡을 수 있다
8. 기능적 분산 처리
수평 분산 처리를 못하는 경우
- 암달의 법칙이 심하게 작용 (동기식)
- 요청과 응답을 받아야하는 경우(비동기식)
- 데이터 일관성이 깨지는 경우(데이터 복제)
기능적 분산 처리 / 수직 분산 처리
수평 분할을 할 수 없을 때의 선택될 수 있는 대안
경매장
- 플레이어 간 입찰 경쟁과 낙찰 과정이 원자성을 가지고 작동
- 게임 서버에서 경매장을 담당하는 처리를 분리, 경매장 처리만 담당하는 서버를 개발
분산 처리를 할 수 있는 범위가 제한적
- 분산의 양은 기능을 쪼갠 만큼만 할 수 있음
- 수평 분산 처리보다 효율이 떨어짐
최후의 수단. 수평 분산 처리 할 수 있는 방법을 최대한 찾아본 후 적용
9. 분산 처리를 엄선해야 하는 이유
- 디버깅이 까다롭다
- 운영체제가 해야 하는 일을 불필요하게 증가시킨다
- 클라우드 서버 환경에서는 클라우드 서버 인스턴스 간에 통신 회선의 신뢰성도 문제가 된다
- 분산 처리는 꼭 해야 하는 이유를 설명할 수 없다면 피하는 것이 좋다
10.분산 처리 전략
- 성능 분석을 하여 분산 처리가 필요한 지점을 엄선
- 데이터 응집력을 확인. 상호 작용이 높은 것은 분산하지 말고, 상호 작용이 적은 것들만 분산
- 지리적 구분, 플레이어 레벨이나 등급에 따라 분산
- 분산 처리 방식은 동기 분산 처리, 비동기 분산 처리, 데이터 동기화에 기반을 둔 로컬 처리
- 불필요한 분산은 하지 마라
11. 분산 서버의 또 다른 장점
- 확장성뿐만 아니라 안정성에도 효과를 줌
12. 고가용성
사용자가 항상 서비스를 이용할 수 있게 하는 것
- 사용자 입장에서는 논리적으로 서버 한 대처럼 보이지만, 이 서버의 정체는 컴퓨터 여러 대로 구성된 서버 클러스터
- 이 서버 중 몇몇 서버가 과부하가 걸리거나 오작동으로 정지하더라도, 나머지 서버가 계속해서 사용자 요청을 처리할 수 있음
장애 극복
- 서버 클러스터에 있는 서버 중 하나가 죽었을 때, 다른 서버가 죽은 서버를 대신해서 일을 하고 그동안 죽은 서버가 다시 살아나는 것
다중화 / 이중화
장애 극복을 위해서 필요 이상의 서버를 둔다
액티브-패시브 패턴
- 액티브 서버만 클라이언트 요청을 전담
- 패시브 서버는 필요할 때 액티브 서버의 데이터를 지속적으로 복제
- 전담하던 액티브 서버가 죽으면 패시브 서버는 액티브 서버로 승격. 클라이언트 요청을 처리
- 죽었던 액티브 서버가 다시 살아나면 이 서버는 패시브 서버가 된다
- 패시브 서버는 그저 백업 역할만 할 뿐 다른 하는 일이 없다. 자원의 낭비로 이어짐
액티브-액티브 패턴
- 서버 두 대가 클라이언트 측 요청을 분담하여 처리하고, 필요할 때면 두 서버는 각자 가진 데이터를 상대방에게 전송한다
- 두 서버의 상태가 서로 동기화 된다
- 경우에 따라 데이터 스테일 문제를 해결하려는 노력이 필요하다
- 두 액티브 서버 사이에 메모리 저장소 역할을 담당하는 서버를 두면 해결
- 데이터에 접근할 때마다 기기 간 통신이 발생한다는 단점이 있다
- 메모리 저장소 서버가 죽는 경우를 해결하지 못한다
- 메모리 저장소 서버도 이중화를 해야한다
13. 데이터베이스의 분산
게임 서버가 분산 처리되어 있다고 해도 데이터베이스를 분산 처리 하지 않으면 서비스 가용성은 떨어진다
파티셔닝
- 더 많은 사용자를 처리하고자 데이터베이스가 수평 확장을 할 때는 갖고 있는 레코드를 서로 다른 데이터베이스에 나누는 것
수직 파티셔닝
- 레코드 일부 필드를 다른 테이블로 나누어 놓고 그것을 다른 데이터베이스 서버에 두는 것
데이터베이스도 고가용성을 위해 이중화를 한다. 같은 내용의 레코드를 서버 두 대 이상에 저장하는 것
'게임 서버 프로그래밍 > 게임 서버 프로그래밍 교과서' 카테고리의 다른 글
#10. 분산 서버 구조 사례 (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 |