注:本文纯属个人思考过程的记录,仅供参考,估计有众多不当之处欢迎指正。


        服务器并行化,对我们现代的程序员来说有着极强烈的吸引力。分布计算、云服务、高并发数据库、虚拟机、无缝大世界网游服务器,等等,所有这些热门技术,都指向了同一个目标:并行计算。


        并行计算的最终形态我认为是——可以通过增加硬件实现几乎无限的服务器负载。


        从需求上来看,但凡是弱互动——即反馈延迟可以很高的服务,比如静态网页、博客、论坛——都可以很好的做并发计算,把负载分摊在多个服务器上。但凡是强互动——实时网络游戏、社交游戏、网页游戏等等——都会在并发方面阻碍重重。


        原因很简单,从实现服务器并发的基础上看,无论多么庞大复杂的系统,最终都被分解为——数据库读写、网络传输。仅此而已。数据库的读写和网络传输,相比本地内存读写太过漫长了,在某些应用中显得不切实际。


        我本来想广泛针对各种应用需求来谈并发,写到这里我发现实际上并发难度最大的需求基本只有游戏(或者说广义上的游戏),下面只思考游戏服务器好了。


        Q:一个最大支持3000玩家不可并发的服务器,和一个单进程800玩家可并发的服务器,你选择哪个?


        我现在觉得,如果是小团队做移动端休闲(小型)网游,那么前者是非常棒的选择。如果实时互动较少,那么游戏会变得和常规网游不太一样——短连接,没有明显的登录、下线的分界线。这种情况下最大承载人数就不是“在线”人数,而是比如说“半小时内活跃用户”,可周旋空间很大,可优化空间也很大,唯一有要求的只是服务器内存而已。而从阿里云服务器提供的价格表来看,内存还是比较便宜的。每个玩家20k数据的话,1G内存可以放下5万玩家。顺便一提阿里云单机最大可以提供32G内存。如果你的服务器代码能充分利用内存,那么从数字上来看你已经解决了一切问题,因为一个百万活跃用户的应用足够让你发家致富笑傲江湖了。


        对一个正常的“网游”——有漂亮的画面和几十几百人同屏玩耍的那种——来说,服务器承载人数充分受到CPU制约,不是简单的内存问题了。这种事情很复杂,但是这是个好事,因为它非常适合几十人的大型网游团队来做,解决这个难题的过程中有很多的挑战和机遇,足够发挥优秀程序员的才干,经理能少操一份心。


        解决大型网游并行处理,有几种常见的技术——按场景划分多服务器职责外加全局和关系服务器,共享内存。有很多国内做的很好的游戏比如《御龙在天》,他们采用的技术都是保密的。所以更得感谢云风,他介绍的skynet值得好好了解:

http://blog.codingnow.com/2012/09/the_design_of_skynet.html


        skynet给了我很大启发,游戏的高性能并行基础,很可能就是这么简单——尽可能的让多线程、多进程协作起来,这样形成的系统简洁而又高效。满足性能要求的同时简单到不易产生BUG。


        另外有一篇coolshell.cn上面的讲异步逻辑的文章找不到了。

        

        被云风的博客和一些其他人“荼毒”久了,对大型服务器架构我有两个观点:

        1、游戏服务器并发,应该尽可能把数据传输放在更快的路径上——能读写内存,就不使用pipe;能使用pipe,就不做网络传输;能网络传输,就不读写DB。所以尽可能把有关联的任务让一个进程分线程去做,一个进程扛不住就单机多进程。一般来说,8核服务器在CPU用光之前还是能做不少事情的。

        多线程可以方便的读写同一块内存,单机多进程可以用共享内存或者使用pipe。无论如何比网络传输能快至少一个量级。用网络传输就会有延迟,延迟哪怕20ms都迫使你不想做成阻塞式逻辑,不阻塞的话要么多线程要么协程要么异步,无论怎么做都会增大逻辑编写难度埋下bug。如果用阻塞的pipe是不是心里舒坦一点呢?

        这块写的有点想当然了,没有测试过,看网上别人的测试,UNIX SOCKET和pipe的效率在同一个数量级上,pipe稍快。但是都是本机测试,如果真的跨主机收发数据肯定还是慢不少。

        当然也会有人嫌这种做法部署起来麻烦——必须保证某几个进程在同一台服务器上。我这只是纯技术层面的探讨。

        值得一提,一般来说共享数据离不开拷贝(按我的经验很少有直接拿共享内存开玩笑的,写乱了会很糟糕),而C/C++处理拷贝很简单易行,基本可以从原始内存摇身一变恢复成一个结构体。而python要用到pickle的方法,总是逃不了要反复新建PyObj。


        2、游戏服务器,每一个服务器都要保证自身逻辑的正确性和健壮性。任何一个逻辑过程都是序列化执行而且明确的。

        我是这个意思:每个问题都有唯一的答案。比如你想找一个玩家,他肯定在某一个场景服务器里,如果他不在场景里,那么全局服总会知道他在哪。

                再比如,某个服务器A以为A和B是好友,服务器B认为A和B不是好友,由于服务器同步问题,这种情况有可能出现。但是总有一个全局服或者社交关系服务器,知道A和B到底是不是好友。

        复杂一点,如果全局有一个计数器G=10,服务器A想把它加1,服务器B也想把它加1。如果A和B直接set G=11,那么肯定是不对的,G应当是12而不是11。A和B应当发送INC 1指令,这样无论哪个指令先到达G最终都会等于12。如果附加一个条件——G最大值是11,超过11的增加会判定为失败。怎么解决呢?

        我认为千万不要把这个问题搞的太复杂,把并行计算技术里的一些投票算法拿到这里用。可以这样解:指定一个协议INC,参数为(当前值,增加值),某个时刻 A发送 INC (10,  1),B发送INC (10, 1)。之后B的包先到达了,全局服判定G==10,ok,有权增加1。然后给服务器B返回最新结果(成功,G=11)。后来A的包到达了,全局服发现G不等于10,则A的请求失败,全局服把最新结果(失败,G=11)发给A,之后怎么处理是写具体游戏逻辑的人负责的,要从游戏设计上才能解决——比如通知玩家为什么失败。


        说到最后总结一下,游戏服务器提升负载能力的关键是——尽可能压榨单主机性能,尽可能避免网络并行计算。




Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