Daly的游戏人生

2012年11月30日 #

服务器程序常见bug总结

   最近整理了过去一年发生过的bug,包含跟其他项目组程序朋友交流的例子, 都是大家发生过的真实营运事故。
   游戏服务器程序,很多bug的原因都是共通的。抽象出了以下10点启示, 作为checklist, 写下来以后写程序review时自检:

1. 安全边界问题
     对于有界限的东西(数值,buffer空间,队列或一切对象容器),一定要考虑越界判断。
     启示:用snprint, strncpy等限制长度.  永远都要考虑超过边界的情况
               数值加法和乘法:考虑上限溢出; 
               减法:考虑负数; 除法,判断分母

2. 输入参数非法
    case1: 扣钱逻辑,减去一个负数,变成了加钱。  
    case2: int型大负数相加,负溢出变成大正数
    启示:test case要全覆盖输入参数范围, 处理各种可能的情况

3. 上下文改变错误
     共享变量/全局变量被外部改变,这似乎很常见,而且有时很隐蔽。在异步回调的情况下更常见。

     check A变量 
     call func_B()
     ....
     A变量被func_B改变了, 但继续信任A变量check的结果。

    启示:白盒复查代码时,注意检查调用后的变化。
               减少共享变量和全局变量的使用
               外部接口调用后,注意共享变量的更新和恢复
     启示:在最接近执行的地方,检查上下文变量。不信任调用者,如果效率不关键,多一遍冗余检查没有坏处

4. 执行中断
     动态脚本抛异常,或者引擎层面的EINTR中断信号,都有可能中断代码执行,需要考虑函数的重入性问题。
     启示:要检查一致性,有些逻辑不允许多次被执行(比如发奖励),需要有状态变量确保只执行1次(避免出刷bug)
     推广到异步环境(多线程,多进程,各种回调),事务的中断也有一个重入性问题,解决方法也只有一个:用一个唯一可辨认的状态变量,保证某些逻辑不会被多次执行(比如购物应用中,用唯一订单号来识别,状态改变是一次性的,当逻辑运行多次,也不会重复加物品,或者重复扣钱了)

5. 终止条件问题--死循环
     case: 异步环境中,RPC远程调用,调用成环,逻辑一直不结束。
     启示:while或递归的终止条件,逻辑全覆盖检查,避免死循环。较深层次的互相调用,要注意是否出现了递归,是否有可能死循环。

6. 关联数据操作的不一致
     例子:Employee对象有company变量, Company中有employee变量,
          如果操作改变其中一方,而另一方没有改变,则造成数据不一致。
      (数据库表可以指定constrain, 关联表删除, 但代码变量中需要程序员自己实现)
       双向引用的数据一致性问题,要特别注意。
       为什么要双向引用?为了查找效率,而避免遍历其中一方.
       这个问题本质是数据一致性问题,编程中遇到的很多bug也归结到这个问题,比如野指针,就是因为数据结构相互引用的操作不一致造成的。
       处理这个问题,个人经验是,他们的attach,detach操作尽可能在同一个模块,不要分散在多个地方随意修改,所有修改都集中在同一级接口做。

       同理适用于new, delete, malloc, free这些分配,释放,都集中在同一层的接口/模块文件中做,debug起来也容易;非常反感在一个地方new, 然后不知道哪个模块去delete, 很容易泄漏或者野指针, 无论如何,想办法传递这些指针,一直传到分配他所在的模块文件中释放,而且new和delete的接口代码要靠近,方便查找问题。

7. 涉及多玩家,防止笔误传错参数
     经典错误: foreach(uid in team) some_func(usernum, xxx)   
     经典错误:有usernum和target两个对象,调用函数搞混了。review时要仔细检查

8. 特殊分支忘了return
     异常判断等if分支忘了return。导致逻辑继续往下走。这属于笔误问题,测试期间未必能留意的到。

9. 异步返回没清变量
    对于异步操作,如果在返回时清变量,这时如果不能保证把变量清掉(比如期间玩家下线无法离线修改该变量),就会出刷。
    启示:对于已奖励标记,一定要保证各种情况下领奖后能正确记录。

