본문으로 바로가기
728x90
반응형
728x170

MMORPG서버 구조에 대해 알아보기 전에 MMOPRG에서는 어떤 요구사항이 있기 때문에 이런 서버 구조가 필요한지 부터 알아보도록 하겠습니다.

MMORPG의 서버쪽에서 가장 큰 문제점은 유저가 많다는 점입니다. 이 말은 동시 접속자 수가 많다는 이야기고, 서버가 감당해야 될 작업량이 많다는 것이죠.

그래서 작업량의 기준을 보면 게임에서는 Actor라고 보면 되는데요. 예를 들면 Player, NPC(몬스터 포함) 그리고 컨트롤 존(Control Zone)이라고 해서 조종되는 영역을 의미하는건데 예를 들면 이런 것이죠. 필드에 독이 깔려있을 때 Player나 NPC가 그 영역에 들어가면 도트 데미지를 받는 것인데  도트 데미지를 어떻게 주는지, 독 우물이라는 Actor가 있고, 그 독 우물이 매번 다른 Actor들이 올라 갔을 때 데미지를 주는 형태로 동작을 해야 합니다. 이런 형태의 것들을 컨트롤 존이라고 합니다.

이것 외에도 LOL 게임을 보게 되면 타워가 있고, 타워 영역 사이에 들어오는 것들을 알아서 공격을 해준다던지

본진 건물이 깨지면 파괴 된다던지 하는 이런 것들도 Actor라고 볼 수 있습니다.

그래서 Actor를 크게 보면

  • Player
  • NPC
  • Control Zone
  • 그 외..

로 볼 수 있습니다. 이런 Actor들을 서버가 매번 매 프레임마다 처리를 해주어야 합니다.

그래서 서버가 할 일을 크게 보면

 

첫번째로 패킷의 Read/Write기능이 필요하죠.


하나의 서버 프로세스에 클라이언트들이 마구 붙어서 패킷을 주고 받는데 전송된 패킷을 서버 안에서 처리를 해주어야 하는데 이동하겠다고 MoveRequest라는 패킷을 보내고, 그 패킷을 받았으면 그 패킷이 하나의 꾸러미 형태로 날아오기 때문에 이 꾸러미를 풀어내서 그 꾸러미가 어떤 의미를 갖고 있는지 패킷을 파싱 한 다음에 그거에 대한 액션을 행해야 합니다. 이동을 요청을 했으니 이동을 시켜줍니다.

이런 형태로 패킷을 Read하고, 파싱해서 액션을 취하는 형태, 액션을 취했으면 응답해주어야 하기 때문에 패킷을 Write하고, Send 하는 것까지 해서 패킷처리가 큰 부분이 있고

 

두번째는 Tick처리 입니다.

 

Tick처리라는 것은 매 프레임 Actor들의 동작을 업데이트 시키는 과정이라 생각하면 됩니다.

어떤 Actor가 어떤 출발지에서 도착지까지 이동하겠다 했을 때 매 Tick마다 이동속도에 맞추어서 Actor의 위치를 업데이트 해나가면서 이동처리 해주는 작업이죠.

 

세번째는 그 외 시스템들입니다.

 

경매장에서 물건을 사고파는 처리들, 공성전, 등등을 의미 합니다. 그래서 패킷처리와 Tick은 처리량을 봤을 때 Actor수에 비례하는데요. Actor수가 늘어나면 패킷처리와 Tick이 같이 늘어납니다.

그렇기 때문에 플레이어가 많아지면 처리량이 급격히 늘어난다고 볼 수 있습니다.

그래서 서버가 감당해야 될 Actor수가 있습니다.

하나의 서버 하나의 프로세스가 5000명을 감당해야 된다고 했을 때 한 프레임이 Tick 수라고 하는데 초당 Tick이 10FPS가 돈다고 했을 때 초당 10번의 Tick이 도는것이고, 이 말은 한 틱당 간격이 100ms당 틱이 한 번씩 돌아야 한다는 의미이고, 처리할 수 있는 시간의 갭이 100ms만큼의 시간이 확보가 된다는 것입니다.

