高效实时数据排行榜实现

Posted mrblue

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高效实时数据排行榜实现相关的知识,希望对你有一定的参考价值。

最新项目需求是要做一个实时排行榜,有积分Score变动就直接影响排行榜,这里讲一种比较高效的实现,欢迎指正。

基本实现原理:

1、排行榜用的数据结构是跳表 SkipList (跳表是一种有序的链表,随机检索、插入和删除的性能非常高,Redis和LevelDB都有采用跳表这种数据结构,是一种空间换时间的算法)

2、通过玩家ID快速检索用一个Map<ID,SkipListNode>

3、数据库只存储上榜的人不存储排名(也可以定期备份,可以把1的有序排行定期备份)

 

过程描述:

1、服务器启动从DB中加载N个上榜的玩家

2、用跳表对其进行插入。插入完跳表是个有序的自然形成排行

3、当有玩家数据变动

  1)如果排行榜已满,先判断Score是否比最后一名低,如果是直接抛弃

  2)如果自己在排行榜,是 ,如果在帮就把自己的SkipListNode删除,然后插入

  3)如果自己不在排行榜,则直接插入自己,删除最后一面,并向数据库发出存储指令(新增自己,删除最后一名,【如果自己和最后一名是一个人则什么也不做】)

 

总结:

这种排行榜的方式基本满足实时数据排行,而且数据库是低频率写入。也有不足支持就是数据库里无法反应排行名次信息(当然可以定期备份内存跳表到数据库)

 

Go代码

//Rank

技术分享图片
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() {
        return false
    }

    ele := r.set.Add(score, id, time.Now().UnixNano())
    if ele == nil {
        return false
    }
    r.changedDB[id] = ele
    if r.set.Length() > r.maxCount {
        if ele := r.set.DeleteFirst(); ele != nil {
            r.changedDB[ele.Key()] = nil
        }
    }
    return true
}

// GetRange 取排名[rankBegin, rankEnd]间所有
func (r *Rank) GetRange(rankBegin uint32, rankEnd uint32) ([]uint64, []uint32) {
    r.rangeScoreRet = r.rangeScoreRet[:0]
    r.rangeIDRet = r.rangeIDRet[:0]
    r.set.Range(rankBegin, rankEnd, true, &r.rangeIDRet, &r.rangeScoreRet)
    return r.rangeIDRet, r.rangeScoreRet
}

// GetFirst 获得排行第一
func (r *Rank) GetFirst() *zset.Element {
    return r.set.Tail()
}

// Save 保存数据库
func (r *Rank) Save() {
    if len(r.changedDB) == 0 {
        return
    }
    msg := &db.RedisRequest{
        PH: &zebra.PackHead{
            Cmd: uint32(r2l.ID_MSG_L2R_SaveRank),
        },
    }
    data := &r2l.L2R_SaveRankData{
        Name: r.redisKey,
    }
    for k, v := range r.changedDB {
        if v == nil {
            data.DeleteItems = append(data.DeleteItems, k)
        } else {
            data.Items = append(data.Items, &db_data.RankItemData{
                ID:        v.Element().Key(),
                Value:     v.Score(),
                TimeStamp: v.Element().Time(),
            })
        }
    }
    msg.Data = data
    if global.StorageMgr != nil {
        global.StorageMgr.SendDataToDB(msg)
    }

    r.changedDB = make(map[uint64]*zset.ZSkipListNode)
}
View Code

 

 

//跳表实现,有针对我们游戏特定改进

技术分享图片
package zset

import (
    "math/rand"
)

const (
    skipListMaxLevel = 8    // (1/p)^maxLevel >= maxNode
    skipListP        = 0.25 // SkipList P = 1/4
)

// Element e
type Element struct {
    time int64
    key  uint64
}

// Key return key
func (e *Element) Key() uint64 {
    return e.key
}

// Time 时间
func (e *Element) Time() int64 {
    return e.time
}

type zSkipListLevel struct {
    forward *ZSkipListNode
    span    uint32
}

// ZSkipListNode is an element of a skip list
type ZSkipListNode struct {
    ele      *Element
    score    uint32
    backward *ZSkipListNode
    level    []zSkipListLevel
    order    int
}

func zslCreateNode(level int, score uint32, ele *Element) *ZSkipListNode {
    zn := &ZSkipListNode{
        ele:   ele,
        score: score,
        level: make([]zSkipListLevel, level),
    }
    return zn
}

// Score return score
func (node *ZSkipListNode) Score() uint32 {
    return node.score
}

// Element return Element
func (node *ZSkipListNode) Element() *Element {
    return node.ele
}

