社交產品業務裏有不少統計計數的功能,好比:java
用戶: 總點贊數,關注數,粉絲數
redis
帖子: 點贊數,評論數,熱度
性能
消息: 已讀,未讀,紅點消息數
優化
話題: 閱讀數,帖子數,收藏數lua
實時性要求高
spa
寫的頻率很高
設計
寫的性能對MySQL是一個挑戰code
能夠採用redis來優化高頻率寫入的性能要求。orm
對於每個實體的計數,設計一個hash結構的counter:ip
//用戶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 //話題中帖子數
相似這種計數器,隨着產品功能的增長,也會愈來愈多,好比回覆數,踩數,轉發數什麼的。
/獲取指定userID的全部計數器HGETALL counter:user:{userID} //獲取指定userID的指定計數器HMGET counter:user:{userID} praiseCnt hostCnt //指定userID點贊數+1HINCRBY counter:user:{userID} praiseCnt
缺點:
這樣設計,若是要批量查詢多個用戶的數據,就比較麻煩,例如一次要查指定20個userID的計數器?只能循環執行 HGETALL counter:user:{userID}。
優勢:
以實體聚合數據,方便數據管理
方案二是用來解決方案一的缺點的,依然是採用hash,結構設計是這樣的:
counter:user:praiseCnt -> userID_1001: 100 -> userID_1002: 200 -> userID_1003: 332 -> userID_1004: 123 ....... -> userID_9999: 213counter:user:hostCnt -> userID_1001: 10 -> userID_1002: 290 -> userID_1003: 322 -> userID_1004: 143 ....... -> userID_9999: 213counter: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管道來優化,一次性發送多個命令給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。