用redis實現計數器
社交產品業務里有很多統計計數的功能,比如:
- 用戶: 總點贊數,關注數,粉絲數
- 帖子: 點贊數,評論數,熱度
- 消息: 已讀,未讀,紅點消息數
- 話題: 閱讀數,帖子數,收藏數
統計計數的特點
- 實時性要求高
- 寫的頻率很高
- 寫的性能對MySQL是一個挑戰
可以采用redis來優化高頻率寫入的性能要求。
redis優化方案一
對於每一個實體的計數,設計一個hash結構的counter:
//用戶
counter:user:{userID}
-> praiseCnt: 100 //點贊數
-> hostCnt: 200 //熱度
-> followCnt: 332 //關注數
-> fansCnt: 123 //粉絲數
//帖子
counter:topic:{topicID}
-> praiseCnt: 100 //點贊數
-> commentCnt: 322 //評論數
//話題
counter:subject:{subjectID}
-> favoCnt: 312 //收藏數
-> viewCnt: 321 //閱讀數
-> searchCnt: 212 //搜索進入次數
-> topicCnt: 312 //話題中帖子數
類似這種計數器,隨着產品功能的增加,也會越來越多,比如回復數,踩數,轉發數什么的。
redis相關的命令
//獲取指定userID的所有計數器
HGETALL counter:user:{userID}
//獲取指定userID的指定計數器
HMGET counter:user:{userID} praiseCnt hostCnt
//指定userID點贊數+1
HINCRBY counter:user:{userID} praiseCnt
缺點:這樣設計,如果要批量查詢多個用戶的數據,就比較麻煩,例如一次要查指定20個userID的計數器?只能循環執行 HGETALL counter:user:{userID}。
優點:以實體聚合數據,方便數據管理
redis優化方案二
方案二是用來解決方案一的缺點的,依然是采用hash,結構設計是這樣的:
counter:user:praiseCnt
-> userID_1001: 100
-> userID_1002: 200
-> userID_1003: 332
-> userID_1004: 123
.......
-> userID_9999: 213
counter:user:hostCnt
-> userID_1001: 10
-> userID_1002: 290
-> userID_1003: 322
-> userID_1004: 143
.......
-> userID_9999: 213
counter:user:followCnt
-> userID_1001: 21
-> userID_1002: 10
-> userID_1003: 32
-> userID_1004: 203
.......
-> userID_9999: 130
獲取多個指定userID的點贊數的命令變成這樣了
HMGET counter:user:praiseCnt userID_1001 userID_1002
上面命令可以批量獲取多個用戶的點贊數,時間復雜度為O(n),n為指定userID的數量。
優點:解決了批量操作的問題
缺點:當要獲取多個計數器,比如同時需要praiseCnt,hostCnt時,要讀多次,不過要比第一種方案讀的次數要少。一個hash里的字段將會非常寵大,HMGET也許會有性能瓶頸。
用redis管道(Pipelining)來優化方案一
對於第一種方案的缺點,可以通過redis管道來優化,一次性發送多個命令給redis執行:
$userIDArray = array(1001, 1002, 1003, 1009);
$pipe = $redis->multi(Redis::PIPELINE);
foreach ($userIDArray as $userID) {
$pipe->hGetAll('counter:user:' . $userID);
}
$replies = $pipe->exec();
print_r($replies);
還有一種方式是在redis上執行lua腳本,前提是你必須要學會寫lua。