让我们谈谈游戏服务器开发(上)

从毕业至今,大约从事了4年的游戏服务器开发工作。其中页游2年,手游2年,端游虽然没有接触过实质的开发,但是同从事端游服务器开发朋友闲聊中也或多或少了解一些。想整理一些思路,沉淀成文字,以讨论页/手游服务器开发为主,端游为辅助。首先谈谈很有争议的开发语言选择吧。

开发语言

我们先看一张2016年6月编程语言排行流行榜201606语言排行

这里不讨论语言的优劣,讲一个故事:

朋友就职一个创业公司做互联网APP的,当时技术合伙人使用的是Ruby搭建了服务器。1年半过去,到手投资花的差不多但产品却没什么起色,技术合伙人因为私事要离职,公司需要找一个资深的Ruby开发来接替他的位置,但是在中国这个环境,Ruby资深且符合他们那个团队风格确实难招。

等到第二轮投资都到位了,合适的人还没招来(当然可能一部分原因钱权没给够,这里细节不明),最后只能狠心请人用Java进行服务器重构,其过程对产品影响是不言而喻的。

现在各类招聘网站上,游戏服务端程序员一般是C/C++居多,Java,node.js,erlang占有一席之地,python与php也可以分到一杯羹,还有对lua有要求的服务端职位。并不是表示C/C++要好用、高级,造成这样的原因很多,可能要用一篇文章来分析,我们这里只从环境上来看:远一点,大学教育对C/C++的推崇,用人单位笔试面试侧重C/C++;近一点,在页/手游还没火起来时,传统服务器开发底层相关基本是C/C++一家独大,包括BAT里的,B和T都是以C/C++作为主流语言;更近一点,正是这种中国语言人才的基数导致了,开始一部分创业成功的服务器使用了上述语言,然后培养了一代又一代使用该语言的相关从业人员 – 比如我。

还听闻过一个端游服务端用纯C实现,他们需求就是服务端要快,尽一切可能的快,因为多人场景PVP的存在,服务端转发校验负载重,所以他们选择了C。为什么不选C++或者Java?选C++或者Java不能满足需求吗?这个结果不得而知。个人猜测这个选择一方面和大公司语言环境有关,一方面出于对纯C效率的认可,也有可能是主程对技术选型的判断,所以选择纯C满足了他们的需求。

大公司肯定有自己成熟的框架体系,没有很多的选择权,创业公司、小公司一般都会在满足需求前提下选择基数大(好招人的)语言,基数小容易作死。

进程,线程,协程

个人比较喜欢这样的服务器框架

master - worker

Master管理全局,Worker要是挂了负责拉起并且记录(会遇见一直挂的情况,要记录次数,满足多少后不拉起)。Worker负责主逻辑,Thread是功能线程,比较常见的是异步日志的Thread,逻辑处理Thread。

协程是又是一个容易引起争议的东西,对于游戏服务器明显好处是在异步调用时可以简化逻辑(下一节会详细说明),要说明显缺点可能就是万一出了BUG,会增加查找BUG的复杂度。但是既然选择了多进程多线程,协程其实不是必须的,就和goto一样,它不是异类,用在合适的地方最合适。

同步,异步,阻塞

解释这些概念,举个生活中的例子,去一餐厅吃饭,人很多得排号,那么有以下几种结果:

  1. 同步阻塞:门口等,一直等到叫自己;

  2. 同步非阻塞:去附近逛逛,但是得时刻关注是不是排到了,万一排到人不在门口…;

  3. 异步阻塞:店家提供短信通知服务,但是自己还是选择门口等(基本不会这样做吧:D);

  4. 异步非阻塞:店家提供短信通知服务,我可以附近逛逛,干干自己的事情,随时等候短信通知

游戏服务器经常会遇见这样的场景

int32_t procTask()
{
    //do something
    return asyncGetScoreFromXXSvr(); //需要去其他Svr处理一些数据
}

int32_t getScoreFromXXSvrCallBack() //其他Svr处理完毕回调
{
    //continue do something
    return 0;
}

