高效實時數據排行榜實現

最新項目需求是要作一個實時排行榜,有積分Score變更就直接影響排行榜,這裏講一種比較高效的實現,歡迎指正。node

基本實現原理:git

一、排行榜用的數據結構是跳錶 SkipList (跳錶是一種有序的鏈表,隨機檢索、插入和刪除的性能很是高,Redis和LevelDB都有采用跳錶這種數據結構,是一種空間換時間的算法)github

二、經過玩家ID快速檢索用一個Map<ID,SkipListNode>golang

三、數據庫只存儲上榜的人不存儲排名(也能夠按期備份,能夠把1的有序排行按期備份)redis

 

過程描述:算法

一、服務器啓動從DB中加載N個上榜的玩家數據庫

二、用跳錶對其進行插入。插入完跳錶是個有序的天然造成排行服務器

三、當有玩家數據變更數據結構

  1)若是排行榜已滿,先判斷Score是否比最後一名低,若是是直接拋棄app

  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
相關文章
相關標籤/搜索