核心思路是:在请求进入视图前,用用户ID/session_key/IP等唯一标识哈希取模实现稳定分组(如%2),将结果存入request.ab_test_group;Redis仅预加载配置到内存字典,避免每次请求访问;模板渲染需通过分组感知的缓存和template tag防止缓存污染;须日志埋点+离线校验保障分流准确性和一致性。

怎么用中间件拦截请求并打上A/B分组标签
核心思路是:在请求进入视图前,根据唯一标识(如用户ID、session_key或IP)哈希后取模,决定分到group_a还是group_b。关键不是随机,而是稳定——同一用户每次请求必须落到同一组。
中间件里别直接用 random.choice(),那会导致刷新页面就变组。正确做法是:
- 优先取
request.user.id(登录用户);未登录则 fallback 到request.session.session_key,若 session 未创建,可退到request.META.get("REMOTE_ADDR")+request.META.get("HTTP_USER_AGENT")拼接后哈希 - 用
hashlib.md5(uid.encode()).hexdigest()[-4:]转成十六进制字符串,再转为十进制整数,对分组总数取模(比如 2 组就% 2) - 把结果存到
request.ab_test_group,后续视图或模板就能直接读取
Redis怎么存AB测试配置而不拖慢请求
别把分组比例硬编码在中间件里——运营要调 70/30,你得重启服务?不行。要用 Redis 存配置,但不能每次请求都 redis.get("ab_config:checkout_page")。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 在 Django 启动时(比如
apps.py的ready()方法)用redis.hgetall("ab_config")预加载到内存字典,比如AB_CONFIG_CACHE = {"checkout_page": "70,30", "pay_button": "50,50"} - 中间件里只查这个本地字典,不碰 Redis;配置变更时,单独提供一个管理命令(
python manage.py reload_ab_config),执行时重新拉取并更新字典 - Redis 中存储格式推荐 Hash:
HSET ab_config checkout_page "70,30",比 String 更易批量管理
这样既避免了每次请求的网络开销,又保留了运行时调整能力。
模板里怎么安全地渲染不同版本且不影响缓存
如果用了 cache_page 或 CDN,直接在模板里 if-else 切版本,会导致缓存污染——group_a 用户刷出的页面被 group_b 用户命中,就全乱了。
必须让缓存 key 包含分组信息:
- 自定义缓存装饰器,比如
@cached_page_by_group(timeout=60),内部用request.ab_test_group拼到 cache key 里(如"view_cache:checkout:group_b") - 模板中不用
{% if request.ab_test_group == 'a' %},改用包含逻辑的 template tag:{% ab_render "checkout/button_v1.html" "checkout/button_v2.html" %},tag 内部自动选文件并参与缓存 key 构造 - 静态资源(JS/CSS)也得按组加载,比如
<script src="%7B%%20static%20'js/checkout_v%7B%7B%20request.ab_test_group%20%7D%7D.js'%20%%7D"></script>,否则 JS 行为和 UI 版本可能错位
怎么验证分流比例真准、数据没丢
上线后最怕的是“看着像 A/B,实际 95% 流量进了 A 组”——尤其当用 IP 做 fallback 时,NAT 环境下一群用户共用一个 REMOTE_ADDR,会严重 skew。
必须加两层校验:
- 在中间件末尾写一行日志:
logger.info(f"AB_GROUP {request.ab_test_group} for {getattr(request.user, 'id', 'anon')}"),用 ELK 或简单 grep 抽样统计比例 - 在关键页面埋点时,强制带上
ab_group字段(如track('checkout_start', {ab_group: request.ab_test_group})),确保分析平台能按组拆分转化率 - 定期跑一个 management command,从最近 1 小时 Nginx 日志或数据库访问记录里抽 1 万条,用相同哈希逻辑重算分组,对比线上上报的 group 分布 —— 偏差 > 3% 就得查哈希逻辑或 uid 来源是否一致
真正难的不是实现分流,而是保证“同一个用户永远同组”这件事,在登录态变化、cookie 清除、多端切换等现实场景下依然成立。这部分往往要结合前端 localStorage 持久化 + 后端 fallback 策略,不能只靠一次中间件计算。


