그리고 그 사이에 모든 Actor의 Tick이 다 돌아야 합니다. 그러니까 모든 Actor의 Update를 처리해야 합니다.

그래서 100ms의 Tick을 처리하는데 동접자가 5000명이다.를 간단하게 산술로 따져봤을 때 5000/100을 해주면 되는데 0.02가 되죠.

하나의 Actor당 0.02ms안에 Upadate를 처리해야 합니다. 굉장히 짧은 숫자인데요.

하나의 Actor당 Upadate를 처리해야 하는 시간이 0.02ms는 너무 짧은 시간입니다. 할 일이 굉장히 많기 때문인데 이 부분은 너무 어려운 과제가 되는 것이고, 그렇게 때문에 MMORPG서버에서는 멀티쓰레드가 필수조건이 됩니다.

멀티쓰레드로 코어를 여러개를 나누어서 작업을 분산할 수 밖에 없습니다. 이렇게 분산해서 처리하면 1개의 CPU당 코어가 4개니까 확보 할 수 있는 시간이 400ms이기 때문에 시간을 좀 더 확보 할 수 있게 되죠.

그래서 멀티쓰레드가 중요한데 멀티쓰레드 환경에서 프로그래밍 하는 것은 굉장히 어렵다.

멀티쓰레드에서 프로그래밍 하기 위해서는 잘 짜여진 구조가 필요합니다.

어떤 구조 없이 멀티쓰레드를 하게 되면 싱글쓰레드보다 효율이 떨어지는 상황이 생길 수 있기 때문에 좋은 구조를 만드는게 중요하죠.

정리해보자면 MMORPG 서버의 근본 원인은 해야 할 일이 너무 많다는 것입니다.

그래서 하나의 CPU가 이 모든일을 다 처리하기에는 주어진 시간이 너무 짧기 때문에 멀티쓰레드가 필요한데, 멀티쓰레드를 하기 위해서는 효율적인 서버구조가 필요하죠.

그렇기 때문에 서버 구조를 잘 알아야 합니다. 본격적으로 MMORPG 서버 구조를 설명해보죠.


첫번째 구조는 싱글쓰레드 구조입니다.

크게 두 가지로 볼 수 있는데 Network I/O, Work Thread 입니다.

Network I/O는 저번에도 설명했듯이 Select, IOCP를 써서 멀티쓰레드 환경에 돌아 갈 수 있도록 만들 수 있습니다.

Network I/O는 멀티쓰레드 형태로 만들어져 있지만 Work Thread는 한계가 있습니다. Work Thread는 싱글 쓰레드로 보면 됩니다.

Work Thread는 패킷이 계속 날라오면 패킷을 계속 Read하고, Read한 패킷을 Toss해주어야 하는데 싱글 쓰레드이기 때문에 Toss할 때 중간에 패킷을 받아놓는 범퍼역할을 해주는 것이 필요합니다.

그 범퍼를 큐(Queue)를 놓고, 받아온 패킷을 큐에 계속 쌓고, 쓰레드가 매 Tick마다(또는 Update프레임마다) 10프레임이면 100ms마다 돌면서 큐에 있는 패킷을 전부 다 읽어와서 큐 안에 있는 것들을 다 비워냅니다.

100ms안에 다 비워야하죠. 그 다음에 모든 Actor를 다 돌면서 Tick을 다 돌고 나면 이 서버가 한 프레임에 할 일이 끝이 납니다.

그 후 100ms가 끝나는 순간 다음 틱이 또 와서 이 과정들을 반복합니다.

이런 형태의 구조는 크게 보면 Network I/O와 작업영역인 Work Thread쪽으로 나뉘어지고, 중간에 Queue가 있는 형태인데 이러한 구조는 어떤 서버의 구조든 다 비슷한 구조로 사용하게 됩니다.


Work Thread쪽이 하나냐 여러개냐에 따라 멀티쓰레드구조냐 싱글스레드구조냐가 달라지게 되는데요 NetWork I/O는 Select 또는 IOCP써서 만들기 때문에 거의 멀티쓰레드라 생각하면 됩니다.

