排行榜设计
更新日期:
最近在做排行榜功能,排行榜无非就是对用户一些数据的排序,在量级不是很大的情况下还是比较简单的,在数据结构上使用数组,set,map都可以,依具体情况而定,这里不做过多讨论。现在遇到的问题是在数据库方面的,也就是对排行榜数据该已什么方式进行存储。
我们用的数据库是mysql,如果排行榜量级在百名之内的话可以把每个进榜用户的数据都当做是一条数据库记录插入表中,因为数据量级比较小,插入删除操作不是太多,对数据库的压力不是很大(定时存储还是要做的,减少对数据的操作频率)。数据库表设计大概是这样的:
rank(ranktype,id,pkpara,…,data)
+-----------+---------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-----------+---------------------+------+-----+---------+-------+ | ranktype | int(10) unsigned | NO | PRI | NULL | | | order | int(10) unsigned | NO | PRI | NULL | | | id | int(10) unsigned | NO | | NULL | | | data | blob | YES | | NULL | | +-----------+---------------------+------+-----+---------+-------+
order记录用户名次,data保存用户数据,如果用户数据较少的话可以不用blob,而是单独拿出来存,这样会更直观。
这样存储明显有个问题,就是当一个人的名次变了,可能会影响到其他很多人的名次,例如一个用户从300名上升到了100名,这时候相当于从300名到100名的所有用户都变了,也就是说这个时候需要更新200条数据库记录。排行榜名次很多的话这显然是不可接受的,因为如果排名有几万名,这种变动一次就有可能在几千名。
我们要做的排名正是有几万名的,所以要做其他方案。从上面可以发现对数据库造成压力的地方其实就是一次需要更新的数据记录太多,所以可以从减少更新记录来入手。在之前项目中我们的做法是把排行榜分成几段,每段存成数据库中的一条记录。表是这样的:
+-----------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------------------+------+-----+---------+-------+
| ranktype | int(10) unsigned | NO | PRI | NULL | |
| id | int(10) unsigned | NO | PRI | NULL | |
| data | blob | YES | | NULL | |
+-----------+---------------------+------+-----+---------+-------+
id表示这是排行榜的第几段,好比排行榜有1w名,分成10段,每段1000名,id为1的就存1到1000名,id为2的存储1001到2000名,以此类推。
这样的话如果排行榜有变化只需要一条或者有限的几条记录就好了,数据库几乎没有压力,但是这种方案不好的地方在于不直观,从数据库层面上你没法知道名次信息,而且以后如果有合并数据的需求的话单从数据库层面是没法做到的(必须通过程序读出数据解析然后合并最后再存到数据库)。
为了解决上面的问题,有了第三种设计方案:
+-----------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------------------+------+-----+---------+-------+
| ranktype | int(10) unsigned | NO | PRI | NULL | |
| id | int(10) unsigned | NO | PRI | NULL | |
| pkpara1 | bigint(20) unsigned | NO | | NULL | |
| pkpara2 | bigint(20) unsigned | NO | | NULL | |
| pkpara3 | bigint(20) unsigned | NO | | NULL | |
| data | blob | YES | | NULL | |
+-----------+---------------------+------+-----+---------+-------+
ranktype(排行榜类型),id(用户id),pkpara1 2 3 4(竞争参数),data(用户数据)
这种方案的思想就是在数据库中不存在名次,只是存储排行榜中的用户数据。当程序启动的时候从数据库中一次把数据全部读出来,然后按照pkpara1 2 3 4进行排名,这样在内存中就能构造出一个排行榜了。如果有用户新进榜只需要简单的对其进行pkpara的赋值,然后可以直接插入数据库,并不需要担心数据库中其他用户的数据。如果有名次的变化也只是对造成变化的那个用户的pkpara进行更新,其他的互不影响。用户掉出排行榜也只是从数据库中删除一条记录。当然操作数据库数据之前要记得更新内存中的排行榜数据。
可见这种方案可以满足大多数需求,但是它需要常驻内存,不然无法更新排行榜,不过在排行参数不大的情况下维持一个几十万甚至几百万的排名还是可以的,有待改进……