大话游戏服务器演进
IEG后台服务器多采用异步多进程架构(关于多进程多线程讨论可以参考【1】),提高并发吞吐,同时模块解耦,方便调试和运维。简单来说游戏后台基本都是接入层+逻辑处理层+存储层
接入层是网络接入:管理网络连接和数据包收发,核心指标是高并发。linux系统的网络异步IO处理经历了select到epoll的演进,主流服务器单机现在可以达到十万级网络并发,开源的有C实现的轻量级的libevent,学习成本低上手快性能高。还有大名鼎鼎的C++网络库ACE(没用过),教科书的典范,适合学习不适合工业化,谁用谁知道( 哈..公司很多人评价过)。我司内部当然也不乏大量成熟稳定的网络库,IEG的tconnd这么多年还是相当稳定了,MIG的taf框架里面也有网络库实现(不过是多线程的框架),SNG也有对应的,总之是五花八门,百花齐放。
逻辑处理层:业务相关的逻辑实现,根据不同的游戏类型不同,大部分都会涉及的大概有:注册登录系统,好友社交系统,排行榜系统,背包系统,技能系统,属性系统,邮件系统,聊天系统,公会系统,PVP系统等等。这里针对分区分服的架构,按照个人的理解可以对这些业务系统做个简单分类:有状态的业务系统,无状态的业务系统,单机的业务系统,跨服的业务系统(全区全服的系统不在这次讨论范围)。这里说明一下:无状态的业务系统是指可以双机热备运行,没有需要保存的状态变量,比如技能系统,玩家无论登录到那台机器都可以升级技能,更新技能数据写入数据库。有状态的业务比如排行榜系统就是有状态的,状态变量就是全服的排行榜,如果设计系统的时候没有把状态变量独立出来,那么这个子系统就不支持双机热备。单机的业务系统是不需要跨服数据交互的,大部分系统都是这个分类。跨服的系统会比较复杂一些,一般重度点游戏都会有公会系统和PVP系统,这些系统都是需要服务器之间通信的,这样使得各个小区变成一个世界系统,可以加强玩家之间的交互,提高活跃度。后续文章会基于分区架构不同对业务系统做一个详细分析。
存储层:存储层是整个业务系统的基石,一般都是采用数据库来落地数据。就像时下牛市很多人炒股投资,最后赚了钱要落地为安放到房子里面去。业务系统对玩家的各种数值的修改,可以放在内存或者缓存,但是最后还是要写入数据库。后台系统使用的数据库主要是SQL关系型数据库和NOSQL数据库【2】。早期的游戏系统都采用的Mysql数据库,但是随着游戏用户规模的不断扩大,业务特性的变化,记录条数较多,记录长度较长,数据访问的压力较大,有较多的读写请求次数。传统关系数据库满足不了业务性能,现在普遍使用key-value的NOSQL数据库。TEG的CMEM和IEG的tcaplus都是nosql行的数据库,比较适合业务使用。不过nosql数据也不是万能的,因为原理无非是内存映射文件或者内存映射索引,所以当热点数据增多超过内存限制时,性能急剧下降,内存数据安全性也会差很多。
图1 游戏服务器三段式
对于游戏后台,逻辑处理比较复杂,,对DB的访问量也非常大,如果逻辑服务器直连写穿到DB,尤其现在手游为了完成KPI,很多商业化系统的玩法都是UI数值系统,各种暴力点击提升数值属性,再牛逼的存储系统也扛不住。于是,上文提到的三段式的游戏后台架构多了一层数据缓存系统,缓解DB的访问压力。
缓存系统主要有:角色信息缓存(RoleInfo Cache),排行榜缓存,好友关系链缓存等一些访问频繁的数据。缓存系统实现指标:1)命中率;2)数据一致性;
- 缓存系统命中率:命中率取决于缓存系统的算法,普遍使用的是哈希算法,哈希算法种类繁多,计算复杂度和哈希命中率是成反比的。需要根据不同的业务需求和性能需求选取合适的哈希策略即可。理论研究的哈希算法有很多:移位异或,md5, Cockoo hash,多阶hash,简易取模等。公司内部使用比较成熟的有多阶hash【1】。
- 缓存系统数据一致性:多一份缓存就多了一份数据拷贝,需要保证数据一致。一致性分为强一致和弱一致。强一致是指任何时刻不同拷贝数据都是一致的,弱一致是指最终数据会保持一致。
- 角色信息缓存系统:角色的Key是服务器内部生成,key基本上是自增的,哈希算法可以用简易的取模配合链表来实现,就可以达到比较高的命中率。如果后续对hash性能要求高可以再改进,使用多阶hash等来优化。角色信息是游戏玩家操作最频繁的数据,业务可以根据需求缓存部分玩家的信息或者全缓存。对于分区分服的系统,如果没有合服需求,可以全缓存这样开发业务起来非常方便;但是对于全区全服的系统,就只能缓存部分玩家的信息,按需淘汰老的换新的(游戏比较适合的LRU)。
- 角色缓存回写DB的策略:(1)时间缓写:根据数据的重要性,提供不同的优先级,重要的数据回写DB可以是0~10s,其他数据可以1分钟左右,根据业务来区分优先级。可以很好的减少对DB的写次数(2)数据合并:同一份数据在回写DB之前的写操作,可以在回写时合并,可以很好的控制DB的写频率。结合时间缓写和数据合并可以很好的控制逻辑服务器的DB的写访问。缓写策略更详细方案【2】
- 角色缓存实例: 魔龙与勇士的英雄核心数据大小大概是40KB,对于10w pcu,如果平均每个用户有1 个请求/s,那么不做缓存的话,对DB的访问就是1w次/s,tcaplus Proxy的流量要1.2Gb/s,一台C1的proxy最大流量跑到700Mb/s ~ 800Mb/s,那么至少需要2台C1的proxy。魔龙服务器的缓存策略是重要数据5-10秒回写。一般数据60秒。现网数据是1w pcu大概是400Mb/s流量,只需要一台Z3的proxy,DB访问频率也极大降低到1200/s。
移动游戏对DB的读访问最多的莫过于各类排行榜:魔龙有十几个排行榜,擂台赛排行,pvp积分排行,战力排行,闯关排行,鲜花排行等。排行榜数据量大,而且更新也多。但是根据业务的需求,用户对排行榜最关心的肯定是自己的排名,其他玩家的排名更新只要控制在玩家可以接受的范围,系统做到柔性可用。
- 排行榜缓存系统:游戏服务器从DB获取排行之后缓存,定时更新到DB,客户端也同时缓存在本地;为保证自己排名实时,当玩家自己的数值发生变化时可以触发服务器的排序逻辑及时更新。
- 关系链缓存系统:由于玩家的关系链需要到第三方系统查询,这个性能不可控,依赖于第三方,并且关系链更新一般比较少,一天之内不会有大的变化,所以关系链是可以缓存的,服务器和客户端都缓存关系链,定时更新。
图2 带缓存的游戏服务器
图3:数据库性能