×

淘宝的购物车为什么永不丢失?分布式会话架构深度解析(附python源码)

万邦科技Lex 万邦科技Lex 发表于2026-05-27 11:18:50 浏览22 评论0

抢沙发发表评论

淘宝购物车之所以能实现“永不丢失”且跨设备同步,核心在于它彻底抛弃了单机内存存储,采用了无状态服务 + 分布式缓存(Tair/Redis)的架构。这种设计让购物车数据不再依赖某台具体的服务器,而是存储在独立的共享集群中。
下面为你深度解析这套架构的核心原理,并附上可落地的 Python 模拟实现。

一、 淘宝购物车架构深度解析

1. 为什么单机 Session 会“丢失”?

传统的购物车数据存在服务器内存(如 Flask/Django 的 session)中,这会导致两个致命问题:
  • 扩容即丢失:当服务器重启或扩容新增节点时,内存数据清空,购物车就没了。

  • 无法同步:手机和电脑访问的是不同的后端服务器,数据无法互通。

2. 淘宝的解决方案:分布式会话

淘宝通过以下三层架构实现数据持久化:
组件
角色
淘宝实现方案
接入层
身份识别
通过 Cookie 或 Token 携带唯一标识(如 session_iduser_id
逻辑层
无状态服务
购物车服务集群,不存储任何用户状态,只处理逻辑
存储层
数据持久化
Tair/Redis 集群(阿里云自研的高性能 KV 存储)
核心流程
  1. 用户访问时,携带 tokenuser_id

  2. 负载均衡将请求随机转发给任意一台购物车服务节点。

  3. 服务节点根据 user_id去 Redis 集群中读取/写入数据。

  4. 数据永远在 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 集群节点上。

四、 总结

淘宝购物车“永不丢失”的秘诀
  1. 无状态化:应用服务器不存数据,可以随意重启、扩容。

  2. 集中存储:购物车数据统一存储在 Redis/Tair 集群中。

  3. ID 贯通:通过 user_idsession_id作为唯一钥匙,打通多端数据。

通过上面的 Python 代码,你可以看到实现一个基础的分布式购物车并不复杂。关键在于将状态(数据)从服务中剥离出来,这是构建任何高可用分布式系统的核心思想。


群贤毕至

访客