我不得不承认Q我的能力不以写出一?00%不会(x)宕机的游戏服务器E序Q这也不能全怪我的能力太弱,谁让咱国内网游玩家数量庞大,哪个游戏刚上U时没有挤的爆满q?q有些或是猎奇,或是谋私的个人和l织Q在刉着千奇百怪,匪夷所思的数据包及(qing)操作程来试探你的服务器。这些都曾是我在服务器宕机后向老板开q理由?/p>
当WOWl于来到中国Ӟ我一Ҏ(gu)喜着l于可以在艾泽拉斯的大陆上自q,一边却咒骂着9C的破服务器,动不动就宕机。当?dng)wؓ(f)游戏E序设计师的我明知道Q这大部分的错误都不应归|于代理?CQ但是,谁让blizzard是我?j)目中的,谁又让WOW成ؓ(f)我游戏制作的教科书呢。好吧,我知道上面这D|力追捧blizzard跟WOW的话可能早已让你恶心(j)q连Q不堪入目了(jin)Q对不vQ忘?sh)(jin)这一节,让我们l?/p>
服务器宕机后都发生了(jin)些什么?
昄的,宕机后玩家会(x)骂,像我在玩WOW旉P骂游戏公司,骂老板Q骂GM。非常抱歉,我们可爱的玩家们gq不清楚Q这个时候最该骂的其实是我们q些E序员(sh)。长久的遗忘被我们当成了(jin)包容Q以至于游戏E序员在公司里都L?jin)趾高气扬,不可一世的坏毛病:(x)看吧Q策划们Q你们做的太烂了(jin)Q数gqQ玩法没新意Q只?x)照抄WOW跟大菠萝Q能怪玩安你们吗?q营不得力,买服务器的钱不知道去?jin)哪里,游戏里卡的要死,偶尔办个zdq没半点吸引力,能不被玩安你是无良q营商吗QGM们能不天天被骂家指着骂吗Q?#8230;…呃,又扯q了(jin)?/p>
赶紧先把服务器重启吧。老板正站在你的n后,一脸愁容,虽然暂时q没有发作,但看得出来:(x)老板很生气,后果很严重!
玩家们很快又回来?jin),不得不?f)玩家们的毅力和执着_而感动,更ؓ(f)自己的错误而愧疚,凌晨时分Q服务器启了(jin)又宕Q宕?jin)又启,如此反复Q可热情的玩家们依然陪着我在折腾。哦Q当q安其拉开门的时候,我也曾这h腾过?/p>
q个时候不是你一个h在战斗。GM们在忙碌地处理着玩家不断打来的投诉电(sh)话:(x)刚买的装备在宕机后消׃(jin)Q花光了(jin)w上所有材料合成的武器回档?jin),但材料却没有q给?#8230;…数据库维护组的同事们也在紧张的恢复着数据Q尽可能的将玩家的损失减到最?/p>
真是一件o(h)人沮丧的事?/p>
真的该试着做点什么了(jin)吧!
既然我们非常不愿意看到宕机的情况发生Q但又无?00%保证写出来的服务器程序一定不?x)出错,那我们就在当机发生后的抢救措施上q功夫Q让玩家的损׃至于太大Q也让我们的l护人员些压力吧?/p>
一个最单也最有效的做法是为每一台服务器都配备物理冗余,同步更新冗余服务器上的状态,当宕机发生时Q立卛_处理切换到后备服务器上。只是,物理冗余的代价太大,从成本方面考虑Q老板可能不大愿意点头?/p>
既然不能做硬冗余Q那再来考虑软的吧?/p>
如果只是单的启动冗余q程Q其实是换汤不换药的做法。原来能?000人的服务器,׃同时q行?jin)两个相同的q程Q得CPU和内存开销都翻?jin)倍,l果是只能跑500Z(jin)。还是要加服务器?/p>
看来只能更深一层,从架构设计上来动手了(jin)?/p>
假设我们的游戏世界是由多个独立场景构成的Q那么在实现上我们可以让q些场景在进E上也独立,q样做的好处是可以得一个场景的宕机不会(x)影响到其他场景的正常q行。如果我们的游戏世界物理上没有分隔,是一个无~的大世界,我们也可以h为的其分成多个独立区域Q所需要做的额外工作是处理好那些站在区域边界附q的对象。事实上Q现在的无缝大世界也都是q样实现的?/p>
有了(jin)q样一个前提,我们再来看这个已宕掉的场景该如何处理?/p>
q是老办法,赶紧先把它拉h吧。一个具体可行的Ҏ(gu)是,由场景管理器Q或者你也有可能叫它世界服务器,来监视各个场景进E的q行状态,当某个场景异常失去联pLQ由理器来其重新启动。这里需要再q?j)思的是,如何让玩家数据正常地发送到新启动的场景q程中,而且q个q程对于客户端来说是透明的?/p>
q个Ҏ(gu)听v来似乎不错,只是Q如果宕掉的是场景管理器q程Q那该怎么办呢Q?/p>
按照前面的描qͼ场景理器可以看作是整个游戏世界的中?j),它以一个指挥者的w䆾l护着游戏世界的有序运行,所以它的宕机对整个游戏世界的媄(jing)响也会(x)是巨大的?/p>
有没有什么办法能够得场景管理器q程再次启动后能够恢复先前的状态呢Q?/p>
我们可以为管理器和场景进E定义一套协议,使得理器不仅能够创建ƈ恢复一个已有场景,而且场景理器还能通过现有的场景进E数据恢复出自己?/p>
一个理Z可行的方案是Q场景管理器与场景进E间保持TCP长连接,q以一定频率进行心(j)跌p,L一方发现联pM断或长时间未收到?j)蟩包后都?x)立即做出处理?/p>
如果是管理器发现场景q程失去联系Q那启动新的场景,如前面所描述的那栗如果是场景q程发现理器失去联p,那就立即启动重连q程Q直接再ơ连接上理器,然后立即自己当前的状态和负责的场景ID报告l管理器。管理器通过q些上报的数据就能恢复出游戏世界内的场景对应关系表,也就是恢复出?jin)自己原来的状态?/p>
q程是恢复出来了(jin)Q可我们忘(sh)(jin)最重要的内容:(x)数据。当场景q程宕机后,上面保存的玩家属性数据也随之丢失?jin),虽然我们能够再次这个场景创建出来,q把原来在这个场景内的客L(fng)数据重新定向q来Q但q些客户端对应的玩家对象的数据却没有?jin),游戏仍然无法l箋(hu)?/p>
也许我们可以再做一点修改,把场景内的玩家数据分d来,保存C个独立的q程上,比如Q我们可以把q个q程叫做数据服务器,或者数据中?j)之cȝ。一个隐含的要求是,数据服务器的逻辑实现非常单,单到你可以认为它是绝对安全的Q不?x)宕机。所以,保存在这里的玩家数据也就是绝对安全的?/p>
让我们在q个问题?sh)稍微再深入一炏V?/p>
场景q程上每ơ执行玩家的游戏逻辑旉要异步地到数据服务器上来存取数据Q这个开销可能太大Q而且?x)得一些游戏逻辑的实现变的很复杂Q那么,把一些会(x)频繁使用到的数据直接保存在场景进E中Q当数据发生改变时同步更新到数据服务器上Q这样可能会(x)比较Ҏ(gu)接受?/p>
老板全都满意?jin)吗Q?/strong>
从理Z来说Q我们已l解决了(jin)场景q程宕机和管理器宕机后的状态恢复问题,q且在场景恢复后也不?x)因Z׃(jin)玩家数据而无法l进行游戏,而且Q只要处理得当,q个q程对客L(fng)来说可以是完全透明的,也就是玩家根本不知道服务器上有个q程意外l束Q我们做?jin)这么多的工作来它恢复了(jin)?/p>
事实上,q个q程的透明也是必须的,我们q不需要嚷L(fng)告诉我们的用P也就是玩Ӟ我们做了(jin)多少多少事情来让你玩的更畅Q又׃(jin)多少多少_֊来解军_为服务器宕机而引L(fng)ȝ(ch)Q对于最l的用户来说Q他只需要n受最好的服务。闲话少_(d)让我们l?/p>
真的已经完全解决?jin)所有问题吗Q?/strong>
惌q样一个场景:(x)我带着几个刚刚降(f)到艾泽拉斯大陆的伙伴冲向?jin)艾(dng)文林Q去开荒霍|正在霍格只剩下一丝血的时候,服务器稍E卡?jin)一下,{我~过来Q面前的霍格骤然消失Q地上也不见怽。找?jin)一圈,它正在出生点摇头晃脑Q也在四处张望,但头上的血条分明是Q满血Q?/p>
怎么回事Q?/p>
处理q张地图的场景进E意外结束了(jin)Q服务器的宕机处理机制很快地恢复?jin)这个场景进E,q且把我的客L(fng)数据重新定向C(jin)新场景。只是,事情q不是一切都完美。因个场景是完全重新创徏的,q意味着所有的怪物也是重新创徏q被摆放C(jin)初始位置Q所以,只剩下一丝血的霍格碰上了(jin)好运?#8230;…
cM的还有,正在护送NPCq回营地Q在E微停顿?jin)一?x)儿之后QNPC又重新回C(jin)原来的地方,{等?/p>
虽然q比h初的“客户端被q断开q接Q服务器端数据丢?#8221;要进步了(jin)许多Q但?x)给我工资的老板仍然可能不太满意Q他希望Q霍格应该还在我的面前,而且只有一丝血Q那个跟着我的NPC也应该还在我旁边……
我要是不能说服老板Q这?#8220;Ҏ(gu)不可能完成的dQ?#8221;Q那也就只能坐下来再试一试?/p>
也许Q可以考虑所有对象的数据都保存到数据服务器上Q当?dng)q要求每个怪物都跟玩家一P有一个唯一IDQ这一点实现v来可能会(x)有些ȝ(ch)?/p>
再不?dng)为对象提供一个从已有的内存数据构造的Ҏ(gu)Q这样便可以使用׃n内存来保存现场数据,再从׃n内存?sh)恢复出原来的对象。理Z来说Q这个方法是可行的,只是Q这三十多个字的文字描述要用C++来实C可能会(x)是一Ҏ(gu)大的?xi)战Q所以,q也仅只是可供参考的一个尝试方案?/p>
我想Q我们走的够q了(jin)
让我们先暂停一?x)儿Q回q头来看一看最初的目的。其实我们想要的只是可能的让服务器q程不要宕机Q如果实在是没有办法Q就可能的让宕机后的玩家损失比较小Q不需要我们做大量的工作去做善后处理?/p>
很简单的需求,g我们U缠的有些过头了(jin)?/p>
写出能够E_q行的程序是对程序员的最基本要求Q如果一个程序连E_性都不具备,那根本都不用再去考虑功能啊、扩展啊{其他标准了(jin)。但是,正如我最开始所说的Q没有一个h能够100%保证他写出来的服务器E序是绝对不?x)崩溃的。我们所能要求的只是可能的仔细Q尽可能的多一些必要的试Q离安全可能的更近一步?/p>
剩下的就是在宕机后如何降低损q问题?sh)(jin)?/p>
对于一般的MMOG来说Q玩家在q入游戏时会(x)从数据库中将该玩家的所有相x(chng)据读到内存,以便快速的q行游戏逻辑的处理,而在玩家下线时再数据的改动存回数据库?/p>
昄的,当服务器q程出现意外宕机Ӟ内存?sh)所有的数据都丢׃(jin)Q这也就造成?jin)玩家数据的回档Q而且玩家在游戏中呆的旉长Q回档的损失p大。所以,一个被q泛采用的做法是为玩家数据实CU定时存盘的机制Q就像现在大多数的单机游戏一PAutoSave。比如,?分钟自动为玩家存?sh)ơ盘Q这样就可以使得回档的最大损失控制在5分钟以内?/p>
另外Q对于一些重要数据的变动Q比如玩家花大量游戏货币购买?jin)一件贵重的武器装备Q这时可玩家数据立卛_一ơ存盘操作,q也有效的减少玩家的重大损失?/p>
听v来这是一不错的技术,在意外宕机的时候最多只回档5分钟Q而且q没有贵重物品的损失Q玩家应该是可以接受的吧?/p>
我已l听C(jin)数据库维护员的咆?/strong>
“数据库已l快要崩溃了(jin)Q你׃能让每秒需要执行的SQL语句一点吗Q?#8221;
“?#8230;……”
我一直以为我们的数据库非常强大,可以处理M的数据,唯一的缺点就是查询速度比直接内存读取要慢很多。所以我使用?jin)异步数据存取的?gu)Qƈ且开启了(jin)多个数据库操作线E来q行的执行我的请求,q行的效果看hq(sh)错?/p>
也许Q我应该来算一,每秒U究竟丢?jin)多条操作hl数据库?/p>
请允许我再自U一回,我已l很久没有提到WOW?#8230;…
大概可信的数字是QW(xu)OW一l服务器的玩家数量在3000?000之间Q去掉最大的敎ͼ再去掉最的敎ͼ最后的q_值是Q?000吧,q4000?/p>
4000人在U,假设也是?分钟定时存盘?sh)ơ,再假设所有玩家的存盘旉是^均分布的。这L(fng)下来Q每U种׃(x)?7个玩家向数据库发出存盘请求操作?/p>
?7个,数据库维护组的同事就跟我说不堪重负了(jin)Q笑话,q数据库服务器是谁买的?
先别急,67是玩家数Q但是每个玩家的存盘h不会(x)只有一条SQL语句?/p>
虽然每个游戏的内定w各有差别Q但是一ƾMMOG需要存入数据库的数据少不了(jin)?x)有技能、物品、Q务、宠物、好友、公?x)这些东ѝ取决于游戏的类型差异,每个游戏都会(x)有自q存盘方式Q比如我可能?x)把所有的技能ID作ؓ(f)一条数据来存储Q但是我也有可能把每个技能作Z条单独的记录来存储,q样可以方便Ҏ(gu)能附加数据的扩展Q等{?/p>
但是Q游戏中的物品存储大概都是相同的Q只能是一?l?物品作ؓ(f)一条记录来存储?/p>
而且Q可以说游戏中存储量最大的是物品数据。算一你的角色背包有多大Q?0| 100|q是200|不要忘(sh)(jin)银行、摆摊位、装备拦、宠物背包和邮箱q些地方也能攄品。ƈ且,在游戏进行过E中Q玩家背包中物品数据的变动也是相当的频繁Q不断的有药品被用掉Q不断地又有些小玩意儿被捡v来,不久后,它们又被卖给?jin)NPC?/p>
虽然你可以用一些y妙的比较法来过滤掉那些实际上没有发生变动的物品更新Q另外也不是所有的玩家物品数据变动都很频繁Q但在实际运营中Q尤其是当玩家的背包格数都很多的时候,物品数据的存盘的会(x)成ؓ(f)一个很大的问题?/p>
除了(jin)物品Q还有玩家的基本属性存盘,C会(x)关系存盘{等Q再加上全局公共数据的存盘,如公?x)数据,拍卖行物品数据,如果老板也要我在游戏中开上一家拍卖行的话?/p>
q么一下来,g是有些多?jin)?/p>
再一ơ的?xi)?/strong>
具体的数字将取决于游戏的cd和设计的数据表结构?/p>
而数据库服务器能承担的每U查询数则取决于数据库服务器的Yg配置情况?/p>
但是一般来_(d)数据库维护h员可能会(x)告诉我,当每U执行的SQL语句数达?000条时Q数据库服务器将?x)感受到明显的压力,我可能也会(x)看到数据库执行队列中的hC直在增长Q也可能?x)看到数据库服务器间歇性地拒绝响应Q等{?/p>
看v来我们又一ơ的面对C(jin)巨大的打h?/p>
q个问题的v因是什么?我们不希望服务器q程宕机时回档太久,所以我们增加了(jin)一个玩家数据定时存盘的机制Q结果却D?jin)数据库h的骤然增多?/p>
那再退回到q个L(fng)处,定时存盘的旉间隔廉点,比如10分钟才存?sh)ơ?数据库的压力?x)有~解Q但最初的问题却又?x)有所暴露。真是个两难的问题?/p>
既想要玩家数据存盘间隔时间短一点,又不想给数据库造成的压力太大?/p>
同样的需求似乎出现过很多回了(jin)Q在中间加一层代理做~冲。我们姑且称q一层代理ؓ(f)数据库代理服务器Q它所要完成的工作是从场景q程攉玩家的定时存盘请求数据,再以一个低一点的频率写入到数据库?/p>
听v来这又像是一个换汤不换药的做法,写入数据库的旉间隔q是变长?jin)。但实际上在前面我们曾l描q过Q如果服务器q程不会(x)出现意外的宕机,玩家数据只需要在他上U时dQ在他下U时写入卛_Q中间添加的q些定时存盘q程完全只是Z(jin)防范宕机回档所造成的巨大损失?/p>
因ؓ(f)q个中间代理层的加入Q我们把场景q程宕机的隐(zhn)与数据丢失的后果隔d来了(jin)Q现在即使场景进E宕机,数据q在数据库代理服务器上,当然q里又隐含了(jin)一个条Ӟ(x)数据库代理服务器需要够稳定,不会(x)在场景进E之前先宕掉。事实上Q因个代理进E的工作是,我们完全有理q信,q个q程是非常稳定的Q那L(fng)话,多久旉才把~存的数据真正写入数据库Q就看你自己的喜好了(jin)?/p>
该结束了(jin)?/strong>
是否有些似曾相识的感觉?
没错Q前面我们曾l描q过一个数据服务器Q也是这栯的?/p>
所以,数据服务器,数据库代理服务器可以合ƈCP来共同保证数据的安全?/p>
再加上场景进E与理器进E的恢复协议Q让服务器的重启对玩家保持透明?/p>
看v来这个晚上可以睡个安E?/p>

]]>