給你一個億Rediskeys+統(tǒng)計雙方的共同好友
在社交應用爆炸式增長的時代,十億級用戶關(guān)系圖譜已是常態(tài)。面對海量好友關(guān)系數(shù)據(jù),如何高效計算共同好友成為核心挑戰(zhàn)。本文將深入探討基于 Redis Set 結(jié)構(gòu)實現(xiàn)億級 Key 下的共同好友實時統(tǒng)計方案,涵蓋數(shù)據(jù)結(jié)構(gòu)設計、集群優(yōu)化、并行計算及生產(chǎn)環(huán)境實戰(zhàn)經(jīng)驗。
一、問題定義與挑戰(zhàn)
需求場景:用戶 U1 和 U2 各自擁有好友集合 F1 和 F2,求交集 F1 ∩ F2(共同好友)。
數(shù)據(jù)規(guī)模:
? 用戶量:10^8(1億)
? 平均好友數(shù):300(符合社交網(wǎng)絡六度分割理論)
? 總關(guān)系邊數(shù):約 3×10^10(300億)
? Key 總量:1億(每個用戶一個好友集合)
核心挑戰(zhàn):
1. 內(nèi)存消耗:存儲 1億個 Set,每個 Set 平均 300 個用戶ID
2. 計算延遲:交集計算需在毫秒級響應
3. 集群擴展:單機 Redis 無法支撐,需分布式方案
二、Redis Set 結(jié)構(gòu)選型與優(yōu)化
2.1 數(shù)據(jù)結(jié)構(gòu)對比
結(jié)構(gòu)類型 | 內(nèi)存占用(300成員) | SINTER 復雜度 | 適用場景 |
Set | ~15KB | O(N*M) | 精確交集計算 |
ZSet | ~25KB | O(N*logM) | 帶權(quán)重的好友關(guān)系 |
HyperLogLog | ~1.5KB | 無法計算交集 | 基數(shù)統(tǒng)計 |
結(jié)論:原生 Set 是精確計算共同好友的最佳選擇。
2.2 內(nèi)存優(yōu)化實踐
# 用戶ID壓縮存儲(原64位整數(shù) → 32位整數(shù))
user_id = 1234567890
compressed_id = user_id & 0xFFFFFFFF # 截斷為32位
# Redis 配置優(yōu)化
config set hash-max-ziplist-entries 512 # 小集合使用ziplist
config set set-max-intset-entries 512 # 整數(shù)集合優(yōu)化
優(yōu)化效果:
? 原始 Set(300成員):約 15KB
? 優(yōu)化后:約 9.8KB(節(jié)省 35%)
? 總內(nèi)存:1億 Key × 9.8KB ≈ 980 TB → 經(jīng)分片后實際部署需求
三、Redis 集群架構(gòu)設計
3.1 分片策略
采用 CRC16 分片算法 將用戶 Set 分布到集群節(jié)點:
public int getShardIndex(String userId, int shardCount) {
int crc = CRC16.crc16(userId.getBytes());
return (crc & 0x7FFF) % shardCount; // 取15位避免負值
}
集群規(guī)格:
? 分片數(shù):200 個(每個分片管理約 50 萬個 Set)
? 單分片數(shù)據(jù)量:50萬 Set × 9.8KB ≈ 48GB
? 總內(nèi)存需求:200 × 48GB = 9.6TB (考慮副本則翻倍)
3.2 數(shù)據(jù)分布示例
用戶ID | CRC16 | 分片索引 |
U1 | 0xA3C1 | 65 |
U2 | 0x7B02 | 130 |
四、共同好友計算引擎
4.1 跨分片計算難題
當 U1 和 U2 處于不同分片時,無法直接使用 SINTER
命令。
4.2 解決方案:并行掃描 + 聚合
func GetCommonFriends(u1, u2 string) []string {
// 1. 定位用戶所在分片
shard1 := getShard(u1)
shard2 := getShard(u2)
// 2. 并行獲取好友集
friends1 := shard1.SMEMBERS(u1)
friends2 := shard2.SMEMBERS(u2)
// 3. 本地計算交集
return intersect(friends1, friends2)
}
性能瓶頸:單個用戶好友列表可能達數(shù)萬(如明星用戶),網(wǎng)絡傳輸成為瓶頸。
4.3 優(yōu)化:分片內(nèi)預計算
-- LUA腳本:在目標分片執(zhí)行本地交集計算
local result = {}
local friends = redis.call('SMEMBERS', KEYS[1])
for _, friend in ipairs(friends) do
if redis.call('SISMEMBER', KEYS[2], friend) == 1 then
table.insert(result, friend)
end
end
return result
執(zhí)行流程:
1. 將 U2 的好友列表復制到 U1 所在分片(臨時 Set)
2. 在 U1 分片執(zhí)行 LUA 腳本計算交集
3. 刪除臨時 Set
優(yōu)勢:減少 50% 網(wǎng)絡傳輸量,利用分片內(nèi)內(nèi)存高速訪問。
五、性能壓測數(shù)據(jù)
測試環(huán)境:Redis Cluster 6 節(jié)點(32核/128GB/萬兆網(wǎng)絡)
好友規(guī)模 | 傳統(tǒng)方案 | 分片預計算 | 提升幅度 |
300 vs 300 | 15ms | 8ms | 47% |
10k vs 300 | 210ms | 45ms | 78% |
50k vs 50k | 1850ms | 320ms | 83% |
注:測試數(shù)據(jù)包含網(wǎng)絡延遲和序列化開銷
六、生產(chǎn)環(huán)境增強策略
6.1 熱點用戶處理
場景:明星用戶(如擁有 500 萬粉絲)的好友查詢。
方案:
# 客戶端緩存策略
location /common_friends {
proxy_cache redis_cache;
proxy_cache_key $arg_u1-$arg_u2;
proxy_cache_valid 200 5s; // 5秒緩存
}
6.2 超時控制與重試
try (Jedis jedis = pool.getResource()) {
jedis.sinterstore("temp_result", "set_u1", "set_u2");
return jedis.smembers("temp_result");
} catch (JedisConnectionException e) {
if (retryCount++ < 3) {
Thread.sleep(50);
retry();
}
}
6.3 內(nèi)存溢出防護
# Redis配置
maxmemory 100gb
maxmemory-policy allkeys-lru # 內(nèi)存不足時LRU淘汰
七、替代方案對比
7.1 Redis vs 圖數(shù)據(jù)庫
維度 | Redis Set | Neo4j |
查詢延遲 | 毫秒級 | 百毫秒級 |
開發(fā)復雜度 | 低(標準命令) | 高(Cypher 學習) |
橫向擴展 | 原生支持 | 需要企業(yè)版 |
成本 | $0.5/GB/月 | $5/GB/月 |
7.2 Redis vs Spark GraphX
val graph = GraphLoader.edgeListFile(sc, "friends.txt")
val commonFriends = graph.collectNeighbors(EdgeDirection.Out)
.filter{ case (vid, neighbors) => Set(u1,u2).contains(vid) }
.values
.reduce((a,b) => a.intersect(b))
適用場景:離線批量計算(分鐘級延遲)
八、架構(gòu)演進建議
1. 冷熱分離
? 熱數(shù)據(jù):Redis Set 存儲最近活躍用戶
? 冷數(shù)據(jù):持久化到 TiKV(兼容 Redis 協(xié)議)
2. 混合存儲
實時請求離線分析客戶端查詢類型Redis ClusterSpark+Redis
3. 異步更新
# 使用消息隊列解耦
kafka_producer.send("friend_update",
user_id=u1,
friend_id=u2,
action="ADD")
九、結(jié)論
通過 Redis Set 分片集群 + 智能路由 + LUA 腳本優(yōu)化,我們實現(xiàn)了:
1. 億級 Key 下 99% 的共同好友查詢 < 100ms
2. 內(nèi)存成本降低 35% 以上(對比未優(yōu)化方案)
3. 集群線性擴展能力,支持未來用戶量增長
隨著 Redis 7.0 對 Set 命令的持續(xù)優(yōu)化(如 SINTERCARD
計數(shù)命令),該方案在超大規(guī)模社交圖譜中仍將保持競爭力。建議結(jié)合業(yè)務場景輔以異步計算和緩存策略,實現(xiàn)成本與性能的最優(yōu)平衡。