针对 10 万 SKU 的商超场景(含 集团SPU、集团 SKU、门店 SKU 三级关联),以下从Redis 数据类型选择、优缺点、变更处理、Key 数量、内存占用五个维度,设计 3 套完整方案并搭配详细示例:

1. 基础前提与业务基数定义

先明确核心数据量(避免后续计算模糊):

  • 集团 SKU 总量:10 万(用户明确)
  • SPU 总量:按 1 个 SPU 对应 5 个 SKU 计算 → 2 万
  • 门店数量:假设 100 家(每家门店需存储全部 10 万 SKU 的门店级配置,如价格、库存)
  • 门店 SKU 总量:100 家 × 10 万 SKU / 门店 = 1 亿(核心内存占用痛点)

2. 方案一:Hash 类型(单实体单 Hash Key)

2.1. 设计思路

用 Redis Hash 类型 存储单个实体(SPU / 集团 SKU / 门店 SKU)的属性,每个实体对应 1 个 Hash Key,Hash 的「字段 = 属性名」「值 = 属性值」,通过 Key 前缀区分实体类型,避免冲突。

Key 设计规则

实体类型Key 格式说明
SPU spu:{spu_id} spu:1001(1001 为 SPU 唯一 ID)
集团 SKU group_sku:{group_sku_id} group_sku:20001(20001 为集团 SKU ID)
门店 SKU store_sku:{store_id}:{group_sku_id} store_sku:301:20001(301 为门店 ID)

Value 结构(Hash 字段示例)

  • SPU:spu:1001 的 Hash 字段
    id=1001, name=可口可乐经典款500ml, category=饮料-碳酸饮料, brand=可口可乐, create_time=2024-01-01
  • 集团 SKU:group_sku:20001 的 Hash 字段
    id=20001, spu_id=1001, spec=500ml/瓶, barcode=6901234567890, weight=0.5, status=1(status=1 = 启用)
  • 门店 SKU:store_sku:301:20001 的 Hash 字段
    store_id=301, group_sku_id=20001, sale_price=3.5, stock=120, shelf_code=A1-05-02, is_on_shelf=1(is_on_shelf=1 = 上架)

2.2. Key 数量计算

总 Key 数 = SPU Key 数 + 集团 SKU Key 数 + 门店 SKU Key 数
= 2 万(SPU) + 10 万(集团 SKU) + 1 亿(门店 SKU:100 家 ×10 万)
= 10012 万(约 1.0012 亿)

2.3. 内存占用分析

Redis Hash 有压缩列表(ziplist)优化:当 Hash 字段数≤512 且单个字段值≤64 字节时,用 ziplist 存储(内存效率极高),超阈值则转哈希表(内存占用上升 30%+)。
按单 Hash 平均 5 个字段、每个字段 + 值约 20 字节计算:

  • 单个 Hash 总占用 = Key 元数据(约 40 字节) + 字段总占用(5×20=100 字节) = 140 字节
  • 1 亿个 Key 总占用 = 1 亿 × 140 字节 = 14000MB ≈ 13.03GB(含少量冗余开销,实际约 15GB)

2.4. 优缺点

