☰
Redis缓存与购物车系统设计实践
2026/10/31 9:23:15 网站建设 项目流程

1. 菜品与套餐缓存架构设计

在用户量较大的外卖平台中,数据库查询压力主要来自高频访问的菜品展示页面。我们采用Redis作为缓存中间件,设计了两级缓存策略:

1.1 菜品缓存实现方案

菜品数据的特点是分类明确、变更频率中等。我们采用分类维度进行缓存,每个菜品分类对应一个Redis键:

// 构造Redis键示例 String key = "dish_" + categoryId; // 缓存查询逻辑 List<DishVO> cachedData = (List<DishVO>) redisTemplate.opsForValue().get(key); if(cachedData != null && !cachedData.isEmpty()) { return cachedData; }

关键设计要点:

  • 缓存键设计:采用dish_分类ID的命名规则,避免键冲突
  • 缓存雪崩防护:为不同分类设置随机过期时间(30分钟±5分钟随机值)
  • 数据一致性:通过双删策略保证缓存与数据库同步

1.2 缓存更新策略

当菜品数据变更时,需要同步清理对应缓存。我们在DishController中封装了统一的缓存清理方法:

private void cleanCache(String pattern) { Set keys = redisTemplate.keys(pattern); if(keys != null && !keys.isEmpty()) { redisTemplate.delete(keys); } }

具体应用场景:

  • 新增菜品:清理对应分类的缓存
  • 批量删除:清理所有菜品缓存(dish_*)
  • 菜品状态变更:清理所有缓存保证数据强一致

2. Spring Cache在套餐缓存中的应用

2.1 Spring Cache核心注解

套餐数据更适合使用声明式缓存,我们采用Spring Cache + Redis的方案:

<!-- 依赖配置 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

主要注解使用场景:

注解应用场景示例
@Cacheable套餐查询接口@Cacheable(cacheNames="setmealCache", key="#categoryId")
@CacheEvict套餐修改/删除接口@CacheEvict(cacheNames="setmealCache", allEntries=true)

2.2 缓存一致性实践

套餐与菜品不同,其关联数据更多,我们采用更激进的缓存清理策略:

@PostMapping @ApiOperation("新增套餐") @CacheEvict(cacheNames = "setmealCache", allEntries = true) public Result save(@RequestBody SetmealDTO setmealDTO) { // 业务逻辑 }

这种设计虽然会短暂增加数据库压力,但能确保:

  1. 套餐基础信息变更立即生效
  2. 关联的菜品数据变化能被及时感知
  3. 避免复杂的关联缓存维护逻辑

3. 购物车系统实现细节

3.1 数据结构设计

购物车采用关系型数据库存储,核心字段设计:

CREATE TABLE `shopping_cart` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, `image` varchar(255) DEFAULT NULL, `user_id` bigint NOT NULL, `dish_id` bigint DEFAULT NULL, `setmeal_id` bigint DEFAULT NULL, `dish_flavor` varchar(50) DEFAULT NULL, `number` int NOT NULL DEFAULT '1', `amount` decimal(10,2) NOT NULL, `create_time` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

设计特点:

  • 用户维度分区:通过user_id关联用户
  • 商品类型区分:dish_id和setmeal_id互斥
  • 数量合并:相同商品多次添加只增加number字段值
  • 冗余字段:存储商品快照信息,避免商品变更影响历史订单

3.2 核心业务逻辑实现

添加购物车的核心流程:

public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) { // 1. 参数转换 ShoppingCart cart = convertToEntity(shoppingCartDTO); // 2. 查询是否已存在 List<ShoppingCart> existing = queryExistingCart(cart); if(!existing.isEmpty()) { // 3. 存在则更新数量 updateCartNumber(existing.get(0)); } else { // 4. 不存在则新增记录 insertNewCart(cart); } }

关键处理逻辑:

  • 线程安全:通过数据库唯一索引防止并发问题
  • 数据校验:检查菜品/套餐是否存在且有效
  • 口味处理:对菜品需要特殊处理口味信息

4. 性能优化与异常处理

4.1 缓存性能优化

针对高并发场景的优化措施:

  1. 缓存预热:定时任务在低峰期预加载热门分类数据
  2. 本地缓存:使用Caffeine作为Redis前置缓存
  3. 批量操作:使用Redis管道技术提升批量删除效率
// 使用管道批量删除 redisTemplate.executePipelined((RedisCallback<Object>) connection -> { for(String key : keys) { connection.del(key.getBytes()); } return null; });

4.2 购物车并发控制

解决购物车并发问题的两种方案:

方案一:乐观锁实现

@Update("UPDATE shopping_cart SET number = #{number}, version = version + 1 WHERE id = #{id} AND version = #{version}") int optimisticUpdate(ShoppingCart cart);

方案二:分布式锁实现

public void safeAddCart(ShoppingCartDTO dto) { String lockKey = "cart_lock:" + dto.getUserId(); try { // 尝试获取锁 Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS); if(Boolean.TRUE.equals(locked)) { addShoppingCart(dto); } else { throw new BusinessException("操作太频繁"); } } finally { // 释放锁 redisTemplate.delete(lockKey); } }

4.3 异常处理策略

针对不同异常的处理建议:

  1. 缓存穿透:

    • 对空结果进行缓存(设置较短过期时间)
    • 使用布隆过滤器预先校验Key
  2. 缓存雪崩:

    • 差异化过期时间
    • 实现熔断降级机制
  3. 数据不一致:

    • 采用延迟双删策略
    • 通过消息队列实现最终一致性

5. 实际开发中的经验总结

5.1 缓存使用注意事项

  1. 键设计规范:

    • 使用冒号分层(如dish:category:1)
    • 避免过长的键名
    • 统一大小写规范
  2. 序列化选择:

    • 优先使用JSON序列化
    • 避免Java原生序列化带来的兼容问题
  3. 内存控制:

    • 设置合理的过期时间
    • 监控Redis内存使用情况
    • 对大对象考虑分片存储

5.2 购物车开发心得

  1. 用户体验优化:

    • 合并相同商品条目
    • 保留商品快照信息
    • 实现快速增减数量功能
  2. 性能优化技巧:

    • 批量查询商品信息
    • 异步记录操作日志
    • 使用冗余字段减少关联查询
  3. 扩展性考虑:

    • 设计支持多店铺的购物车
    • 预留优惠券计算接口
    • 支持购物车商品收藏功能

在具体实现时,我们发现使用Redis Hash结构存储购物车数据可能更适合大型电商平台,但对于外卖这种轻量级场景,关系型数据库的方案更易于维护和保证事务一致性。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询