使用ZSET实现topN点赞排行

1.8k 词

为什么要使用ZSET?

Redis常用的存储多个数据的数据结构对比

List Set SortedSet(ZSET)
排序方式 按添加顺序排序 无法排序 根据score值排序
唯一性 不唯一 唯一 唯一
查找方式 按索引或首位查找 根据元素查找 根据元素查找
查找性能

需求分析

  • 需要实现快速检索:判断当前用户是否已经点过赞
  • 需要实现排序:实现topN

很显然,我们要选择ZSET

如何实现排序?

我们需要按照用户点赞时间的先后顺序,取出前n个用户,并在数据库中查找相关信息。
所以,我们可以把时间戳作为score来存入ZSET

初步代码实现

逻辑很简单,先取出ZSET中前n个数据,然后在数据库中完成查询即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public List<UserDTO> queryByLikes(Long id) {
String key = RedisConstants.BLOG_LIKED_KEY + id;
// 查询Redis中的top5
Set<String> top5 = redisTemplate.opsForZSet().range(key, 0, 4);
if (top5 == null || top5.isEmpty()) {
return Collections.emptyList();
}
// 解析出用户id
List<Long> ids = top5.stream().map(Long::valueOf).toList();
List<UserDTO> userDTOS = userService.listByIds(ids)
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.toList();
return userDTOS;
}

但是,这么写有个问题
MyBatisPlus的listByIds方法,执行的Sql语句是select * from tb where id in (id1, id2, id3 ....),这样的话,得到的结果是MySql数据库的默认顺序,我们想要的是排序后的topN,而不是无序的。
那么,该怎么办呢?
我们可以用Sql语句select * from tb where id in (id1, id2, id3...) order by field(id3, id2, id1..)field里的内容是我们希望得到的顺序,于是,我们有了这样的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public List<UserDTO> queryByLikes(Long id) {
String key = RedisConstants.BLOG_LIKED_KEY + id;
// 查询Redis中的top5
Set<String> top5 = redisTemplate.opsForZSet().range(key, 0, 4);
if (top5 == null || top5.isEmpty()) {
return Collections.emptyList();
}
// 解析出用户id
List<Long> ids = top5.stream().map(Long::valueOf).toList();
// 进行用户查询 where id in (5, 1) order by field(id, 5, 1)
// 不能用mybatisplus的默认查询,因为那样的话会有一次对id的排序,使得前面的排序失效
String idStr = StrUtil.join(",", ids);
List<UserDTO> userDTOS = userService.lambdaQuery()
.in(User::getId, ids)
.last("order by field(id, " + idStr + ")").list()
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.toList();
return userDTOS;
}

于是,我们的topN功能就实现啦!