切換語言為:簡體

如何用Redis高效實現點贊功能?用Set?還是Bitmap?

  • 爱糖宝
  • 2024-11-12
  • 2035
  • 0
  • 0

在眾多軟體應用中,點贊功能幾乎成了所有應用中的“標配”。但實現一個高效的點贊功能並不簡單,尤其是在面對大規模的使用者量和高併發場景時。

今天,我們就從實際需求出發,探索如何利用 Redis 的數據結構來設計一個點贊系統,從而理解 SetBitmap 數據結構的優缺點。

需求分析

我們設定這樣一個需求場景:在一篇文章的評論下實現點贊功能,每位使用者只能對同一條評論點贊一次,再次點贊則視為“取消點贊”。此外,我們還需要統計每條評論的總點贊數。

這聽上去不復雜,但當需求量級提升,比如使用者量達到千萬級別,系統會需要承擔巨大的資料儲存和高頻的讀寫壓力。爲了滿足高效能和低延遲的需求,我們可以選擇 Redis 來管理點贊資料。

方案一:使用 Redis 的 Set 數據結構

首先考慮使用 Redis 的 Set 數據結構。Set 非常適合儲存一組不重複的元素,因此可以用來記錄每條評論下點讚的使用者 ID。方案設計如下:

  1. 儲存設計:每條評論的點贊資料可以儲存為一個 Redis Set,鍵格式為 comment:likes:{comment_id},其中 {comment_id} 是評論的唯一標識。

  2. 點贊操作:當用戶點贊時,將使用者 ID 新增到該評論對應的 Set 中。

  3. 取消點贊:如果使用者再次點選,則從 Set 中移除使用者 ID。

  4. 統計總點贊數:直接獲取 Set 的元素數量,即為當前評論的點贊總數。

  5. 檢查使用者是否點贊過:可以透過 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 位就可以表示每個使用者的點贊狀態,大大節省儲存空間。

  1. 儲存設計:每條評論的點贊資料可以儲存為一個 Bitmap,鍵的格式為 comment:likes:{comment_id}

  2. 點贊操作:使用 SETBIT 將使用者的位設定為 1

  3. 取消點贊:再次點選則用 SETBIT 將該位設定為 0

  4. 統計總點贊數:透過 BITCOUNT 指令統計 Bitmap 中位為 1 的數量,即為點贊總數。

  5. 檢查使用者是否點贊過:可以用 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

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.