// zSkipList represents a skip list
type zSkipList struct {
    header, tail *ZSkipListNode
    length       uint32
    level        int // current level count
}

// zslCreate creates a skip list
func zslCreate() *zSkipList {
    zsl := &zSkipList{
        level: 1,
    }
    zsl.header = zslCreateNode(skipListMaxLevel, 0, nil)
    return zsl
}

// insert element
func (list *zSkipList) insert(node *ZSkipListNode) *ZSkipListNode {
    var update [skipListMaxLevel]*ZSkipListNode
    var rank [skipListMaxLevel]uint32

    x := list.header
    for i := list.level - 1; i >= 0; i-- {
        if i == list.level-1 {
            rank[i] = 0
        } else {
            rank[i] = rank[i+1]
        }

        for x.level[i].forward != nil &&
            (x.level[i].forward.score < node.score ||
                x.level[i].forward.score == node.score &&
                    node.ele.Time() < x.level[i].forward.ele.Time()) {
            rank[i] += x.level[i].span
            x = x.level[i].forward
        }
        update[i] = x
    }

    level := len(node.level)
    if level > list.level {
        for i := list.level; i < level; i++ {
            rank[i] = 0
            update[i] = list.header
            update[i].level[i].span = list.length
        }
        list.level = level
    }

    x = node
    for i := 0; i < level; i++ {
        x.level[i].forward = update[i].level[i].forward
        update[i].level[i].forward = x
        x.level[i].span = update[i].level[i].span - (rank[0] - rank[i])
        update[i].level[i].span = (rank[0] - rank[i]) + 1
    }
    for i := level; i < list.level; i++ {
        update[i].level[i].span++
    }
    next := x.level[0].forward
    if next != nil && x.score == next.score && x.ele.Time() == next.ele.Time() {
        x.order = next.order + 1
    }

    if update[0] == list.header {
        x.backward = nil
    } else {
        x.backward = update[0]
    }
    if x.level[0].forward == nil {
        list.tail = x
    } else {
        x.level[0].forward.backward = x
    }
    list.length++
    return x
}

// delete element
func (list *zSkipList) delete(node *ZSkipListNode) *ZSkipListNode {
    var update [skipListMaxLevel]*ZSkipListNode
    x := list.header
    for i := list.level - 1; i >= 0; i-- {
        for next := x.level[i].forward; next != nil &&
            (next.score < node.score ||
                next.score == node.score &&
                    (node.ele.Time() < next.ele.Time() ||
                        node.ele.Time() == next.ele.Time() && node.order < next.order)); next = x.level[i].forward {
            x = next
        }
        update[i] = x
    }
    x = x.level[0].forward
    if x != nil && x.score == node.score && x.ele.key == node.ele.key {
        for i := 0; i < list.level; i++ {
            if update[i].level[i].forward == x {
                update[i].level[i].span += x.level[i].span - 1
                update[i].level[i].forward = x.level[i].forward
            } else {
                update[i].level[i].span--
            }
        }
        if x.level[0].forward == nil {
            list.tail = x.backward
        } else {
            x.level[0].forward.backward = x.backward
        }
        for list.level > 1 && list.header.level[list.level-1].forward == nil {
            list.level--
        }

        list.length--
        return x
    }
    return nil
}

// Find the rank for an element.
// Returns 0 when the element cannot be found, rank otherwise.
// Note that the rank is 1-based
func (list *zSkipList) zslGetRank(node *ZSkipListNode) uint32 {
    var rank uint32
    x := list.header
    for i := list.level - 1; i >= 0; i-- {
        for next := x.level[i].forward; next != nil &&
            (next.score < node.score ||
                next.score == node.score &&
                    (node.ele.time < next.ele.time ||
                        node.ele.time == next.ele.time && node.order <= next.order)); next = x.level[i].forward {
            rank += x.level[i].span
            x = next
        }
        if x.ele != nil && x.ele.key == node.ele.key {
            return rank
        }
    }
    return 0
}

func (list *zSkipList) randomLevel() int {
    lvl := 1
    for lvl < skipListMaxLevel && rand.Float64() < skipListP {
        lvl++
    }
    return lvl
}

// Finds an element by its rank. The rank argument needs to be 1-based.
func (list *zSkipList) getElementByRank(rank uint32) *ZSkipListNode {
    if rank == list.length {
        return list.tail
    }

    if rank == 1 {
        return list.header.level[0].forward
    }

    var traversed uint32
    x := list.header
    for i := list.level - 1; i >= 0; i-- {
        for x.level[i].forward != nil && traversed+x.level[i].span <= rank {
            traversed += x.level[i].span
            x = x.level[i].forward
        }
        if traversed == rank {
            return x
        }
    }
    return nil
}

