go4game - go 언어로 만드는 게임 서버
By SeukWon Kang
몇 년 전부터 생각하고 있던 go언어를 사용한 게임 서버의 개발을 시작했습니다.
일단 첫 번째 작동 버전은 언제나처럼 github에 있습니다.
지금까지 만들어진 것은 서버를 구성하기 위한 가장 기본 적인 구조들 GameService, World, Team, GameObject 과 client 와 tcp 로 패킷을 주고 받을 수 있는 구조들 정도 입니다. 지금 사용하는 패킷은 int32 len + json 형태의 패킷입니다.
참고로 그동안 제가 적은 go 관련 스터디 포스트 들입니다. The Go Programming Language : 2009년 글입니다.
그리고 아직 완료하지 못한 The Way to Go OnlyGill 를 읽으며 메모한 내용. 구글 go 언어를 공부하면서 몇몇 중요 포인트를 메모중입니다. (1/N) 구글 go 언어를 공부하면서 몇몇 중요 포인트를 메모중입니다. (2/N) 구글 go 언어를 공부하면서 몇몇 중요 포인트를 메모중입니다. (3/N) 구글 go 언어를 공부하면서 몇몇 중요 포인트를 메모중입니다. (4/N) 구글 go 언어를 공부하면서 몇몇 중요 포인트를 메모중입니다. (5/N)
작업하면서 go언어를 공부할때는 못느꼈던 점들을 메모해봤습니다.
처음 go를 접하면 goroutine과 channel에 신경이 가게 되는데 실제 프로그램을 해보니 interface{}가 완전 축복입니다. go언어는 컴파일 언어에 정적 타이핑, 정적 바인딩 언어 입니다만 이 interface를 통해서 python의 ducktyping 같은 효과를 낼 수 있습니다.
그리고 go를 개발하기위한 환경 설정에서 folder구조가 의외로 중요합니다.
GOPATH/
bin
pkg
src
github.com/
kasworld
go4game
runserver
runclient
형태를 지켜야 합니다. (github 를 쓰는 경우 )
그리고 라이브러리가 아닌 주 실행 파일을 만드는 경우는 또다른 독립 폴더를 만들어야 합니다. go4game 같은 경우는 아래로 runclient와 runserver 폴더가 따로 있습니다. 일단 이런 구조를 지키면 github에서 라이브러리를 가져오는 것은 go get github.com/kasworld/go4game 과 같이 아주 간단하게 이루어 집니다. ( 그전에 GOPATH -작업 루트 폴더- 가 지정되어 있어야 합니다. ) 그리고 같은 폴더내에 있는 소스끼리는 소스간 import 의존성이 없습니다. 그냥 한 파일 이라고 생각하고 작업해도 됩니다. 게다가 go는 소스내에서 선언의 위치 와 사용의 위치에 제한이 없습니다. 뒤에 정의 되어 있는 함수/struct를 앞에서 사용해도 에러를 내지 않습니다. 이런 관계로 import 순서나 순환 참조에 대한 고민을 할 필요가 없어집니다.
go4game은 시스템 내의 대부분의 object가 goroutine을 사용해서 정말 살아있는 object( data 과 정적 codeblock 이 아닌 )로 만들고 그 object간 channel를 사용해 message를 주고 받는 형태로 설계를 하고 있습니다.
이 경우 goroutine이 엄청나게 많아 지는 관계로 각 object간 communication을 담당할 channel도 많아지는 문제가 있습니다. 아직은 프로토타입 단계라서 여러가지를 시도해 보고 있는데 일단은 각 object가 command를 수신할 channel를 만들고 그 channel을 외부에서 사용하는 형태를 취하고 있습니다.
이렇게 많은 goroutine과 channel을 사용하게 되니 각 goroutine이 잘 종료되는지를 확인할 필요가 생깁니다. (즉 goroutine이 leak 되는 지 확인 ) runtime.NumGoroutine()을 사용해서 현재 goroutine의 총 수를 계속 확인, 더이상 필요없는 goroutine이 background에서 실행 중인지를 확인합니다.
channel을 만들 때 일단 synchronous 로 만들고 나중에 필요하면 asynchronous로 확장합니다. synchronous로 만들어야 로직상의 버그로 block되는 부분을 빨리 발견 할 수 있습니다. 이 block되는 것을 막는 방법은 모든 channel send/recv를 반드시 select 내의 case 안에 사용하는 것입니다. ( channel의 send/recv는 일단 blocked code 입니다. ) 이렇게 만들어진 go4game의 현재 성능은 1000 client가 초당 60 패킷을 보내는 것을 처리할 정도의 성능을 보입니다. i2500 , 3.3Ghz , 4C4T 에서 약 60000 패킷/sec 정도 단일 core만을 사용해야 했던 python/twisted 에 비하면 정말 많이 빠름니다. ( 일단 cpu를 모두 사용하니 당연하긴 합니다. ) 앞 글에 썼던 python/twisted 와 동일 조건인 단순 echo로 성능 테스트를 하면 대충 270000 패킷정도 처리합니다. ( 2.7배 정도 ) 실제 테스터가 같은 머신에서 작동한것이니 pypy 와 golang은 단일 코어에서의 성능차이는 크지 않다고 봅니다. 다만 go는 cpu가 많아지면 성능이 올라가는 구조로 만들 수 있다는 게 차이입니다. 이 문제만 아니라면 pypy/stackless 로도 go와 거의 같은 구조/성능을 얻을 수 있습니다. GIL의 문제가 해결될 pypy-stm 이 기대 되는 이유기도 합니다.