Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

服务端崩溃 #111

Closed
exfun opened this issue Aug 13, 2018 · 55 comments
Closed

服务端崩溃 #111

exfun opened this issue Aug 13, 2018 · 55 comments

Comments

@exfun
Copy link

exfun commented Aug 13, 2018

同时开几百个客户端出现奔溃,大神帮忙看看。

goroutine 935 [IO wait]:
internal/poll.runtime_pollWait(0x7fa51f17b400, 0x72, 0x0)
C:/Go/src/runtime/netpoll.go:173 +0x57
internal/poll.(*pollDesc).wait(0xc420432a98, 0x72, 0xffffffffffffff00, 0xf42ba0, 0xf3c6b8)
C:/Go/src/internal/poll/fd_poll_runtime.go:85 +0xae
internal/poll.(*pollDesc).waitRead(0xc420432a98, 0xc42052a700, 0x2, 0x4)
C:/Go/src/internal/poll/fd_poll_runtime.go:90 +0x3d
internal/poll.(*FD).Read(0xc420432a80, 0xc42052a700, 0x2, 0x4, 0x0, 0x0, 0x0)
C:/Go/src/internal/poll/fd_unix.go:126 +0x18a
net.(*netFD).Read(0xc420432a80, 0xc42052a700, 0x2, 0x4, 0xc42145bdd8, 0xc42145bd50, 0x3)
C:/Go/src/net/fd_unix.go:202 +0x52
net.(*conn).Read(0xc420532560, 0xc42052a700, 0x2, 0x4, 0x0, 0x0, 0x0)
C:/Go/src/net/net.go:176 +0x6d
github.com/name5566/leaf/network.(*TCPConn).Read(0xc420fb7d40, 0xc42052a700, 0x2, 0x4, 0xc42052a700, 0xc42145bdf8, 0x0)
D:/goprojects/src/github.com/name5566/leaf/network/tcp_conn.go:96 +0x52
io.ReadAtLeast(0xf40720, 0xc420fb7d40, 0xc42052a700, 0x2, 0x4, 0x2, 0xa966c0, 0xc4201b4201, 0xc42052a700)
C:/Go/src/io/io.go:309 +0x86
io.ReadFull(0xf40720, 0xc420fb7d40, 0xc42052a700, 0x2, 0x4, 0xc420172f60, 0xc421636fc2, 0x2b)
C:/Go/src/io/io.go:327 +0x58
github.com/name5566/leaf/network.(*MsgParser).Read(0xc42040fec0, 0xc420fb7d40, 0xc421636fc0, 0x0, 0x0, 0x0, 0x0)
D:/goprojects/src/github.com/name5566/leaf/network/tcp_msg.go:70 +0x85
github.com/name5566/leaf/network.(*TCPConn).ReadMsg(0xc420fb7d40, 0xb38da0, 0xc42163c510, 0xb386a0, 0xc420fb7d70, 0x0)
D:/goprojects/src/github.com/name5566/leaf/network/tcp_conn.go:108 +0x34
github.com/name5566/leaf/gate.(*agent).Run(0xc420fb7d70)
D:/goprojects/src/github.com/name5566/leaf/gate/gate.go:94 +0x3a
github.com/name5566/leaf/network.(*TCPServer).run.func1(0xf44160, 0xc420fb7d70, 0xc420fb7d40, 0xc42051a000, 0xf4b7a0, 0xc420532560)
D:/goprojects/src/github.com/name5566/leaf/network/tcp_server.go:102 +0x35
created by github.com/name5566/leaf/network.(*TCPServer).run
D:/goprojects/src/github.com/name5566/leaf/network/tcp_server.go:101 +0x40a

@name5566
Copy link
Owner

name5566 commented Aug 13, 2018

你改了代码吗?似乎和源码对不上。另外,你这个是崩溃的 log?

@exfun
Copy link
Author

exfun commented Aug 13, 2018

我只加了一个方法,在agent的接口里面,写帧消息用的。其它没有动,同时开个300个连接就会奔溃,几十个连接同时运行逻辑没有问题。

@exfun
Copy link
Author

exfun commented Aug 13, 2018

我配置了log路径,但是好像这些奔溃的log记录不了。
goroutine 999 [chan receive]:
github.com/name5566/leaf/network.newTCPConn.func1(0xc42053aa80, 0xf4b7a0, 0xc42000c6f0)
D:/goprojects/src/github.com/name5566/leaf/network/tcp_conn.go:26 +0x64
created by github.com/name5566/leaf/network.newTCPConn
D:/goprojects/src/github.com/name5566/leaf/network/tcp_conn.go:25 +0xd5

