在上一节实现关注功能时,我们不仅把数据存入了 MySQL,还特意在 Redis 的 Set 集合里同步了一份当前用户的关注列表(follows:{userId})。这就是为了今天这节课的王炸功能——共同关注做准备的。
📚 实战篇 06. 好友关注 - 共同关注学习文档
一、 业务场景与痛点分析
在很多社交 App(如小红书、微博、抖音)的个人主页上,通常会展示这样一个提示:“你和 Ta 共同关注了 xxx、yyy”。
这个功能不仅能拉近用户之间的距离,还能极大地促进社交裂变。
传统 MySQL 的痛点:
如果我们纯靠 MySQL 来查两个人的共同关注,SQL 语句大概长这样:
SQL
-- 查找用户 A 和 用户 B 的共同关注
SELECT follow_user_id FROM tb_follow WHERE user_id = A
AND follow_user_id IN (
SELECT follow_user_id FROM tb_follow WHERE user_id = B
);
或者是使用 INNER JOIN 连表查询。当用户量级较小的时候,这没什么问题。但如果是一个百万、千万级日活的社交应用,这种高频的多表嵌套/关联查询会瞬间榨干数据库的 CPU 性能。
二、 破局核心:Redis Set 的交集运算 (SINTER)
既然我们在关注时,已经把每个用户关注的博主 ID 放到了 Redis 的 Set 集合 中:
- 用户 A 的关注列表:
follows:A->{101, 102, 103, 104} - 用户 B 的关注列表:
follows:B->{103, 104, 105}
那么,“共同关注”在数学上的本质,就是求这两个集合的交集(Intersection) 。
Redis 为 Set 数据结构原生提供了极其高效的集合运算命令:
- 求交集(共同关注):
SINTER key1 key2 ...(返回多个集合中都有的元素) - 求并集(合并关注列表):
SUNION key1 key2 ... - 求差集(我关注了,Ta没关注):
SDIFF key1 key2 ...
利用 SINTER,原本在 MySQL 中需要扫表比对的复杂运算,在 Redis 内存中只需要极低的时间复杂度就能瞬间算出结果!
三、 核心代码落地
当用户查看某位博主的个人主页时,前端会发起查询共同关注的请求,并带上该博主的 id。
我们在 FollowController 中定义接口,并在 FollowServiceImpl 中实现如下逻辑:
Java
@Override
public Result followCommons(Long targetUserId) {
// 1. 获取当前登录用户
Long userId = UserHolder.getUser().getId();
// 2. 拼接当前用户和目标博主的 Redis Key
String key1 = "follows:" + userId;
String key2 = "follows:" + targetUserId;
// 3. 求交集 (SINTER key1 key2)
// opsForSet().intersect() 方法底层就是执行了 SINTER 命令
Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key1, key2);
// 4. 判空:如果没有共同关注,直接返回空集合
if (intersect == null || intersect.isEmpty()) {
return Result.ok(Collections.emptyList());
}
// 5. 解析出共同关注的用户 ID 集合
List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
// 6. 根据 ID 去数据库查询这些用户的详细信息 (头像、昵称等),并转换为 DTO 返回给前端
// 这里使用了 MyBatisPlus 的 listByIds 批量查询
List<UserDTO> users = userService.listByIds(ids)
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
// 7. 返回结果
return Result.ok(users);
}
四、 学习总结与拓展思路
这一节的代码极其简短,但却蕴含了非常经典的架构设计思想: “空间换时间”与“计算前置” 。
我们在写操作(关注/取关)时多花了一点微不足道的代价(同步双写一份 ID 列表到 Redis Set),却在读操作(求共同关注)时获得了极其夸张的性能提升。
💡 面试拓展拷问:
除了“共同关注”,你还能用 Redis Set 实现哪些社交功能?
- “你可能认识的人”: 可以用
SDIFF。比如获取目标用户的关注列表,减去你自己的关注列表,剩下的就是目标用户关注了、但你还没关注的人,可以直接推荐给你。 - “微博共同好友的粉丝抽奖”: 可以用
SRANDMEMBER随机从交集里抽出幸运儿。
到目前为止,我们达人探店的“发笔记”、“点赞”、“关注”三大基石功能已经全部搭建完毕。
社交应用最核心的灵魂即将登场——Feed 流(信息流) 。当你关注了 10 个博主,这 10 个博主发了新笔记,你的 App 首页是如何把他们的新动态按照时间顺序推送到你眼前的?