在查看官方issues時(shí)饰抒,也發(fā)現(xiàn)了一些個(gè)人覺(jué)得有用的討論,摘錄一下
一诀黍、issues 希望可以增強(qiáng)下log的功能
Q:leaf的log蠻好用的了袋坑,可以log到文本。但是現(xiàn)在需要做日志分析眯勾。要能支持json格式枣宫,異步log等等。希望可以加強(qiáng)下吃环。
看了下zap日志庫(kù)也颤,感覺(jué)過(guò)去復(fù)雜了。有沒(méi)有簡(jiǎn)單實(shí)用郁轻,能分析的日志庫(kù)翅娶?
A:Leaf 的 log 的定位并非用于數(shù)據(jù)分析,最好額外加一套用于數(shù)據(jù)分析的機(jī)制,同時(shí)也保留 Leaf 的 log故觅。
二厂庇、issues 服務(wù)端崩潰
zcwtop:請(qǐng)問(wèn)下,你是如何設(shè)計(jì)“一局游戲開(kāi)一個(gè)Goroutine”的输吏。能否給個(gè)簡(jiǎn)單的demo或者思路权旷。
我自己寫,一局游戲基本上是每個(gè)操作都放在goroutine里運(yùn)行的贯溅,用的是leaf的順序goroutine拄氯。這樣感覺(jué)goroutine會(huì)非常多,不知道這樣設(shè)計(jì)是不是錯(cuò)了它浅?
exfun:為什么每個(gè)操作都要單獨(dú)開(kāi)goroutine译柏。我之前就是開(kāi)太多了,幾十局游戲開(kāi)運(yùn)行完全跑不出問(wèn)題姐霍。跑幾萬(wàn)的次數(shù)就崩潰鄙麦,這就是我開(kāi)個(gè)issue求助的原因,找BUG找半天。我聽(tīng)了作者了。盡量少開(kāi)拖刃,一局游戲的邏輯操作都放一個(gè)goroutine里執(zhí)行,數(shù)據(jù)同步放到另外一個(gè)goroutine專門處理骂因,我們用的Postgress。這樣防止并發(fā)下未知的RACE赃泡。簡(jiǎn)單點(diǎn)好
zcwtop:是啊寒波。我現(xiàn)在感覺(jué)是太多了。我現(xiàn)在的設(shè)計(jì)是這樣的升熊,假如一局游戲的結(jié)構(gòu)是
type T struct {
*g // leaf 平行的go
...
}
func (t *T) add {
t.Go(func() {
// ...
}, func(){
//...
})
}
func (t *T) del {
t.Go(func() {
// ...
}, func(){
//...
})
}
基本上每個(gè)操作都是這樣俄烁。如果改成一個(gè)go運(yùn)行,我該如何改级野? 或者你是如何寫的页屠,能否給個(gè)參考。
exfun:我沒(méi)有用leaf這個(gè)勺阐,用go 關(guān)鍵字聲明的。leaf的只用了它的消息和工具類這些矛双。我是一局游戲比如game *Game, 然后會(huì)在一個(gè)goroutinue里面執(zhí)行渊抽,比如 go game.Process() , 然后在process里面完成單局所有的事情议忽。游戲局完了之后懒闷,這個(gè)goroutine就會(huì)執(zhí)行完,等待GC回收。
zcwtop:那是不是這個(gè)Process是一個(gè)死循環(huán)愤估,用select多路復(fù)用等待通道消息帮辟,然后執(zhí)行。我理解是不是類似這樣處理:
func (g *Game) Process(...) {
begin()
//...
for {
select {
case c:=<-通道1:
handle1()
case c := <-通道2:
handle2()
}
}
//...
end()
}
func (g *Game)handle1() {
//...
}
func (g *Game)handle2() {
//...
}
exfun:寫好點(diǎn)應(yīng)該是這樣的玩焰,leaf負(fù)責(zé)消息轉(zhuǎn)發(fā)chan通知由驹,來(lái)了就處理,注意游戲結(jié)束條件昔园。別一直結(jié)束不了卡住蔓榄,這樣就會(huì)有問(wèn)題。超時(shí)的地方得處理默刚!我們是在線答題的游戲甥郑,比較簡(jiǎn)單。
zcwtop:人多的時(shí)候一定要注意別資源競(jìng)爭(zhēng)(多核下就很容易崩潰)荤西,要么用鎖澜搅,原子,條件變量控制邪锌∶闾桑或者用chan來(lái)傳遞消息。以下是我一個(gè)Game一個(gè)goroutine來(lái)控制游戲過(guò)程的流程Process() 秃流。重構(gòu)后的代碼赂蕴,比之前清晰多了,這也是我第一次寫golang舶胀,還需要學(xué)習(xí)的東西很多概说。
func (GR *GameRoom) Process() {
// 處理游戲過(guò)程結(jié)束/正常/異常
defer func() {
GR.End()
if e := recover(); e != nil {
panic(e)
}
}()
GR.Playing = true
gameStartTime := conf.GameConfig.StartTime
robotTime := conf.GameConfig.RobotTime
Playing:
for {
select {
case m := <-GR.MsgChan:
player := m.P
switch m.Mtype {
case MSG_JOIN:
GR.onJoin(player)
case MSG_ANSWER:
GR.onAnswer(player, m.Answer, m.Pm)
}
case wf := <-GR.WaitChan:
switch wf {
case WAIT_GAME:
// 游戲開(kāi)始倒計(jì)時(shí),補(bǔ)充一部分AI
joinRobots(GR, utils.RandIntFrom2(18, 25))
// 等待是否補(bǔ)充足房間
time.AfterFunc(time.Second*time.Duration(robotTime), func() {
GR.WaitChan <- ON_RO_JOIN
})
case ON_RO_JOIN:
// 不足AI
joinRobots(GR, 0)
// 剩余倒計(jì)時(shí)
time.AfterFunc(time.Second*time.Duration(gameStartTime-robotTime), func() {
GR.WaitChan <- ON_GAME_START
})
case ON_GAME_START:
// 游戲開(kāi)始
GR.Start()
case ON_RO_ANS_1:
// 機(jī)器人第一次回答
helpRobotAnswer(GR, true, false)
time.AfterFunc(time.Second*time.Duration(1), func() {
GR.WaitChan <- ON_RO_ANS_2
})
case ON_RO_ANS_2:
// 機(jī)器人第二次回答
helpRobotAnswer(GR, false, false)
time.AfterFunc(time.Second*time.Duration(1), func() {
GR.WaitChan <- ON_RO_ANS_3
})
case ON_RO_ANS_3:
helpRobotAnswer(GR, false, true)
// 倒計(jì)時(shí)回答結(jié)束
time.AfterFunc(time.Second*time.Duration(GR.AnswerTimeout-4), func() {
GR.WaitChan <- ON_ANSWER_END
})
case ON_ANSWER_END: // 回答結(jié)束嚣伐,結(jié)算
GR.onAnswerOver()
case ON_SEND_QUESTION: // 出題
GR.sendQuestion()
case MSG_END_GAME: // 游戲結(jié)束
//break game
break Playing
// goto END
}
}
}
//END:
log.Debug("Game goroutine over ->%s", GR.Uuid)
}
三糖赔、Leaf 中如何做消息廣播
游戲服務(wù)器一定需要用戶管理,最常見(jiàn)的方式就是建立用戶 ID 到用戶實(shí)例的映射關(guān)系(還有可能存在用戶帳號(hào) ID轩端、用戶名到用戶實(shí)例的映射關(guān)系)放典。例如:
users = make(map[int]*User)
User 本身為了簡(jiǎn)單,可以直接組合 Agent:
type User struct {
gate.Agent
}
這樣的話基茵,廣播消息就是:
for _, user := range users {
user.WriteMsg(msg)
}
一個(gè)最簡(jiǎn)單的廣播的例子:打開(kāi) Leafserver game/internel/chanrpc.go 文件奋构,加入一個(gè)全局變量。
var agents = make(map[gate.Agent]struct{})
agents 的管理:
// agent 被創(chuàng)建時(shí)
func rpcNewAgent(args []interface{}) {
a := args[0].(gate.Agent)
agents[a] = struct{}{}
}
// agent 被關(guān)閉時(shí)
func rpcCloseAgent(args []interface{}) {
a := args[0].(gate.Agent)
delete(agents, a)
}
由此可見(jiàn) agents 中保存了當(dāng)前所有連接拱层,廣播的處理:
for a := range agents {
a.WriteMsg(msg)
}