哈希算法题目:设计一个基于哈希的分布式购物车系统(支持高并发和容错)
字数 1048 2025-11-04 08:32:53
哈希算法题目:设计一个基于哈希的分布式购物车系统(支持高并发和容错)
题目描述
设计一个分布式购物车系统,要求支持以下操作:
- 添加商品:用户将商品加入购物车,需记录商品ID、数量、加入时间。
- 删除商品:从购物车移除指定商品。
- 修改数量:更新购物车中某商品的数量。
- 查询购物车:返回当前用户购物车中的所有商品及总价。
分布式要求:
- 支持高并发(同一用户可能同时操作购物车)。
- 数据需分片存储,避免单点故障。
- 保证最终一致性(允许短暂数据延迟,但最终结果正确)。
解题步骤
步骤1:确定数据模型和哈希分片策略
数据模型:
每个用户的购物车数据可表示为键值对,例如:
- 键(Key):
user_id:cart(如user123:cart) - 值(Value):商品列表的序列化数据,如 JSON 格式:
{ "items": { "product_id_1": {"quantity": 2, "added_time": "2023-10-01T10:00:00Z"}, "product_id_2": {"quantity": 1, "added_time": "2023-10-01T10:05:00Z"} }, "version": 3 // 用于并发控制 }
分片策略:
- 使用一致性哈希(Consistent Hashing)将用户数据分布到多个存储节点(如 Redis 集群)。
- 优点:节点扩容或缩容时,仅影响少量数据迁移。
- 哈希函数:对用户ID(如
user123)计算哈希值(如 SHA-256),映射到哈希环上的位置,决定数据存储节点。
步骤2:处理并发冲突
问题:若用户同时发起多次操作(如添加和修改商品),直接覆盖写入可能导致数据丢失。
解决方案:
- 乐观锁(Optimistic Locking):
- 每次读取购物车时记录版本号(如
version字段)。 - 写入前检查版本号是否变化:若变化则拒绝操作,提示用户重试。
- 每次读取购物车时记录版本号(如
- 示例流程:
# 伪代码:更新购物车商品数量 def update_quantity(user_id, product_id, new_quantity): key = f"{user_id}:cart" while True: cart_data = redis.get(key) # 读取数据及版本号 if cart_data is None: # 购物车为空则初始化 cart_data = {"items": {}, "version": 0} else: cart_data = json.loads(cart_data) # 更新商品数量 cart_data["items"][product_id] = { "quantity": new_quantity, "added_time": cart_data["items"].get(product_id, {}).get("added_time", current_time()) } cart_data["version"] += 1 # 尝试写入(带版本检查) if redis.set_if_version_match(key, json.dumps(cart_data), cart_data["version"] - 1): break # 写入成功 else: continue # 版本冲突,重试
步骤3:实现容错和最终一致性
容错设计:
- 数据副本:每个分片存储多个副本(如主从复制),主节点故障时自动切换至从节点。
- 故障检测:通过心跳机制监控节点状态,异常节点从哈希环中移除。
最终一致性保证:
- 使用消息队列(如 Kafka)异步处理跨分片操作(如合并用户多设备购物车)。
- 示例:用户手机和网页端同时操作购物车时,操作按时间顺序排队执行,避免冲突。
步骤4:优化性能
- 本地缓存:频繁读取的购物车数据缓存在用户本地(如浏览器缓存),减少服务端压力。
- 批量操作:支持批量添加/删除商品,减少网络请求次数。
- 惰性计算总价:总价仅在查询时计算,避免每次更新都计算。
总结
本题通过一致性哈希分片解决数据分布问题,乐观锁控制并发冲突,副本机制和消息队列实现容错和最终一致性。实际系统中还需结合具体存储引擎(如 Redis Cluster)和监控工具完善细节。