goroutine 1000 [IO wait]:
internal/poll.runtime_pollWait(0x7fba5e063380, 0x72, 0x0)
C:/Go/src/runtime/netpoll.go:173 +0x57
internal/poll.(*pollDesc).wait(0xc421135598, 0x72, 0xffffffffffffff00, 0xf42ba0, 0xf3c6b8)
C:/Go/src/internal/poll/fd_poll_runtime.go:85 +0xae
internal/poll.(*pollDesc).waitRead(0xc421135598, 0xc42055e900, 0x2, 0x4)
C:/Go/src/internal/poll/fd_poll_runtime.go:90 +0x3d
internal/poll.(*FD).Read(0xc421135580, 0xc42055e990, 0x2, 0x4, 0x0, 0x0, 0x0)
C:/Go/src/internal/poll/fd_unix.go:126 +0x18a
net.(*netFD).Read(0xc421135580, 0xc42055e990, 0x2, 0x4, 0xc4217d1dd8, 0xb25fe0, 0xf52420)
C:/Go/src/net/fd_unix.go:202 +0x52
net.(*conn).Read(0xc42000c6f0, 0xc42055e990, 0x2, 0x4, 0x0, 0x0, 0x0)
C:/Go/src/net/net.go:176 +0x6d
github.com/name5566/leaf/network.(*TCPConn).Read(0xc42053aa80, 0xc42055e990, 0x2, 0x4, 0xc42055e990, 0xc4217d1df8, 0x0)
D:/goprojects/src/github.com/name5566/leaf/network/tcp_conn.go:96 +0x52
io.ReadAtLeast(0xf40720, 0xc42053aa80, 0xc42055e990, 0x2, 0x4, 0x2, 0xa966c0, 0xc4201a0201, 0xc42055e990)
C:/Go/src/io/io.go:309 +0x86
io.ReadFull(0xf40720, 0xc42053aa80, 0xc42055e990, 0x2, 0x4, 0xc420192780, 0xc421885472, 0x2c)
C:/Go/src/io/io.go:327 +0x58
github.com/name5566/leaf/network.(*MsgParser).Read(0xc420394000, 0xc42053aa80, 0xc421885470, 0x0, 0x0, 0x0, 0x0)
D:/goprojects/src/github.com/name5566/leaf/network/tcp_msg.go:70 +0x85
github.com/name5566/leaf/network.(*TCPConn).ReadMsg(0xc42053aa80, 0xb3ea20, 0xc42186be40, 0xb386a0, 0xc42053aab0, 0x0)
D:/goprojects/src/github.com/name5566/leaf/network/tcp_conn.go:108 +0x34
github.com/name5566/leaf/gate.(*agent).Run(0xc42053aab0)
D:/goprojects/src/github.com/name5566/leaf/gate/gate.go:94 +0x3a
github.com/name5566/leaf/network.(*TCPServer).run.func1(0xf44160, 0xc42053aab0, 0xc42053aa80, 0xc4201de000, 0xf4b7a0, 0xc42000c6f0)
D:/goprojects/src/github.com/name5566/leaf/network/tcp_server.go:102 +0x35
created by github.com/name5566/leaf/network.(*TCPServer).run
D:/goprojects/src/github.com/name5566/leaf/network/tcp_server.go:101 +0x40a

@name5566
Copy link
Owner

leaf 不是这样用的。你还需要再看看文档。

@name5566
Copy link
Owner

除非非常清楚,否则不要改动任何 leaf 的源码。可以改动 leafserver 的代码。

@exfun
Copy link
Author

exfun commented Aug 13, 2018

我就是按照leaf-server的demo上面加的,leaf源码加了一个方法。

@name5566
Copy link
Owner

问题就出在你加的这个方法上。

@exfun
Copy link
Author

exfun commented Aug 13, 2018

谢了,我把leaf源码还原看看,如果再连接多了再崩溃我在回来请教你。这个周要上线,几百个连接就崩溃的那瞬间我快哭了@@,第一次用golang表示鸭梨大。

@name5566
Copy link
Owner

