购物车数据应采用Redis+本地缓存+MySQL分层存储:Redis为主存(Hash结构,支持原子操作与自动过期),本地缓存加速读热点,MySQL异步持久化兜底并支持乐观锁防超卖。

购物车模块是电商系统中用户行为最密集、并发要求较高、且与库存、订单强耦合的核心模块。设计时需兼顾实时性、一致性、可扩展性和用户体验,不能只考虑“存商品”,更要应对秒杀、库存扣减、跨端同步、失效清理等真实场景。
购物车数据应存在哪?Redis + 本地缓存 + 持久化兜底
纯数据库(如MySQL)扛不住高频读写,纯内存又怕服务重启丢数据。主流方案是分层存储:
- 主存储用 Redis(Hash 或 JSON String):以 userId 为 key,商品 skuId 为 field,value 存数量、选中状态、加入时间等。支持原子增减(INCRBY)、批量读(HGETALL)、过期自动清理(EXPIRE 30d)。
- 本地缓存(Caffeine/Guava)做读热点加速:对高频访问的购物车摘要(如商品数、总价)加一层 L1 缓存,TTL 控制在 10–30 秒,避免 Redis 穿透。
- MySQL 作为最终一致的持久化备份:用户登录/提交订单/定时任务触发时,将 Redis 中有效数据异步落库。表结构建议含 user_id、sku_id、quantity、is_selected、create_time、update_time、version(用于乐观锁防超卖)。
添加/修改/删除商品必须保证幂等与原子性
前端重复点击、网络重试、多端操作(App + Web)都可能导致脏写。关键做法:
- 所有写操作带唯一请求 ID(如 traceId 或业务生成的 cartOpId),服务端用 Redis SETNX 记录已处理 ID,5 分钟内拒绝重复请求。
- 数量变更用 Lua 脚本封装:先查当前值 → 判断库存余量 → 更新数量 → 返回新总数。整个过程在 Redis 单线程中执行,杜绝竞态。
- 删除商品时,不直接 DEL key,而是用 HDEL + 设置空值过期(防止缓存穿透),或统一走“逻辑删除”字段(is_deleted=1)。
购物车与库存联动:预占库存 + 异步校验
不能等到下单才查库存(体验差+超卖风险高),也不能实时扣减(影响库存系统吞吐)。折中方案:
立即学习“Java免费学习笔记(深入)”;
- 用户加入购物车时,调用库存服务预占(freeze)对应 SKU 的数量(如冻结 2 件),返回冻结成功/失败。冻结记录有 TTL(如 30 分钟),超时自动释放。
- 购物车页面展示时,实时调用库存接口查询“可用库存 = 总库存 - 已售 - 已冻结”,动态禁用超量添加按钮,并提示“仅剩 X 件”。
- 结算页加载时,再次批量校验所有商品的冻结状态和实时库存,任一失效则前端高亮提醒,后端返回可结算子集。
合并登录态:未登录购物车如何无感迁入?
用户浏览时加购(Cookie/Local Storage 存临时 cartId),登录后需合并。常见策略:
- 前端在登录成功响应头中携带原临时 cartId;后端查出该临时购物车商品,逐条尝试加入用户正式购物车。
- 冲突处理:若同一 sku 已存在,则数量相加;若新商品价格/规格已变更,按最新快照覆盖并标记“已更新”。
- 合并完成后,立即清空临时购物车(删 Cookie / 失效 Redis 临时 key),并广播消息通知其他端(如 Web 端)刷新。
基本上就这些。购物车看着简单,但细节全在边界里——比如“删除商品后是否保留历史记录”、“优惠券怎么绑定到购物车子项”、“跨店铺购物车如何隔离”……实际落地得结合业务权衡。不复杂,但容易忽略。