// ZSet set
type ZSet struct {
    dict map[uint64]*ZSkipListNode
    zsl  *zSkipList
}

// NewZSet create ZSet
func NewZSet() *ZSet {
    zs := &ZSet{
        dict: make(map[uint64]*ZSkipListNode),
        zsl:  zslCreate(),
    }
    return zs
}

// Add a new element or update the score of an existing element
func (zs *ZSet) Add(score uint32, key uint64, t int64) *ZSkipListNode {
    if node := zs.dict[key]; node != nil {
        oldScore := node.score
        if score == oldScore {
            return nil
        }
        if next := node.level[0].forward; score > oldScore && (next == nil || score < next.score) {
            node.score = score
            node.ele.time = t
        }  else if score < oldScore && (node.backward == nil || score > node.backward.score) {
            node.score = score
            node.ele.time = t
        } else {
            zs.zsl.delete(node)
            node.score = score
            node.ele.time = t
            zs.zsl.insert(node)
        }
        return node
    } else {
        ele := &Element{
            key:  key,
            time: t,
        }
        lvl := zs.zsl.randomLevel()
        node := zslCreateNode(lvl, score, ele)
        zs.zsl.insert(node)
        zs.dict[key] = node
        return node
    }
}

// Delete the element ‘ele‘ from the sorted set,
// return 1 if the element existed and was deleted, 0 otherwise
func (zs *ZSet) Delete(id uint64) int {
    node := zs.dict[id]
    if node == nil {
        return 0
    }
    zs.zsl.delete(node)
    delete(zs.dict, id)
    return 1
}

// Rank return 1-based rank or 0 if not exist
func (zs *ZSet) Rank(id uint64, reverse bool) (uint32, uint32) {
    node := zs.dict[id]
    if node != nil {
        rank := zs.zsl.zslGetRank(node)
        if rank > 0 {
            if reverse {
                return zs.zsl.length - rank + 1, node.score
            }
            return rank, node.score
        }
    }
    return 0, 0
}

// Score return score
func (zs *ZSet) Score(id uint64) uint32 {
    node := zs.dict[id]
    if node != nil {
        return node.score
    }
    return 0
}

// Range return 1-based elements in [start, end]
func (zs *ZSet) Range(start uint32, end uint32, reverse bool, retKey *[]uint64, retScore *[]uint32) {
    if start == 0 {
        start = 1
    }
    if end == 0 {
        end = zs.zsl.length
    }
    if start > end || start > zs.zsl.length {
        return
    }
    if end > zs.zsl.length {
        end = zs.zsl.length
    }
    rangeLen := end - start + 1
    if reverse {
        node := zs.zsl.getElementByRank(zs.zsl.length - start + 1)
        for i := uint32(0); i < rangeLen; i++ {
            *retKey = append(*retKey, node.ele.key)
            *retScore = append(*retScore, node.score)
            node = node.backward
        }
    } else {
        node := zs.zsl.getElementByRank(start)
        for i := uint32(0); i < rangeLen; i++ {
            *retKey = append(*retKey, node.ele.key)
            *retScore = append(*retScore, node.score)
            node = node.level[0].forward
        }
    }
}

// Length return the element count
func (zs *ZSet) Length() uint32 {
    return zs.zsl.length
}

// MinScore return min score
func (zs *ZSet) MinScore() uint32 {
    first := zs.zsl.header.level[0].forward
    if first != nil {
        return first.score
    }
    return 0
}

// Tail return the last element
func (zs *ZSet) Tail() *Element {
    if zs.zsl.tail != nil {
        return zs.zsl.tail.ele
    }
    return nil
}

// DeleteFirst the first element
func (zs *ZSet) DeleteFirst() *Element {
    node := zs.zsl.header.level[0].forward
    zs.zsl.delete(node)
    delete(zs.dict, node.ele.key)
    return node.ele
}
View Code

 

以上是关于高效实时数据排行榜实现的主要内容,如果未能解决你的问题,请参考以下文章

[原创]游戏中的实时排行榜实现

好好的 Tair 排行榜不用,非得自己写?20 行代码实现高性能排行榜

好好的 Tair 排行榜不用,非得自己写?20 行代码实现高性能排行榜

好好的 Tair 排行榜不用,非得自己写?20 行代码实现高性能排行榜

InfluxDB的安装使用模型介绍

排行榜的高效数据结构,即记录列表(名称、点数) - 高效搜索(名称)、搜索(排名)和更新(点数)