P14 给你一个亿 Redis keys 统计双方的共同好友
面试题:用户好友关系用 Redis 存(一亿个 key),怎么统计两个用户的共同好友?
存储方案
每个用户一个 Set 存好友 ID:
text
key:friends:{userId}
value:set of 好友 userId统计共同好友
交集运算:
bash
SINTER friends:10001 friends:10002SINTER 直接返回两个集合的交集,即共同好友。如果只要数量:
bash
SINTERCARD friends:10001 friends:10002 # Redis 7.0+或 SINTERSTORE 把结果存到临时 key 再 SCARD。
为什么用 Set
- 好友关系天然是集合,无重复、无序;
- Set 底层是哈希表/整数集合,
SADD/SREM/SISMEMBER都是 O(1); - Redis 的集合运算在服务端完成,不用把数据拉到客户端,网络开销小。
大规模优化点
1. 大 key 问题
一亿用户意味着可能有超大 Set(大 V 用户几百万好友)。大 key 会造成:
- 单次操作耗时增加、阻塞 Redis 单线程;
- 内存倾斜、迁移/扩容困难。
对策:
- 分片存储:好友按 userId 取模分桶(
friends:{uid}:{bucket}),统计时对多个分片分别 SINTER 再合并; - 限制单用户好友上限或分层(普通好友/特别关注分开存);
- 用
SINTERCARD只算数量不取结果,避免大集合交集的网络传输。
2. 热点统计缓存
高频查询的共同好友结果缓存(TTL 短一点),避免重复交集计算。
3. 冷热分离
活跃用户好友在 Redis,非活跃用户好友落数据库,按需加载。
加分点
- 对比方案:用数据库表
(user_id, friend_id)查询,需要 join 或 IN 查询,量大时性能远不如 Redis 集合运算; - 交集可以用小集合驱动大集合:用好友数少的集合做基准,减少计算量(SINTER 内部已优化);
- 好友关系变更要同步:
SADD/SREM增量维护,而不是全量重建。
一句话总结
好友关系用 friends:{userId} 的 Set 存储,共同好友直接 SINTER 求交集;数据量大时按用户分片、用 SINTERCARD 控制开销、缓存热点结果。