美文网首页程序员
使用redis的有序集合实现排行榜功能

使用redis的有序集合实现排行榜功能

作者: secondplayer | 来源:发表于2017-07-09 14:39 被阅读11736次

排行榜是业务开发中常见的一个场景,如何设计一个好的数据结构能够满足高效实时的查询,下面我们结合一个实际例子来讨论一下。

场景

选手报名参加活动,观众可以对选手进行投票,每个观众对同一名选手只能投一票,活动期间最多投四票。后台需要提供如下接口:

  • 接口1:返回TOP 10的选手信息及投票数
  • 接口2:返回活动总参与选手数及总投票数
  • 接口3:对于每个选手,返回自己的投票数,排名,距离上一名差的票数

基于数据库的方案

首先需要一张表存储投票记录,一次投票就是一条记录。这张表相当于投票明细,判断每人只投一张票以及最多投四张表都依赖对这张表的查询。
如果直接对这张表做TOP 10的查询,则需要根据选手id做聚合查询,这样每次查询必然耗时。为了优化查询,可以增加另一张排行榜表,用一个定时任务每隔一段时间对原表做聚合查询,然后将结果写进排行榜表里,表里包含投票数及排名的字段,这样查询TOP 10和排名的时候直接查这张表。引入另一张表加快了性能,但牺牲了实时性,活动说明里需加上类似“榜单数据每10分钟同步一次”的话来告知用户。

基于redis的方案

对于排行榜的需求,redis有一个数据结构非常适合做这件事,那就是有序集合(sorted set)。

redis的有序集合相关命令

有序集合和集合一样可以存储字符串,另外有序集合的成员可以关联一个分数(score),这个分数用于集合排序。下面以投票为例说明常见的命令,vote_activity是有序集合的key。

#给Alice投票
redis> zincrby vote_activity 1 Alice
"1" 
#给Bob投票
redis> zincrby vote_activity 1 Bob
"1"
#给Alice投票
redis> zincrby vote_activity 1 Alice
"2"
#查看Alice投票数
redis> zscore vote_activity Alice
"2"
#获取Alice排名(从高到低,zero-based)
redis> zrevrank vote_activity Alice
(integer) 0
#获取前10名(从高到低)
redis> zrevrange vote_activity 0 9
1) "Alice"
2) "Bob"
#获取前10名及对应的分数(从高到低)
redis> zrevrange vote_activity 0 9 withscores
1) "Alice"
2) "2"
3) "Bob"
4) "1"
#获取总参与选手数
redis> zcard vote_activity
(integer) 2

接口实现

回到最开始的场景,大部分需求都已经得到满足,还剩下两个数据需要单独说一下。接口2中的总投票数没有直接的接口获得,一种方法是先用ZRANGE遍历所有的key,然后对score进行求和,另一种方法是对总票数单独用一个数据结构存储。接口3的距离上一名差的票数,先用ZREVRANK获取自己排名,然后用ZREVRANGE获取上一排名的分数,最后用自己的分数减去上一名的分数即可,代码示例如下:

def get_next_step(redis_key, member):
    next_step = None
    score = redis.zscore(redis_key, member)
    rank = redis.zrevrank(redis_key, member)
    if rank > 0:
        next_member = redis.zrevrange(redis_key, rank - 1, rank - 1, withscores=True)
        next_step = next_member[0][1] - score
    return next_step

另外如果两个key的score相同,排序逻辑是按照key的字母序排序。在有些情况下这个可能不满足实际要求,因此需要按实际情况重新设计key。比如如果要求同分数情况下按时间排序,那么key最好加上时间戳前缀。

redis与数据库的同步

redis通常是作为缓存层加速查询的,如果数据没有做持久化则有概率会丢失数据。一个方案是用定时任务定时同步redis与数据库的数据,数据库里存储着原始数据,通过计算数据库的数据和redis做对比,可以修正由于redis不稳定导致的数据不一致。这里需要注意的是在同步过程时redis的数据有可能还在增长,因此最好先读redis的数据,然后记下时间,查询指定时间段里的数据库的数据,最后再用ZINCRBY增量修正redis数据,而不是直接用ZADD覆盖redis数据。

总结

redis的有序集合是一个非常高效的数据结构,可以替代数据库里一些很难实现的操作。它的一个典型应用场景就是排行榜,通过ZRANK可以快速得到用户的排名,通过ZRANGE可以快速得到TOP N的用户列表,它们的复杂度都是O(log(N)),用来替代数据库查询可以大大提升性能。

相关文章

  • 通过redis的有序集合[zset] 实现延迟队列

    php使用redis的有序集合zset实现延迟队列 我们通过redis的有序集合zset来实现简单的延迟队列,将消...

  • 使用redis的有序集合实现排行榜功能

    游戏中存在各种各样的排行榜,比如玩家的等级排名、分数排名等。玩家在排行榜中的名次是其实力的象征,位于榜单前列的玩家...

  • 使用redis的有序集合实现排行榜功能

    排行榜是业务开发中常见的一个场景,如何设计一个好的数据结构能够满足高效实时的查询,下面我们结合一个实际例子来讨论一...

  • 使用Redis实现一个排行榜功能

    该功能主要使用到Redis的有序集合(SortedSet)来实现的,主要参考的https://blog.csdn....

  • php 中redis 分页实现

    redis实现分页 使用有序集合zadd 使用hash存储具体的数据 取数据通过有序集合的ZREVRANGE---...

  • (Redis篇-5)- Redis的跳表?

    redis使用跳跃表作为有序集合键的底层实现之一,当一个有序集合包含的元素数量比较多时,redis会使用跳表结构对...

  • 跳表

    跳表的基本结构: Redis为什么使用跳表实现有序集合? 1.redis的有序集合中有一个很重要的操作是,按照区间...

  • Redis数据结构之跳跃表

    Redis中使用跳跃表作为有序集合键底层实现之一(如果有序集合中数据量较大或有序集合中的成员是较长的字符串)。 跳...

  • Redis实现AutoComplete

    使用redis的有序集合实现前缀的autocomplete。1.首先把数据存入redis有续集,原文件格式如下: ...

  • Redis实现用户关注功能

    最近项目要涉及到粉丝关注问题,权衡再三还是使用Redis实现比较方便,使用Redis的有序集合可以做到根据关注的时...

网友评论

    本文标题:使用redis的有序集合实现排行榜功能

    本文链接:https://www.haomeiwen.com/subject/nxgkhxtx.html