**摘要:** 搭建一个生产可用的AB实验系统,听起来像是大公司才有的奢侈装备。但2026年的技术栈已经让这件事变得极其简单——一个开发者用一周时间、一台VPS,就能搭建出支持百万级事件处理的AB实验系统。本文从零开始,手把手带你走完从SDK埋点到效果分析的全过程。
AB实验系统没那么复杂
很多中小团队对AB实验望而却步,觉得那是"等我们有数据工程师了再考虑"的事情。
但实际上,一个最小化可用的AB实验系统只需要三个组件:
这三个组件加在一起,一个全栈开发者一周就能搞通。
Step 1:埋点SDK的设计与集成
再高级的AB实验,第一步都是"能收集到数据"。埋点SDK就是干这件事的。
核心API设计
一个功能完整的埋点SDK,最少只需要四个核心API:
// 1. 初始化 — 传入SDK的配置信息
HermesTracker.init({
apiKey: 'project_api_key',
apiHost: 'https://tracking.yourdomain.com'
});
// 2. 事件追踪 — 记录用户行为
HermesTracker.track('button_click', {
buttonId: 'signup_cta',
page: 'landing'
});
// 3. 用户识别 — 关联登录用户和匿名行为
HermesTracker.identify('user_12345', {
plan: 'premium',
source: 'organic'
});
// 4. 获取实验分组 — 判断当前用户在哪个实验组
const variant = await HermesTracker.getVariant('landing_redesign_v1');
if (variant === 'treatment') {
showNewLandingPage();
} else {
showCurrentLandingPage();
}
技术要点:批量发送与重试
生产环境下的SDK需要考虑两个关键问题:效率和可靠性。
class EventQueue {
constructor() {
this.buffer = [];
this.flushInterval = 5000; // 5秒批量发送一次
this.maxBatchSize = 10; // 或累积10条立即发送
this.retryQueue = [];
this.maxRetries = 3;
}
push(event) {
this.buffer.push(event);
if (this.buffer.length >= this.maxBatchSize) {
this.flush();
}
}
async flush() {
if (this.buffer.length === 0) return;
const batch = this.buffer.splice(0);
try {
// 优先使用 Beacon API(页面关闭也能发送)
if (navigator.sendBeacon) {
const blob = new Blob([JSON.stringify({ events: batch })],
{ type: 'application/json' });
navigator.sendBeacon('/api/v1/track', blob);
} else {
// fallback: fetch + keepalive
await fetch('/api/v1/track', {
method: 'POST',
body: JSON.stringify({ events: batch }),
headers: { 'Content-Type': 'application/json' },
keepalive: true
});
}
} catch (e) {
// 失败入重试队列
this.retryQueue.push(...batch);
this.scheduleRetry();
}
}
}
这里用了 sendBeacon 作为首选传输方式——它最大的好处是:即使用户关闭了浏览器页面,待发送的数据也不会丢失。这是普通XHR/fetch做不到的。
匿名用户识别
在用户登录之前,SDK需要生成一个唯一的匿名ID。常见做法是使用 UUID v4 + localStorage 持久化:
function getAnonymousId() {
let aid = localStorage.getItem('hermes_anonymous_id');
if (!aid) {
aid = crypto.randomUUID();
localStorage.setItem('hermes_anonymous_id', aid);
}
return aid;
}
这个匿名ID是后续用户分桶(bucket assignment)的基础——哪怕用户没有登录,我们也能稳定识别同一用户的不同访问。
Step 2:实验分流引擎
分流是AB实验的核心技术环节——如何把用户公平、可重复地分配到不同的实验组。
哈希分流算法
最常用的方案是确定性哈希分流:对 (实验ID + 用户ID) 做哈希,将结果映射到 0-9999 的桶中,再根据每个版本分配的桶范围决定用户归属。
import hashlib
def assign_variant(user_id: str, experiment_key: str, variants: list[dict]) -> str:
"""
确定性分流算法
variants: [{"key": "control", "rollout": 0.5}, {"key": "treatment", "rollout": 0.5}]
返回: variant key
"""
seed = f"{experiment_key}:{user_id}"
# 取 MD5 前8位,转为0-9999的整数
hash_val = int(hashlib.md5(seed.encode()).hexdigest()[:8], 16)
bucket = hash_val % 10000
cumulative = 0
for variant in variants:
cumulative += int(variant["rollout"] * 10000)
if bucket < cumulative:
return variant["key"]
return variants[-1]["key"] # fallback
这个算法的三个关键特性:
分流位置选择:客户端 vs 服务端
| 维度 | 客户端分流 | 服务端分流 |
|------|-----------|-----------|
| 延迟 | 零(本地计算) | 需要一次API调用 |
| 实现复杂度 | 低(SDK内实现) | 中(需要服务端接口) |
| 安全性 | 用户可篡改分组 | 不可篡改 |
| 适用场景 | UI实验、A/B测试 | 后端策略实验、付费实验 |
对于大多数前端AB实验场景,客户端分流就足够了。如果涉及后端算法或需要防作弊,再叠加服务端分流。
Step 3:后端API与数据存储
有了SDK和分流逻辑,接下来需要搭建数据接收层和存储层。
FastAPI 事件接收
from fastapi import FastAPI
from pydantic import BaseModel
import asyncpg
app = FastAPI()
class EventPayload(BaseModel):
event: str
distinct_id: str
properties: dict = {}
timestamp: str | None = None
class BatchEventPayload(BaseModel):
events: list[EventPayload]
@app.post("/api/v1/track")
async def track_events(payload: BatchEventPayload):
conn = await asyncpg.connect("postgresql://...")
# 批量写入,提升吞吐量
await conn.executemany("""
INSERT INTO events (event_name, distinct_id, properties, created_at)
VALUES ($1, $2, $3, COALESCE($4::timestamptz, NOW()))
""", [
(e.event, e.distinct_id, json.dumps(e.properties), e.timestamp)
for e in payload.events
])
await conn.close()
return {"status": "ok", "count": len(payload.events)}
数据库Schema
一个最简化的AB实验数据模型只需要两张核心表:
-- 事件表:记录所有用户行为
CREATE TABLE events (
id BIGSERIAL PRIMARY KEY,
project_id UUID NOT NULL,
event_name VARCHAR(128) NOT NULL,
distinct_id VARCHAR(128) NOT NULL, -- 匿名ID或用户ID
properties JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_events_project_time ON events(project_id, created_at DESC);
-- 实验配置表
CREATE TABLE experiments (
id UUID PRIMARY KEY,
project_id UUID NOT NULL,
key VARCHAR(128) UNIQUE NOT NULL,
name VARCHAR(255),
status VARCHAR(32) DEFAULT 'draft', -- draft | running | stopped
variants JSONB NOT NULL, -- [{"key":"control","rollout":0.5}, ...]
targeting_rules JSONB DEFAULT '[]',
created_at TIMESTAMPTZ DEFAULT NOW()
);
Step 4:效果分析
实验跑了一段时间后,最关键的问题是:结果有没有统计显著性?
简单t检验实现
AB实验最常见的是对比两个版本的关键指标——比如点击率(CTR)、转化率。我们可以用scipy来做t检验:
from scipy import stats
import numpy as np
def analyze_experiment(control_events: list, treatment_events: list):
"""
对实验组和对照组的核心指标做独立样本t检验
返回: (p_value, is_significant, lift)
"""
control_mean = np.mean(control_events)
treatment_mean = np.mean(treatment_events)
# 独立样本t检验
t_stat, p_value = stats.ttest_ind(treatment_events, control_events)
# 相对提升
lift = (treatment_mean - control_mean) / control_mean
return {
"control_mean": round(control_mean, 4),
"treatment_mean": round(treatment_mean, 4),
"lift": f"{lift*100:.2f}%",
"p_value": round(p_value, 4),
"is_significant": p_value < 0.05, # 95%置信水平
"sample_size": {
"control": len(control_events),
"treatment": len(treatment_events)
}
}
输出的核心参数解读:
生产环境部署验证
以上所有组件搭建完成后,部署验证只需三步:
# 1. 验证API可用
curl -s https://tracking.yourdomain.com/api/v1/health
# → {"status": "ok"}
# 2. 验证SDK可加载
curl -s -o /dev/null -w "%{http_code}" https://tracking.yourdomain.com/sdk/hermes-tracker.js
# → 200
# 3. 验证事件链路:
# 浏览器加载SDK → 触发pageview → API收到事件 → 数据库可查到
推荐的演进路径
自托管方案最务实的策略是渐进式演进:
*本文讨论的AB实验系统架构,已被 EXP-003 埋点SaaS产品验证。我们使用 FastAPI + PostgreSQL + 纯前端JS SDK + React Dashboard 构建了生产级方案,所有组件已通过健康检查并部署上线。如需查看完整实现源码或体验Demo,欢迎留言交流。*