NetWork I/O쪽으로 패킷이 막 날라오면 날라온 패킷을 큐에 계속 쌓고, 큐가 패킷을 쌓으면 매 Tick마다 매 업데이트 프레임마다 이 큐를 하나씩 다 가지고 와서 모두 처리하고, Actor의 Tick도 다 돌고나서 하나의 프레임이 끝나고 다음으로 넘어갑니다.

 

이 구조의 장점은 매우 심플하다는 것인데요. 싱글쓰레드이고 명확합니다. 싱글쓰레드가 하는 일은 큐에서 다 가져오고 다 처리하고, Tick 다 돌고 다음프레임 가는 것을 반복합니다.

그리고 싱글쓰레드이기 때문에 멀티쓰레드 상황에서 발생하는 데드락이라던지, 일의 우선순위, 타이밍이슈 등이 전부 없다. 그래서 굉장히 단순해진다. 그래서 작업하기 좋은데

단점으로는 짧은 시간 내에 많은 Actor를 처리할 수 없다는 점이다.

그래서 이런 서버를 만들면 동접이 1000 ~ 2000 잘되면 3~4000까지도 받을 수 있는데 그 이상은 어렵다고 생각하면 됩니다.

일의 우선순위를 잘 분배해서 얼마나 효율적으로 짜냐에 따라 이 구조가 얼마나 Actor를 핸들링 할 수 있냐가 결정되지만 5천이상 처리하기는 어렵습니다.

 

그래서 과거의 MMORPG서버들이 많이 사용했습니다.

두번째 구조는 싱글 쓰레드 + α 입니다.

여기서 α는 Dedicated쓰레드를 넣는 것입니다. Dedicated쓰레드는 정해진 일을 하는 쓰레드 라고 생각하면 됩니다.

구조는 똑같습니다. Network I/O 가있고, Work Thread가 있고 그 외에 α를 추가시키는 것입니다. 가령 DB I/O 쓰레드르 넣거나 하는 특정한 일을 하는 쓰레드를 추가 시킵니다.

그래서 기본적으로는 쓰레드가 하나 짜리인 WorkThread에서 모든 Actor를 처리하는데 간혹 경매장에 아이템을 올렸거나 할 때 α로 추가시킨 경매장 Thread에 Request를 보내고, 경매장 Thread가 경매장과 관련된 일들을 처리하는 형태로 되어 있습니다.

그래서 Work Thread와 경매장 Thread이 서로 다르기 때문에 어떤 통신을 할 때에는 Locking을 하거나 Async형태의 메세지로 전달을 하거나 하는 수단이 필요하죠.

싱글 쓰레드 + α의 구조는 싱글 쓰레드의 단점을 어느정도 보완한다고 볼 수 있습니다.

그래서 이 구조에서 싱글쓰레드가 하는 일은

  • 패킷처리
  • Actor Upadate

이고 그 외의 것들은 α에 뽑아내서 일들을 처리시킵니다.

이렇게 하면 성능이 올라가겠지만 여전히 5천명 이상은 무리지 않을까하는 추측이 있습니다.

그래도 이 구조는 싱글 쓰레드와 α 모두 이득을 가져갑니다. 싱글 쓰레드 형태의 단순함을 가져가면서 동시에 멀티쓰레드의 효과도 어느정도 가져가는 형태입니다.

세번째 구조는 멀티 쓰레드입니다.

가장 무식하게 멀티 쓰레드 하는 방식이라 생각하면 됩니다.

싱글 쓰레드 구조와 비슷하게 Network I/O가 있고, 그 옆에 Queue가 있고, 그 옆에 여러개의 Work Thread가 있습니다.
Network I/O에서 패킷이 들어오면 Queue에 쌓고, 여러개의 Work Thread들이 각각 Queue들을 가져와서 처리합니다.

그 후에 Actor List가 있는데 그 List를 뽑아내서 처리합니다.

이 구조는 그냥 단순 멀티쓰레드 구조입니다. 싱글쓰레드와 같은 구조인데 Work Thread만 여러개 있는 구조라고 생각하면 됩니다.