我在很长的一段时间,测试过很多次数的上万次的连接,对实际游戏中的多种不同的功能做压力测试,并没有出现过问题。之前有其他同学用更好的机器,更多的连接数测试过,没有出现过问题。实际中使用 leaf 的游戏在这几年的运行中也没有发现过网络层的问题。所以,尽量不要动 leaf 的源码,这会让解决问题变的很复杂。

@exfun
Copy link
Author

exfun commented Aug 13, 2018

我删除了,重新go get, 还是出现这个问题。

@exfun
Copy link
Author

exfun commented Aug 13, 2018

是不是和锁有关系
goroutine 812 [runnable]:
sync.(*Mutex).Unlock(0xc420335a40)
C:/Go/src/sync/mutex.go:175 +0xc8
github.com/name5566/leaf/network.(*TCPConn).Write(0xc420335a40, 0xc421790240, 0x38, 0x38)
D:/goprojects/src/github.com/name5566/leaf/network/tcp_conn.go:93 +0xa1
github.com/name5566/leaf/network.(*MsgParser).Write(0xc420340000, 0xc420335a40, 0xc42178e5d0, 0x2, 0x2, 0xf4ff20, 0xb48de0)
D:/goprojects/src/github.com/name5566/leaf/network/tcp_msg.go:151 +0x19f
github.com/name5566/leaf/network.(*TCPConn).WriteMsg(0xc420335a40, 0xc42178e5d0, 0x2, 0x2, 0x2, 0x2)
D:/goprojects/src/github.com/name5566/leaf/network/tcp_conn.go:112 +0x52
github.com/name5566/leaf/gate.(*agent).WriteMsg(0xc420335a70, 0xb48de0, 0xc42178c960)
D:/goprojects/src/github.com/name5566/leaf/gate/gate.go:131 +0x1b4
server/play.Broadcast(0xc42178e480, 0xf47760, 0xc42178c960)
F:/go/leafserver/src/server/play/manager.go:34 +0xd0
server/play.newRobots(0xc420121440, 0xc420513540, 0x7, 0x7)
F:/go/leafserver/src/server/play/robots.go:112 +0x16e
server/play.processGame(0xc420121440, 0xa)
F:/go/leafserver/src/server/play/manager.go:115 +0x40
created by server/play.createNewGameRoom
F:/go/leafserver/src/server/play/manager.go:103 +0x380

@name5566
Copy link
Owner

锁应该不会导致崩溃。而且 leaf 里面是不用锁的。你是什么场景下需要用锁?

@exfun
Copy link
Author

exfun commented Aug 13, 2018

我是一局游戏开了一个Goroutine, leaf game模块负责消息的转发,创建游戏局。消息来了再game模块有改变玩家的状态,而游戏局的Goroutine要清算玩家的数据,所以就在游戏房间的struct上加了sync.Mutex。这样会不会出问题。

@name5566
Copy link
Owner

好像不会有问题。

@exfun
Copy link
Author

exfun commented Aug 13, 2018

我试了,锁我注释了。还是会崩溃,我试试是不是消息的问题。

@exfun
Copy link
Author

exfun commented Aug 13, 2018

我只发心跳空消息,leaf收到后就回复,客户端1-5秒发送一次。2w 个客户端没有问题。
完了,不知道哪里的问题了,但是有报错网络有问题。

root@ubuntu:~# netstat -antp | grep leaf | wc -l
40006

@name5566
Copy link
Owner

逻辑上使用多 goroutine 容易出问题。感觉就是在用多线程写逻辑一样,不是一个好的选择。

@exfun
Copy link
Author

exfun commented Aug 13, 2018

我只有一个保存数据有了chan + goroutine, 还有每个游戏局开一个goroutine.
还有在每局游戏的goroutine用了多个 <- time.After()。时间紧迫,大神有没有什么建议。

@name5566
Copy link
Owner

没有建议。找问题,只能拿着代码,一点一点分析。没有代码,只能是雾里看花了。对于设计,总的来说,很重要的是“简洁”。好好把握这个,实际中就不会出大问题。

@exfun
Copy link
Author

exfun commented Aug 13, 2018

看来今晚要加班搞了。苍天啊,大地啊。

@name5566
Copy link
Owner

该加班的还得加班:)

leaf 在很早期,雏形阶段,用来做过棋牌游戏,也是在逻辑上使用了多 goroutine,总的来说,不是特别好的实践。不过实际情况也还行,维持同时上千人在线毫无压力。多 goroutine 的代码,每一行都可能带来额外的心智负担。

@exfun
Copy link
Author

exfun commented Aug 14, 2018

