基于Erlang的全区服分数竞技场的设计的优化

在上一篇文章Erlang动态代码载入小实验中,我提到的竞技场设计中存在一些性能问题,这篇文章主要是针对上一篇文章提到的性能问题在整体设计方案不进行大改的情况下进行优化。

主要性能问题

回顾上一篇文章,我们知道之前的设计方案的主要问题是:频繁的在分数ETS中拿取和更新新的玩家List,而且该List的大小有可能是几十万的级别的,主要的性能问题是ETS和排行榜进程之间的数据拷贝。

在同一分数段的所有玩家都用List来存储的话,每次在一个分数中增加一个玩家的代价是,先从ets中lookup拿出所有这一分数的玩家,然后在这个列表中增加新的玩家,最后再把新的玩家列表更新回去,删除也是如此。如果每个分数的玩家列表都不是很大的话,这个应该问题也不会很大,但是由于同一分数段的玩家比较多,所以这个方案的性能就很差了。

优化一

既然主要的性能问题出在List的更新和删除的操作,所以这个优化方案的主要方法是把List替换成ETS,当List的长度大于N(N可以自己设置,比如100、200之类)时,同一分数的所有玩家都存储在ETS中,这样在一个分数的玩家列表中增加一个玩家也只是在ETS中增加一个玩家ID而已,删除一个玩家的话,也只是在ETS中删除一个玩家ID,这两个操作都非常的快。

经过这一次的优化,竞技场玩家已经可以在正式环境中上线,但是在游戏最高峰的时间段,玩家还是会有点卡,此时游戏服务器(16核)的cpu几乎全部跑满,所以还需要进一步的优化。

优化二

通过上一次优化我们知道,服务器在高峰的时候几乎把cpu全部跑满。这时候我就开始怀疑寻找对手的算法是否有问题,之前寻找对手都是现算的,把玩家的对手的排名算出来,然后在分数的ETS里面开始从头到尾遍历所有分数,找出符合对手排名的玩家。这样子寻找一个玩家的三个对手,大概要遍历分数ETS一千多次,在平时的时候还是非常快的,下面是我用eprof测量的在平时寻找一次对手的一些关键操作的开销:

1
2
3
4
legend_arena_global_rank:query_apprentice_rank/7              389   4.25   290  [      0.75]
legend_arena_global_rank:query_apprentice_key/3 1076 7.45 508 [ 0.47]
ets:lookup_element/3 1465 34.31 2340 [ 1.60]
ets:prev/2 1459 45.52 3105 [ 2.13]

再下面的是我用eprof测试的在小高峰期寻找一次对手的一些关键操作的开销:

1
2
3
4
legend_arena_global_rank:query_apprentice_rank/7              386   1.16    216  [      0.56]
legend_arena_global_rank:query_apprentice_key/3 1029 2.95 551 [ 0.54]
ets:lookup_element/3 1415 39.47 7371 [ 5.21]
ets:prev/2 1409 54.38 10157 [ 7.21]

通过上面两次的测量可以看到:分数ETS在大量访问的时候出现了性能下降,本来ets:prev/2操作只需要2.13us,在高峰期居然需要7.21us;本来ets:lookup_element/3操作只需要1.6us,在高峰期居然需要5.21us。

所以这次的主要优化方法是把对分数ETS的访问次数降下来,建立一些排名的缓存,当一个玩家寻找对手的时候直接在缓存中寻找,同时每秒钟刷新一次缓存(确保排名比较正确)。这样不管是在高峰期还是平时,分数ETS都不会有非常明显的访问量的提升。

总结

通过这次的优化,我认为不管用什么语言来实现一个系统,都要了解这个语言的优势和劣势,这样才能找出一个合理的解决方案来解决一些比较棘手的问题。

感谢支持,我会继续努力!