특징은 성능이 올라갈 수도 있는데 이 부분은 확실하지 않죠. 그냥 무식하게 멀티쓰레드구조가 되기 때문에 충돌이 굉장히 자주 일어납니다.

큐에서 먼저 뽑아 가는 놈이 임자라 Lock이 일어나고, Actor List에서도 먼저 뽑아가는 놈이 임자라 Lock이 일어납니다.

그리고 게임과 웹서버의 다른 점, 중요한 점이 뭐냐면 웹 서버는 어떤 Request가 오면 서버가 그 Request만 만 핸들링해서 Resoponse를 주면 끝나지만 게임은 어떤 Request가 왔어도 그 Request만 핸들링하면 안됩니다.

만약 A가 B를 공격했다. 했을 때 그 때 할 일은 A가 스킬을 써서 공격했을 때 A의 마나를 깎고, 데미지를 B에게 주고, B의 HP를 깎아야 합니다. 그래서 게임이라는 것은 근본적으로 보면 Actor간에 Interact입니다.

Actor간에 Interaction이 아주 빈번하게 일어나는데 문제는 쓰레드가 다른데 A라는 Actor가 S1이라는 쓰레드에서 처리되고, B라는 Actor는 S2라는 쓰레드에서 처리 되었을 때 A가 B를 공격했다. 그러면 이 멀티 쓰레드 환경에서는 단순하게 Locking을 처리하면 굉장히 복잡한 일들이 발생하고, Deadlock이 굉장히 많이 발생합니다.

Interaction이 굉장히 빈번하게 일어나기 때문입니다. B도 A를 때릴 수도 있고, 동시에 공격할 수 있어서 어느 쪽에서 먼저 Lock을 잡냐에 따라 Deadlock이 많이 발생합니다.

그래서 Locking을 잘 관리하지 않으면 복잡한 문제가 굉장히 많이 발생할 수 있습니다.

그래서 멀티쓰레드로 성능이 올라갈 수 있지만 굉장히 복잡한 문제가 많이 생길 수 있기 때문에 좋은 구조라고 볼 수 없죠.

그래서 단순한 멀티쓰레드로 해서는 문제가 해결되지 않습니다. 중요한 건 구획화를 잘 해야 합니다. 잘 나누어야 한다는 말이죠.

싱글쓰레드로는 감당이 안되기 때문에 멀티쓰레드를 하긴 해야하는데 어떻게 쓰레드들을 나눌 것인지, 어떻게 Actor들을 구획화시켜서 분리를 시킬 것인지가 관건이 됩니다.

정리하자면 싱글쓰레드 방식이 있고 굉장히 심플하기 때문에 추천하는 방식이지만 감당할 수 있는 Actor가 줄어듭니다.

그렇다고 해서 이게 큰 문제가 되진 않습니다.

멀티 쓰레드가 아니라 멀티 프로세스 구조로 가도 상관이 없습니다.

멀티 쓰레드라는 것은 하나의 프로세스에서 쓰레드만 여러개를 나눠서 각각 따로 동작하는 것이고, 멀티 프로세스라는 것은 하나의 컴퓨터에 여러 프로세스를 띄우는 것이고 각 프로세스에는 쓰레드가 하나씩 있습니다.

이렇게 보면 서로 간의 쓰레드마다 독립된 메모리 영역을 가지고 있기 때문에 서로 Interaction을 할 이유가 없고, Locking을 할 이유가 없고, 서로 충돌 할 이유가 없죠.

그런데 효과는 똑같이 가져갑니다.

그런데 멀티 프로세스 구조에서는 메모리 영역이 다르기 때문에 서로 Interaction자체가 안됩니다. (할려면 방법이야 있지만 그렇게 해서 쓸 거면 멀티쓰레드가 낫죠.)

그래서 Interaction을 못하기 때문에 A라는 유저가 B라는 유저를 때리는게 불가능 합니다.
게임에선 Interaction이 생명인데 이 구조는 여기에 안 맞지 않냐? 라고 말할 수 있는데 생각해보면 서로 못 때리는게 당연한 경우도 있습니다.

 

