这篇文章主要介绍我之前开发的一个基于Erlang的全区服分数竞技场的设计思路,同时会给出该设计的在现实项目中出现的问题,并且在后续的几篇文章中优化设计。
关键需求
该分数竞技场的实际需求比较多,这边只列举对我们设计有影响的几个关键需求,以下是该分数竞技场的关键需求:
- 每个玩家拥有一个分数,该分数大概是6000以内的数字。
- 玩家要实时知道自己的分数和排名。
- 玩家可以手动刷新自己的对手,玩家的对手由于玩家的排名乘以30%、60%、90%左右的排名的玩家随机出来。比如一个1000名的玩家,他的对手可能是由278、632、945名次的玩家组成。
- 玩家挑战对手,如果战胜,则玩家自己加分,对手扣分,反之亦然。
- 该竞技场每天在某一时间点进行结算发奖。
玩家数量级
玩家的数量级有两个,分别是测试环境和正式环境:
- 测试环境的玩家帐号有几十万,日活是2万左右。
- 正式环境的玩家帐号有几百万,日活是60万左右。
设计思路
下面介绍该分数竞技场的具体设计思路:
- 由于该竞技场是采用分数来排名,而且分数的区间比较小,所以我想到了使用桶排序来对玩家分数排名。
- 由于该竞技场是所有玩家共同访问的,所以打算用ETS来实现这个桶排序。
- 由于分数需要是有序存储的,所以该ETS为ordered_set类型,而且Key为分数,Value有两个字段:
- list——存储该分数的所有玩家的key
- cnt——存储该分数的玩家总数
- 玩家的排名为:从该分数ETS分数最大的元素开始遍历,遍历到玩家所在的分数的前一个分数,在遍历的同时累加遍历到的cnt值,玩家的排名为累加值加1,同一分数玩家的排名一致。例如:玩家分数为5000分,在5000分之前有100个5020分,1个5500分,则玩家为第102名。
- 当玩家进行一场挑战的时候,把玩家key从他原来的分数list里面移除,并且该分数的cnt-1;同时把玩家key加入到新的分数的list,并且该新分数的cnt+1;对手的分数改变也是进行同样的操作;这些操作在gen_server中进行,确保数据不会被脏写。
运行结果
- 该设计在测试环境中测试通过了,而且没有发现什么异常。
- 在正式环境中,只有少量玩家访问的情况下,访问时间到达几秒的级别,只能暂时关闭该功能进行优化。
主要问题
这边列举两个比较严重的问题:
- 同一分数的玩家数量很多,同一分数最多的玩家有50万人,使用list来存储玩家的key,每次对这个list增删代价巨大。
- 由于ETS是另外一个单独的进程,每次从ETS中拿一个50万人的list,然后再把新的list更新回去,代价同样巨大。
主要优化目标
由于留给优化的时间比较短,所以要在原有的设计思路下对该分数竞技场进行优化,达到能够上线的标准。