MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Redis ASC和DESC选项实现的排序规则组合

2021-06-023.6k 阅读

Redis 排序规则基础概述

在 Redis 中,排序是一项重要的操作,而 ASC(升序)和 DESC(降序)选项是控制排序方向的关键设置。Redis 的排序功能基于其有序集合(Sorted Set)数据结构,这一数据结构为每个成员关联一个分数(score),排序正是基于这些分数来进行的。

有序集合以有序的方式存储成员,在执行排序操作时,Redis 会根据 ASC 或 DESC 选项,对成员的分数进行相应顺序的排列。例如,当设置为 ASC 时,分数低的成员会排在前面;而设置为 DESC 时,分数高的成员会排在前列。

ASC 排序规则

ASC 排序基础原理

当使用 ASC 选项进行排序时,Redis 会按照分数从小到大的顺序对有序集合中的成员进行排列。这意味着,分数最低的成员将成为排序结果中的第一个元素,而分数最高的成员则排在最后。

假设我们有一个有序集合,包含以下成员和分数:

成员(member)分数(score)
"apple"3
"banana"1
"cherry"2

当执行 ASC 排序时,Redis 会首先比较各个成员的分数。由于 "banana" 的分数为 1,是最低的,所以它会排在第一位;接着是分数为 2 的 "cherry";最后是分数为 3 的 "apple"。排序后的结果为:["banana", "cherry", "apple"]。

ASC 排序的应用场景

  1. 排行榜场景(低分数优先):在一些游戏或竞赛的排行榜中,如果是按照积分越少排名越靠前的规则,ASC 排序就非常适用。例如,在高尔夫球比赛中,选手的击球次数越少排名越高。可以将选手的名字作为有序集合的成员,击球次数作为分数,通过 ASC 排序就能轻松得到正确的排名顺序。
  2. 时间序列数据(早期时间优先):如果有序集合中的分数代表时间戳,ASC 排序可以将最早发生的事件排在前面。比如,在一个记录网站访问日志的系统中,以访问时间为分数,使用 ASC 排序可以按时间先后顺序查看访问记录,方便进行数据分析,了解网站的早期访问情况。

ASC 排序代码示例

在 Python 中,使用 redis - py 库来操作 Redis 实现 ASC 排序。

import redis

# 连接 Redis 服务器
r = redis.Redis(host='localhost', port=6379, db = 0)

# 向有序集合中添加成员和分数
r.zadd('fruits', {'apple': 3, 'banana': 1, 'cherry': 2})

# 执行 ASC 排序
sorted_result_asc = r.zrange('fruits', 0, -1, withscores = True)

print("ASC 排序结果:")
for member, score in sorted_result_asc:
    print(f"成员: {member.decode()}, 分数: {score}")

在上述代码中,首先通过 zadd 方法向名为 fruits 的有序集合中添加了三个成员及其对应的分数。然后,使用 zrange 方法进行 ASC 排序,0-1 表示获取所有成员,withscores = True 表示结果中同时包含成员和分数。最后,遍历打印出排序后的结果。

DESC 排序规则

DESC 排序基础原理

与 ASC 排序相反,当使用 DESC 选项时,Redis 会按照分数从大到小的顺序对有序集合中的成员进行排列。分数最高的成员将成为排序结果中的第一个元素,而分数最低的成员则排在最后。

以之前提到的有序集合为例,当执行 DESC 排序时,由于 "apple" 的分数为 3,是最高的,所以它会排在第一位;接着是分数为 2 的 "cherry";最后是分数为 1 的 "banana"。排序后的结果为:["apple", "cherry", "banana"]。

DESC 排序的应用场景

  1. 排行榜场景(高分数优先):在大多数常见的游戏排行榜、销售排行榜等场景中,都是分数越高排名越靠前。例如,在一个在线游戏中,玩家的积分越高排名越靠前。将玩家名字作为有序集合的成员,积分作为分数,通过 DESC 排序就能生成正确的排行榜顺序,方便玩家查看自己和其他玩家的排名情况。
  2. 最新数据优先场景:如果有序集合中的分数代表时间戳,DESC 排序可以将最新发生的事件排在前面。比如,在一个新闻网站的文章展示系统中,以文章发布时间为分数,使用 DESC 排序可以让最新发布的文章显示在页面顶部,方便用户及时获取最新信息。

DESC 排序代码示例

同样在 Python 中,使用 redis - py 库来实现 DESC 排序。

import redis

# 连接 Redis 服务器
r = redis.Redis(host='localhost', port=6379, db = 0)

# 向有序集合中添加成员和分数
r.zadd('fruits', {'apple': 3, 'banana': 1, 'cherry': 2})

# 执行 DESC 排序
sorted_result_desc = r.zrevrange('fruits', 0, -1, withscores = True)

print("DESC 排序结果:")
for member, score in sorted_result_desc:
    print(f"成员: {member.decode()}, 分数: {score}")

在这段代码中,同样先向 fruits 有序集合添加成员和分数。然后,使用 zrevrange 方法进行 DESC 排序,0-1 同样表示获取所有成员,withscores = True 使结果包含成员和分数。最后,打印出 DESC 排序后的结果。

复合排序规则组合

多字段排序

在实际应用中,有时需要根据多个字段进行排序。虽然 Redis 本身的有序集合主要基于单一分数进行排序,但可以通过一些技巧实现多字段排序。一种常见的方法是将多个字段合并成一个分数值。

例如,假设有一个电商商品的有序集合,需要根据销量和价格进行排序。可以将销量乘以一个较大的数(比如 10000)再加上价格作为最终的分数。这样在排序时,销量的权重相对较大,先比较销量,销量相同的情况下再比较价格。

假设商品 "product1" 的销量为 100,价格为 50,计算分数为 100 * 10000 + 50 = 1000050;商品 "product2" 的销量为 80,价格为 40,计算分数为 80 * 10000 + 40 = 800040

在执行排序时,如果希望销量高的商品排在前面,价格低的商品也排在前面,可以使用 DESC 排序这个合成的分数。代码示例如下:

import redis

# 连接 Redis 服务器
r = redis.Redis(host='localhost', port=6379, db = 0)

# 商品信息
products = {
    'product1': (100, 50),
    'product2': (80, 40)
}

# 计算并添加分数到有序集合
for product, (sales, price) in products.items():
    score = sales * 10000 + price
    r.zadd('products', {product: score})

# 执行 DESC 排序
sorted_products = r.zrevrange('products', 0, -1, withscores = True)

print("按销量和价格 DESC 排序结果:")
for product, score in sorted_products:
    sales = int(score / 10000)
    price = score % 10000
    print(f"商品: {product.decode()}, 销量: {sales}, 价格: {price}")

在上述代码中,首先定义了商品的销量和价格信息。然后通过计算合成分数并添加到名为 products 的有序集合中。最后,执行 DESC 排序并解析出原来的销量和价格信息进行打印。

条件排序

  1. 基于条件选择 ASC 或 DESC:在某些情况下,排序规则需要根据特定条件来选择 ASC 或 DESC。例如,在一个用户活跃度统计系统中,如果是普通用户,希望活跃度低的排在前面(ASC 排序),以便优先处理低活跃度用户的问题;而对于 VIP 用户,希望活跃度高的排在前面(DESC 排序),给予他们更多关注。

假设我们有一个有序集合存储用户活跃度,成员为用户名,分数为活跃度值。可以通过以下代码实现这种条件排序:

import redis

# 连接 Redis 服务器
r = redis.Redis(host='localhost', port=6379, db = 0)

# 向有序集合中添加用户活跃度数据
r.zadd('user_activity', {'user1': 50, 'user2': 30, 'vip_user1': 80, 'vip_user2': 90})

# 用户类型判断及排序
def sort_user_activity(user_type):
    if user_type == 'normal':
        sorted_result = r.zrange('user_activity', 0, -1, withscores = True)
    else:
        sorted_result = r.zrevrange('user_activity', 0, -1, withscores = True)
    return sorted_result

# 普通用户排序
normal_users = sort_user_activity('normal')
print("普通用户排序结果(ASC):")
for user, activity in normal_users:
    print(f"用户: {user.decode()}, 活跃度: {activity}")

# VIP 用户排序
vip_users = sort_user_activity('vip')
print("VIP 用户排序结果(DESC):")
for user, activity in vip_users:
    print(f"用户: {user.decode()}, 活跃度: {activity}")

在上述代码中,定义了 sort_user_activity 函数,根据传入的用户类型选择 ASC 或 DESC 排序。然后分别对普通用户和 VIP 用户进行排序并打印结果。

  1. 动态调整排序字段:除了根据条件选择 ASC 或 DESC,还可能需要动态调整排序所依据的字段。例如,在一个项目管理系统中,有时需要根据项目的完成进度排序,有时需要根据项目的优先级排序。

假设我们有一个有序集合存储项目信息,成员为项目名称,分数可以根据不同需求进行设置。可以通过以下代码实现动态调整排序字段:

import redis

# 连接 Redis 服务器
r = redis.Redis(host='localhost', port=6379, db = 0)

# 项目信息
projects = {
    'project1': {'progress': 0.5, 'priority': 3},
    'project2': {'progress': 0.3, 'priority': 1},
    'project3': {'progress': 0.7, 'priority': 2}
}

# 根据指定字段计算并添加分数到有序集合
def add_projects_to_zset(field):
    for project, info in projects.items():
        score = info[field]
        r.zadd('projects', {project: score})

# 根据指定字段和排序方向进行排序
def sort_projects(field, order):
    add_projects_to_zset(field)
    if order == 'asc':
        sorted_result = r.zrange('projects', 0, -1, withscores = True)
    else:
        sorted_result = r.zrevrange('projects', 0, -1, withscores = True)
    return sorted_result

# 按完成进度 ASC 排序
progress_asc = sort_projects('progress', 'asc')
print("按完成进度 ASC 排序结果:")
for project, score in progress_asc:
    print(f"项目: {project.decode()}, 完成进度: {score}")

# 按优先级 DESC 排序
priority_desc = sort_projects('priority', 'desc')
print("按优先级 DESC 排序结果:")
for project, score in priority_desc:
    print(f"项目: {project.decode()}, 优先级: {score}")

在上述代码中,定义了 add_projects_to_zset 函数根据指定字段计算分数并添加到有序集合,sort_projects 函数根据指定字段和排序方向进行排序。然后分别按完成进度 ASC 排序和按优先级 DESC 排序并打印结果。

排序规则组合中的性能考量

大数据量下的排序性能

当有序集合中的数据量非常大时,排序操作可能会对性能产生较大影响。无论是 ASC 还是 DESC 排序,Redis 在执行排序时都需要遍历有序集合中的所有成员,并根据分数进行比较和排列。

  1. 分数计算复杂度:在复合排序中,如果分数的计算较为复杂,例如涉及多个字段的复杂运算,这会增加排序的时间开销。例如,在前面电商商品多字段排序的例子中,如果计算合成分数的公式更为复杂,每次添加成员到有序集合时计算分数的时间就会变长,进而影响整体排序性能。
  2. 内存占用:大数据量下,排序操作可能会占用较多的内存。Redis 在排序过程中可能需要额外的内存空间来存储临时的排序结果。如果服务器内存不足,可能会导致性能下降甚至出现错误。例如,在一个包含数百万个成员的有序集合中进行排序时,若服务器内存有限,可能会因为无法分配足够的内存来存储排序中间结果而使排序操作失败。

优化策略

  1. 分批处理:对于大数据量的有序集合,可以采用分批处理的方式进行排序。例如,将有序集合分成多个较小的子集,分别对这些子集进行排序,然后再将排序后的子集合并。这样可以减少每次排序的数据量,降低内存压力和排序时间。
  2. 索引优化:合理利用 Redis 的索引机制。虽然 Redis 有序集合本身已经基于分数建立了索引,但在复合排序中,如果能够预先对某些字段建立额外的索引(例如使用 Redis 的哈希表存储辅助索引信息),可以加快排序过程中数据的查找和比较速度。例如,在电商商品排序中,可以为商品的销量和价格分别建立哈希表索引,在计算合成分数和排序时可以更快地获取相关数据。
  3. 缓存策略:对于频繁进行排序操作且数据相对稳定的有序集合,可以采用缓存策略。将排序结果缓存起来,当再次需要相同排序结果时,直接从缓存中获取,而无需重新执行排序操作。例如,在一个每日更新数据但排序需求频繁的销售排行榜中,可以每天更新数据后执行一次排序,并将排序结果缓存一天,期间用户查询排行榜时直接从缓存获取结果,提高响应速度。

排序规则组合在实际项目中的应用案例

社交平台好友活跃度排序

在一个社交平台中,需要对用户的好友按活跃度进行排序。活跃度的计算涉及多个因素,如发布动态的数量、评论他人动态的数量以及登录天数等。

假设我们使用 Redis 有序集合来存储好友活跃度信息,成员为好友的 ID,分数为活跃度值。活跃度的计算可以通过将发布动态数量乘以 10,评论数量乘以 5,登录天数乘以 3 后相加得到。

  1. 数据初始化
import redis

# 连接 Redis 服务器
r = redis.Redis(host='localhost', port=6379, db = 0)

# 好友活跃度相关数据
friends_activity = {
    'friend1': {'posts': 5, 'comments': 3, 'login_days': 10},
    'friend2': {'posts': 3, 'comments': 5, 'login_days': 8},
    'friend3': {'posts': 7, 'comments': 2, 'login_days': 12}
}

# 计算并添加活跃度分数到有序集合
for friend, activity in friends_activity.items():
    score = activity['posts'] * 10 + activity['comments'] * 5 + activity['login_days'] * 3
    r.zadd('friends_activity', {friend: score})
  1. 排序及展示
# 按活跃度 DESC 排序
sorted_friends = r.zrevrange('friends_activity', 0, -1, withscores = True)

print("按活跃度 DESC 排序后的好友列表:")
for friend, score in sorted_friends:
    print(f"好友 ID: {friend.decode()}, 活跃度分数: {score}")

在这个案例中,通过复合排序规则,综合考虑多个因素计算活跃度分数,并使用 DESC 排序将活跃度高的好友排在前面,方便用户查看最活跃的好友。

电商平台商品搜索排序

在电商平台的商品搜索功能中,排序是非常关键的环节。用户可能希望按照价格、销量、评价等多个因素进行排序。

假设我们有一个 Redis 有序集合存储商品信息,成员为商品 ID,分数根据用户选择的排序方式进行计算。

  1. 按价格排序
import redis

# 连接 Redis 服务器
r = redis.Redis(host='localhost', port=6379, db = 0)

# 商品信息
products = {
    'product1': {'price': 50,'sales': 100, 'rating': 4.5},
    'product2': {'price': 30,'sales': 80, 'rating': 4.0},
    'product3': {'price': 70,'sales': 120, 'rating': 4.8}
}

# 根据价格添加分数到有序集合
for product, info in products.items():
    score = info['price']
    r.zadd('products_by_price', {product: score})

# 按价格 ASC 排序
sorted_by_price_asc = r.zrange('products_by_price', 0, -1, withscores = True)

print("按价格 ASC 排序后的商品列表:")
for product, price in sorted_by_price_asc:
    print(f"商品 ID: {product.decode()}, 价格: {price}")
  1. 按销量和评价综合排序
# 根据销量和评价计算并添加分数到有序集合
for product, info in products.items():
    score = info['sales'] * 10 + info['rating'] * 5
    r.zadd('products_by_sales_rating', {product: score})

# 按销量和评价综合 DESC 排序
sorted_by_sales_rating_desc = r.zrevrange('products_by_sales_rating', 0, -1, withscores = True)

print("按销量和评价综合 DESC 排序后的商品列表:")
for product, score in sorted_by_sales_rating_desc:
    sales = int(score / 10)
    rating = (score % 10) / 5
    print(f"商品 ID: {product.decode()}, 销量: {sales}, 评价: {rating}")

在电商平台商品搜索排序案例中,通过不同的复合排序规则组合,满足了用户多样化的排序需求,提升了用户体验。

通过对 Redis ASC 和 DESC 选项实现的排序规则组合的深入探讨,我们了解了从基础排序原理到复杂的复合排序应用,以及性能考量和实际项目案例。在实际开发中,合理运用这些排序规则组合,可以有效地解决各种数据排序需求,提升系统的性能和用户体验。