优点缺点
1. 小范围变更效率高:改价格仅需HSET store_sku:301:20001 price 3.6,无需修改整个实体 1. 门店 SKU Key 数达 1 亿,Key 元数据占用 4GB(1 亿 ×40 字节),管理成本高
2. 支持部分属性查询:用HMGET spu:1001 name category获取关键属性,减少网络传输 2. 关联查询需额外维护索引(如查 SPU 下所有 SKU 需用 Set 存spu:1001:group_skus
3. 压缩列表优化:内存效率优于 String(JSON)方案 3. 批量删除某门店 SKU 需遍历索引 Set,再批量 DEL,耗时较长

2.5. 变更处理(新增 / 删除 / 变更)

变更类型单条操作示例批量操作示例(用 Pipeline)
新增门店 SKU HSET store_sku:301:20002 store_id 301 sale_price 2.8 stock 80 批量执行 100 条HSET,减少网络往返
删除门店 SKU DEL store_sku:301:20001 1. 先查索引 Set store:301:sku_ids(存该门店所有 SKU ID)
2. 遍历生成store_sku:301:{id}
3. 批量DEL
变更价格(小范围) HSET store_sku:301:20001 sale_price 3.6 1. 查目标 SKU 列表(如某品类)
2. 批量HSET修改价格
批量新增 SPU HMSET spu:1002 id 1002 name 百事可乐... 批量HMSET,一次 Pipeline 处理 500 条

3. 方案二:String 类型(JSON 格式,单实体单 String Key)

3.1. 设计思路

用 Redis String 类型 存储单个实体的完整信息,Value 以JSON 格式序列化(包含所有属性),Key 设计与方案一完全一致(通过前缀区分实体类型),开发无需理解 Hash 字段操作,仅需 JSON 序列化 / 反序列化。

Key 设计规则(同方案一)

  • SPU:spu:{spu_id} → spu:1001
  • 集团 SKU:group_sku:{group_sku_id} → group_sku:20001
  • 门店 SKU:store_sku:{store_id}:{group_sku_id} → store_sku:301:20001

Value 结构(JSON 示例)

  • SPU:spu:1001 的 Value
    {"id":1001,"name":"可口可乐经典款500ml","category":"饮料-碳酸饮料","brand":"可口可乐","create_time":"2024-01-01","spec_desc":"单瓶500ml铝罐装"}
  • 门店 SKU:store_sku:301:20001 的 Value
    {"store_id":301,"group_sku_id":20001,"sale_price":3.5,"promo_price":3.2,"stock":120,"shelf_code":"A1-05-02","is_on_shelf":1,"last_update":"2024-05-20 14:30:00"}

3.2. Key 数量计算

与方案一完全相同:10012 万(约 1.0012 亿)(每个实体对应 1 个 String Key)。

3.3. 内存占用分析

String 类型无压缩列表优化,且 JSON 有额外格式开销(引号、逗号等):

  • 单个实体 JSON 平均长度:约 200 字节(比 Hash 字段总和多 50%,因 JSON 冗余)
  • 单个 String 总占用 = Key 元数据(40 字节) + JSON 长度(200 字节) = 240 字节
  • 1 亿个 Key 总占用 = 1 亿 × 240 字节 = 24000MB ≈ 22.35GB(比方案一高约 70%)

3.4. 优缺点

优点缺点
1. 开发成本低:仅需 JSON 序列化 / 反序列化,跨语言兼容性好 1. 小范围变更效率低:改价格需全量 GET→解析→修改→SET,传输完整数据
2. 全量读取高效:商品详情页需所有属性时,一次 GET 即可 2. 内存占用高:JSON 格式冗余,比方案一高 70%+
3. 无需维护 Hash 字段:适合属性频繁新增的场景(如新增 “促销标签” 直接加 JSON 字段) 3. 不支持部分属性查询:必须全量读取后解析,无用数据传输多

3.5. 变更处理(新增 / 删除 / 变更)

变更类型单条操作示例批量操作示例
新增集团 SKU SET group_sku:20002 '{"id":20002,"spu_id":1001,"spec":"1L/瓶"...}' Pipeline 批量执行SET,一次处理 1000 条
删除 SPU DEL spu:1001 批量DEL目标 SPU Key 列表
变更库存(小范围) 1. GET store_sku:301:20001 → 解析 JSON
2. 修改stock=119 → 重序列化
3. SET回 Redis
1. 批量GET目标 SKU JSON
2. 批量解析修改
3. Pipeline 批量SET
批量新增门店 SKU 循环生成 JSON 字符串,用 Pipeline 批量SET 一次 Pipeline 处理 500 条,避免网络阻塞

4. 方案三:分层聚合 Hash(减少 Key 数,按维度聚合)

4.1. 设计思路

核心解决方案一 / 二的 “Key 数过多” 问题:将多个子实体聚合到一个父 Hash 中(如 “某门店的 10 万 SKU” 拆分为 100 个 Hash 分桶),用 “聚合维度 + 分桶” 控制单个 Hash 的字段数(避免超 ziplist 阈值),大幅减少 Key 数量。

Key 设计规则(核心:聚合 + 分桶)

实体类型Key 格式说明
门店 SKU 聚合 store_sku_agg:{store_id}:{bucket_id} store_sku_agg:301:1(301 门店,1 号分桶),每个分桶存 1000 个门店 SKU
SPU 聚合 spu_agg:{spu_id} Hash 字段含 SPU 基础属性 + 旗下集团 SKU(字段 = 集团 SKU ID,值 = SKU 属性)
集团 SKU 独立存储(可选) group_sku:{group_sku_id} String 格式存完整属性,避免 SPU 聚合过大

分桶逻辑

  • 分桶依据:bucket_id = group_sku_id % 100(100 个分桶 / 门店,每个分桶存 1000 个 SKU:10 万 / 100=1000)
  • 目的:单个 Hash 字段数≤1000(控制在 ziplist 优化范围内,若超 512 字段转哈希表,内存效率下降)

Value 结构(聚合 Hash 示例)

  • 门店 SKU 聚合分桶:store_sku_agg:301:1(301 门店,1 号分桶)的 Hash 字段
    20001='{"sale_price":3.5,"stock":120,"shelf_code":"A1-05-02"}', 20002='{"sale_price":2.8,"stock":80,"shelf_code":"A1-05-03"}', ...(共 1000 个字段,对应集团 SKU ID 20001-21000)
  • SPU 聚合:spu_agg:1001 的 Hash 字段
    id=1001, name=可口可乐经典款500ml, 20001='{"spec":"500ml/瓶","barcode":"6901234567890"}', 20002='{"spec":"1L/瓶","barcode":"6901234567906"}'

4.2. Key 数量计算

总 Key 数 = 门店 SKU 聚合 Key 数 + SPU 聚合 Key 数 + 集团 SKU 独立 Key 数(可选)
=(100 家门店 × 100 分桶 / 门店) + 2 万(SPU) + 10 万(集团 SKU)
= 1 万 + 2 万 + 10 万 = 13 万(比方案一 / 二减少 99.87%)

4.3. 内存占用分析

  • 单个门店分桶 Hash(1000 个字段):
    单个字段 = 集团 SKU ID(如 “20001”,5 字节) + JSON 值(100 字节) → 105 字节 / 字段
    单个分桶总占用 = Key 元数据(40 字节) + 1000×105 字节 = 105040 字节 ≈ 102.6KB
  • 100 门店 ×100 分桶 = 10000 分桶 → 10000×102.6KB ≈ 1.002GB
  • SPU 聚合(2 万 Key):单个≈690 字节 → 2 万 ×690 字节≈1.32MB
  • 集团 SKU 独立存储(10 万 Key):单个≈140 字节 → 10 万 ×140 字节≈13.3MB
  • 总内存 ≈ 1.02GB(仅为方案一的 7.8%,方案二的 4.6%)

4.4. 优缺点

优点缺点
1. Key 数极少(13 万 vs 1 亿):Key 元数据仅约 5.2MB(13 万 ×40 字节),内存压力极小 1. 分桶逻辑复杂:需计算bucket_id,开发需额外处理分桶映射
2. 批量操作高效:删除某门店 SKU 仅需DEL store_sku_agg:301:*,无需遍历 2. 小范围变更略繁琐:改价格需先算分桶→HGET→解析 JSON→HSET
3. 内存效率极高:总内存仅 1GB 级,适合大门店数场景 3. 单个分桶超 1000 字段时,ziplist 转哈希表,内存效率下降

4.5. 变更处理(新增 / 删除 / 变更)

变更类型单条操作示例批量操作示例
新增门店 SKU 1. 算分桶:20003%100=3 → 桶 3
2. HSET store_sku_agg:301:3 20003 '{"sale_price":4.0,"stock":50}'
1. 按分桶分组目标 SKU
2. 同分数桶用HMSET批量设
3. 不同分桶用 Pipeline
删除门店 SKU 1. 算分桶:20001%100=1 → 桶 1
2. HDEL store_sku_agg:301:1 20001
1. 按分桶分组待删 SKU
2. 批量HDEL各分桶字段
变更促销价(小范围) 1. 算分桶→HGET store_sku_agg:301:1 20001→解析 JSON
2. 修改promo_price=3.0→重序列化
3. HSET
1. 批量算分桶→批量HGET→解析修改
2. 按分桶批量HSET
批量删除门店 DEL store_sku_agg:301:*(通配符删除该门店所有分桶) 直接通配符删除,无需遍历,耗时 < 1 秒

5. 关键问题解答

5.1. Key 多了是否占用大量内存?

  • 是,但核心看 Key 数量级:
    • 亿级 Key(方案一 / 二):Key 元数据约 4GB(1 亿 ×40 字节),占总内存的 30%-40%,且 Redis 遍历 / GC 效率下降;
    • 十万级 Key(方案三):Key 元数据仅 5-10MB,可忽略,内存主要来自 Value,效率极高。
  • 建议:门店数 > 50 家时,必须用方案三(聚合 Hash)控制 Key 数;门店数 < 10 家时,方案一(Hash)内存可接受(约 1.5GB)。

5.2. 方案选择建议

场景推荐方案核心原因
大门店数(>50 家)、内存敏感 方案三(聚合 Hash) Key 数少、内存效率极高,批量操作快
小门店数(<10 家)、变更频繁 方案一(Hash) 小范围变更效率高,无需处理分桶逻辑
开发成本优先、属性频繁新增 方案二(JSON) 仅需 JSON 序列化,跨语言兼容,新增属性无需改结构

6. 总结对比表

方案数据类型总 Key 数(100 门店)总内存(约)小范围变更效率批量操作效率开发成本
方案一(Hash) Hash 1.0012 亿 15GB ★★★★★ ★★☆☆☆
方案二(JSON) String 1.0012 亿 25GB ★★☆☆☆ ★★☆☆☆
方案三(聚合 Hash) Hash+String 13 万 1.02GB ★★★☆☆ ★★★★★
 
 posted on 2025-09-06 14:31  xibuhaohao  阅读(9)  评论(0)    收藏  举报