GameAnvil은 크게 두 가지 방법으로 서버를 구성할 수 있습니다. 가장 대표적인 방법은 NHN Cloud의 콘솔을 통해 구동할 서버를 GUI 상에서 구성하는 것입니다. 이 방법은 클라우드 상에서 VM 기반의 서비스 또는 테스트를 위해 사용하는 방법입니다. 하지만 이 방법은 개발 과정에서 사용하기는 번거롭고 불편합니다. 그래서 사용자가 개발 중에 PC에서 직접 서버를 구성할 수 있도록 GameAnvilConfig.json 파일을 제공합니다. 이 파일의 기본 경로는 프로젝트의 resources/를 사용합니다. 만일 여러 개의 프로세스로 서버를 구성할 경우에는 각각의 프로세스마다 고유한 GameAnvilConfig.json이 필요합니다.
이 구성 파일에 입력하는 값 중 일부는 서버 코드상에서 엔진에 등록할 때 사용하는 값과 일치해야 합니다. 예를 들어 앞서 아래와 같이 구성한 GameNode에서 사용된 "MyGame"이라는 서비스명은 반드시 GameAnvilConfig.json에도 동일하게 설정되어 있어야 합니다. 그렇지 않으면 해당 서비스를 찾지 못하여 서버 구동 과정에서 오류가 발생합니다.
var gameServiceBuilder = gameAnvilServerBuilder.createGameService("MyGame");
gameServiceBuilder.gameNode(SampleGameNode::new, config -> {
// 여기에 핸들러를 추가합니다
});
그럼, 이러한 내용을 한 번 살펴보도록 하겠습니다.
GameAnvilConfig은 서버를 유연하게 구성하기 위해 매우 많은 수의 설정을 제공합니다. 대부분 엔진 기본값으로 충분하므로 여기에서는 사용자의 이해가 필요한 설정값들만 설명합니다. 크게 다섯 가지로 나뉩니다.
노드 구성과 상관없이 필수적인 공통 정보를 설정합니다.
"common": {
"ipcIp": "127.0.0.1", // 내부 네트워크끼리 통신하는 IP. (머신의 IP를 지정)
"meetEndPoints": ["127.0.0.1:18000"], // 대상 노드의 common IP와 ipcPort 등록. (해당 서버 endpoint 포함가능 , 리스트로 여러개 가능)
"debugMode": false // 디버깅 시 각종 timeout이 발생하지 않도록 하는 옵션, 리얼에서는 반드시 false여야 함
},
각 구성 항목의 설명은 다음과 같습니다.
이름 | 설명 | 기본값 |
---|---|---|
icpIp | 노드마다 공통으로 사용하는 IP로(머신의 IP를 지정) 값이 없을 경우 private ip로 자동 지정됩니다. | - |
meetEndPoints | 대상 노드의 icpIp와 ipcPort 등록합니다. 해당 서버 endpoint 포함 가능 리스트로 여러 개 등록 가능합니다. |
- |
debugMode | 디버깅 시 각종 timeout이 발생하지 않도록 하는 옵션으로 서비스 시에는 방드시 false 여야 합니다. | false |
사실 로케이션 노드는 전체 서버의 유저와 방 위치 정보를 담당하는 시스템 노드입니다. 엔진이 관리하며 직접 사용하는 용도이므로 사용자가 추가적인 구현을 할 필요는 없습니다. 하지만 이러한 시스템 노드도 몇 개의 노드로 구성할지는 사용자가 선택하기 나름이기 때문에 별도의 구성 방법을 제공합니다. 개발 과정에서는 아래의 사용 예시 그대로 사용해도 무방합니다. 반면에 실제 서비스를 위한 구성은 게임의 콘텐츠나 볼륨에 따라 알맞게 적용해야 하므로 GameAnvil 담당자와 별도의 논의를 진행하는 것을 추천합니다. 각 설정 항목은 아래와 같습니다.
"location": {
"clusterSize": 1, // 총 몇개의 머신(VM)으로 구성되는가? [1 ~ 5]
"replicaSize": 1, // 복제 그룹의 크기 (master + slave의 개수) [2 ~ 5]
"shardFactor": 2 // sharding을 위한 인수 [2 ~ 5]
},
[참고]
해당 설정(location) 키값이 없거나 clusterSize가 0이면 해당 프로세스에서 로케이션 노드를 생성하지 않습니다.
각 구성 항목의 설명은 다음과 같습니다.
이름 | 설명 | 기본값 |
---|---|---|
clusterSize | 구성되는 서버의 수(VM)를 설정 헙나다. | 0 |
replicaSize | 복제 그룹의 크기( master + slave )의 개수를 설정합나다. | 0 |
shardFactor | sharding을 위한 인수를 설정합니다. -전체 shard의 개수 = clusterSize x replicaSize x shardFactor -하나의 머신(VM)에서 구동할 shard의 개수 = replicaSize x shardFactor < br />-고유한 shard의 총 개수(master 샤드의 개수) = clusterSize x shardFactor |
0 |
master 로케이션 노드로 요청하여 유저와 룸 등의 위치 정보를 조회할 수 있습니다. 단, 모든 로케이션 노드가 클러스터링이 완료된 이후에만 요청을 보낼 수 있습니다. 로케이션 노드를 사용하는 것으로 설정한 경우 엔진 내부에서는 로케이션 노드를 구동하고 모든 로케이션 노드가 클러스터링이 완료되었는지 체크합니다. 일정 시간 내에 전체 클러스터링이 완료되지 않으면 에러 로그를 남깁니다.
replicaSize를 2 이상으로 설정하는 경우, master 로케이션 노드와 slave 로케이션 노드가 존재하게 됩니다. 만약 master 로케이션 노드가 죽은 경우 slave 로케이션 노드가 master의
역할을 대체하도록 location fail-over 기능이 구현되어 있습니다. master 로케이션 노드가 있던 서버를 재구동하는 경우에는 VmOption에 -DrestartedAfterDown=true
를
추가하여 구분될 수 있도록 합니다. 이때 재구동되는 로케이션 노드는 모두 slave로 구동됩니다.
매치 노드는 매치 메이킹을 수행하는 노드입니다. 즉, 사용자가 구현한 매치 메이커를 구동합니다. 이러한 매치 노드는 몇 개의 노드를 구동할지만 결정하면 됩니다. 일반적인 개발 과정이나 작은 규모의 서비스는 하나의 매치 노드로도 충분합니다.
"match": {
"nodeCnt": 1 // [0 ~ 99]
},
[참고]
해당 설정(match) 키값이 없거나 nodeCnt가 0이면 해당 프로세스에서 매치 노드를 생성하지 않습니다.
각 구성 항목의 설명은 다음과 같습니다.
이름 | 설명 | 기본값 |
---|---|---|
nodeCnt | 매치 노드의 개수를 설정합니다. 0일 경우 매치 노드를 생성하지 않습니다. |
0 |
게이트웨이 노드는 클라이언트가 접속을 맺는 노드입니다. 그러므로 접속할 클라이언트 규모에 맞춰 적당한 개수의 노드를 준비해야 합니다.
"gateway": {
"connectGroup": { // 커넥션 종류.
"TCP_SOCKET": {
"port": 18200 // 클라이언트와 연결되는 포트. [1 ~ 66535]
}
},
"nodeCnt": 4, // 노드 개수. (노드 번호는 0 부터 부여 됨) [0 ~ 99]
"duplicateLoginServices": {
"MyGame": ["MyChatting"]
}
},
[참고]
해당 설정(gateway) 키값이 없거나 nodeCnt가 0이면 해당 프로세스에서 게이트웨이 노드를 생성하지 않습니다.
각 구성 항목의 설명은 다음과 같습니다.
이름 | 설명 | 기본값 |
---|---|---|
connectGroup | 게이트웨이 노드는 "TCP_SOCKET"과 "WEB_SOCKET" 소켓을 지원합니다. | - |
connectGroup : port | 클라이언트가 접속할 포트를 지정합니다. | 0 |
nodeCnt | 게이트웨이 노드의 개수를 설정합니다. 0일 경우 게이트웨이 노드를 생성하지 않습니다. |
0 |
duplicateLoginServices | 중복 로그인 가능 서비스를 설정합니다. | - |
게임 노드는 실제 게임 관련 객체들이 생성되고 콘텐츠가 플레이되는 노드입니다. 게임 콘텐츠의 특성에 맞게 노드 수와 채널 등을 구성할 수 있습니다.
"game": [
{
"serviceId": 1, // 서버군에서 유니크해야함 [0 ~ 99]
"serviceName": "MyGame", // 서버군에서 유니크해야함
"nodeCnt": 4, // 노드 개수, nodeCnt가 0일 경우 Game Node를 생성하지 않는다.머신 core 수로 넣는 것을 권고 [0 ~99]
"channelIDs": [
["초보"],
["중수"],
["초보"],
["중수"]
], // 노드마다 부여할 채널 ID. (유니크하지 않아도 됨. "" 문자열로 채널 구분없이 중복사용도 가능)
"userTimeout": 5000 // disconnect 이후의 유저객체 제거 타임아웃. [0 ~ 604800000(7일)]
}
],
[참고]
서비스 이름은 반드시 서버 코드상에서 사용자가 작성한 게임 노드 클래스를 엔진에 등록하기 위해 사용한 서비스 이름을 입력해야 합니다. 다음은 게임 노드에 대한 예제입니다. "MyGame"이라는 서비스명을 사용했습니다.
java var gameServiceBuilder = gameAnvilServerBuilder.createGameService("MyGame"); gameServiceBuilder.gameNode(SampleGameNode::new, config -> { // 여기에 핸들러를 추가합니다 });
그러므로 반드시 GameAnvilConfig에서 다음과 같이 서비스명을 "MyGame"으로 설정해야 합니다. 다음 예는 "MyGame" 서비스명을 서비스 아이디 "1"에 매핑 한 예제입니다. 이 서비스명과 서비스 아이디는 전체 구성에서 유일한 값이어야 합니다.
json "game": [ { "serviceId": 1, // 서버군에서 유니크해야함 [0 ~ 99] "serviceName": "MyGame", // 서버군에서 유니크해야함 "nodeCnt": 4, // 노드 개수, nodeCnt가 0일 경우 Game Node를 생성하지 않는다.머신 core 수로 넣는 것을 권고 [0 ~99] "channelIDs": [ ["초보"], ["중수"], ["초보"], ["중수"] ], // 노드마다 부여할 채널 ID. (유니크하지 않아도 됨. "" 문자열로 채널 구분없이 중복사용도 가능) "userTimeout": 5000 // disconnect 이후의 유저객체 제거 타임아웃. [0 ~ 604800000(7일)] }, { "serviceId": 2, // 서버군에서 유니크해야함 [0 ~ 99] "serviceName": "MyChatting",// 서버군에서 유니크해야함 "nodeCnt": 1, // 노드 개수, nodeCnt가 0일 경우 Game Node를 생성하지 않는다.머신 core 수로 넣는 것을 권고 [0 ~99] "channelIDs": [[""]], // 노드마다 부여할 채널 ID. (유니크하지 않아도 됨. "" 문자열로 채널 구분없이 중복사용도 가능) "userTimeout": 5000 // disconnect 이후의 유저객체 제거 타임아웃. [0 ~ 604800000(7일)] } ],
위의 구성 예제는 추가로 MyChatting 서비스를 보여줍니다. 서비스 아이디는 'MyGame'과 다른 '2'인 것을 볼 수 있습니다.
[참고]
해당설정 처럼 기본적으로 채널 설정은 스레드 1개당 게임 노드 1개 설정을 합나다.
채널을 많이 사용을 하는데 이중 몇몇 채널에만 유저가 몰리게 되면 많은 스레드로 인해서 성능 저하가 발생할 수 있습니다. 그래서 하나의 스레드에 여러 게임 노드 설정을 지원합니다.
json "game": [ { "serviceId": 1, // 서버군에서 유니크해야함 [0 ~ 99] "serviceName": "MyGame", // 서버군에서 유니크해야함 "nodeCnt": 4, // 노드 개수, nodeCnt가 0일 경우 Game Node를 생성하지 않는다.머신 core 수로 넣는 것을 권고 [0 ~99] "channelIDs": [ ["초보", "중수"], // 스레드1 ["고수"] // 스레드2 ], // 노드마다 부여할 채널 ID. (유니크하지 않아도 됨. "" 문자열로 채널 구분없이 중복사용도 가능) "userTimeout": 5000 // disconnect 이후의 유저객체 제거 타임아웃. [0 ~ 604800000(7일)] } ],
게임 노드: 총 3개 (nodeCnt 값 무시)게임 노드 스레드: 총 2개
스레드1: 게임노드1("초보"), 게임노드2("중수")
스레드2: 게임노드3("고수")
[참고]
해당 설정("game") 키-값이 없거나 nodeCnt가 0이면 해당 프로세스에서 게임 노드를 생성하지 않습니다.
각 구성 항목의 설명은 다음과 같습니다.
이름 | 설명 | 기본값 |
---|---|---|
serviceId | 서비스 아이디로서 전체 서버 구성에서 유일한 숫자 값이어야 합니다. 단, 0 ~ 99 사이의 값만 사용 가능합니다. |
0 |
serviceName | 서비스명으로 전체 서버 구성에서 유일한 문자열이어야 합니다. 이 서비스명은 게임 노드를 엔진에 등록하기 위해 사용됩니다. |
|
nodeCnt | 게임 노드의 개수를 설정합니다. 0일 경우 게임 노드를 생성하지 않습니다. |
0 |
channelIDs | 노드마다 부여할 채널 아이디로서 유일한 값일 필요는 없습니다. 단, ""는 채널을 사용하지 않음을 의미합니다. |
|
userTimeout | disconnect 이후의 유저 객체 제거 타임아웃 시간(ms)을 설정합니다. User 상태가 disconnect 되고 나서 User 객체가 살아있는 시간으로 해당 시간이 지나기 전에 다시 연결이 되지 않으면 logout 처리가 되면서 User 객체가 삭제됩니다. 클라이언트의 접속 끊김 이후 유저 객체를 서버에서 제거하지 않고 얼마 동안 관리할지 설정합니다. 0일 경우 User 객체가 유지되지 않고 바로 삭제됩니다. |
0 |
서포트 노드는 보조적인 역할을 수행하는 노드입니다. 클라이언트와 직접 통신도 가능하므로 게임에 관련된 정보를 교환하거나 주기적인 작업 혹은 게임 외적으로 독립된 구현이 필요한 작업 등을 위임하여 처리하기에 알맞습니다.
"support": [
{
"serviceId": 3, // [0 ~ 99]
"serviceName": "DB",
"nodeCnt": 2, // [0 ~99]
"restIp": "127.0.0.1",
"restPort": 18611 // REST 연결 포트. [ 0 ~ 65535]
},
{
"serviceId": 4,
"serviceName": "SampleWebSupport",
"nodeCnt": 2,
"restPort": 18612
}
},
[참고]
앞서 살펴본 게임 노드와 마찬가지로 서버 코드상에서 사용자가 작성한 서포트 노드 클래스를 엔진에 등록하기 위해 사용한 서비스 이름은 이곳 설정에서 반드시 입력해야 합니다.
[참고]
해당 설정("support") 키-값이 없거나 nodeCnt가 0이면 해당 프로세스에서 서포트 노드를 생성하지 않습니다.
각 구성 항목의 설명은 다음과 같습니다.
이름 | 설명 | 기본값 |
---|---|---|
serviceId | 서비스 아이디로서 전체 서버 구성에서 유일한 숫자 값이어야 합니다. 단, 0 ~ 99 사이의 값만 사용 가능합니다. |
0 |
serviceName | 서비스명으로 전체 서버 구성에서 유일한 문자열이어야 합니다. 이 서비스명은 서포트 노드를 엔진에 등록하기 위해 사용됩니다. |
- |
nodeCnt | 서포트 노드의 개수를 설정합니다. 0일 경우 서포트 노드를 생성하지 않습니다. |
0 |
restIp | RESTful 요청을 위한 IP 주소를 지정합니다. (설정값이 없을 경우에는 해당 머신의 사설 IP로 자동 지정) |
- |
restPort | RESTful 요청을 위한 포트를 지정합니다. | 0 |
GameAnvil 서버의 구동을 위해 개발팀에서 사용하는 VM 옵션의 핵심적인 부분을 공유합니다. 여기에서 권장하는 VM 옵션은 그동안 여러 차례의 대규모 성능 테스트를 통해 검증되었습니다. 이를 참고하여 사용자가 원하는 대로 적절하게 변경하면서 사용하시길 바랍니다.
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.invoke=ALL-UNNAMED
-Xms6g
-Xmx6g
-XX:MaxGCPauseMillis=100
-XX:+UseStringDeduplication
-Xloggc:gc.log
--add-opens java.base/java.lang=ALL-UNNAMED
구문은 GameAnvil에서 Virtual Thread를 커스텀 하여 사용하고 있기 때문에 필요한 jvm 옵션입니다.
이 설정을 제거 시 정상적으로 동작하지 않을 수 있습니다.--add-opens java.base/java.lang.invoke=ALL-UNNAMED
구문은 GameAnvil에서 리플렉션 성능 최적화를 위한 구문입니다. 이 옵션을 제거해도 시작할 수는
있지만 성능이 저하될 수 있습니다.GC 로그를 위한 옵션은 메모리 릭 등을 추적하기 위해 필수입니다. 그러므로 특별한 이유가 없다면 최소한 개발 과정에서는 다음과 같은 GC 로그 관련 옵션들을 추가하기를 권합니다. 하지만 실제 서비스에서는 성능에 영향을 줄 수 있으므로 일부 최적화된 옵션들만 필요에 따라 추가해야 할 수도 있습니다. 앞서 권장한 옵션들과 더불어 각 Java 버전에 따라 다음과 같은 옵션들을 추가할 수 있습니다.
-Xlog:gc*,safepoint:YOUR_PATH/gc.log:time,level,tags,uptime
다음과 같이 파일 로테이션 옵션을 추가할 수도 있습니다.
-Xlog:gc*,safepoint:YOUR_PATH/gc.log:time,level,tags,uptime:filecount=100,filesize=10M