最新项目需求是要做一个实时排行榜,有积分Score变动就直接影响排行榜,这里讲一种比较高效的实现,欢迎指正。
基本实现原理:
1、排行榜用的数据结构是跳表 SkipList (跳表是一种有序的链表,随机检索、插入和删除的性能非常高,Redis和LevelDB都有采用跳表这种数据结构,是一种空间换时间的算法)
2、通过玩家ID快速检索用一个Map<ID,SkipListNode>
3、数据库只存储上榜的人不存储排名(也可以定期备份,可以把1的有序排行定期备份)
过程描述:
1、服务器启动从DB中加载N个上榜的玩家
2、用跳表对其进行插入。插入完跳表是个有序的自然形成排行
3、当有玩家数据变动
1)如果排行榜已满,先判断Score是否比最后一名低,如果是直接抛弃
2)如果自己在排行榜,是 ,如果在帮就把自己的SkipListNode删除,然后插入
3)如果自己不在排行榜,则直接插入自己,删除最后一面,并向数据库发出存储指令(新增自己,删除最后一名,【如果自己和最后一名是一个人则什么也不做】)
总结:
这种排行榜的方式基本满足实时数据排行,而且数据库是低频率写入。也有不足支持就是数据库里无法反应排行名次信息(当然可以定期备份内存跳表到数据库)
Go代码
//Rank
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
package rank import ( "time" "common/zebra" "logic/db" "logic/service/game/rank/zset" "logic/service/global" "protos/in/db_data" "protos/in/r2l" "github.com/golang/protobuf/proto" l4g "github.com/ivanabc/log4go" ) // Rank r type Rank struct { redisKey string set *zset.ZSet maxCount uint32 changedDB map[uint64]*zset.ZSkipListNode rangeIDRet []uint64 rangeScoreRet []uint32 } // NewRank n func NewRank(redisKey string, maxCount uint32) *Rank { return &Rank{ redisKey: redisKey, set: zset.NewZSet(), maxCount: maxCount, changedDB: make(map[uint64]*zset.ZSkipListNode), rangeIDRet: make([]uint64, 0, 5000), rangeScoreRet: make([]uint32, 0, 5000), } } // GetPlayerRank 根据玩家id获取玩家数据 // return - (排名,积分) func (r *Rank) GetPlayerRank(id uint64) (uint32, uint32) { return r.set.Rank(id, true) } // LoadRankSync 同步加载排行榜数据库 func (r *Rank) LoadRankSync() { if global.StorageMgr == nil { return } items := global.StorageMgr.LoadHashDataSync(r.redisKey) if items == nil { return } data := &db_data.RankItemData{} for _, v := range items { if err := proto.Unmarshal(v, data); err != nil { l4g.Error("db loadRank unmarshal error: %s", err.Error()) continue } r.InitAdd(data.GetID(), data.GetValue(), data.GetTimeStamp()) } } // InitAdd i func (r *Rank) InitAdd(id uint64, score uint32, t int64) { if r.set.Length() >= r.maxCount && score < r.set.MinScore() { r.changedDB[id] = nil return } r.set.Add(score, id, t) if r.set.Length() > r.maxCount { if ele := r.set.DeleteFirst(); ele != nil { r.changedDB[ele.Key()] = nil } } } // ChangeScore c func (r *Rank) ChangeScore(id uint64, score uint32) bool { if r.set.Length() >= r.maxCount && score <= r.set.MinScore() {