当前位置:首页 > 实时排行榜单的设计与实现 -- Redis 有序集合(sorted set)的使用

实时排行榜单的设计与实现 -- Redis 有序集合(sorted set)的使用

发布于 2018-07-27 阅读 242 次 Redis

Top排行榜在网站中是一个出现率非常高的模块

文章热门榜
等级排行榜
商品销量榜
...

首先我们可以分析一下排行榜的需求,比如 “游戏等级排行榜单” :

1.用户,等级以及排行位置

这样我们可以很快的得出需要构建的数据结构 level_top_list

主键UID等级排名
iduidlevelindex

每次用户等级变更的时候去实时修改 level_top_list 中的用户记录,
然后再通过 index 排序来获取排行榜单

SELECT * FROM `level_top_list` ORDER BY `index` DESC limit 0,10

这样看起来似乎完美的解决了需求

然而在大访问量网站下,实时频繁去 读/写 数据库是一件很糟糕的事情。尤其在还有 order by 排序的时候

那么我们可以怎么解决这个问题呢?
通过 Redis 的有序集合(sorted set)来实现我们的 level_top_list 榜单功能

我们看下 sorted set 的数据结构:

`KEY_NAME SCORE VALUE`

sorted set 有三个值:

  • KEY_NAME Redis 的键名,我们设为 level_top_list
  • SCORE 记录值/ 分数,这里我们可以用来存放 等级( index )
  • VALUE 键值,可以存放用户uid

在用户等级发生变更时候,我们可以通过 ZADD 命令来设置用户的排行榜信息

ZADD level_top_list {用户等级} {uid}

实例:

redis 127.0.0.1:6379> ZADD level_top_list 100 "10001"
(integer) 1
redis 127.0.0.1:6379> ZADD level_top_list 20 "10002"
(integer) 1
redis 127.0.0.1:6379> ZADD level_top_list 47 "10003"
(integer) 1

上面我们设置了 10001,10002,10003 三个用户的等级信息,现在我们来读取一下我们的等级排行榜

redis 127.0.0.1:6379> ZREVRANGE level_top_list 0 -1 WITHSCORES
1) "10001"
2) "100"
3) "10003"
4) "47"
5) "10003"
6) "20"

通过 ZREVRANGE 命令我们可以按分数值递减(高->低)来获取榜单

#数值递减(高->低)
ZREVRANGE key start stop [WITHSCORES]

#数值递减(低->高)
ZRANGE key start stop [WITHSCORES]

有时候插入排行榜记录时候不一定像等级这样的完整的 score 值。比如文章阅读量排行榜,在用户点击阅读后需要给当前文章的阅读量加 1 ,这时候就不能用 ZADD 了。

ZINCRBY key increment member

ZINCRBY 命令对有序集合中指定成员的分数加上增量 increment, increment可以是 正或负

ZINCRBY level_top_list 1 10001
#给 10001 用户等级加一级 100 + 1 = 101

ZINCRBY level_top_list -2 10003
#给 10003 用户等级减2级 47 - 3 = 44

特殊情况,如何处理相同的 score

1.在榜单中出现了相同的值。比如多个用户达到了 20 级。那有序集合(sorted set)会怎么处理呢?在 Redis 的官方文档中有提到

具有相同 score 值的成员按字典序( lexicographical order ) 来排列 ( 该属性是有序集提供的,不需要额外的计算 )

简单的说 Redis 内部会有自己的一个排序,对于要求不高的榜单来说是可以了

2.冲刺榜单,这类榜单一般都会通过 score 和 时间 两个维度来排序。相同值下,按时间先后排序。这种情况下通过 Redis 内部的排序已经无法解决了

其实解决思路就是要在相同的 score 下构造 不同的 score 值

所以可以给我们的 score 再加上一个时间维度的变量,而这个变量需要用时越长值越小

我们看个图:

开始时间               到达 n 时间         结束时间
   |______________________|_________________|
   |          A           |         B       |

在上图中 B 段是不是就是很符合我们的要求,花费时间越长则剩余时间越短。即 “剩余时间”。

那么 score 就可以为: 原始值 + 剩余时间
同理如果在榜单读取时候需要显示 原始score值,就需要通过 score - 剩余时间 反解

相关文章