前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何使用 Go 语言写游戏服务器?

如何使用 Go 语言写游戏服务器?

作者头像
李海彬
发布2018-03-22 14:51:34
3K0
发布2018-03-22 14:51:34
举报

之前先后用Erlang,nodejs做过tcp,http的游戏服务器。接触了golang一两个月(纯新手),想在最近的tcp网游项目中使用,但又担心以下问题: # 如何高性能的搭建tcp底层,并且能负载到同时在线N多人 # 如何架构整个服务器端(包括网络层,缓存层,持久化层,日志层,逻辑分发处理层,通信协议层,以及如何有效部署) # goroutine间如何高效通信 # 担心go1.5版本及以后的gc问题 # 如何调试程序和快速定位线上问题 # 压力测试负载能力 希望用过golang的前辈给出一些建议~~

作者:达达 链接:http://www.zhihu.com/question/35385236/answer/62654548 来源:知乎 著作权归作者所有,转载请联系作者获得授权。

谢邀,昨天就有看到这问题,因为题太大不知道从何答起,所以就没答,今天易伟前辈邀请,不答不行了。 真有趣团队是从Go 1.0开始使用Go开发游戏服务端的,所以小经验有点,但是我们还处在不断学习摸索的阶段,所以太高深的学问不多,下面我就按题主的问题顺序尝试一个个的回答吧: # 如何高性能的搭建tcp底层,并且能负载到同时在线N多人 Go自身在特定平台会使用对应平台的io重用方案,比如epoll,kqueue等,所以底层部分效率已经不错了,比起自己用C/C++去封装底层或调用libevent之类的库,优势是Go将事件机制封装成了CSP模式,编程变得方便了,但是需要付出goroutine调度的开销,在游戏项目上实践的经验是调度开销可接受,无需做额外工作来优化。如果自己要在Go里面调用epoll重新封装一个网络层,这样做所能提升的效率和付出的代价对比起来,性价比太低了,不值得这么做。 所以用Go搭建TCP底层是很省事的,主要关注几个点: 1. 尽量减少系统的IO调用次数,比如使用bufio这个包来减少实际IO次数 2. 尽量减少不必要的数据拷贝,比如消息的封包解包过程,细心点设计是可以做到极少的数据拷贝的 3. IO阻塞时的边界情况处理,比如一个请求处理过程中,如果消息回发导致处理过程阻塞,是否会影响到其他后续请求,又或者广播过程中消息发送阻塞,是否应该把阻塞的连接关闭等 我这有个简单的库可以提供参考:funny/link · GitHub # 如何架构整个服务器端(包括网络层,缓存层,持久化层,日志层,逻辑分发处理层,通信协议层,以及如何有效部署) 这个议题挺大的,但是题主已经明确罗列出了这些项目层级和模块划分,说明是已经有经验的了。Go语言跟其他语言一样分层分模块,没太大特别之处。 Go在组织游戏项目的时候有一点需要提前预防,就是业务模块间的递归引用。Go从语法上是禁止包递归引用的。但是游戏的业务模块很多,交叉是很平常的事情,所以需要提前设计一个项目结构来防止业务上的交叉碰到Go的语法限制。 具体的代码我就不写在这边了,思路就是通过公共接口的注册来解耦包的引用关系,我这边有个演示项目可以参考一下,并没有什么高深的设计在里面,就是一个脑筋急转弯而已。funny/go-project-demo · GitHub 缓存层、持久层的实现方式不同团队差异巨大,这边我能分享的经验只有一点,就是尽量不要人工去维护缓存和持久化之间的关系,尽量做成自动的,这样才不会人工引发BUG导致数据损坏。 如果要说具体说法,我们目前是MySQL做持久存储,这样做数据分析和备份什么的都比较方便也比较可靠。缓存则是根据MySQL的结构自动生成代码映射到Go里边的。 Go做大数据量的缓存的时候需要小心GC的负载,如果你的缓存设计是内存吃得多但是对象很少,就不用担心这一点。如果是像我一样一条MySQL数据对应一个Go对象到内存里的,就要小心处理,要嘛做成按需加载的,减少对象数量。要嘛就是干脆用堆外内存来存储缓存数据,这样GC不会有负担。堆外内存的一些技巧我之前网上也有分享过了,原理比较简单,就是用cgo机制让C来分配内存。 通讯层也是各个项目差异很大的部分,我们团队是自己实现一套二进制协议格式,也有团队是用protobuf,也有用json,各式各样都有。这个按个人喜好和传统来做就可以了,差异不会差到哪里去的。 如果做自定义格式的协议,我这有个二进制操作的库可以用用:funny/binary · GitHub 部署方面其实跟语言无关,单进程的结构都很好运维和部署,多进程都会麻烦一些,所有语言都一样的,这方面我没有太值得分享的经验。 # goroutine间如何高效通信 goroutine就是靠chan通讯了,没什么好办法。如果关心goroutine通讯的各种开销,最好是按自己的应用场景测试看看。 有些场景下chan通讯是不划算的,比如一个简单的map数据获取,可能用锁就可以了。有些场景用chan是必须的,比如做个多人互动功能。 还有就是带buffer的chan和不带buffer的chan的差异,最好通过试验来让自己有个直观认识,除了异步和同步的差异,还会有边界情况的处理差异,比如带buffer的chan阻塞了,在功能设计上需要考虑,否则可能引发严重问题,这个上面其实也讲了。 # 担心go1.5版本及以后的gc问题 如果有这个担心,就最好从项目初期就提前预防,比如从设计上就避免产生大量对象,或者就是前面说的堆外内存分配,或者是通过多进程结构来分散负担。 还有就是提前做好测试,对量级有个心理预期。 游戏已经比实时交易系统好很多了,正常的用Go是不用担心GC延迟导致服务质量不符合需求的,游戏会产生大量对象的地方就是缓存了,这个地方小心设计基本上就没什么问题了。 1.5版本的GC我还没测试过,因为用了堆外内存,现在不怎么关心这个了。。。 # 如何调试程序和快速定位线上问题 调试Go确实有点麻烦,如果要用GDB调试Go,你最好关掉Go的编译优化,否则可能出现调试不了的情况。另外就是靠打印了,所以我们项目里面有这样一个模块:funny/debug · GitHub 线上问题定位要靠提前留好定位措施来实现,最常用的就是排查死锁和排查内存泄漏,可以参考一下这个模块:funny/pprof · GitHub 死锁的时候通过lookup goroutine来获取所有goroutine的堆栈跟踪信息,然后排查死锁的原因。 内存泄漏或者效率问题通过cpuprof和memprof来定位问题:Go语言程序的状态监控 保存cpuprof和memprof的工具函数在 funny/pprof 包里也有。 # 压力测试负载能力 游戏的完整压力测试我没做过,感觉没法做,游戏操作逻辑太复杂了。所以我的测试方式是对逐个可能成为瓶颈的点做benchmark或对算法做benchmark,来估计一个整体的效果。 另外就是开发期间持续监控所有请求的响应时间,我们团队的要求是在小于1毫秒,实际线上平均是30多微秒(不包含IO过程),有这样的响应速度,应该不用担心负载问题,如果有负载问题,会在请求执行时间上暴露出来。 用来监控请求执行时间的模块也在这个包里:funny/pprof · GitHub 能力范围内只能回答这些了,我最近在研究怎么进一步提高开发和运维的整体效率,所以感觉自己还很多东西不懂,懂的只是一些皮毛的东西,当抛砖引玉了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2016-08-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Golang语言社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档