go로 새 프로젝트 시작하기

69
Go로 새 프로젝트 시작하기 Go 모바일 게임 서버에 도전하면서 겪었던 고난과 역경의 분투기 DEVSISTERS 이준성 1

Upload: joonsung-lee

Post on 10-Feb-2017

45.719 views

Category:

Software


3 download

TRANSCRIPT

Page 1: Go로 새 프로젝트 시작하기

Go로 새 프로젝트 시작하기Go로 모바일 게임 서버에 도전하면서 겪었던 고난과 역경의 분투기

DEVSISTERS 이준성

1

Page 2: Go로 새 프로젝트 시작하기

이준성[email protected]

github.com/hodduc

2010~ | KAIST CS

| 개발 동아리 SPARCS

2012년 | ACM-ICPC 2012 Daejeon 1st place

2013년 6월 | ACM-ICPC 2013 World Final 48th place

2013~2014년 | 파이오링크

2014년 8월 | 데브시스터즈 입사, 쿠키런 서버 개발.

현재 | Go를 이용한 신규 프로젝트 진행 중

발표자 소개

Go로 새 프로젝트 시작하기 2

Page 3: Go로 새 프로젝트 시작하기

왜 Go인가요?

1

3

Page 4: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

기존 쿠키런 서버

and

4

Page 5: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

목표

복잡하지 않은 서버구조Spring 부들부들…

모든 서버 컴포넌트를 Scalable하게 만들기== MySQL 안 쓰거나 의존하지 않기

다른 언어와 잘 결합할 것C++ (quic), lua (게임 로직), …

5

Page 6: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

후보군

6

Page 7: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

Type safety

7

Page 8: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

Type safety 멀티코어 활용

8

Page 9: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

Type safety 멀티코어 활용 IDE support

9

Page 10: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

Type safety 멀티코어 활용 IDE support 서드파티 완성도

10

Page 11: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

자…바를 써야하나!?

11

Page 12: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

개발하기 쉬운가?

어렵다

매우 쉽다

쉽다

12

Page 13: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

개발하기 쉬운가? 짜야 하는 코드의 양

어렵다

매우 쉽다

쉽다

매우 많다

매우 적다

적다고 생각했다

13

Page 14: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

개발하기 쉬운가? 짜야 하는 코드의 양 Deploy하기 쉬운가?

어렵다

매우 쉽다

쉽다

매우 많다

매우 적다

적다

어렵다

어렵다

매우 쉽다고 생각했다

14

Page 15: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

하고 싶은가?

15

Page 16: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기 16

Page 17: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

Go를 선택해서 좋았던 점

17

Page 18: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

Go를 선택해서 좋았던 점

• Channel을 활용한손쉬운 Concurrency 구현

18

Page 19: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

Go를 선택해서 좋았던 점

• Channel을 활용한손쉬운 Concurrency 구현

• 가벼운고루틴!

19

일회용으로 마구 만들어 써도 충분한 성능의 고루틴

→ 각 유저마다 각각 하나의 고루틴이 로직 담당

→ 자연스럽게 순차적인 요청 처리가 보장됨

Page 20: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

Go를 선택해서 좋았던 점

• Channel을 활용한손쉬운 Concurrency 구현

• 가벼운고루틴!

• 간결한코드사용으로인한 생산성향상

20

Page 21: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

Go를 선택해서 좋았던 점

• Channel을 활용한손쉬운 Concurrency 구현

• 가벼운고루틴!

• 간결한코드사용으로인한 생산성향상

• Interface의 편리성

21

특히 Cap’n proto나 protobuf와 같은 serialization library와의

궁합이 좋음

Page 22: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

Go를 선택해서 좋았던 점

• Channel을 활용한손쉬운 Concurrency 구현

• 가벼운고루틴!

• 간결한코드사용으로인한 생산성향상

• Interface의 편리성

• 빠른컴파일속도와실행속도덕분에테스트가빠르고쉬움

22

Page 23: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

Go를 선택해서 좋았던 점

• Channel을 활용한손쉬운 Concurrency 구현

• 가벼운고루틴!

• 간결한코드사용으로인한 생산성향상

• Interface의 편리성

• 빠른컴파일속도와실행속도덕분에테스트가빠르고쉬움

• 코딩스타일을알아서맞춰줌

23

Page 24: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

고통받았던 부분들

24

Page 25: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

고통받았던 부분

• 쓸만한 Full-featured Debugger가 없다

the ability to use the debugger to understand a Go program's full environment will likely never work, and improving gdbsupport is not a priority for the team.

- Rob Pike, on March 2014

대부분의 Debugger (gdb, lldb, delve, godebug, …) 가

• 복잡한 상황에서 Go 객체 내부를 제대로 print하지 못하거나

• Function call 을 지원하지 않거나

