Redis中的有序集合(sorted set),通常称为ZSET,是一种非常有用的数据结构。它类似于集合(SET),也是由不重复的元素组成的,但有序集合中的每个元素都会关联一个双精度浮点数作为分数(score),这个分数用于对元素进行排序。有序集合中的元素是唯一的,但是分数可以重复。

ZSET的主要操作包括:

  1. 添加元素:使用ZADD命令向有序集合中添加一个或多个元素,每个元素都会关联一个分数。
  2. 查询元素:使用ZRANGE命令可以获取有序集合中指定范围内的元素,按照分数从小到大排序。使用ZREVRANGE命令可以获取逆序排列的元素。
  3. 查询元素和分数ZRANGE命令配合WITHSCORES选项可以同时获取元素及其关联的分数。
  4. 查询指定分数范围的元素:使用ZRANGEBYSCORE命令可以获取指定分数范围内的元素。
  5. 删除元素:使用ZREM命令可以从有序集合中删除一个或多个元素。
  6. 修改元素分数:使用ZINCRBY命令可以增加元素的分数,或者使用ZADD命令重新设置元素的分数。
  7. 获取元素排名:使用ZRANK命令可以获取元素在有序集合中的排名(按分数从小到大),使用ZREVRANK命令可以获取逆序排名。
  8. 统计指定分数范围的元素个数:使用ZCOUNT命令可以统计指定分数范围内的元素数量。
  9. 删除指定分数范围的元素:使用ZREMRANGEBYSCORE命令可以删除指定分数范围内的所有元素。

有序集合在很多场景下都非常有用,比如实现排行榜功能、带权重的队列、范围查询等。通过分数,可以轻松地对元素进行排序和范围查询,这使得ZSET成为Redis中非常强大的数据结构之一。

基于ZSET实现的排行榜功能

什么是排行榜

排行榜系统是一种用于记录、展示排名或评分的系统。

日常生活中常见的有今日头条热榜、游戏玩家排行、汽车销量排行榜、音乐排行榜等都属于这一范畴。

排行榜天然具有聚焦用户注意力的特性,比如说资讯/新闻热点排行榜,可以引导用户点击新闻,提高用户活跃度与新闻曝光率。对于类似今日头条这种平台,热榜可能是它的一个门面系统,承载着核心入口流量的作用。

使用Redis的ZSET实现游戏玩家排行榜功能

要使用Redis实现排行榜功能,最简单的方式就是使用ZSET。可以按照以下步骤实现:

  1. 用户每次点赞/评论/分享等操作,都会触发排行榜的更新。
  2. 用户每次操作时,都调用ZSET的ZINCRBY命令,更新用户的分数。
  3. 用户每次操作时,都调用ZSET的ZADD命令,更新用户的分数。
  4. 用户每次操作时,都调用ZSET的ZREM命令,更新用户的分数。

主要步骤

使用ZADD新增玩家和分数信息

将玩家的分数和玩家信息添加到ZSET中,分数作为排序依据。

ZADD player_board score member [score member ...]

其中player_board是ZSET的键,score是玩家的分数,member是玩家的信息。

使用ZRANGE获取排行榜信息

获取排行榜的前N名玩家信息。使用ZRANGE命令,并配合WITHSCORES选项,可以获取玩家信息和分数。

ZRANGE player_board 0 N-1 WITHSCORES

其中N-1表示排行榜的最后一名的索引,WITHSCORES表示同时获取玩家的分数。

千万数据量下的性能表现

写入数据:

import redis

# 设置Redis连接的主机和端口
redis_host = 'localhost'
redis_port = 6379

# 创建Redis连接对象
redis_connect = redis.Redis(host=redis_host, port=redis_port)


# 定义生成值的函数,根据索引生成字典
def generate_value(index):
    return {'player-' + str(index): index}  # 返回一个字典,键为'goods_'加索引,值为索引


# 循环从1到1000001,生成数据并写入Redis的有序集合
for i in range(1, 1000001):
    value = generate_value(i)  # 生成当前索引的值
    redis_connect.zadd('player_board', value)

# 打印完成信息
print('写入数据完成!')

查询前10名玩家信息:

import time
import redis

# 设置Redis连接的主机和端口
redis_host = 'localhost'
redis_port = 6379

# 创建Redis连接对象
redis_connect = redis.Redis(host=redis_host, port=redis_port)

# 定义起始和结束索引,以及迭代次数
start_index = 0
end_index = 9
num_iterations = 100

# 初始化总时间
total_time = 0

# 进行指定次数的迭代
for _ in range(num_iterations):
    start_time = time.time()  # 记录开始时间
    # 从Redis中获取指定范围的有序集合数据
    result = redis_connect.zrange('player_board', start_index, end_index, withscores=True)
    end_time = time.time()  # 记录结束时间
    elapsed_time = end_time - start_time  # 计算经过的时间
    total_time += elapsed_time  # 累加总时间

    # 打印结果和经过的时间
    print('Result: ', result)
    print('Elapsed time: ', elapsed_time, 'seconds')

# 计算平均时间
average_time = total_time / num_iterations
# 打印平均时间
print('Average time: ', average_time * 1000, 'ms')

在mbp Intel i5 16G 内存的Macbook Pro上,100次查询的平均时间为0.39ms。

Average time:  0.3937220573425293 ms

修改玩家分数

使用ZINCRBY命令,增加玩家的分数。

ZINCRBY player_board increment member

其中increment是增加的分数,member是玩家的信息。

也可以使用ZADD命令,更新玩家的分数。

ZADD player_board score member

删除玩家信息

使用ZREM命令,删除玩家的信息。

ZREM player_board member

使用ZRANGEBYSCORE获取指定分数范围的玩家信息

使用ZRANGEBYSCORE命令,获取指定分数范围的玩家信息。

ZRANGEBYSCORE player_board min max [WITHSCORES] [LIMIT offset count]

其中minmax是分数范围,WITHSCORES表示同时获取玩家的分数,LIMIT表示限制返回的数量,offset表示跳过前N个元素,count表示返回的数量。

查找指定玩家分数和排名

使用ZREVRANK命令,获取指定玩家在排行榜中的排名。

ZREVRANK player_board member

排行榜实现方案

这里延伸一下。那么除了ZSET之外,还有哪些方案可以实现排行榜功能呢?

方案一:使用MySQL的order by

如果数据量不大,可以使用MySQL的order by来实现排行榜功能。

方案二:使用内存堆排序

在涉及多表存储,但又不推荐联表查询的情况下,可以使用内存堆排序来实现排行榜功能。比如优先级队列Priority Queue。

方案三:使用Redis的Sorted Set

在数据量很大的情况下,可以使用Redis的Sorted Set来实现排行榜功能。

标签:

分类:

更新时间: