在眾多軟體應用中,點贊功能幾乎成了所有應用中的“標配”。但實現一個高效的點贊功能並不簡單,尤其是在面對大規模的使用者量和高併發場景時。
今天,我們就從實際需求出發,探索如何利用 Redis 的數據結構來設計一個點贊系統,從而理解 Set
和 Bitmap
數據結構的優缺點。
需求分析
我們設定這樣一個需求場景:在一篇文章的評論下實現點贊功能,每位使用者只能對同一條評論點贊一次,再次點贊則視為“取消點贊”。此外,我們還需要統計每條評論的總點贊數。
這聽上去不復雜,但當需求量級提升,比如使用者量達到千萬級別,系統會需要承擔巨大的資料儲存和高頻的讀寫壓力。爲了滿足高效能和低延遲的需求,我們可以選擇 Redis 來管理點贊資料。
方案一:使用 Redis 的 Set
數據結構
首先考慮使用 Redis 的 Set
數據結構。Set
非常適合儲存一組不重複的元素,因此可以用來記錄每條評論下點讚的使用者 ID。方案設計如下:
儲存設計:每條評論的點贊資料可以儲存為一個 Redis
Set
,鍵格式為comment:likes:{comment_id}
,其中{comment_id}
是評論的唯一標識。點贊操作:當用戶點贊時,將使用者 ID 新增到該評論對應的
Set
中。取消點贊:如果使用者再次點選,則從
Set
中移除使用者 ID。統計總點贊數:直接獲取
Set
的元素數量,即為當前評論的點贊總數。檢查使用者是否點贊過:可以透過
SISMEMBER
指令快速檢查某個使用者 ID 是否存在於該評論的點贊Set
中。
Set 方案的程式碼實現
以下是 PHP 實現點贊、取消點贊和統計點贊數的程式碼示例:
// 點贊介面 function likeComment($userId, $commentId) { $redis = new Redis(); $redis->connect('127.0.0.1', 6379); // 生成唯一鍵 $likeSetKey = "comment:likes:{$commentId}"; // 判斷使用者是否已點贊 if ($redis->sIsMember($likeSetKey, $userId)) { // 已點贊,取消點贊 $redis->sRem($likeSetKey, $userId); $liked = false; } else { // 未點贊,新增點贊 $redis->sAdd($likeSetKey, $userId); $liked = true; } // 獲取當前總點贊數 $totalLikes = $redis->sCard($likeSetKey); // 返回點贊狀態 return [ 'status' => 'success', 'liked' => $liked, 'totalLikes' => $totalLikes ]; }
使用 Set 方案的優缺點
優點:
靈活性高:支援不連續的使用者 ID,適合大多數應用場景。
操作簡單:Redis 原生支援集合操作,查詢、新增、刪除等操作效能較高。
缺點:
儲存空間較大:
Set
中每個使用者 ID 都會佔用儲存空間,隨著點贊使用者增多,Set
的儲存開銷也會增長。
方案二:使用 Redis 的 Bitmap
數據結構
如果使用者 ID 是連續的,比如從 0
開始順序增長,那麼可以使用 Redis 的 Bitmap
數據結構來進一步提升儲存效率。Bitmap
以每個使用者的 ID 作為位(bit)位置,只需 1 位就可以表示每個使用者的點贊狀態,大大節省儲存空間。
儲存設計:每條評論的點贊資料可以儲存為一個
Bitmap
,鍵的格式為comment:likes:{comment_id}
。點贊操作:使用
SETBIT
將使用者的位設定為1
。取消點贊:再次點選則用
SETBIT
將該位設定為0
。統計總點贊數:透過
BITCOUNT
指令統計Bitmap
中位為1
的數量,即為點贊總數。檢查使用者是否點贊過:可以用
GETBIT
查詢指定使用者的點贊狀態。
Bitmap 方案的程式碼實現
以下是使用 Bitmap
實現的 PHP 程式碼示例:
function likeComment($userId, $commentId) { $redis = new Redis(); $redis->connect('127.0.0.1', 6379); // 生成唯一鍵 $likeBitmapKey = "comment:likes:{$commentId}"; // 獲取使用者當前的點贊狀態 $isLiked = $redis->getBit($likeBitmapKey, $userId); if ($isLiked) { // 已點贊,取消點贊 $redis->setBit($likeBitmapKey, $userId, 0); $liked = false; } else { // 未點贊,設定為點贊 $redis->setBit($likeBitmapKey, $userId, 1); $liked = true; } // 獲取當前點贊總數 $totalLikes = $redis->bitCount($likeBitmapKey); return [ 'status' => 'success', 'liked' => $liked, 'totalLikes' => $totalLikes ]; }
使用 Bitmap 方案的優缺點
優點:
儲存效率高:每位只佔 1 bit,適合大量用戶數據的二元狀態儲存,極大節省記憶體。
適合批次統計:透過
BITCOUNT
等命令可以快速統計點贊數量,效能極佳。
缺點:
使用者 ID 需連續:
Bitmap
適合連續的 ID(如從0
到某個上限),對於離散的 ID 或存在大量空位的 ID,不適用。操作複雜性較高:當用戶 ID 離散或不連續時,使用
Bitmap
不僅不節省空間,操作複雜性也會增加。
選擇合適的數據結構
特點 | Redis Set |
Redis Bitmap |
---|---|---|
使用者 ID 分佈 | 適合不連續的 ID | 適合連續的、緊湊的 ID |
儲存空間 | 隨點贊數增長而增大 | 每個使用者點贊狀態佔 1 bit,空間佔用小 |
統計點贊數 | 透過 SCARD 精確統計 |
透過 BITCOUNT 高效統計 |
查詢使用者狀態 | 支援任意使用者 ID 查詢點贊狀態 | 支援按位查詢連續 ID 的狀態 |
適用場景 | 離散使用者 ID,點贊、關注等集合操作 | 連續 ID,批次二元狀態的快速統計 |
總結
在實際專案中,選擇合適的數據結構至關重要。對於點贊功能,如果使用者 ID 是不連續的且規模不大,Set
更靈活、易於使用;而對於使用者 ID 連續的大規模應用,Bitmap
則能極大提升儲存和統計效率。在實際應用中,我們可以根據使用者 ID 分佈、儲存需求和效能要求來選擇最優方案。
其實 Bitmap
這種數據結構更加適合於用作使用者簽到、打卡等場景。
作者:左詩右碼
連結:https://juejin.cn/post/7435894119103905801