• Goroutine이 여러 개일 때 제대로 동작하지 않는다

25

Page 26: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

고통받았던 부분

• 쓸만한 Full-featured Debugger가 없다

• 잊을만하면튀어나오는서드파티라이브러리버그

26

Page 27: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기 27

Page 28: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

고통받았던 부분

• 쓸만한 Full-featured Debugger가 없다

• 잊을만하면튀어나오는서드파티라이브러리버그

• 상속없는것까진괜찮은데…Simple Typing이언어 철학인건 알겠는데…그래도 Generic은 필요하다

Int8Contains, Int16Contains, Int32Contains, Int64Contains,

Int8Random, Int16Random, …을 하나하나 만드는건 좀…

28

Page 29: Go로 새 프로젝트 시작하기

왜 Go인가요?

Go로 새 프로젝트 시작하기

고통받았던 부분

• 쓸만한 Full-featured Debugger가 없다

• 잊을만하면튀어나오는서드파티라이브러리버그

• 그래도 Generic은 필요하다

• 코드의많은부분이에러처리코드다

if err := (…); err != nil { return err }

에러 처리 제대로 하다보면 코드가 생각보다 이쁘진 않다

29

Page 30: Go로 새 프로젝트 시작하기

삽질, 삽질, 삽질…

2

30

Page 31: Go로 새 프로젝트 시작하기

Concurrent Map

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 31

Go의 map은 thread-safe 하지 않다

Page 32: Go로 새 프로젝트 시작하기

Concurrent Map

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 32

type Director struct {pidMap map[int]*Actor

}

func (d *Director) Start() (*Actor, Pid) {pid := d.createPid()actor := NewActor(pid)

d.pidMap[pid] = actorreturn actor, pid

}

panic: runtime error: invalid memory address or nil pointer dereference

Page 33: Go로 새 프로젝트 시작하기

Concurrent Map

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 33

type Director struct {sync.RWMutexpidMap map[int]*Actor

}

func (d *Director) Start() (*Actor, Pid) {pid := d.createPid()actor := NewActor(pid)

d.Lock()defer d.Unlock()d.pidMap[pid] = actorreturn actor, pid

}

Page 34: Go로 새 프로젝트 시작하기

Typed nil

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 34

func AssertPositive(i int) *MathError {if i < 0 {return &MathError{i}

}return nil

}

func Root(i int) (int, error) {err := AssertPositive(i)if err != nil {return 0, err

}return int(math.Sqrt(float64(i))), err

}

func main() {sqrt, err := Root(81)if err != nil {fmt.Println("error is not nil:", err)

} else {fmt.Println(sqrt)

}}

Page 35: Go로 새 프로젝트 시작하기

Typed nil

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 35

func AssertPositive(i int) *MathError {if i < 0 {return &MathError{i}

}return nil

}

func Root(i int) (int, error) {err := AssertPositive(i)if err != nil {return 0, err

}return int(math.Sqrt(float64(i))), err

}

func main() {sqrt, err := Root(81)if err != nil {fmt.Println("error is not nil:", err)

} else {fmt.Println(sqrt)

}}

Page 36: Go로 새 프로젝트 시작하기

Typed nil

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 36

func AssertPositive(i int) *MathError {if i < 0 {return &MathError{i}

}return nil

}

func Root(i int) (int, error) {err := AssertPositive(i)if err != nil {return 0, err

}return int(math.Sqrt(float64(i))), err

}

func main() {sqrt, err := Root(81)if err != nil {fmt.Println("error is not nil:", err)

} else {fmt.Println(sqrt)

}}

type: *MathErrorValue: nil

type: error (interface)Value: typed nil >.<

Error interface로 캐스팅될 때

값은 nil이지만 type이 nil이 아니므로

Interface nil과 같지 않다

Page 37: Go로 새 프로젝트 시작하기

Typed nil

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 37

func AssertPositive(i int) *MathError {if i < 0 {return &MathError{i}

}return nil

}

func Root(i int) (int, error) {err := AssertPositive(i)if err != nil {return 0, err

}return int(math.Sqrt(float64(i))), nil

}

func main() {sqrt, err := Root(81)if err != nil {fmt.Println("error is not nil:", err)

} else {fmt.Println(sqrt)

}}

Page 38: Go로 새 프로젝트 시작하기

Typed nil

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 38

func RootStr(s string) (int, error) {i, err := strconv.Atoi(s)if err != nil {return 0, err

}

err = AssertPositive(i)if err != nil {return 0, err

}return int(math.Sqrt(float64(i))), nil

}

func main() {sqrt, err := RootStr("81")if err != nil {fmt.Println("error is not nil:", err)

} else {fmt.Println(sqrt)

}}

Page 39: Go로 새 프로젝트 시작하기