10. 瞬爆容量上限
     case1:  网络待发送队列,因为瞬间大量请求,塞满抛异常,导致流程受影响。
     case2:  大量连接请求,listen的accept没有规定单次读事件的accept,用了while(true), 导致爆机
                在listen fd的读事件回调中, 通常会accept所有新的连接请求,如果用while(true)而不设一个上限,就有可能被攻击(想象一下客户端也用一个死循环来做connect)。
                一方面要限制单次接受的socket次数, 另外各个状态要有超时机制,踢掉不寻常的连接,以防被攻击占尽资源。

     case3: 异步情况下,要限制操作者连续频繁的操作。(比如在请求入口处增加最少时间间隔限制,避免玩家狂点,形成雪崩效应)
                (同时要考虑用户体验,不要让玩家死等,可以做一个提示跳转,或者等候的动画)

参考资料:
附上最近看的一篇文章
<Writing-reliable-online-game-services> 作者曾是魔兽争霸和星际争霸,battle.net的开发者,
里面讲的point也是游戏里经常遇到的可靠性问题。
http://www.codeofhonor.com/blog/wp-content/uploads/2012/04/Patrick-Wyatt-Writing-reliable-online-game-services.pdf



posted @ 2012-11-30 14:14 Daly 阅读(1964) | 评论 (5)编辑 收藏

2012年8月5日 #

