淘宝购物车之所以能实现“永不丢失”且跨设备同步,核心在于它彻底抛弃了单机内存存储,采用了无状态服务 + 分布式缓存(Tair/Redis)的架构。这种设计让购物车数据不再依赖某台具体的服务器,而是存储在独立的共享集群中。
下面为你深度解析这套架构的核心原理,并附上可落地的 Python 模拟实现。
一、 淘宝购物车架构深度解析
1. 为什么单机 Session 会“丢失”?
传统的购物车数据存在服务器内存(如 Flask/Django 的
session)中,这会导致两个致命问题:- 扩容即丢失:当服务器重启或扩容新增节点时,内存数据清空,购物车就没了。
- 无法同步:手机和电脑访问的是不同的后端服务器,数据无法互通。
2. 淘宝的解决方案:分布式会话
淘宝通过以下三层架构实现数据持久化:
组件 | 角色 | 淘宝实现方案 |
|---|---|---|
接入层 | 身份识别 | 通过 Cookie 或 Token 携带唯一标识(如 session_id或 user_id) |
逻辑层 | 无状态服务 | 购物车服务集群,不存储任何用户状态,只处理逻辑 |
存储层 | 数据持久化 | Tair/Redis 集群(阿里云自研的高性能 KV 存储) |
核心流程:
- 用户访问时,携带
token或user_id。 - 负载均衡将请求随机转发给任意一台购物车服务节点。
- 服务节点根据
user_id去 Redis 集群中读取/写入数据。 - 数据永远在 Redis 里,服务器宕机或重启完全不影响数据安全。
3. 匿名购物车与登录合并策略
这也是淘宝体验好的关键细节:
- 匿名状态:使用浏览器指纹或临时 ID 作为 Key,存入 Redis(设置较短过期时间)。
- 登录瞬间:系统对比“匿名购物车”和“用户购物车”,智能合并冲突商品,然后删除临时数据。
二、 Python 实现分布式购物车(Flask + Redis)
以下代码模拟了淘宝购物车的核心架构,你可以直接运行体验。
1. 环境准备
pip install flask redis
2. 核心代码实现
# app.py
import json
import uuid
from flask import Flask, request, jsonify, make_response
import redis
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
# 连接Redis集群(这里用单节点模拟)
redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
def get_user_cart_key(user_id):
"""生成购物车在Redis中的存储Key"""
return f"shopping_cart:{user_id}"
def get_or_create_user_id():
"""
获取用户ID:优先从Cookie取,没有则生成匿名ID
模拟淘宝的匿名->登录合并机制
"""
user_id = request.cookies.get('user_id')
if not user_id:
# 生成匿名用户ID(实际场景会结合浏览器指纹)
user_id = f"anonymous_{uuid.uuid4().hex}"
return user_id
@app.route('/cart/add', methods=['POST'])
def add_to_cart():
"""添加商品到购物车(模仿淘宝的增量更新)"""
data = request.json
product_id = data.get('product_id')
quantity = data.get('quantity', 1)
# 1. 获取用户身份
user_id = get_or_create_user_id()
cart_key = get_user_cart_key(user_id)
# 2. 使用Redis Hash存储(Key: cart:123, Field: product_id, Value: quantity)
# 命令:HSET cart:user123 6688 2
redis_client.hset(cart_key, product_id, quantity)
# 3. 设置过期时间(匿名用户7天,登录用户永久)
if user_id.startswith('anonymous'):
redis_client.expire(cart_key, 7 * 24 * 60 * 60) # 7天过期
resp = make_response(jsonify({"code": 0, "msg": "添加成功"}))
resp.set_cookie('user_id', user_id, max_age=365 * 24 * 60 * 60)
return resp
@app.route('/cart/list', methods=['GET'])
def get_cart():
"""获取购物车列表(支持跨设备同步)"""
user_id = get_or_create_user_id()
cart_key = get_user_cart_key(user_id)
# 一次性获取该用户购物车所有商品 HGETALL cart:user123
cart_data = redis_client.hgetall(cart_key)
# 组装商品详情(实际业务会去商品服务查询价格、库存)
items = []
for pid, qty in cart_data.items():
items.append({
"product_id": pid,
"quantity": int(qty),
"title": f"模拟商品{pid}",
"price": 99.99
})
return jsonify({
"user_id": user_id,
"items": items,
"total": len(items)
})
@app.route('/cart/merge', methods=['POST'])
def merge_cart():
"""登录后合并匿名购物车(淘宝核心逻辑)"""
anonymous_id = request.cookies.get('user_id')
login_id = request.json.get('login_id') # 假设登录后传入
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
if not anonymous_id or not login_id:
return jsonify({"code": 1, "msg": "参数错误"})
anon_key = get_user_cart_key(anonymous_id)
login_key = get_user_cart_key(login_id)
# 1. 获取匿名购物车数据
anon_items = redis_client.hgetall(anon_key)
# 2. 合并到登录账户(这里采用“登录账户优先”策略)
for pid, qty in anon_items.items():
# 如果登录账户没有该商品,则添加
if not redis_client.hexists(login_key, pid):
redis_client.hset(login_key, pid, qty)
# 3. 删除匿名购物车
redis_client.delete(anon_key)
resp = make_response(jsonify({"code": 0, "msg": "合并成功"}))
resp.set_cookie('user_id', login_id, max_age=365 * 24 * 60 * 60)
return resp
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)3. 测试命令
启动服务后,使用以下命令测试:
# 添加商品
curl -X POST http://127.0.0.1:5000/cart/add \
-H "Content-Type: application/json" \
-d '{"product_id": "6688", "quantity": 2}' \
-c cookies.txt
# 查看购物车(会自动携带Cookie)
curl http://127.0.0.1:5000/cart/list -b cookies.txt三、 生产级架构进阶
1. Redis 数据结构优化
淘宝不会将整个购物车存为一个 JSON 字符串(性能差),而是使用 Hash 结构:
- Key:
cart:{user_id} - Field:
{sku_id}(商品唯一标识) - Value:
{quantity}(数量)
这样可以对单个商品进行原子操作(
HINCRBY),无需读取整个列表。2. 高并发与一致性
- 写并发:使用
HSETNX或 Lua 脚本保证在并发添加时数据不错乱。 - 读缓存:虽然 Redis 很快,但在双11级别流量下,淘宝还会在客户端(App/Web)做一层本地缓存,减少服务端压力。
3. 数据分片(Sharding)
当用户量上亿时,单台 Redis 扛不住。淘宝采用一致性哈希算法,将不同用户的购物车数据分布到不同的 Redis 集群节点上。
四、 总结
淘宝购物车“永不丢失”的秘诀:
- 无状态化:应用服务器不存数据,可以随意重启、扩容。
- 集中存储:购物车数据统一存储在 Redis/Tair 集群中。
- ID 贯通:通过
user_id或session_id作为唯一钥匙,打通多端数据。
通过上面的 Python 代码,你可以看到实现一个基础的分布式购物车并不复杂。关键在于将状态(数据)从服务中剥离出来,这是构建任何高可用分布式系统的核心思想。