Typed nil

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 39

func RootStr(s string) (int, error) {i, err := strconv.Atoi(s)if err != nil {return 0, err

}

err = AssertPositive(i)if err != nil {return 0, err

}return int(math.Sqrt(float64(i))), nil

}

func main() {sqrt, err := RootStr("81")if err != nil {fmt.Println("error is not nil:", err)

} else {fmt.Println(sqrt)

}}

Page 40: Go로 새 프로젝트 시작하기

Typed nil

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 40

Nil을 반환하는 상황에서는 “nil”이라는 상수를 사용할 것

(X) if err == nil { return response, err }(O) if err == nil { return response, nil }

Page 41: Go로 새 프로젝트 시작하기

Typed nil

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 41

Nil을 반환하는 상황에서는 “nil”이라는 상수를 사용할 것

서로 다른 타입의 err 변수를 가급적 재사용하지 말 것

if err := Something(); err != nil { ... }

이 구문을 이용하면 if문 안에서 새로운 Scope가 생성되므로 보다 안전

Page 42: Go로 새 프로젝트 시작하기

Typed nil

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 42

Nil을 반환하는 상황에서는 “nil”이라는 상수를 사용할 것

서로 다른 타입의 err 변수를 가급적 재사용하지 말 것

특별한 이유가 없다면 모든 함수의 return type을 “error” 인터페이스로 통일할 것

Page 43: Go로 새 프로젝트 시작하기

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 43

코드로 코드 만들기

Page 44: Go로 새 프로젝트 시작하기

코드로 코드 만들기

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 44

//go:generate ./some_runnable_command_here.sh –opt1 --opt2=foobar

$ go generate$ go build$ go test

Page 45: Go로 새 프로젝트 시작하기

코드로 코드 만들기

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 45

cat <<< "package generatedvar BaseDir = \"$BASEDIR\"var Version = struct {

ServerRevision stringProtocolRevision stringDeployDate string

}{\"$SERVER_HASH\",\"$PROTOCOL_HASH\",\"$DEPLOY_DATE\",

}" > generated/version.go

Page 46: Go로 새 프로젝트 시작하기

코드로 코드 만들기

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 46

type Ints []int

func (a Ints) Val() []intfunc (a Ints) Copy() Intsfunc (a Ints) Cut(i int, j int)func (a Ints) Insert(i int, x int) Intsfunc (a Ints) Append(x ...int) Intsfunc (a Ints) Reverse() func (a Ints) Filter(f func(i int, value interface{}) bool) Intsfunc (a Ints) Each(f func(i int, value interface{})) func (a Ints) Map(f func(i int, value interface{}) int) Ints

Type Int32s []int32......

Type Int64s []int64

Page 47: Go로 새 프로젝트 시작하기

코드로 코드 만들기

삽질, 삽질, 삽질…

Go로 새 프로젝트 시작하기 47

• shell script로만들기• 빌드 시점의 환경에 대한 정보가 필요할 때

• Go, Python 등을이용해프로그래밍하기• 언어의 한계를 극복! 하거나, 실수하기 쉬운 단순 작업을 여

러 번 해야 할 때

• Go의 ast, parser 모듈이 유용함• 내가 짠 코드를 ast, parser로 읽어서 적당한 기계 코드

를 패키지에 추가로 삽입

• 용도에따라외부툴 이용하기등등…

Page 48: Go로 새 프로젝트 시작하기

뭐뭐 쓰셨어요?

3

48

Page 49: Go로 새 프로젝트 시작하기

vim-go

우리가 사용한 라이브러리들

Go로 새 프로젝트 시작하기 49

Page 50: Go로 새 프로젝트 시작하기

Godeps

우리가 사용한 라이브러리들

Go로 새 프로젝트 시작하기 50

• Dependency Manager & Vendoring tool

• Copy all dependencies into Workspace directory ( ~= virtualenv in Python )

• Fix Go version, Library version, …