网游服务器多进程架构的思考

    by  Daly
    网游服务器程序优化要解决的最主要矛盾无非就是在保证流畅游戏体验(响应时间在可接受范围)的前提下,容纳更多的玩家,当然还要保证开发的便捷性。一个靠谱的MMOG游戏服务器基本上都是多线程或多进程的架构, 利用多个CPU核把串行处理变成并行处理,以容纳更大的并发玩家规模。
    然而并行处理程序会使开发的复杂度增加,一不小心很容易出一些诡异bug。为什么这样说呢?实际环境的大部分程序,函数的执行结果与状态数据相关(外部状态,全局数据),并且函数执行可能会改变这些状态。如果把处理模块拆成多进程,进程间的这些状态数据的一致性和处理时序,会影响到结果的正确性。多进程状态数据的管理,读写和同步更新机制,便是本文要探讨的主要问题。 
       如果函数能变成无状态的(结果只与输入参数相关),则分拆成多进程毫无压力。于是业界开始探讨erlang这种函数式编程语言,并有已有实际游戏项目(参看:http://www.qingliangcn.com/) 。不过笔者觉得,erlang的无状态,本质上是把状态数据通过函数参数传递,这样意味着频繁而大量的数据复制和传递,是否更适合于MMORPG开发很难说,本文不予讨论,可见文章末尾参考资料。下面探讨一下状态数据在多进程之间的问题。

     为了容易描述,整个架构如下图
                      G
    client <--->║ <------> A
                     ║ <------> B

     其中G表示接入网关,负责把client协议分发到内网对应处理进程,A,B是负责不同功能的处理进程,client表示客户端,玩家状态数据只有个v和w两个。用reqA,reqB分别表示client对A, B的处理请求,respA, respB表示A,B返回给client的处理结果。
     游戏逻辑大部分情况下需要保证状态数据的强一致性,基于过期的数据进行处理会得到错误的结果(分布式数据一致性的工程问题见文末的参考资料)。举个有点蹩脚的例子,假设client先后发出reqA, reqB两个请求,reqA是换武器,reqB是发起攻击,变量v是攻击输出量(dps)。reqB在reqA之后发出,攻击理应是按穿上武器后的dps数值来计算的。但多进程情况下,却有可能reqB先于reqA处理(比如A进程很忙),这时reqB的逻辑会基于还没穿上装备时的变量v来计算结果。下面分别讨论几种解决数据一致性问题的方案。
模式一:共享内存
     适合于单机多进程或多线程的模式。
     优点:数据只有一份,可以保证强一致性。
     缺点:进程无法扩展到多台服务器;
          需要加锁,加锁相当于把处理串行化,还是有可能被某一个较忙的进程卡住。如果精心设计和划分数据,减少锁的粒度可以提高性能,但细粒度的锁(设计成类似MySQL的行级锁),在涉及多个玩家数据的交互逻辑时,稍有不慎又容易导致死锁。随手写一个:
        假设进程A和B同样执行以下类似的逻辑
         foreach( user in mapA) {
              lock(user);
              lock(user‘s friend);
              do_something();
              unlock(user's friend);
              unlock(user_id);
         }
         由于遍历的是map, 进程A和B中的user顺序有可能交叉, 假设交叉的两个user互为friend,就可能死锁了。
         参考资料[4]采用了这种模式的方案。
模式二:状态数据只由一个进程管理
      把状态数据根据游戏逻辑进行划分,比如变量v只由A读写, 变量w只由B读写。假如A逻辑需要用到w,则通过异步请求B获取w。
      优点:保证强一致性;数据只有一份,无需进程间复制更新。
      缺点:异步请求增加了响应时间(嗯,又从并行变成了串行); 异步写起来的代码有点ugly,到处是callback, 回来要检查上下文,不然又是诡异bug.
      适用范围:如果状态数据能比较好的划分(即绝大多数情况下,某个数据只会在某个进程的逻辑中用到),用这种方案比较适合,因为简单。比如玩家位置只由AOI进程管理,玩家好友由聊天进程管理。
模式三:多个writer, 类似MVCC方案
      这是完全的分布式设计。每个进程有自己版本的状态数据,进程间可互相同步更新, 状态数据v分别在A,B都有一份。互相update时,根据版本信息进行merge。 
      这种方案不能保证强一致性,而且merge时会有可能发生冲突,需要逻辑开发者仲裁这种冲突(比如按时间先后)。不同于互联网应用,游戏需要较强的数据一致性和实时性,这种方案比较复杂且不太可控。
模式四:Master-Slave模式
      这个是对模式二的一个扩展,某个状态数据还是只由一个进程进行写操作,但其他进程会维持一份cache进行读操作,比如变量v由进程A管理,v的更新会同步到进程B,进程B逻辑如果要用到v,直接读自己的cache就可以了。对于变量v
     特点:这种方式也是不能保证强一致性,只能保证最终一致性。作为模式二的补充,有些数据不需要保证更新时序,根据过期数据进行处理也可以接受(这个是代价,需要权衡玩家体验),可以采取这种方式。而对于不能接受的,走模式二。某些需求reqA,reqB虽然先后发出,如果respA还没反馈回来的话,即使逻辑上reqB先于reqA处理,在玩家体验上也是可以接受的。比如reqA穿装备, 然后reqB攻击,但是respA还没返回,客户端还是看作是没穿上装备,这时候按照老的属性计算攻击值是可接受的。广域网几百毫秒的延迟,reqB要晚于reqA + respA这种概率很小了,如果真的发生,服务器已经很卡了。
    又比如聊天进程,reqA离开场景,然后reqB发聊天消息往当前场景频道,需要知道当前场景的玩家列表(假设场景玩家列表在AOI进程管理),如果reqB先到达聊天进程,拿到旧的场景玩家列表, 那么这个广播就不准确了。这种不一致性的代价可以忍受的话就没问题(在这个聊天栏例子,在跳场景的瞬间发错人了也可以忍),实际情况,进程间通信几个毫秒,发生这种处理时序反转的几率其实非常小了。
综上,如果要设计多进程结构,个人比较推崇模式四。这时又引申出几个问题:状态数据如何合理划分?何时更新?同步给谁?
如何划分?
     有些功能很好划分。比如聊天进程,状态数据只与好友列表有关,这个需求可以忍受过期数据,好友关系由主进程修改,同步到聊天进程。玩家position, 由AOI进程管理,修改同步到主进程,主进程几乎没有需要用到position的逻辑。
    但有些数据就可能很纠结,比如背包数据。玩家交易,在线奖励,战斗都需要修改背包物品数据,而且必须保证强一致性,否则就可能出现丢失或物品复制,该由谁做这个数据的管理者呢?如果AOI进程管理,物品使用效果可以马上生效,但是交易和在线奖励也需要验证背包物品,这些逻辑也放到AOI进程么,如果放,则又牵扯出更多的变量,如果不放,则需要退化成模式2的异步请求。如果放主进程,则使用物品后产生的效果不能立刻同步到AOI进程。可以经过仔细对比,AOI与背包数据交互的频率远高于主进程,于是背包数据可由AOI进程管理。
何时更新?
     两种选择:一有修改立马发送更新给其他进程;队列buffer住所有更新,定时送出去(比如每2秒同步一次);既然是无法保证强一致性,后者性能容易优化些。比如AOI进程中的位置信息变化很频繁,但主进程对位置实时性不敏感(比如只用于持久化,掉线重上后的位置恢复),则更新间隔可以长一些,否则会有频繁而大量的位置数据更新;定时更新也利于同步间隔内数据修改的合并,减少同步量。
同步给谁?
     某类数据有修改时,需要通知哪些进程,意味着要维持一个映射表。可以在编码阶段,在数据定义时静态写死某类数据要通知哪一类功能进程; 也可以在运行期设计成pub-sub模式(或者叫observer模式), 动态增删订阅者。笔者觉得前者可控一点,因为进程要用到哪些数据,在编码阶段是可以清楚规划的,根据这个原则把数据划分成一个个模块,比如玩家数据分为基本角色属性,avatar, 位置/朝向, 好友数据....  然后决定归属。
    多进程可以提升系统并发规模,但同时有各种异步调用和数据一致性问题,带来的代价就是bug的风险增加(尤其团队水平不能保证个个都很高的情况下,一个菜鸟程序员就够受了,还很难跟踪),开发难度增大。这个需要仔细profile和实验确定瓶颈在哪,真的跑满CPU或者卡IO才有必要分出去,想当然的把模块拆分很多进程,设计看上去很优雅也很牛逼,往往是麻烦的开始 ——> 开发效率降低,出bug意味着啥?加班,加班,深夜运维的夺命追魂call... ...
参考资料
[1] 当webgame邂逅erlang.  明朝网络的庆亮。 http://www.slideshare.net/qingliangcn/webgameerlang-8241397#btnNext
[2] 陈杰谈网游服务器后端技术.  西山居陈杰的ppt, 讲多进程架构下的寻路算法 http://timyang.net/architecture/game-backend/
[3] nosql ecosystem. 13节讲述分布式系统的数据一致性问题
[4] 结构化数据的共享内存, 云风 http://blog.codingnow.com/2011/12/dev_note_6.html

posted @ 2012-08-05 17:01 Daly 阅读(3612) | 评论 (3)编辑 收藏

2012年7月17日 #

网络游戏不同类型的技术分类

    不同的游戏类型需要有不同的技术设计,尤其服务器端,没有一个通用游戏引擎可以适应所有类型。所以大部分游戏的服务器端引擎都是根据产品需求手工打造,商业引擎通常也得经过别扭的折腾改造才能用得比较好(比如Bigworld ^_^ )。 本文对常见的几种网络游戏的服务端技术做一个技术特点的分类。
    约束性能和容量规模的因素归纳起来是:数据共享域,消息广播域,运算共享域。这几个因素涉及的对象数量会直接影响了架构涉及,下面分别来说。
 
     分类特征:
     玩家数据总量,同时在线数
     这里是指,玩家登录后,需要从多大规模的数据中读取自己的数据。social game全局共享的角色数据(统一世界),随着玩家数量增多线性增长,往往单机储存不能满足需求,需要分布式储存技术。而对于传统MMORPG,由于是分服(服务器之间是平衡世界,角色数据相互独立),相当于天然地用服务器id做了数据分区(而不需要像分布式储存那样考虑分区算法的扩展性问题),除了登录信息,没有全局数据,单机储存即可解决问题。
     AOI范围与频率
     这里指的是游戏过程中,即时广播涉及的对象数量以及消息密度,比如同屏玩家数。竞技和休闲类游戏通常比较小,而传统MMORPG通常较大。
     范围和频率还影响client间传输消息的方式,通常有两种:client间直传(P2P); 中心服转发。从延时来看,广域网游戏这两者区别到不大(前提是中心服不卡,其实可以看做一个路由器)。如果广播范围 x 频率较大,中心服的带宽成本很高(嗯嗯,带宽是很贵的)。如果采用纯P2P,则需要考虑客户端作弊问题。当然还可以用混合的方法,即中心服监督下的P2P,比如有些竞技游戏在服务端也做一层校验,查出外挂的可能,当然这个不容易实现得好。 大部分情况下,服务器监督一切,最省心。

     AOI跳转方式
     是指在不同运算共享域和广播域直接切换的方式,分为无缝和跳转点。举个例子,休闲游戏和竞技游戏通常是玩家主动点击进入或退出房间/频道,不同房间/频道分隔了广播域。而即时制MMORPG(比如WOW)区域之间是无边界的。这两者区别影响运算性能的扩展性,即实现多进程处理的技术难度。对于前者可以很容易实现多进程分担处理任务。后者的无缝AOI,要实现多进程的话,在边界处需要较复杂的进程数据同步技术。
     实时性/同步要求
     网络延时的前提下,同步方案主要是用户体验和数据正确性之间的权衡。竞技和动作游戏强调打击感和位置准确性,需要很高的同步要求。很多游戏采用帧同步方案,即一旦对应帧数据未到,卡住整个客户端(dota的等待连线)。也有采用运动补偿的方式(也称追影),即客户端预判,当和服务端位置不一致时,通过加速等方式平滑追上。为了减少延时带来的影响,一部分计算放在客户端,关键计算等待服务器返回。在等待服务器返回结果的过程中,通常结合美术和技术手段"欺骗"玩家的视觉(比如起手动作),达到较好的体验。

Social game
     玩法:策略经营类,好友互动
     玩家数量大,冷数据总量大 (海量玩家同一交互域),同时在线高;
     AOI范围中。频率低。消息在好友之间分发(好友数量一般在一百以内)
     实时性/同步要求:低,不需要实时。运算较简单(看成是海量数据的CURD应用)
     技术特点:跟微博,QQ群等传统互联网应用比较接近:数据量大,AOI范围中,实时要求低。主要难点在于读写规模大,总数据量大,cache热度不明显(无明显热数据)。
     性能扩展:依赖于分布式储存和读写技术,与一般社交网络技术类似。由于需要预先加好友,设定好友数量上限可以限制数据广播的规模。

休闲棋牌类游戏
     玩家数量大;冷数据总量大;但登录后通常进入房间。
     房间之间分隔了广播域,游戏局之间玩家互相独立(除了聊天频道)。意味着较容易根据房间和游戏分服/进程,性能扩展容易。
     AOI范围低(一局游戏的几个人),实时性一般。运算一般(棋牌算法计算)。
     架构上通常分前端(登录和房间逻辑)和后端(具体一局游戏),分服设计较容易。通常一个前端要对应很多种不同类型的后端逻辑(各种类型游戏),需要制定一个容易开发和接入的框架。
     容量扩展:由于游戏局的独立性,分进程/分服做运算扩展比较简单。 通过分区分房间限制了数据广播规模。

竞技类游戏
     玩法:dota, FPS, 格斗动作类
     AOI范围低(10人以下),交互频率高
     实时交互和同步要求极高(技术难点)
     容量扩展:通常与休闲类一样,先进入房间(有些叫频道)限制数据广播规模。不同房间互相独立,因此也较容易通过增加进程/服务器分散运算规模。

即时制MMORPG
     通常技能有cooldown, 玩家之间可以穿插(没有动态碰撞检测),同步要求低于动作类和dota网游。单服同时在线人数有限(1w人左右),逻辑复杂, IO通常单机就可搞定。AOI通常是运算瓶颈,要提高容量就要分进程或分线程。对于区域间无缝世界,在边界处的对象,由于互相可见且可战斗,分管两个区域的进程间需要较复杂的同步机制。比如bigworld用的是对象代理技术,即在原区域是real对象,对端区域建立一个ghost(代理对象), 对real对象的所有状态改变即时同步到对端进程,反之对ghost操作也同步到real。也就是说玩家A在两个进程都有自己的副本,且都可写,需要借鉴分布式技术中,多Writer的数据一致性设计。
     对MMORPG来说,单服人数越高,游戏的社区性和人气感就越强, 但人数越多,就越容易卡住服务器。现在都是多核的世界,基本上都是多线程/进程的架构了,多writer/reader, 数据同步,锁这些是常见技术考量点。一般功能模块交互性不强,分进程/线程难度不大,但AOI这块分进程要比较折腾。

     题外话
     这两年公司的校园招聘,程序员的title是虚拟世界架构师(汗 -_-!)。真正的虚拟世界应该是:数据规模大,AOI范围大,实时交互。以上还没有一种游戏同时符合几个特征,都不同程度通过分区/分服/分房间/分场景/分频道分隔了单个进程的处理规模,单台服务器的数据规模和带宽规模。当然,即使技术上可行,玩家脑子同时能处理的对象数比电脑要差多了(呃,试想数万人同处一个场景,然后走来走去,这个有游戏性可言?),这时候瓶颈不在服务器,超密集的角色,客户端的渲染效率变成瓶颈。

posted @ 2012-07-17 09:45 Daly 阅读(2288) | 评论 (5)编辑 收藏

2012年7月2日 #

替代系统malloc/new--选择合适的内存跟踪方案

 
替代系统自带的malloc/new原因无非两个: 
reason 1. 做内存profile或查找问题   
reason 2. 自定义的分配方案提高性能

不过文章[1]中说明了,替代全局new不是一个好做法. 其实要达到以上两点目的,笔者认为用valgrind工具链就可以了。

解决方案:
1. 用valgrind和massif
     valgrind的memcheck做内存泄露和bug的查找, 里面的massif工具包做内存性能profile, 足矣。比自己山寨的一个profiler要好。
     注意:tcmalloc目前还不能很好支持valgrind,  实测中jemalloc可以

2.  linux下C的程序可以用wrap的方式(相当于python的decorator)
     编译加上选项:gcc -Wl,-wrap,malloc
     可以做到对malloc这个函数,linker会调用__wrap_malloc代替之, 若要调用原来的malloc函数__real_malloc
     缺点:依赖于编译器支持; 对c++的new不起作用 --> 不实用
     启示:这个方法作为function装饰器,对于调试别的问题倒有帮助。(例如不改变函数的情况下,wrap一层,输出些调试信息)
3. 用__malloc_hook
    参考: http://linux.die.net/man/3/__malloc_hook
     #include <malloc.h>
     void *(*__malloc_hook)(size_t size, const void *caller);
     缺点:依赖GNU编译工具链;  容易死循环(想利用原有malloc,要参考例子中,把原__malloc_hook变量保存起来使用,并恢复现场)

4. LD_PRELOAD注入.so ,替代原
     环境变量LD_PRELOAD指定程序运行时优先加载的动态连接库,这个动态链接库中的符号优先级是最高的。标准C的各种函数都是存放在libc.so.6的文件中,在程序运行时自动链接。使用LD_PRELOAD后,自己编写的malloc的加载顺序高于glibc中的malloc,这样就实现了替换。用法 LD_PRELOAD=" ./mymalloc.so"
      缺点:在生产环境不现实。因为LD_PRELOAD相当于库注入,有安全性问题,是必须禁止的。(生产环境很多时候用-static连接)
5. 用宏或另外的函数替代new/malloc
   比如定义一个宏或者指定的函数,规定所有的分配释放都调用他。这样相当于给项目引入了额外的代码规则(而且是一立项就要遵循这个规则,否则该方法无效),不能很自然的new/delete, 如果分配和释放调用得不一致,会产生问题的。某产品组就是用宏,然后加上__FILE__, __LINE__之类的信息。

 有时候valgrind的效率是个问题(尤其生产环境),这种方案有其价值所在, 就是代码看上去比较ugly罢了

   用宏的例子:
   #define _New(Type, Catergory)                    (Type*)MyMemController::New((new Type), #Type, 1, sizeof(Type),   Catergory, __FILE__, __LINE__, false)
   #define _NewArray(Type, N, Catergory)          (Type*)MyMemController::New((new Type[N]), #Type, N, sizeof(Type)*(N), Catergory, __FILE__, __LINE__, true)

   
MALLOC的替代品:
     自己写一个malloc其实很复杂,要考虑线程安全等各种问题,性能到头来可能更差。google 的tcmalloc,  facebook使用的jemalloc.   多线程下性能较好,可以考虑使用。
     缺点:笔者尝试过。tcmalloc不能正确用valgrind,只能用自带gperftools(运行中会core)
                 jemalloc可以使用valgrind,不过还没完全验证是否都准确。
tcmalloc相关:
    在64位系统上要装libunwind, 对x86-64架构使用还有些问题

源码包的INSTALL文档里面也提到了这个问题。
 CAUTION: if you install libunwind from the url above, be aware that
   you may have trouble if you try to statically link your binary with
   perftools: that is, if you link with 'gcc -static -lgcc_eh ...'.
   This is because both libunwind and libgcc implement the same C++
   exception handling APIs, but they implement them differently on
   some platforms.  This is not likely to be a problem on ia64, but
   may be on x86-64.

主要是64位机frame-pointer的影响, 他的profile工具里的backtrace用libunwind这个库,这个库又有版本问题,各种囧啊....
笔者试过系统x86-64, freebsd,用静态链接。实际用了一下,问题很多很折腾,等他fix了再说吧.

windows下可以参考:

jemalloc暂时未发现有什么兼容性问题,运行得挺好的。
 
Reference
[1] <不要重载全局operator new>

[2] effective c++条款50:了解new和delete的合理替换时机

[3] 游戏引擎中的内存分配策略
[4] 更好的内存管理jemalloc
[5] tcmalloc官网(gperftools)

posted @ 2012-07-02 13:01 Daly 阅读(5687) | 评论 (0)编辑 收藏

2012年7月1日 #

基于binlog的游戏数据储存引擎

    最近组内发表一篇小论文,是关于改进游戏储存系统的IO性能思路。老大原来早有相同的想法,并且已经实现了大部分模块,后来和老大一同努力,新的储存引擎终于逐步完善。在外服环境跑了两个多月,性能和可靠性得到了明显的提升。具体的细节就不方便发表了,实践证明,用binlog来做MMORPG的数据储存是行得通的。

几个事实:
   1. 磁盘IO的瓶颈在寻道,顺序写性能比随机写性能高一个数量级。

目前典型硬盘的顺序写入速度大约是60MB/s , 而寻道时间在5~8ms (200/)。可以看到硬盘IO的主要瓶颈在于磁头寻道,也就是随机写。在linux开发服(非虚拟机,Xeon 3.0G 4/16G内存)上做了一个benchmark

顺序写50MB: 700ms

5000个文件,每个10KB(50MB): 12

        10000次随机写,每次1KB(10MB): 21
   2. 游戏数据都是K-V数据,关系查询需求极少;k-v数据的update很频繁(实测是每玩家每5秒一次修改)
   3. MMORPG单服的玩家同时在线数量是10K级别, 这个数量级可以有效估算binlog的规模,使得方案可行。

     一般MMORPG系统的存盘策略: 定时存盘。就是过一段时间(比如5分钟)把在线有修改过的玩家数据,整个snapshot存下去(mysql也好,文件系统也好)。这样有两个主要问题:一到保存点,IO随机写暴增,玩家卡机;如果系统down机, 数据就会有几分钟的回档。而性能和数据可靠性两则是矛盾的,存盘间隔过小,玩家卡机,过大,故障后数据回档时间长。需知现在的MMORPG,贵价武器价值都成千上万RMB,数据可靠性对游戏营运影响还是很大的。
    so,   可以用定制的binlog来记录玩家数据,也就是说,不记录整个snapshot,而是每个k-v变化时记录opcode马上写入binlog文件, binlog的格式根据游戏情况可以高度定制,尽量减少空间。由于是顺序写,性能可以非常高。如果down机,可以根据binlog来恢复,基本上没有回档。不过要解决一个问题:binlog增长过大 --> 崩溃恢复时间过程 & binlog文件本身损坏的风险增大 & 磁盘空间用光。因此binlog需要有rotate机制, rotate的时候需要存一次在线玩家数据的snapshot, 这样旧的binlog就可以存到远处或者丢弃。rotate的过程中需要考虑恢复时玩家数据一致性和完备性等等一系列细节问题,后来一一解决了。
    这是最近做的成就感的事。几年没写blog了,笔记都记在evernote里,最近又想在公开的地方写点东西,发个文纪念一下。


posted @ 2012-07-01 18:05 Daly 阅读(1813) | 评论 (6)编辑 收藏

2010年6月18日 #

Nginx源码学习(2) ---- 模块化及配置

     摘要:   阅读全文

posted @ 2010-06-18 16:15 Daly 阅读(2286) | 评论 (0)编辑 收藏

山寨版小游戏~~~

     摘要: 实习做的两个小游戏,山寨版的雷电和网络对战的休闲游戏。  阅读全文

posted @ 2010-06-18 13:38 Daly 阅读(646) | 评论 (1)编辑 收藏

Nginx源码学习(1) ---- 基础数据结构

     摘要: nginx的基础数据额结构分析  阅读全文

posted @ 2010-06-18 12:27 Daly 阅读(3550) | 评论 (1)编辑 收藏

2010年5月26日 #

高效的Timer实现

     摘要: 很多程序都需要处理一系列定时事件, 本文就见过的程序中,几种实现Timer的方法。用到的数据结构一般有链表, 堆, RB树,hash table等,还有一些比较优化的方法。  阅读全文

posted @ 2010-05-26 20:44 Daly 阅读(3898) | 评论 (2)编辑 收藏

2010年5月2日 #

资源和内存管理学习总结

     摘要: 总结了几种资源和内存管理的实现思路。包括buddy算法,STL中的allocator实现思路,游戏中的资源管理  阅读全文

posted @ 2010-05-02 00:02 Daly 阅读(2033) | 评论 (1)编辑 收藏

仅列出标题  下一页