找到原因了,我自己写了一个计算sync.Map{}的len方法引起的,改成了你的util.Map,现在已经跑到了几万个。还有一个问题就是windows和虚拟机的问题,我放到了VPS。打开了文件数限制。现在不报错了,只是1核心跑1万局游戏,有点慢。:)

@name5566
Copy link
Owner

不错,不用加班了:)细节慢慢优化。

@exfun
Copy link
Author

exfun commented Aug 14, 2018

嗯,优化的地方还有很多,要好好学习一下才行。你很喜欢海贼王么,头像都是路飞 : 0

@jjjjilyf
Copy link

免责声明:如果只有发送方的go-routine,没有其他的go-routine,那么会发生死锁,go程序会检测出死锁并崩溃。

@exfun
Copy link
Author

exfun commented Aug 15, 2018

@jjjjilyf 什么意思

@jjjjilyf
Copy link

之前你写的那个崩溃的地方 是不是这个引起的

@exfun
Copy link
Author

exfun commented Aug 15, 2018

是RACE竞争引起的,多个goroutine访问了同一个变量。现在没有问题,但是我加锁和用util.Map解决的。感觉后期还得重构一遍

@jjjjilyf
Copy link

@jjjjilyf
Copy link

请问下我也用了util.map但是获取不了 里面的数据,里面存了key从1-20的数据,用map.Get(1)获取不到数据。

@exfun
Copy link
Author

exfun commented Aug 23, 2018

那肯定你是用的有问题,和util.Map没有关系

@jjjjilyf
Copy link

我找了下原因,是因为 我的key存的时候是uint16,取得时候是uint32.。。。

@jjjjilyf
Copy link

请问下 你们是怎么测试的?比如游戏里的匹配,存取数据库,这些是不是建立1000000个Client并建立和服服务器的连接,把那些卡住程序执行的条件去掉?用go 来发送消息?这样测试服务器的负载情况吗?

@exfun
Copy link
Author

exfun commented Aug 24, 2018

就写模拟客户端啊,一个goroutine一个客户端,数据库这些也得测试啊。
image

@exfun
Copy link
Author

exfun commented Aug 24, 2018

For循环里面有个时间间隔,时间越短,服务器的压力会越大。差不多就行了。我们是一个答题的游戏,2G内存开个2-3万跑得动。

@jjjjilyf
Copy link

多谢啦大哥^^ 我参考写一下,我们是一个足球观战微信游戏。。。

@jjjjilyf
Copy link

我们用的FlatBuffer协议,你们用的是Json吗?

@exfun
Copy link
Author

exfun commented Aug 24, 2018

肯定用google protobuf 啊

@jjjjilyf
Copy link

本来想用的pb的后来改成fb了,不用序列化和反序列化,听说专门为游戏做的协议

@jjjjilyf
Copy link

请问下是不是在执行 go test的时候 也会默认执行包里包含的init()函数,因为找不到server.json各种报错

@zcwtop
Copy link

zcwtop commented Aug 26, 2018

请问下,你是如何设计“一局游戏开一个Goroutine”的。能否给个简单的demo或者思路。
我自己写,一局游戏基本上是每个操作都放在goroutine里运行的,用的是leaf的顺序goroutine。这样感觉goroutine会非常多,不知道这样设计是不是错了?

@jjjjilyf
Copy link

没错,就是这样设计

@exfun
Copy link
Author

exfun commented Aug 29, 2018

@zcwtop 为什么每个操作都要单独开goroutine。我之前就是开太多了,几十局游戏开运行完全跑不出问题。跑几万的次数就崩溃,这就是我开个issue求助的原因,找BUG找半天。我听了作者了。尽量少开,一局游戏的逻辑操作都放一个goroutine里执行,数据同步放到另外一个goroutine专门处理,我们用的Postgress。这样防止并发下未知的RACE。简单点好

@zcwtop
Copy link

zcwtop commented Aug 29, 2018