{"ImportPath": "github.com/devsisters/gb-server","GoVersion": "go1.4.2","Deps": [

{"ImportPath": "golang.org/x/net/context","Rev": "0b492c5a9642fa1365f77ad20bbb259a25572507"

},{

"ImportPath": "golang.org/x/oauth2","Rev": "ce5ea7da934b76b1066c527632359e2b8f65db97"

},{"ImportPath": "google.golang.org/api/googleapi","Rev": "c34364630fd76916db716b46fd3a75403b161768"

},....

Page 51: Go로 새 프로젝트 시작하기

go-errors

우리가 사용한 라이브러리들

Go로 새 프로젝트 시작하기

var Crashed = errors.Errorf("oh dear")

func Crash() error {return errors.New(Crashed)}

if err != nil {if errors.Is(err, crashy.Crashed) {fmt.Println(err.(*errors.Error).ErrorStack())

} else {panic(err)

}}

51

Page 52: Go로 새 프로젝트 시작하기

GoConvey

우리가 사용한 라이브러리들

Go로 새 프로젝트 시작하기

http://goconvey.co/

52

Page 53: Go로 새 프로젝트 시작하기

GoConvey

우리가 사용한 라이브러리들

Go로 새 프로젝트 시작하기

http://goconvey.co/

53

Page 54: Go로 새 프로젝트 시작하기

GoConvey

우리가 사용한 라이브러리들

Go로 새 프로젝트 시작하기

http://goconvey.co/

54

Page 55: Go로 새 프로젝트 시작하기

Test function

testing/quick

우리가 사용한 라이브러리들

Go로 새 프로젝트 시작하기

func TestOddMultipleOfThree(t *testing.T) {f := func(x int) bool {y := OddMultipleOfThree(x)return y%2 == 1 && y%3 == 0

}if err := quick.Check(f, nil); err != nil { t.Error(err)

}}

Target functionInject randomvalid parameter

Check if target functionis working well

55

Page 56: Go로 새 프로젝트 시작하기

우리가 공개한 라이브러리들(자랑) (뿌듯)

4

56

Page 57: Go로 새 프로젝트 시작하기

Cine

우리가 공개한 라이브러리들

Go로 새 프로젝트 시작하기

github.com/devsisters/cine

• Actor model for Go (like erlang)

• All actor is identified by unique PID

• Remote actor is treated same as local one

• Supports synchronous / asynchronous function call

Cine

Host 1 Host 2 Host 3

Actor Actor Actor Actor Actor

57

Page 58: Go로 새 프로젝트 시작하기

Cine

우리가 공개한 라이브러리들

Go로 새 프로젝트 시작하기

github.com/devsisters/cine

type Phonebook struct {cine.Actorbook map[string]int

}

cine.Init("127.0.0.1:8000")phonebook := Phonebook{cine.Actor{}, make(map[string]int)}pid := cine.StartActor(&phonebook)

// For asynchronous call (ignore all errors)cine.Cast(pid, nil, (*Phonebook).Add, "Jane", 1234)

// For synchronous callret, _ := cine.Call(pid, (*Phonebook).Lookup, "Jane")number := ret[0].(int)

58

Page 59: Go로 새 프로젝트 시작하기

GoQuic

우리가 공개한 라이브러리들

Go로 새 프로젝트 시작하기

github.com/devsisters/goquic

https://www.youtube.com/watch?v=hQZ-0mXFmk8

59

Page 60: Go로 새 프로젝트 시작하기

우리가 공개한 라이브러리들

Go로 새 프로젝트 시작하기

QUIC vs TCP+TLS+SPDY/HTTP2

• 0-RTT support

• Multiplexing without Head-of-line blocking

• Forward error correction

• Connection Migration

60

Page 61: Go로 새 프로젝트 시작하기

GoQuic

우리가 공개한 라이브러리들

Go로 새 프로젝트 시작하기

github.com/devsisters/goquic

Chromium

Extract QUIC core devsisters/

libquicGo binding w/ cgo devsisters/

goquic

devsisters.github.io/goquic

// servergoquic.ListenAndServe(":8080", 1, nil)

// clientclient := &http.Client{

Transport: goquic.NewRoundTripper(false),}resp, err := client.Get("http://example.com/")

61

Page 62: Go로 새 프로젝트 시작하기

그래서……?

5

62

Page 63: Go로 새 프로젝트 시작하기

Q. 그래서 Go를 새 프로젝트에 써도 될까요?

그래서……?

Go로 새 프로젝트 시작하기 63

Page 64: Go로 새 프로젝트 시작하기

Q. 그래서 Go를 새 프로젝트에 써도 될까요?

그래서……?

Go로 새 프로젝트 시작하기

A. 여러가지 난관이 있었지만 데브시스터즈에서는전체적으로 만족스럽게 개발 중

단, 프로젝트 셋업 초기에시간 투자와 삽질은 각오할 것

64

Page 65: Go로 새 프로젝트 시작하기

Q. 그래서 쿠키런2 언제 나와요?

그래서……?

Go로 새 프로젝트 시작하기 65

Page 66: Go로 새 프로젝트 시작하기

Q. 그래서 쿠키런2 언제 나와요?

그래서……?

Go로 새 프로젝트 시작하기

A. 비밀입니다 (웃음)

66

Page 67: Go로 새 프로젝트 시작하기

Q. 그래서 Go 개발자 뽑나요?

그래서……?

Go로 새 프로젝트 시작하기 67

Page 68: Go로 새 프로젝트 시작하기

Go 개발자 모십니다

Page 69: Go로 새 프로젝트 시작하기

[email protected]

감사합니다