🚀 系统设计实战 19:设计购物车系统(Shopping Cart)摘要:购物车是电商系统的流量入口,涉及离线/在线购物车合并、多端同步、高并发读写、价格计算等核心问题。本文深入剖析 Redis Hash 存储方案、离线合并策略、购物车结算优化,并提供完整的 Java 实现。🎯 场景引入你往购物车加了 3 件商品,关掉 App 第二天打开还在。换到电脑端,购物车也同步了。核心挑战:多端同步:手机、电脑、iPad 上的购物车如何实时一致?库存联动:购物车里的商品下架了、涨价了,怎么处理?高并发:双十一前夜,上亿用户疯狂加购,系统如何扛住?🎯 场景引入你打开手机准备使用设计购物车系统服务。看似简单的操作背后,系统面临三大核心挑战:挑战一:高并发——如何在百万级 QPS 下保持低延迟?挑战二:高可用——如何在节点故障时保证服务不中断?挑战三:数据一致性——如何在分布式环境下保证数据正确?一、问题背景1.1 核心挑战购物车的特点: 1. 读写极高:用户频繁添加、修改数量、删除商品 2. 临时性:大部分商品会被删除或遗忘,只有少数转化为订单 3. 多端同步:手机加购,电脑可见 4. 合并问题:未登录加购 → 登录后合并 核心矛盾: 高频读写 vs 数据持久化 未登录体验 vs 登录后同步 实时价格 vs 缓存性能1.2 核心需求需求说明添加商品加入购物车,支持 SKU 选择修改数量增减商品数量删除商品移除购物车中的商品查看列表展示商品列表、实时价格、总价离线合并登录后合并未登录时的购物车多端同步手机、电脑、平板数据一致结算选择商品生成订单1.3 容量估算指标数值DAU5000 万购物车操作 QPS~10 万峰值 QPS(大促)~100 万平均每人购物车商品数~15购物车数据总量~7.5 亿条单条数据大小~200 字节总存储量~150 GB二、整体架构┌─────────────────────────────────────────────────────────────┐ │ 客户端 │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ 未登录购物车 │ │ 已登录购物车 │ │ 结算页 │ │ │ │ (LocalStorage)│ │ (服务端) │ │ │ │ │ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │ └─────────┼─────────────────┼────────────────────┼─────────────┘ │ │ │ ▼ ▼ ▼ ┌───────────────────────────────────────────────────────────┐ │ API Gateway │ └───────────────────────┬───────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────┐ │ Cart Service(购物车服务) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │ │ │ 增删改查 │ │ 合并服务 │ │ 价格计算 │ │ 结算服务 │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬──────┘ │ └───────┼─────────────┼────────────┼──────────────┼─────────┘ │ │ │ │ ▼ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────┐ │ 存储层 │ │ ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌──────────┐ │ │ │ Redis │ │ MySQL │ │ 商品服务 │ │ 促销服务 │ │ │ │ (主存储) │ │(持久化) │ │ (价格/库存)│ │ (优惠) │ │ │ └──────────┘ └──────────┘ └────────────┘ └──────────┘ │ └─────────────────────────────────────────────────────────┘三、数据模型设计// 时间复杂度:O(N),空间复杂度:O(1)/** * 购物车项 */publicclassCartItem{privateLonguserId;privateLongskuId;privateIntegerquantity;privateBooleanselected;// 是否选中(结算用)privateLongaddTime;// 加入时间// 以下字段不存储,查询时实时获取privatetransientStringskuName;privatetransientLongprice;// 实时价格(分)privatetransientIntegerstock;// 实时库存privatetransientStringimageUrl;}/** * 购物车(Redis Hash 结构) * Key: cart:{userId} * Field: {skuId} * Value: JSON {quantity, selected, addTime} */四、核心模块实现4.1 购物车 CRUD 服务@ServicepublicclassCartService{@AutowiredprivateStringRedisTemplateredisTemplate;@AutowiredprivateProductServiceproductService;privatestaticfinalintMAX_CART_SIZE=100;privatestaticfinalStringCART_KEY_PREFIX="cart:";/** * 添加商品到购物车 */publicCartItemaddItem(LonguserId,LongskuId,intquantity){StringcartKey=CART_KEY_PREFIX+userId;// 1. 检查购物车容量Longsize=redisTemplate.opsForHash().size(cartKey);if(size!=nullsize=MAX_CART_SIZE){thrownewCartFullException("购物车已满,最多 "+MAX_CART_SIZE+" 件商品");}// 2. 检查商品是否存在且有库存ProductInfoproduct=productService.getProduct(skuId);if(product==null||product.getStock()=0){thrownewProductUnavailableException("商品不存在或已售罄");}// 3. 如果已在购物车中,数量累加Stringexisting=(String)redisTemplate.opsForHash().get(cartKey,skuId.toString());CartItemDatadata;if(existing!=null){data=JsonUtils.fromJson(existing,CartItemData.class);data.setQuantity(Math.min(data.getQuantity()+quantity,99));// 最多 99 件}else{data=newCartItemData(quantity,true,System.currentTimeMillis());}// 4. 写入 RedisredisTemplate.opsForHash().put(cartKey,skuId.toString(),JsonUtils