比较典型的异步非阻塞策略,前后逻辑在不同的接口。对于性能要求不太高的程序可以采用同步阻塞简化逻辑代码,或者使用协程达到“异步非阻塞”

int32_t procTask()
{
    //do something
    syncGetScoreFromXXSvr(); //同步处理数据,会造成阻塞
    //coGetScoreFromXXSvr(); //协程处理,假阻塞
    //continue do something
    return 0
}

具体如何选择策略要根据具体的需求,异步非阻塞策略一般来说可以满足各种需求,追求代码逻辑简洁可以考虑同步阻塞(前提是使用多线程处理逻辑)或者协程,还有一种Future/Promise的模式也可以简化异步逻辑。

这里多插一句,socket相关阻塞接口的协程要注意在阻塞之前加入poll/epoll事件,然后yield出去(例如recv),处理完毕后resume回来。机制比较复杂,可以参考:

微信开源的libco协程组件

支持服务器平行扩容(非单点负载均衡)

服务端开发支持服务的平行扩容在设计之初十分关键,譬如一台服务器的处理能力有限,达到这个上限该怎么办?若服务支持平行扩容我们增加对应处理服务器即可,一般情况在部署之初就设置至少2台服务器,一台不幸宕机,另外一台继续提供服务。当然还有一种情况是:业务不给力,需要消减服务器成本,原本有3台运行的服务器,现下架一台,2台就可以完全满足需求的情况。

服务器平行扩容要点是:逻辑服尽量不存储和处理对象相关的内容,游戏服务器处理对象最直接的就是玩家信息。玩家数据放入缓存,每次请求进入逻辑服从缓存重新拉取信息,如果服务一定会对逻辑相关内容的处理 – 比如游戏排行榜,必须考虑到平行服务器之间的数据同步,可以参考这篇博文

附一个平行服务处理的框架图:

服务器平行扩容例子 (2)

step1(请求)->step2(从缓存拉取数据)->step3(数据返回)->step4(更新缓存,若没变动这步省略)->step5(通知客户端结果)

这里也可以在接入层用特定的hash规则将同一个玩家放入到同一个Server中处理,譬如玩家A一直连接使用的Server1,这样可以避免每次去数据缓存拉取全量信息,先拉取version比对,version不匹配再拉取全量。

既然提到version还有一个例子:在step4更新时会出现这种情况,你更新数据不是最新了 – 就是说在step3->step4逻辑处理过程中,有其他逻辑更新了你的缓存数据,造成了version不匹配,分两种情况:一、若是Client->Server的处理,在step5返回特定错误码让客户端重做这次协议;二、若是Server->Server,即Server内部之间的调用出现,需要一个辅助线程来重试更新数据。

还有一种单点主备切换的模式,这种模式可以解决服务单点问题,但是一旦负载超出预期容易造成雪崩。所以对于特定服务,比如有上限要求的可以使用这种模式。

最后

写的比较杂,有些东西是想到哪里写到哪里,游戏服务器开发涵盖的内容和细节比较多,有技术上的、也有非技术上的,故分了上下两篇,下篇的主要内容暂时规划为反作弊,通讯协议和一些非技术相关的内容。

(全文结束)


转载文章请注明出处:漫漫路 - lanindex.com

4 Comments

 Add your comment
  1. 个人感觉用纯C实现服务器那个例子,重点不在于用C语言,而是写代码的人。公司注重的也不是用什么语言,而是C到这种地步的人,其能力绝非泛泛之辈。个人浅见,不喜勿喷。

    • 很多情况下公司/项目会让一些牛人来决策一些方案,这些牛人肯定绝非泛泛之辈,对方案的背景,可行性,风险应该都会有充分的分析。
      从公司层面来看,它不关注你怎么实现,而是在乎你能否实现,说的更实际点能否带来利润。所以说你的对,其实最终还是看有权利选择使用这门语言的人。

  2. 看楼主的文章是一种享受,思路清晰,表达直接,学习一个,能写出这种水平的,起码是个腾讯T4吧

Leave a Comment

Your email address will not be published.