@exfun 是啊。我现在感觉是太多了。我现在的设计师是这样的,假如一局游戏的结构是
type T struct {
*g // leaf 平行的go
...

func (t *T) add {
t.Go(func() {
// ...
}, func(){
//...
})
}

func (t *T) del {
t.Go(func() {
// ...
}, func(){
//...
})
}

基本上每个操作都是这样。

如果改成一个go运行,我改如何改? 或者你是如何写的,能否给个参考。

@exfun
Copy link
Author

exfun commented Aug 29, 2018

我没有用leaf这个,用go 关键字声明的。leaf的只用了它的消息和工具类这些。
我是一局游戏比如game *Game, 然后会在一个goroutinue里面执行,比如 go game.Process() , 然后在process里面完成单局所有的事情。游戏局完了之后,这个goroutine就会执行完,等待GC回收。

@zcwtop
Copy link

zcwtop commented Aug 29, 2018

@exfun 那是不是这个Process是一个死循环,用select多路复用等待通道消息,然后执行
我理解是不是类似这样处理:
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
Copy link
Author

exfun commented Aug 29, 2018

写好点应该是这样的,leaf负责消息转发chan通知,来了就处理,注意游戏结束条件。别一直结束不了卡住,这样就会有问题。超时的地方得处理!我们是在线答题的游戏,比较简单。

@zcwtop
Copy link

zcwtop commented Aug 29, 2018

thank you。感谢解答。我得重新设计一下。
你服务器崩溃的问题解决了这个issue就可以close了

@exfun exfun closed this as completed Aug 29, 2018
@jjjjilyf
Copy link

照这样写的话 还需要框架吗?都是自己处理了

@zcwtop
Copy link

zcwtop commented Aug 29, 2018

@jjjjilyf 我想两种方式都写一次,然后都压力测试一下。

@jjjjilyf
Copy link

@exfun 请问Util.map里Get到的元素怎么判断是否为nil,用 == nil 来判断,判断不出来,存的是个指针类型的数据。

@zcwtop
Copy link

zcwtop commented Sep 15, 2018

@exfun 哈哈,遇到了同样的问题

@exfun
Copy link
Author

exfun commented Sep 17, 2018

@zcwtop 人多的时候一定要注意别资源竞争(多核下就很容易崩溃),要么用锁,原子,条件变量控制。或者用chan来传递消息。以下是我一个Game一个goroutine来控制游戏过程的流程Process() 。重构后的代码,比之前清晰多了,这也是我第一次写golang,还需要学习的东西很多。

func (GR *GameRoom) Process() {
	// 处理游戏过程结束/正常/异常
	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:
				// 游戏开始倒计时,补充一部分AI
				joinRobots(GR, utils.RandIntFrom2(18, 25))
				// 等待是否补充足房间
				time.AfterFunc(time.Second*time.Duration(robotTime), func() {
					GR.WaitChan <- ON_RO_JOIN
				})
			case ON_RO_JOIN:
				// 不足AI
				joinRobots(GR, 0)
				// 剩余倒计时
				time.AfterFunc(time.Second*time.Duration(gameStartTime-robotTime), func() {
					GR.WaitChan <- ON_GAME_START
				})
			case ON_GAME_START:
				// 游戏开始
				GR.Start()
			case ON_RO_ANS_1:
				// 机器人第一次回答
				helpRobotAnswer(GR, true, false)
				time.AfterFunc(time.Second*time.Duration(1), func() {
					GR.WaitChan <- ON_RO_ANS_2
				})
			case ON_RO_ANS_2:
				// 机器人第二次回答
				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)
				// 倒计时回答结束
				time.AfterFunc(time.Second*time.Duration(GR.AnswerTimeout-4), func() {
					GR.WaitChan <- ON_ANSWER_END
				})
			case ON_ANSWER_END: // 回答结束,结算
				GR.onAnswerOver()
			case ON_SEND_QUESTION: // 出题
				GR.sendQuestion()
			case MSG_END_GAME: // 游戏结束
				//break game
				break Playing
				// goto END
			}
		}
	}
	//END:
	log.Debug("Game goroutine over ->%s", GR.Uuid)
}

@zcwtop
Copy link

zcwtop commented Sep 17, 2018

@exfun 我的错误也是写map没加锁导致的。这个错误捕获还挺不容易,报错了要马上截图才看到。后面tcp等看到的错误都是这个引起的。leaf框架本身没有问题,都是自己的代码逻辑错误引起的。

我现在一局游戏的逻辑跟你上面的代码是类似的。

自己写的机器人暂时只开最多5000个测试,先把断线重连逻辑完善了再压。

@exfun
Copy link
Author

exfun commented Sep 17, 2018

这个issue可以留给那些后来的人,一定要注意map哈哈。希望遇到同样的问题的人能尽快解决问题,如果崩溃了。和框架没有关系,一定是自己埋地雷了,找同步,竞争问题。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants