目录
- 一、前言
- 二、Redis Zset 的基本操作
- 三、通过Redis 命令模拟排行榜功能
- 3.1、排行榜生成
- 3.2、排行榜查询
- 四、SpringBoot 使用 Redis Zset 有序集合实现排行榜功能
一、前言
排行榜功能是非常常见的需求,例如商品售卖排行榜单、游戏中的积分排行榜、配送员完单排行榜等。实现排行榜功能需要高效地对大量数据进行排序和查询,如果直接进行数据库查询对应业务排行榜资源开销会非常大,一般会将对应榜单需要的数据做单独存储记录,查询时只要对榜单数据表进行遍历排序即可,因为榜单数据表的数据是无序的,还要对具体数据进行排序,并且每次数据变动都要更新表中数据,当数据量过大或者高并发时性能一般,要想高效地实现排行榜功能需要使用 Redis Zset 有序集合,可以非常高效方便地实现排行榜功能。
需要Redis常用命令集文章可以查看:https://blog.csdn.net/weixin_44606481/article/details/133672258
二、Redis Zset 的基本操作
# 添加以一个过着多个元素,score为评分,集合按照从低到高及进行排序,评分可以重复
zadd <key> <score1> <value1> <score2> <value2>
# 获取有序集合中成员的数量
zcard <key>
# 统计score评分在某个范围内的数据的数量
zcount <key> <min> <max>
# 查一定范围的元素,end为-1时,查询所有,按照分数从小到大排序,withscores加上他,连着评分一起查出
zrange <key> <start> <end> [LIMIT offset count] [withscores]
# 查一定范围的元素,end为-1时,查询所有,按照分数从大到小排序,withscores加上他,连着评分一起查出
zrevrange <key> <start> <end> [LIMIT offset count] [withscores]
# 查询score评分在某个范围内的数据,从小到大排序,min 和 max 可以是 -inf 和 +inf来表示无穷小和无穷大,withscores加上他,连着评分一起查出
zrangebyscore <key> <min> <max> [withscores] [limit offset count]
# 查询score评分在某个范围内的数据,从大到小排序,min 和 max 可以是 -inf 和 +inf来表示无穷小和无穷大,withscores加上他,连着评分一起查出
zrevrangebyscore <key> <max> <min> [withscores] [limit offset count]
# 为元素的score加上指定的增量
zincrby <key> <increment> <value>
# 删除数据
zrem <key> <value1> <value2>
# 返回集合中value的排名,按分数递增排序。分数值最小者排名为0
zrank <key> <value>
# 返回集合中value的排名,按分数递减排序。分数值最大者排名为0
zrevrank <key> <value>
三、通过Redis 命令模拟排行榜功能
这里模拟一个商品销量排行榜缓存key为PRODUCT:RANK:SALES
,假设有6个商品,商品ID分别为 P001-P006。
3.1、排行榜生成
- 1、初始化排行榜数据(为了演示这里先给每个商品销量设置0,一般在业务中会先将数据库中的销量数据查询出来初始化到排行榜中)
127.0.0.1:6379> zadd PRODUCT:RANK:SALES 0 P001 0 P002 0 P003 0 P004 0 P005 0 P006
(integer) 6
- 2、给商品ID为P002、P005、P006的商品分别添加销量7、2、9
127.0.0.1:6379> zincrby PRODUCT:RANK:SALES 7 P002
"7"
127.0.0.1:6379> zincrby PRODUCT:RANK:SALES 2 P005
"2"
127.0.0.1:6379> zincrby PRODUCT:RANK:SALES 9 P006
"9"
3.2、排行榜查询
- 1、查询排行榜全部数据按照分数从大到小排序
127.0.0.1:6379> zrevrange PRODUCT:RANK:SALES 0 -1 withscores
1) "P006"
2) "9"
3) "P002"
4) "7"
5) "P005"
6) "2"
7) "P004"
8) "0"
9) "P003"
10) "0"
11) "P001"
12) "0"
- 2、查询排行榜销量前三的数据按照分数从大到小排序
127.0.0.1:6379> zrevrange PRODUCT:RANK:SALES 0 2 withscores
1) "P006"
2) "9"
3) "P002"
4) "7"
5) "P005"
6) "2"
- 3、查询排行榜销量大于等于1的数据从小到大排序,并且进行分页,每页2条数据查询第1页
# 不分页
127.0.0.1:6379> zrangebyscore PRODUCT:RANK:SALES 1 +inf withscores
1) "P005"
2) "2"
3) "P002"
4) "7"
5) "P006"
6) "9"
# 分页 limit 0 2 (0:代表偏移量 2:显示条数)
127.0.0.1:6379> zrangebyscore PRODUCT:RANK:SALES 1 +inf withscores limit 0 2
1) "P005"
2) "2"
3) "P002"
4) "7"
四、SpringBoot 使用 Redis Zset 有序集合实现排行榜功能
需要SpringBoot集成Redis文章可以查看:https://blog.csdn.net/weixin_44606481/article/details/133907103
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.HashSet;
import java.util.Set;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class RedisZSetTest {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 商品排行榜key
private String cacheKey = "PRODUCT:RANK:SALES";
/**
* 初始化排行榜
*/
@Test
public void initRank() {
// 初始化前先删除对应key
redisTemplate.delete(cacheKey);
// 创建一个存储 TypedTuple 的集合 用于批量添加,也可以单独添加
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
tuples.add(new DefaultTypedTuple<>("P001", 0D));
tuples.add(new DefaultTypedTuple<>("P002", 0D));
tuples.add(new DefaultTypedTuple<>("P003", 0D));
tuples.add(new DefaultTypedTuple<>("P004", 0D));
tuples.add(new DefaultTypedTuple<>("P005", 0D));
tuples.add(new DefaultTypedTuple<>("P006", 0D));
Long add = redisTemplate.opsForZSet().add(cacheKey, tuples);
System.out.println("初始化成功:"+ add);
}
/**
* 添加排行榜数据
*/
@Test
public void addRank() {
redisTemplate.opsForZSet().incrementScore(cacheKey,"P002",7D);
redisTemplate.opsForZSet().incrementScore(cacheKey,"P005",2D);
redisTemplate.opsForZSet().incrementScore(cacheKey,"P006",9D);
System.out.println("添加排行榜数据成功");
}
/**
* 查询排行榜数据
*/
@Test
public void queryRank() {
// 查询排行榜全部数据按照分数从大到小排序
Set<ZSetOperations.TypedTuple<Object>> set1 = redisTemplate.opsForZSet().reverseRangeWithScores(cacheKey, 0L, -1L);
System.out.println("查询排行榜全部数据按照分数从大到小排序:");
set1.forEach(tuple -> {
System.out.println(tuple.getValue() + " : " + tuple.getScore());
});
// 查询排行榜销量前三的数据按照分数从大到小排序
Set<ZSetOperations.TypedTuple<Object>> set2 = redisTemplate.opsForZSet().reverseRangeWithScores(cacheKey, 0L, 2L);
System.out.println("查询排行榜销量前三的数据按照分数从大到小排序:");
set2.forEach(tuple -> {
System.out.println(tuple.getValue() + " : " + tuple.getScore());
});
// 查询排行榜销量大于等于1的数据从小到大排序,并且进行分页,每页2条数据查询第1页
Set<ZSetOperations.TypedTuple<Object>> set3 = redisTemplate.opsForZSet().rangeByScoreWithScores(cacheKey, 1D, Double.MAX_VALUE, 0L, 2L);
System.out.println("查询排行榜销量大于等于1的数据从小到大排序,并且进行分页,每页2条数据查询第1页:");
set3.forEach(tuple -> {
System.out.println(tuple.getValue() + " : " + tuple.getScore());
});
}
}
查询结果