写这篇文章是对自己2011bug战斗时光一个交代,随着时间的推移,当初印象深刻的痛苦和压力慢慢消逝,到现在甚至是需要很长时间来弄清楚这中间的关系,趁着现在头脑还算清楚,记录下吧。
场景管理
为了说明Bug产生的原因,先描述下场景管理的实现方式吧。
1、游戏场景是游戏地图的一个实例(假设地图是class),一个地图可以创建多个场景,场景主要负责管理玩家的移动、广播等处理。
2、场景的广播是采取经典的九宫格方式来实现的,每一个格子的我们定义为Area对象,一个场景的格子组成其实是一个二维数组。
3、玩家进入场景的时候,根据坐标可以知道要进入哪个格子,每个格子内会保留一个Head指针,标记最新进入的玩家对象。玩家对象上有2个指针,标记玩家所在格子的前一个和后一个对象。可以通过格子内的Head指针便利Area内的所有玩家对象。
4、玩家移动切换格子的时候,先从原来的格子内Leave,这会调用原来Area对象的Leave函数。再进入新的格子,调用Area的Enter函数。很明显,Leave函数就是一个链表删除操作,如果玩家是Head,则设置新的Head。
Enter操作就是将新进入的玩家链接到原队列里,新进入的玩家会被设置为Head。
问题表现
根据上面的描述,如果一切按照正常程序,这个方案运转是没问题的。最初上线的时候,也没有出现问题,但是在出了一个资料片之后,服务器基本上每隔半小时左右就会发现有死循环或者宕机问题。
死循环的表现很明显,就是在遍历场景玩家的时候,出现死循环。宕机则更加复杂些,每次宕机位置不同,总的来说大概有3-4个地方,每个地方单看都不合常理。
直接分析上面的表现,都找不到真正的的原因,只好扩大搜索范围了。
比较倒霉的是那个资料片的主要系统都是我开发的,所以自然嫌疑最大,然后大家集中精力来分析我的代码,由于每个人风格都不同,所以大家看的不太明白的地方都会来问我,所以那个晚上基本就在解答设计疑问了。
被轮了大半个晚上,知道凌晨2点,大家也没分析出问题,只好先回去睡觉了。结果早上7点,测试给我打电话了,没办法只好跑过去了,一到公司,发现围了一堆老大,老大们很严肃:这个问题很严重,必须尽快解决。
没办法,只好继续上阵了,战斗到下午2点,突然灵光一闪,想到了原因,当时感觉真的心力交瘁了,更加感慨的是其实这个问题真和我没啥关系。。。
原因
真正导致这次事故的其实是一个小操作:玩家重登录(手机玩家断网的时候,服务器会保存一段时间在线状态)的时候,有的时候由于其他原因,会卡在不能地图的物理层(不能行动的点),玩家完全不能移动。为了解决这个问题,有个同事在玩家重登录的时候,直接设置了玩家的坐标到一个可移动的点。
这个看似无关紧要的操作,真正导致了服务器1天多时间内不停的宕机。下面来记录下分析过程吧。
1、玩家在重登录前,其实是在场景中的,也就是在一个具体的Area里。
2、由于玩家上线后,直接设置了坐标,而我们后续的计算是通过坐标来获取Area对象的,其实这里就出现问题了,玩家其实是在A格子的链表上,但是根据坐标计算获得的格子是B。
3、玩家移动后,切换格子,需要从原格子Leave,然后进入新的格子,但是基于上面的原因,所以其实涉及到的有3个格子,(1)、玩家真实所在的格子链表(A)。(2)、通过坐标计算所得的格子(B),这个格子对象上会调用Leave操作。
(3)要进入的新格子(C)。
4、由于玩家其实在格子A,但是我们调用的是B.Leave(player);C.Enter(player),从这里看,肯定是有问题的,但是细看则不然,由于玩家对象是记录了前一个和后一个对象,所以B.Leave本身并不会破坏B的链表结构,C.Enter看上去也没问题,那么,问题在哪里?
5、真正的原因其实是格子A对象被破坏了,B.Leave(player)上是将玩家从它自己的链表上删除了,链表本身是没有被破坏了,关键的原因是如果玩家在格子A是Head,那么实际上在玩家被删除后,Head应该被改变,但是由于操作的是格子B,所以,A其实被破坏了,很奇妙,这个对象没有操作,却被破坏了。后面的问题就简单了,如果玩家进入的C就是A,则会是一个很明显的死循环,如果玩家进入的C是一个新的格子,则格子A的对象都不能被感知了。
posted on 2012-07-15 22:13
feixuwu 阅读(386)
评论(0) 编辑 收藏 引用