WOW라는 게임을 예로 들면 여러개의 대륙이 있는데 A라는 대륙에 있는 오크와 B대륙에 있는 휴먼은 서로 때리거나, 채팅하거나 할 필요가 없어요. 분리되어 있기 때문이죠.

그렇기 때문에 A대륙을 담당하는 프로세스와 B대륙을 담당하는 프로세스가 서로 통신할 이유도 없고, 서로 Interaction할 이유도 없습니다. 그런데 A대륙과 B대륙과의 경계선에서는 거리가 차이가 얼마 나지 않아 메세지를 주고 받을 수 있겠지만 그럴 때는 힘들어지기 때문에 B가 A쪽으로 넘어갈 때는 로딩화면이 나온다던지, 뭔가 타고 넘어가야 한다거나, 넘어가는 통로를 좁게해서 그 통로에서는 전투가 일어나지 않게 한다거나 아무런 Interaction이 일어나지 못하게 만들거나하는 식으로 제한을 가하면 각 영역을 나눠서, 구획화를 시켜서 각각을 멀티프로세서로 만들고, 그 프로세스에는 싱글 쓰레드 구조로 돌려서 심플함을 가져가면서 성능도 가져가는 방법이 있습니다.

 

그래서 싱글쓰레드라고 해서 나쁘다고 보면 안됩니다. 하지만 하나의 월드가 있고 모든 유저가 각각 월드에 모두가 Interaction 할 수 있고 중간에 로딩이 없고, 단절포인트가 없고, (이 것을 Seamless서버 라고 합니다.) 물 흐르듯이 가고 싶고 Actor가 많고, 하나의 프로세스로 가야 하는 상황이 생기는 경우에 멀티 쓰레드로 갈 수 밖에 없죠.

멀티 쓰레드로 가기 위해서는 구조, 구획화가 중요합니다. 그냥 무식하게 멀티쓰레드로 갔다가 답이 안나오는 상황이 생깁니다.

  싱글 쓰레드 구조 싱글 쓰레드 + α 구조 멀티 쓰레드 구조
구조 패킷을 읽어오는 Network I/O와 중간에 패킷을 저장하는 큐(Queue)가 있으며 이 큐를 처리하는 한 개의WorkThread가 있음. 싱글 쓰레드 구조에 Dedicated쓰레드만 추가 된 형태 1. 패킷을 읽어오는 Network I/O와 중간에 패킷을 저장하는 큐(Queue)가 있으며 이 큐를 처리하는 여러 개의 WorkThread가 있음.
2. 가장 무식하게 멀티 쓰레드 하는 방식이라 생각하면 됨.
장점 1. 매우 심플하고, 하는 일이 명확함.
2. 멀티쓰레드 상황에서 발생하는 문제들이 생기지 않음. (ex 데드락, 우선순위, 타이밍 이슈, 등)
1. 싱글 쓰레드의 단점을 어느정도 보완한다.
2. 싱글 쓰레드 형태의 단순함을 가져가면서 동시에 멀티쓰레드의 효과도 볼 수 있다.
성능이 올라갈 수는 있음.
단점 짧은 시간 내에 많은 Actor들을 처리 불가.   1. Actor간에 Interaction이 일어났을 때 Locking과 Deadlock이 많이 발생.
2. 굉장히 복잡한 문제가 많이 발생.
특징 1. 동접자 5천명 이상 처리하기 힘듦.
2. 과거의 MMORPG 서버들이 많이 사용.
1. 패킷처리와 Actor Upate만 싱글 쓰레드가 하고 나머지 일은 Dedicated쓰레드가 처리.
2. 동접자 5천명 이상 처리하기 힘듦
1. 구획화를 잘 해야 한다.
2. 동접자 5천명 이상 처리 가능

 

 

ckdqja135/Typescript-restful-starter

Contribute to ckdqja135/Typescript-restful-starter development by creating an account on GitHub.

github.com

 

728x90
반응형
그리드형

댓글을 달아 주세요