⽤redis做榜单,分数相同时根据时间先后排序

10 篇文章 0 订阅

        项⽬⾥⾯有⼤量的榜单需求,很多场景下都是⽤zset来实现的。需求⾥⾯⽆⼀例外的都提到⼀个要求:分数相同的情况下,先到该分数的排前⾯。由于zset是分数优先,分数相同的时候⽤zset的member的字典序排列,并不满⾜先来后到这种需求。以前的做法基本都是分数拼凑⼀个时间量的做法:
         将zset的score值分成两部分:⾼位存分数,低位存时间差时间差⼀般是定⼀个截⽌时间x,x-now作为时间差⽤户a在x1时间达到了分数N,⽤户b在时间x2达到了分数N,x1<x2,那么x-x1>x-x2,所以拼凑后分数值,a的⼤于b的,所以a排b前⾯,实现了先来后到。
        这个⽅法存在这么些问题:
x不好确定。定的太远,那么x-now的值会⽐较⼤,时间差部分需要的位数更多,挤压分数的空间。x定的太⼩,项⽬延后下线或者复⽤
后忘记修改也是坑。时间精度的问题。精度越⾼,需要的位数越多;精度低,那么出现相同时间的概率就越⾼,时间相同,那⼜变成字典序了。
        当前项⽬⾥⾯⽤了⼀个更通⽤的⽅法:⽤浮点数来表征score(zset的score本来就是⽤double),整数部分表⽰分数,⼩数部分表征先来后到:整数部分是分数,⼩数部分表⽰先来后到。先到达这个分数,⼩数值越⼤,这样排名就靠前。⽤户a到达分数n的时候,获取当前分数为n(整数部分是n),score值最⼩的member:如果当前没有member分数是n,说明⽤户是第⼀个到达n的(那些曾经是n,但是分数变化了的不⽤管),那么⼩数部分就赋⼀个⼤点的⼩数,⽐如0.9,那么⽤户的score就是n+0.9。如果查询到的member的score值是n+k1(k1是⼩数部分),那就构造⼀个⽐k1更⼩的⼩数k2,a的分数就是n+k2,因为k2<k1,所以a在zset⾥⾯会排在这个⽤户的后⾯,也就是后到排后⾯。构造的⽅法有很多,⽐如k2=1/(0.1+1/k1) 
实现这个功能的lua脚本如下:

local call=redis.call
-- return new score
local function add(key, field, score)
  -- get old score
  local o=call('zscore', key, field)
  local n
  if o then
    o=math.floor(o)
    n=o+score
  else
    n=score
  end
  local ss = call('zrangebyscore', key, '('..n, '('..(n+1), 'withscores', 'limit', 0, 1)
  local ns
  if #ss == 0 then
    ns = n+0.9
  else
    local dec=tonumber(ss[2])-n
    ns = n+1/(0.1+1/dec)
  end
  call('zadd', key, ns, field)
  return ns
end
return add(KEYS[1], ARGV[1], ARGV[2])

推荐文章:Redis SortedSet 中 score 的精度问题
 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值