收到来自运营方消息,外网玩家无限回档,关键回档的都是排行榜前几名。瞬间菊花一紧,R君甚有吃我之势。天天夹着尾巴做人。不能光夹着,事情还是解决的。自此开始无尽痛苦之旅。
将玩家登录日志,经验增长日志,服务器运行日志,MYSQL存储日志,综合日志分析得出如下:
玩家本人日常操作,所有的一切正常。证明存储不当,倒导的。
玩家下线后, MYSQL数据被莫名其妙修改,服务器运行日志里头该玩家有被离线加载过,作者与H君,对整个人游戏离线加载的代码,进行多次反复查验,如果这里出了问题那玩家应该早就回档了。读写玩家肯定没得问题,那么这份神秘的数据来自于哪里呢,Game服务器Player有多个?共享内存里头有残留?redis里头有多份player cache? Game服所有代码都是用UUID作KEY,不可能存在两份,共享内存爆了? 不可能,如果爆了,出现问题也不可能 都是排行榜上的。redis操作失误?很简单的 set get用错了?关键是key validtime 也才3分钟,玩家加载与释放有多处都是超过三分钟,KEY不断被刷新的可能 性,被否定。
自此,作者与H君,陷入无限痛苦之中,那份神秘的玩家数据来自于何处。代码看不出任何BUG,redis,mysql基本上也不会出问题。
对mysql所有游戏服数据库进行全局查询,该玩家的数据,竟然存在于多个游戏服数据库里头呢? 结合最近提交的功能。终于发现问题所在。
分析结论:
游戏服务器框架 如下 多Gate->GameServer->DataServer->redis作为缓存->mysql为最终数存储
功能描述:查看跨服排行榜 玩家装备,卡牌,信息
代码流程:client-> 发送被查询玩家UUID->Gate->GameServer 查找内存里头有没有该玩家->DataServer 首先查询redis,然后查询mysql
如果都查不到数据,gameserver通知中心服务器控制器,查询玩家。并返回数据
问题原因:因为多个DataServer共用一个redis,而 redis缓存是如此设计的 get player:uuid set player:uuid , 假如1服A玩家查询2服B玩家,
1服和2服在同一个物理机上,共用同一个redis, 1服的A玩家是能够通GameServer 1服查询并加载 B玩家,从而导致 B玩家数据存在于多个GameServer中,当GameServer 定时刷新时,清除长时间不上线的玩家时,就会把数据写回redis, 这个时候redis里头的B玩家数据,就不一定是来自于2服的新数据,很可能 新数据被 1服里头的B玩家老数据顶替掉。定时存mysql启动线程执行时,这份老数据就会被写入mysql从而导致玩家回档。
修改方案:get player:uuid set player:uuid 改成 get player:serverid:uuid set player:serverid:uuid
总结:
在使用 redis时,由其是同一程序,多个进程启动时,设计key时最好带上进程唯一标识码,而且在数量级大,还能减少查询时间复杂度