电商系统Redis缓存与购物车架构设计实践

发布时间:2026/11/4 21:39:22
电商系统Redis缓存与购物车架构设计实践

1. 项目背景与需求分析

烘焙坊电商系统的缓存模块和购物车功能是提升用户体验的关键组件。在高峰期,我们的系统监测到商品详情页的QPS达到1200+,数据库负载经常超过75%,这直接影响了用户浏览商品和下单的流畅度。同时,未登录用户的购物车数据丢失问题导致30%的潜在客户流失。

重要提示:缓存设计必须考虑数据一致性和雪崩效应,特别是在促销活动期间流量激增的场景下。

2. 技术选型与架构设计

2.1 缓存方案对比

我们对比了三种主流方案:

  1. 本地缓存(Caffeine):访问速度最快(<1ms)但集群环境下数据不一致
  2. Redis单机版:读写性能约5万QPS,存在单点故障风险
  3. Redis哨兵集群:可用性达99.95%,性能约8万QPS

最终选择Redis哨兵集群方案,主要考虑因素:

  • 数据一致性要求:购物车需要跨会话持久化
  • 容灾需求:必须保证双十一期间服务可用
  • 性能指标:预期峰值QPS 2万+
// 哨兵配置示例 @Bean public RedisConnectionFactory redisConnectionFactory() { SentinelConfiguration config = new SentinelConfiguration() .master("baking-master") .sentinel("sentinel1", 26379) .sentinel("sentinel2", 26380); return new JedisConnectionFactory(config); }

2.2 购物车数据结构设计

采用Hash结构存储购物车数据:

  • Key: "cart:{userId}"
  • Field: 商品SKU
  • Value: JSON格式包含{数量, 加入时间, 选中状态}
HSET cart:10086 SKU1001 '{"quantity":2,"selected":true,"addTime":1659321000}'

3. 核心功能实现细节

3.1 多级缓存实现

构建本地缓存+Redis的两级缓存体系:

  1. 本地缓存:存储热点商品(TTL 30秒)
  2. Redis缓存:全量商品数据(TTL 5分钟)
public Product getProduct(Long id) { // 1. 查本地缓存 Product product = localCache.get(id); if (product != null) return product; // 2. 查Redis product = redisTemplate.opsForValue().get("product:"+id); if (product != null) { localCache.put(id, product); return product; } // 3. 查数据库 product = dbQuery(id); redisTemplate.opsForValue().set("product:"+id, product, 5, MINUTES); return product; }

3.2 购物车合并逻辑

处理未登录→登录状态的购物车合并:

graph TD A[获取Cookie购物车] --> B{用户登录?} B -->|否| C[仅操作Cookie] B -->|是| D[合并Redis和Cookie数据] D --> E[去重处理] E --> F[数量叠加策略]

实际开发中发现:合并时要注意商品规格匹配,不同规格的相同商品应视为不同项

4. 性能优化实践

4.1 缓存预热方案

每日凌晨3点执行预热脚本:

  1. 查询未来24小时主推商品
  2. 批量加载到Redis
  3. 设置阶梯式TTL(1-3小时随机)
#!/bin/bash # 预热TOP100商品 redis-cli --eval warmup.lua , 100

4.2 热点Key检测

通过Redis监控发现热点Key模式:

  • 使用redis-cli --hotkeys识别
  • 对product:1234这类热点商品:
    • 增加本地缓存比例
    • 采用缓存标记策略

5. 异常处理与监控

5.1 缓存击穿防护

实现互斥锁方案:

public Product getProductWithLock(Long id) { String lockKey = "lock:product:" + id; try { Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS); if (locked) { Product product = dbQuery(id); redisTemplate.opsForValue().set("product:"+id, product); return product; } Thread.sleep(50); return getProductWithLock(id); } finally { redisTemplate.delete(lockKey); } }

5.2 监控指标配置

Prometheus监控关键指标:

  • 缓存命中率(redis_keyspace_hits/redis_keyspace_misses)
  • 购物车操作耗时(histogram_quantile(0.95, rate(cart_api_duration_seconds_bucket[1m])))

6. 踩坑实录

  1. 缓存雪崩问题:
  • 现象:大量商品缓存同时失效导致DB压力飙升
  • 解决:采用随机TTL(基础值±20%浮动)
  1. 购物车并发修改:
  • 现象:商品数量出现负值
  • 解决:改用Lua脚本保证原子性
-- 购物车数量修改脚本 local key = KEYS[1] local field = ARGV[1] local delta = tonumber(ARGV[2]) local current = tonumber(redis.call('HGET', key, field)) or 0 local newVal = math.max(0, current + delta) redis.call('HSET', key, field, newVal) return newVal
  1. 内存泄漏预警:
  • 发现:Redis内存每周增长5%
  • 原因:未设置购物车数据过期时间
  • 修复:对30天未更新的购物车设置TTL

7. 扩展思考

  1. 未来可引入RedisJSON模块处理复杂购物车数据结构
  2. 考虑将购物车持久化到MySQL的归档表用于数据分析
  3. 探索RedisGraph实现商品推荐功能

我在实际部署中发现,当Redis内存使用超过70%时,响应时间会出现明显波动。建议设置内存警戒线为60%,并配置自动告警规则。另外,购物车合并逻辑中的规格匹配算法经过三次迭代,最终采用"商品ID+规格ID"的联合匹配方式,准确率从82%提升到99.6%。