在 Redis 中存儲分類樹,通常需要選擇合適的數據結構來表現層級關系。以下是使用 字符串(String) 和 哈希(Hash) 兩種常見方案的舉例說明,結合電商分類場景(如 電子產品 > 手機 > 智能手機 > 品牌
)展開:
方案一:字符串(String)存儲路徑
數據結構設計
- 鍵名:
category:path:{node_id}
- 例如:
category:path:1001
- 例如:
- 值:完整分類路徑(用分隔符連接)
- 例如:
電子產品>手機>智能手機>蘋果
- 例如:
操作示例
-
添加分類
# 添加根節點(電子產品) SET category:path:1001 "電子產品"# 添加子節點(手機) SET category:path:1002 "電子產品>手機"# 添加葉節點(蘋果) SET category:path:1005 "電子產品>手機>智能手機>蘋果"
-
查詢分類
# 獲取蘋果的完整路徑 GET category:path:1005 # 返回 "電子產品>手機>智能手機>蘋果"# 查詢所有手機相關分類(通過模式匹配) KEYS category:path:1002* # 返回匹配的鍵(需謹慎使用 KEYS 命令)
-
刪除分類
# 刪除蘋果分類(需同時處理其子節點,此處假設無子節點) DEL category:path:1005
-
遍歷分類樹
# 查詢所有根節點(假設根節點路徑不含 ">") SCAN 0 MATCH category:path:* COUNT 100 # 遍歷所有鍵,過濾不含 ">" 的值
優缺點
- 優點:實現簡單,路徑直觀。
- 缺點:
- 查詢子節點需解析路徑字符串(如通過
STRSPLIT
拆分>
)。 - 更新路徑需級聯修改所有子節點(如重命名“手機”為“移動設備”,需更新所有子節點路徑)。
- 查詢子節點需解析路徑字符串(如通過
方案二:哈希(Hash)存儲層級關系
數據結構設計
- 鍵名:
category:node:{node_id}
- 字段:
name
: 分類名稱parent_id
: 父節點 ID(根節點為0
或nil
)level
: 層級深度(可選)
操作示例
-
添加分類
# 添加根節點(電子產品) HSET category:node:1001 name "電子產品" parent_id 0 level 1# 添加子節點(手機) HSET category:node:1002 name "手機" parent_id 1001 level 2# 添加葉節點(蘋果) HSET category:node:1005 name "蘋果" parent_id 1003 level 4
-
查詢分類
# 獲取蘋果的父節點 ID HGET category:node:1005 parent_id # 返回 "1003"# 查詢手機的所有子節點(需遞歸查詢) # 步驟1:找到手機的節點ID(假設為1002) # 步驟2:查詢所有 parent_id=1002 的節點 SCAN 0 MATCH category:node:* COUNT 100 | xargs -I{} redis-cli HGET {} parent_id | grep 1002
-
刪除分類
# 刪除蘋果分類(需同時刪除其子節點,此處假設無子節點) DEL category:node:1005
-
遍歷分類樹
# 查詢所有根節點(parent_id=0) SCAN 0 MATCH category:node:* COUNT 100 | xargs -I{} redis-cli HGET {} parent_id | grep 0
優缺點
- 優點:
- 結構清晰,便于查詢父子關系。
- 修改分類名稱無需級聯更新子節點(僅修改當前節點
name
字段)。
- 缺點:
- 查詢子節點需遞歸或多次訪問 Redis。
- 需維護
parent_id
和level
字段,增加數據一致性風險。
方案三:優化方案 - 路徑枚舉 + 哈希
結合兩種方案優點,使用 哈希存儲屬性 + 字符串存儲路徑枚舉:
數據結構設計
- 哈希:
category:node:{node_id}
存儲name
和parent_id
。 - 字符串:
category:path:{node_id}
存儲完整路徑(如電子產品>手機>蘋果
)。
操作示例
-
添加分類
# 添加蘋果分類 HSET category:node:1005 name "蘋果" parent_id 1003 SET category:path:1005 "電子產品>手機>智能手機>蘋果"
-
查詢路徑
# 獲取蘋果的完整路徑 GET category:path:1005
-
查詢子節點
# 通過哈希查詢父節點 ID,再通過路徑枚舉匹配子節點 HGET category:node:1003 parent_id # 假設1003是智能手機的節點ID KEYS category:path:1003* # 匹配所有以智能手機路徑開頭的節點
優缺點
- 優點:
- 路徑查詢高效(直接通過字符串匹配)。
- 屬性修改靈活(通過哈希單獨更新)。
- 缺點:
- 數據冗余(同時存儲哈希和字符串)。
- 需維護兩種數據結構的一致性。
方案四:使用 RedisJSON 存儲樹形結構
如果 Redis 版本支持 RedisJSON 模塊,可直接存儲 JSON 樹形結構:
數據結構設計
- 鍵名:
category:tree
- 值:JSON 對象,例如:
{"id": 1001,"name": "電子產品","children": [{"id": 1002,"name": "手機","children": [{"id": 1003,"name": "智能手機","children": [{"id": 1004, "name": "蘋果"},{"id": 1005, "name": "華為"}]}]}] }
操作示例
-
添加分類
JSON.SET category:tree . '{"id":1001,"name":"電子產品","children":[{"id":1002,"name":"手機","children":[]}]}'
-
查詢子節點
JSON.GET category:tree $.children[0].children # 返回手機分類的子節點
-
更新分類
JSON.SET category:tree $.children[0].children[0].name '移動設備' # 重命名手機為移動設備
優缺點
- 優點:
- 結構自然,支持嵌套查詢。
- 減少數據冗余(單個鍵存儲完整樹)。
- 缺點:
- 需 Redis 版本 ≥ 4.0 且安裝 RedisJSON 模塊。
- 修改深層節點需精確 JSON 路徑(如
$.children[0].children[0].name
)。
總結與選型建議
方案 | 適用場景 | 優點 | 缺點 |
---|---|---|---|
字符串路徑 | 簡單分類樹,查詢需求少 | 實現簡單,路徑直觀 | 更新路徑需級聯修改 |
哈希層級 | 需頻繁查詢父子關系 | 結構清晰,修改靈活 | 查詢子節點需遞歸 |
路徑枚舉+哈希 | 平衡路徑查詢與屬性修改 | 路徑查詢高效,屬性修改靈活 | 數據冗余,維護復雜 |
RedisJSON 樹形 | 復雜分類樹,需嵌套查詢 | 結構自然,支持復雜操作 | 依賴模塊,路徑操作復雜 |
推薦方案:
- 簡單場景:使用 字符串路徑 或 哈希層級。
- 中等復雜度:使用 路徑枚舉+哈希。
- 復雜場景:使用 RedisJSON 樹形(需確保環境支持)。