久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

    1. 站長資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

      Redis實現(xiàn)排行榜及相同積分按時間排序功能的實現(xiàn)

      本篇文章給大家?guī)砹岁P(guān)于Redis的相關(guān)知識,其中主要介紹了Redis實現(xiàn)排行榜及相同積分按時間排序,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,希望對大家有幫助。

      Redis實現(xiàn)排行榜及相同積分按時間排序功能的實現(xiàn)

      推薦學習:Redis視頻教程

      在日常的開發(fā)中,經(jīng)常會碰到需要對用戶的分值等進行排序,比如在游戲里面需要對戰(zhàn)斗力進行排行,在組隊活動中需要對各個隊伍的貢獻值進行排行,在微信中需要對各個好友的步數(shù)進行排行,此時一般會選擇redis的有序集合對用戶的分數(shù)進行存儲,從而實現(xiàn)排行榜的需求,但是不同的場景排行榜的方式也略有不同,以下根據(jù)自己日常的開發(fā)進行了一下歸納總結(jié)。

      需求:對組隊活動中各個隊伍的貢獻值進行排行。

      不考慮積分相同

      Redis的Sorted Set是String類型的有序集合。集合成員是唯一的,這就意味著集合中不能出現(xiàn)重復(fù)的數(shù)據(jù)。

      每個元素都會關(guān)聯(lián)一個double類型的分數(shù)。redis正是通過分數(shù)來為集合中的成員進行從小到大的排序。

      有序集合的成員是唯一的,但分數(shù)(score)卻可以重復(fù)。

      下面先不考慮積分相同的情況,實現(xiàn)排行榜:

      // 準備數(shù)據(jù),其中value為每個隊伍的ID,score為隊伍的貢獻值 > zadd z1 5 a 6 b 1 c 2 d 10 e (integer) 5  // 分頁查詢排行榜所有的隊伍和貢獻值,要使用zrevrange,而不是zrange,貢獻值越大越排在前面 > zrevrange z1 0 2 withscores 1) "e" 2) "10" 3) "b" 4) "6" 5) "a" 6) "5"  // 增加某個隊伍的貢獻值 > zincrby z1 3 d "5" > zincrby z1 4 c "5"  // 查詢排行榜所有的隊伍 > zrevrange z1 0 -1 withscores  1) "e"  2) "10"  3) "b"  4) "6"  5) "d"  6) "5"  7) "c"  8) "5"  9) "a" 10) "5"  // 查詢某個隊伍的排名 > zrevrank z1 d (integer) 2

      Redis默認實現(xiàn)是相同分數(shù)的成員按字典順序排序(09,AZ,a~z),上面使用的是zrevrange,所以是倒序,所以相同分數(shù)排序就不能根據(jù)時間優(yōu)先來排序。

      積分相同按時間排序,排名唯一

      在上面的實現(xiàn)中,如果兩個隊伍的貢獻值相同,也就是積分值相同,無法根據(jù)時間的先后進行排行。

      所以需要設(shè)計一個分數(shù) = 貢獻值 + 時間戳 ,誰分數(shù)大誰排前面,最后還要能根據(jù)分數(shù)能解析出來貢獻值。

      設(shè)計1

      使用整型存儲分數(shù)值,redis中score本身是一個double類型,能精確存儲的最大整型數(shù)字為2^53=9007199254740992(16位)。而精確到毫秒的時間戳需要13位,此時留給存儲貢獻值只有3位數(shù)了,當前如果時間只要精確到秒,只需要10位,這樣留給貢獻值就有6位。

      整體設(shè)計:高3位表示貢獻值,低13位表示時間戳。

      如果我們簡單地把score結(jié)構(gòu)由:貢獻值 * 10^13 + 時間戳 拼湊,因為分數(shù)越大越靠前,而時間戳越小則越靠前,這樣兩部分的判斷規(guī)則是相反的,無法簡單把兩者合成一起成為score。

      但是我們可以逆向思維,可以用同一個足夠大的數(shù)Integer.MAX減去時間戳,時間戳越小,則得到的差值越大,這樣我們就可以把score的結(jié)構(gòu)改為:貢獻值 * 10^13 + (Integer.MAX-時間戳),這樣就能滿足我們的需求了。

      設(shè)計2

      由于redis的score值是double類型,可以使用整數(shù)部分存儲貢獻值,小數(shù)部分存儲時間戳,同樣時間戳的部分使用一個最大值減去它。

      這樣,整體設(shè)計變?yōu)椋?code>分數(shù)=貢獻值 + (Integer.MAX-時間戳) * 10^-13

      弊端:由于分數(shù)值是由兩個變量來計算得出,所以在給隊伍增加貢獻值時,無法簡單的使用之前的zincrby來改變score的值了,這樣在并發(fā)情況下為隊伍增加貢獻值就會導(dǎo)致score值不準確。

      錯誤情況模擬:

      假設(shè)現(xiàn)在隊伍A的貢獻值為10隊伍A中的隊員X為隊伍增加貢獻值1,在程序中算出score為11.xxx隊伍A中的隊員Y為隊伍增加貢獻值1,在程序中算出score為11.yyy隊伍A中的隊員X調(diào)用redis的zadd命令設(shè)置隊伍的貢獻值為11.xxx隊伍A中的隊員Y調(diào)用redis的zadd命令設(shè)置隊伍的貢獻值為11.yyy最后算出隊伍A的貢獻值為11,無法保證增加貢獻值這一個操作的原子性。

      此時需要借助lua腳本來保證計算和設(shè)置貢獻值這兩個操作的原子性:

      // 其中KEYS[1]為排行榜key,KEYS[2]為隊伍ID // 其中ARGV[1]為增加的貢獻值,ARGV[2]為Integer.MAX-時間戳 local score = redis.call('zscore', KEYS[1], KEYS[2])  if not(score) then 	score=0  end  score=math.floor(score) + tonumber(ARGV[1]) + tonumber(ARGV[2])  redis.call('zadd', KEYS[1], score, KEYS[2]) return 1

      由于redis中無法使用時間函數(shù),所以(Integer.MAX-時間戳) * 10^-13部分由腳本外程序計算好傳入。

      分頁查詢排行榜,查詢隊伍的排名等功能都可以繼續(xù)使用上面的命令。

      積分相同按時間排序,并列排名

      所謂并列排行榜,就是存在相同排名情況的排行榜。

      我們期望的結(jié)果如下表:

      隊伍ID 貢獻值 排名
      a 100 1
      b 99 2
      c 99 2
      d 88 4
      e 87 5

      當然現(xiàn)實中也有排名不跳過的情況,我這里考慮的是排名跳過的情況。

      redis中score的設(shè)計還是采用上面的分數(shù)=貢獻值 + (Integer.MAX-時間戳) * 10^-13,只是在查詢排名時需要進行計算。

      比如要查上表中隊伍b的排名,思路如下:

      • 首先查到隊伍b的score
      • 再查到跟隊伍b的score的整數(shù)部分相同(也就是貢獻值一樣),排在第一個的隊伍的value(隊伍ID)
      • 根據(jù)上一步得到的隊伍ID查詢此隊伍的排名就是隊伍b的排名

      使用命令實現(xiàn)上面的步驟如下:

      > zscore 排行榜key teamId > zrevrangebyscore(排行榜key, 上一步得到的score+1, 上一步得到的score, limit, 0 , 1) > zrevrank(排行榜key, 上一步得到的teamId)

      為了性能考慮,可以使用下面的腳本一次查出來:

      // KEYS[1]表示排行榜key // KEYS[2]表示要查詢的隊伍的ID local rank = 0  local score = redis.call('zscore', KEYS[1], KEYS[2])  if not(score) then     score=0  else      score=math.floor(score)      local firstScore = redis.call('zrevrangebyscore', KEYS[1], score+1, score, 'limit', 0, 1)      rank=redis.call('zrevrank', KEYS[1], firstScore[1])  end  return {score,rank}

      下面附上分頁查詢排行榜的腳本,假如一頁10條,不用下面的腳本需要查詢10次上面的腳本,如果連上面的腳本都沒有使用的話就要查詢30次redis。

      // 排行榜key // ARGV[1]分頁起始偏移 // ARGV[2]分頁結(jié)束偏移 local list = redis.call('zrevrange', KEYS[1], ARGV[1], ARGV[2], 'withscores')  local result={}  local i = 1  for k,v in pairs(list) do      if k%2 == 0 then          local teamId = list[k-1]          local score = math.floor(v)          local firstScore = redis.call('zrevrangebyscore', KEYS[1], score+1, score, 'limit', 0, 1)          local rank=redis.call('zrevrank', KEYS[1], firstScore[1])          local l = {teamId=teamId, contributionValue=score, teamRank=rank+1}          result[i] = l i = i + 1      end  end  return cjson.encode(result)

      此腳本使用了cjson庫,返回的是一個json。

      推薦學習:Redis視頻教程

      贊(0)
      分享到: 更多 (0)
      網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號