购物车数据在Redis中一般用哪种数据结构存储?如果商品属性非常多,Hash和String哪种更好?
在Redis中实现购物车功能,数据结构的选型非常关键。针对你的问题,我分两部分为你详细解答。
一、 购物车数据在Redis中一般用哪种数据结构?
业界最主流、最标准的做法是使用 Hash(哈希)。
为什么是 Hash?
购物车的核心数据模型其实非常简单:“哪个用户 -> 选了哪个商品 -> 买了多少个”。
这个模型与 Redis 的 Hash 结构完美契合:
- Key:
cart:user:{user_id}(代表某个用户的购物车) - Field:
sku_id(代表商品ID) - Value:
quantity(商品的数量,或者包含数量和添加时间的简单JSON字符串)
Hash 结构的优势:
- 增删改查极为方便:
- 添加商品/修改数量:
HSET cart:user:1001 sku:10086 1 - 增加商品数量(如点击“+”号):
HINCRBY cart:user:1001 sku:10086 1 - 删除某个商品:
HDEL cart:user:1001 sku:10086 - 获取购物车所有商品:
HGETALL cart:user:1001 - 获取购物车商品种类数:
HLEN cart:user:1001
- 添加商品/修改数量:
- 性能极高:对单个商品的修改不需要拉取整个购物车的数据,避免了并发修改时的冲突(Race Condition)。
二、 如果商品属性非常多,Hash 和 String 哪种更好?
这里需要先纠正一个架构设计上的常见误区:不要在购物车 Redis 中存储大量的商品属性!
在分布式系统中,正确的做法是“购物车只存关系,不存详情”。
- 购物车只存:
sku_id和数量(最多加一个勾选状态或加入时间)。 - 大量的商品属性(名称、图片、价格、规格参数等)应该存在单独的商品缓存(Product Cache)中。
- 读取时:应用层先从购物车取出
sku_id列表,再通过批量查询(如MGET或管道 Pipeline)从商品缓存中查出详情,在内存中进行拼装。
但如果我们抛开购物车,单从“缓存具有大量属性的商品对象”这个角度来看,String 和 Hash 哪个更好呢?
这取决于你的 数据结构特点 和 业务操作模式:
1. 推荐使用 String (存 JSON) 的场景:
- 数据结构复杂/嵌套:如果商品属性里包含数组、多层对象(例如:
{"tags":["new", "hot"], "specs":{"color":"red", "size":"L"}}),Hash 很难优雅地存储这种嵌套结构,而 JSON 一目了然。 - 总是整体读取:如果每次展示商品,都需要把图片、名字、价格、规格全部展示出来,直接
GET一个 JSON 字符串并在应用层反序列化,效率非常高。 - 属性极少单独修改:商品信息(除了库存)通常是运营后台修改的,修改频率低,且通常是整体覆盖更新。
- 缺点:如果只想修改其中一个字段(比如只改一下价格),需要把整个 JSON 取出来,修改后再重新
SET回去,并发修改时容易相互覆盖(需要配合乐观锁或Lua脚本)。
2. 推荐使用 Hash 的场景:
- 数据是扁平的:属性都是简单的 Key-Value 关系。
- 频繁发生“局部修改”:如果你经常需要只更新商品的某一个属性,而不想拉取其他 50 个无用的属性。使用
HSET product:10086 price 99可以节省大量的网络带宽和序列化开销。 - 需要对某个数值属性做原子加减:比如单独针对商品的“销量”、“浏览量”进行
HINCRBY操作。 - 缺点:如果每次都要获取全部几十个属性,
HGETALL的性能略逊于直接GET一个 String。
3. 终极解决方案:RedisJSON (如果你使用的是较新版本的 Redis)
如果你使用的是 Redis 6.0+ 并安装了 RedisJSON 模块,那么你可以直接以 JSON 格式存储 String,同时又具备 Hash 的局部修改能力。
- 你可以存入复杂的嵌套 JSON。
- 你可以单独获取或修改 JSON 中的某一个深层节点:
JSON.SET product:10086 $.specs.color '"blue"'。
这种方式结合了 String 和 Hash 的所有优点。
总结建议
- 购物车本身:使用 Hash(Key是用户,Field是商品ID,Value是数量/简单JSON)。
- 商品详情缓存:
- 如果你需要高频修改某个具体属性(如库存、浏览量),使用 Hash。
- 如果你只是做商品展示,属性层级复杂且基本是整体读取,使用 String (JSON)。
- 如果环境允许,使用 RedisJSON 是最优解。