Redis购物车缓存必须带用户标识、用Hash结构、数量变更用HINCRBY、显式设置过期并主动清理;否则易串号、性能差、数据错乱或内存泄漏。

Redis缓存键设计必须带用户标识,否则购物车数据会串号
ThinkPHP 默认不强制区分用户上下文,直接用 cache() 存购物车,很容易所有用户共用一个 key,比如写成 cache('cart', $data) —— 这在并发或多人测试时立刻出问题。正确做法是把用户唯一标识(如 $uid 或 session_id())嵌入 key 中,例如 cache('cart_'.$uid, $data, 3600)。
更稳妥的方式是使用 Redis 原生命令控制,配合 ThinkPHP 的 Cache::store('redis')->handler() 获取原生 Redis 实例,避免框架缓存驱动的 key 冲突风险。注意:若用 think-redis 扩展,其默认前缀(如 think:)也会影响 key 可读性,可在配置中设 prefix 为空或自定义为 cart:。
购物车数据结构选 Hash 而非 String,增删改查才真正高效
有人图省事把整个购物车数组 json_encode() 后塞进 String 类型缓存里,结果每次加商品都要 GET → decode → array_push → encode → SET,网络往返 + 序列化开销大,还容易丢更新(并发写覆盖)。Redis 的 HASH 天然适配购物车:每个商品用 sku_id 当 field,value 存数量或 JSON 字符串(如 {"num":2,"spec":"blue"})。
ThinkPHP 中可通过原生 Redis 实例操作:
立即学习“PHP免费学习笔记(深入)”;
redis()->hSet('cart_'.$uid, $sku_id, json_encode(['num'=>$num, 'spec'=>$spec]));
redis()->hGet('cart_'.$uid, $sku_id);
redis()->hDel('cart_'.$uid, $sku_id);
redis()->hLen('cart_'.$uid);
这样加减商品、查总数、清空都只需单命令,无竞态,也不依赖 PHP 层锁。
数量变更必须用 HINCRBY 避免读写分离导致的脏数据
前端频繁点“+1”“-1”,如果先 hGet 出来再 hSet 回去,遇上 Redis 主从延迟或并发请求,极易出现数量错乱(比如两个请求同时读到 num=1,各自加 1 后都写回 2,实际应为 3)。正确方式是交给 Redis 原子操作:HINCRBY cart_123 sku_456 1。
ThinkPHP 中调用方式:
redis()->hIncrBy('cart_'.$uid, $sku_id, $delta); // $delta 可正可负
// 注意:hIncrBy 只支持整数,若需存浮点数量或复杂结构,改用 hGet/hSet + Lua 脚本保证原子性
另外,HINCRBY 对不存在的 field 默认初始化为 0,无需预判是否存在,但返回值是变更后的数值,可用于前端同步显示。
过期时间不能只靠 cache() 的 ttl 参数,要主动清理失效购物车
cache('cart_'.$uid, $data, 3600) 看似设了 1 小时过期,但 ThinkPHP 的文件/redis 缓存驱动对 TTL 的实现有差异:部分版本在 set 时未透传 expire 到 Redis,或受配置中 default_expire 干扰。最稳的方式是显式调用 expire:
- 写入后立即
redis()->expire('cart_'.$uid, 3600) - 用户登录时检查 key 是否存在,不存在则初始化空 cart;存在则续期
redis()->expire('cart_'.$uid, 3600) - 清空购物车别只删 key,用
redis()->del('cart_'.$uid),避免残留过期 key 占内存
还有一个易忽略点:用户登出或长时间未操作,服务端应主动触发清理,而不是等 Redis 自动过期——因为过期是被动扫描,可能堆积大量僵尸 key。



















