← A/B 测试知识库

A/B 测试实验平台工程架构

A/B 测试 知识库


如果你计划自研 A/B 实验平台,本文提供从 SDK 到数据管道到管理后台的完整工程架构指引。如果你采购商业平台,本文也可作为技术选型的评估框架。

目录

  1. 整体架构概览
  2. SDK 层设计
  3. 分流服务层设计
  4. 配置管理层设计
  5. 数据管道层设计
  6. 分析引擎层设计
  7. 管理后台设计
  8. 监控与告警
  9. 性能与 SLA 目标
  10. 技术选型建议

一、整体架构概览

1.1 四层架构


                         管理后台层                                
                                                                 
           
   实验管理     指标管理     权限管理     实验知识库       
   CRUD        字典        RBAC        归档 & 搜索      
           
                                                                 

                         分析引擎层                                
                                                                 
           
   统计检验     CUPED       报告生成     异常检测        
   t-test      方差削减     自动分析     SRM / AA       
           
                                                                 

                         数据管道层                                
                                                                 
           
   埋点采集     实时计算     离线计算     指标存储        
   SDK 上报    Flink       Spark       ClickHouse     
           
                                                                 

                分流服务层 + SDK 层(业务链路)                      
                                                                 
                
   业务 SDK      分流服务          配置中心              
   多语言          哈希 + 路由         实验配置存储          
                
                                                                 

1.2 核心数据流

1. 实验配置流(下行):
   管理后台  配置中心  分流服务缓存  SDK 本地缓存

2. 用户分流流(在线):
   用户请求  业务服务  SDK  本地缓存查 Flag  执行对应逻辑

3. 数据上报流(上行):
   用户行为  业务埋点 SDK  事件总线  实时/离线计算  指标存储

4. 分析流(离线):
   指标存储  分析引擎  实验报告  管理后台展示

二、SDK 层设计

2.1 SDK 的核心职责

业务 SDK 需要做的事情(越少越好):

1. 获取实验配置(启动时拉取 + 定期更新)
2. 对每个用户请求判断:user_id 属于实验的哪个组?
3. 返回分组结果,业务代码据此执行不同逻辑
4. 上报曝光事件(用户真正看到了什么)

SDK 不需要做的事情:
- 统计检验
- 指标计算
- 数据存储

2.2 SDK 接口设计

# Python SDK 示例接口
class ExperimentClient:
    def get_variant(
        self,
        experiment_id: str,
        user_id: str,
        attributes: dict = None
    ) -> VariantResult:
        """
        返回用户在该实验中的分组。

        Returns:
            VariantResult:
                - variant: str  # "control" | "treatment_A" | ...
                - config: dict  # 实验组对应的策略参数(可选)
                - in_experiment: bool
        """
        pass

    def track_exposure(
        self,
        experiment_id: str,
        user_id: str,
        variant: str
    ) -> None:
        """
        上报用户真正看到了实验策略的事件。
        这是数据质量的关键避免分析时"用户被分了组但没看到策略"。
        """
        pass

    def get_all_variants(
        self,
        user_id: str
    ) -> dict[str, VariantResult]:
        """
        获取该用户当前参与的所有实验及其分组。
        用于调试和问题排查。
        """
        pass

2.3 SDK 本地缓存策略

启动时:
  SDK 初始化  从分流服务全量拉取当前活跃的实验配置
   缓存到本地内存(JSON/Protobuf)

运行时:
  SDK 内存缓存直接判断分组,不产生网络请求
   延迟 < 0.1ms

配置更新:
  方案 A(轮询):每 N 秒拉取一次配置更新(简单,但有延迟)
  方案 B(推送):分流服务通过 WebSocket / gRPC Stream 推送配置变更
  方案 C(混合):轮询兜底 + 推送加速

推荐:方案 A 起步(简单可靠),方案 C 在成熟期优化

2.4 容错与降级

SDK 容错层级:

1. 内存缓存命中  直接返回(正常路径)
2. 缓存未命中  调用分流服务
3. 分流服务超时/不可用  使用本地磁盘缓存(上次拉取的配置)
4. 磁盘缓存不可用  返回默认值(通常是 control / 原策略)
5. 全链路不可用  SDK 抛出 soft error,业务逻辑走原策略

关键原则:
  - SDK 挂了不能影响业务主流程
  - 降级策略 = 原策略(宁可实验数据不准,不能业务受损)
  - 所有异常都要有结构化日志 + 告警

2.5 多语言 SDK 的一致性保证

问题:Java、Go、Python、JavaScript 四种语言的 SDK,如何保证同一 user_id 的分流结果一致?

方案

核心分流逻辑用同一套算法规范,所有语言实现必须通过一致性测试:

1. 定义算法规范文档(不依赖特定语言实现)
2. 构建一致性测试集:
   - 10 万个 user_id
   - 100 个 experiment_id
   - 5 个 layer_id
   - 计算每种组合的 bucket 值
3. 每种语言的 SDK 实现必须通过 100% 一致性验证
4. CI 流程中自动运行一致性测试

2.6 SDK 的发布规范

版本管理:
  - 主版本号:不兼容的 API 变更
  - 次版本号:向后兼容的功能新增
  - 修订号:Bug 修复

SDK 升级策略:
  - 业务方升级 SDK 的窗口期通常 1-4 周
  - SDK 重大升级需要兼容期(新旧 SDK 共存)
  - 分流算法变更  新算法用新的 layer_id 起新层,旧层不变

三、分流服务层设计

3.1 分流服务的核心职责

分流服务是一个高可用、低延迟的在线服务:

1. 哈希计算:user_id + experiment_id + layer_id  bucket_id
2. 分组映射:bucket_id  group_name(查配置)
3. 配置缓存:实时同步最新的实验配置
4. 一致性保证:同一用户在整个实验周期内分组不变

3.2 分流算法

核心哈希:
  bucket = MurmurHash3_128(salt + ":" + user_id + ":" + layer_id) % 10000

输入:
  - salt:固定随机种子(每个 layer 不同,保证层间独立)
  - user_id:用户唯一标识(字符串)
  - layer_id:层标识

输出:
  - bucket  [0, 9999](10000 个桶,每桶 0.01% 流量)

分组映射(从配置中获取):
  Experiment:
    layer_id = "layer_homepage"
    control:  bucket [0, 4999]     50% 流量
    treatment: bucket [5000, 9999]  50% 流量

3.3 分流服务的高可用架构

                   
                     负载均衡     
                     (Envoy/Nginx)
                   
                          
            
                                      
       
      分流服务-1    分流服务-2    分流服务-3  
      (无状态)      (无状态)      (无状态)    
       
                                       
           
                          
                    
                     配置中心    
                     (Etcd/    
                      Consul)  
                    

设计要点

要点 实现
无状态设计 分流服务不存储任何状态,所有配置从配置中心读取
水平扩展 任意扩展实例,负载均衡自动分配
配置热更新 配置中心变更 分流服务 Watch 到变更 内存缓存刷新
本地缓存兜底 配置中心不可用时,使用本地磁盘缓存的配置快照

3.4 分流服务的性能要求

延迟:
  P50 < 0.2ms(内存哈希计算)
  P99 < 1ms
  P999 < 5ms

吞吐:
  单实例 > 50,000 QPS
  (仅做哈希计算 + 查内存表,不涉及 IO)

可用性:
  SLA > 99.99%(年宕机 < 53 分钟)

3.5 分流一致性的黄金法则

1. user_id 一旦进入实验,Hash 值永久不变
    不能中途改 salt、layer_id、分桶数

2. 实验分流比例可以变(调整哪些桶属于哪个组)
    但只能"增加流量",不能"减少流量"
    正确:control [0,7999]  [0,4999],treatment [8000,9999]  [5000,9999]
    错误:直接改变已分配用户的组别

3. 实验停止后,分流逻辑保留至少 7 天
    确保离线数据管道中的延迟数据仍能正确归属

四、配置管理层设计

4.1 实验配置的数据模型

{
  "experiment_id": "exp_homepage_redesign_2026",
  "status": "running",
  "layer_id": "layer_homepage",
  "layer_type": "exclusive",
  "buckets_total": 10000,
  "salt": "a3f8b2c1_homepage",
  "start_time": "2026-06-01T00:00:00Z",
  "end_time": "2026-06-15T00:00:00Z",
  "variants": [
    {
      "name": "control",
      "bucket_range": [0, 4999],
      "config": {
        "homepage_version": "v3.2.1",
        "theme": "default"
      }
    },
    {
      "name": "treatment",
      "bucket_range": [5000, 9999],
      "config": {
        "homepage_version": "v4.0.0-beta",
        "theme": "new_design"
      }
    }
  ],
  "metrics": {
    "primary": "ctr_core_module",
    "guardrail": ["page_load_time", "crash_rate"]
  }
}

4.2 配置变更的推送链路

管理员操作  管理后台
                
                
            配置中心(Etcd / Consul)
                
         
                     
   分流服务-1  分流服务-2  分流服务-3
   (Watch 配置变更,< 1s 生效)
                     
                     
     业务 SDK 轮询拉取配置更新
     (30-60s 的更新延迟)

延迟分析

环节 典型延迟 说明
管理员保存 配置中心 < 100ms API 写入
配置中心 分流服务 < 500ms Watch 机制推送
分流服务 SDK 30-60s SDK 轮询间隔
SDK 缓存刷新 < 1ms 内存操作

总端到端延迟:通常 30-60 秒,对实验场景完全可接受。

4.3 配置的版本管理与回滚

每次配置变更记录:
  - 版本号
  - 变更内容(diff)
  - 变更时间
  - 操作人
  - 审批记录

回滚机制:
  - 配置中心保留最近 N 个版本
  - 管理员一键回滚到历史版本
  - 回滚后,分流服务 < 1s 内生效,SDK < 60s 内生效

实验停止(非回滚):
  - 实验结束后状态变为"已完成"
  - 分流配置保留 7-14 天(允许延迟数据和日志查询)
  - 过期后自动清理

五、数据管道层设计

5.1 埋点规范

所有实验相关的事件必须包含以下字段:

{
  "event_id": "uuid",
  "event_type": "page_view | click | purchase | ...",
  "timestamp": "2026-06-10T14:30:00.000Z",
  "user_id": "user_12345",
  "device_id": "device_abc",
  "platform": "ios | android | web",
  "app_version": "6.2.0",

  "experiments": [
    {
      "experiment_id": "exp_homepage_redesign_2026",
      "variant": "treatment",
      "is_exposed": true
    }
  ],

  "event_properties": {
    "page_name": "homepage",
    "module_id": "recommend_feed",
    "duration_ms": 3200
  }
}

关键规范

要求 说明
experiments 必须是数组 一个用户可能同时参与多个正交层的实验
is_exposed 标记用户是否真正看到了实验策略(用于计算实际触达率)
timestamp 统一使用 UTC 时区,禁止混用本地时间
字段不得缺失 上述字段任意一个缺失都应在数据管道中计数告警

5.2 数据管道架构

业务服务                            数据平台
                                      
     埋点 SDK  Event Bus  实时计算(Flink)
                  (Kafka)           
                                     实时实验看板
                                       (效应量趋势)
                                     实时 SRM 检测
                              
                               离线仓库(Hive / Iceberg)
                                     
                                      离线计算(Spark SQL)
                                            
                                             日级指标宽表
                                               (user_id  date  experiment  metrics)
                                            
                                             实验报告生成
                                               (统计检验 + CI + 可视化)
                                            
                                             指标字典校验
                                                (口径一致性检查)

5.3 指标计算的两种模式

模式对比

维度 实时计算 离线计算
用途 实验监控看板、SRM 检测 正式实验报告、统计检验
时效 秒/分钟级 小时/天级
精确度 近似(丢数据可接受) 精确(数据完整性要求高)
指标复杂度 简单聚合(SUM/COUNT/AVG) 复杂(Join、UDF、CUPED)
技术栈 Flink + ClickHouse Spark + Hive / Iceberg

为什么不能只用实时计算?

5.4 数据质量保障

数据质量检查流水线:

1. 完整性检查
    实验事件的 experiments 字段是否为空?
    user_id 是否为 null 或 "unknown"?
    是否有重复事件(幂等性检查)?

2. 一致性检查
    同一 user 在同一 experiment 中的 variant 是否一致?
    实验时间段内的数据是否正确归因?

3. 及时性检查
    数据从产生到进入离线仓库的延迟是否在 SLA 内?
    每天的数据量是否与前 N 天在合理范围内?(防止数据管道中断)

4. 异常检测
    某组的数据量突然暴增/暴跌?
    某组的关键指标值异常偏离历史趋势?

六、分析引擎层设计

6.1 分析引擎的核心模块

分析引擎 = 数据处理 + 统计检验 + 结果输出


            分析引擎                      
                                        
  输入:实验 ID + 分析时间窗口             
                                        
                
   数据处理      统计检验               
                                    
   1.SRM       1.t-test             
   2.异常值     2.CUPED              
   3.过滤      3.Bootstrap           
   4.聚合      4.多重校正             
                
                         
                                       
                
         报告生成                       
    - 效应量 + CI                      
    - 时间趋势                        
    - HTE 探索                        
    - 可视化                          
                
                                        
  输出:实验报告(Markdown + 图表)        

6.2 分析引擎的技术实现

# 分析引擎核心逻辑(简化示例)

class ExperimentAnalyzer:
    def analyze(self, experiment_id: str) -> ExperimentReport:
        # 1. 加载数据
        data = self.load_experiment_data(experiment_id)

        # 2. SRM 检验(第一步!)
        srm_result = self.check_srm(data)
        if not srm_result.passed:
            raise SRMException(f"SRM failed: {srm_result}")

        # 3. 数据质量检查与清洗
        data = self.clean_data(data)
        # - 移除异常值(Winsorization)
        # - 过滤 bot 流量
        # - 处理缺失值

        # 4. CUPED 方差削减
        data_cuped = self.apply_cuped(
            data,
            covariate_metric="pre_experiment_gmv_7d"
        )

        # 5. 统计检验
        primary_result = self.welch_t_test(
            data_cuped,
            metric="oec",
            alpha=0.05
        )

        # 6. 护栏指标检查
        guardrail_results = {
            metric: self.welch_t_test(data_cuped, metric=metric, alpha=0.10)
            for metric in self.get_guardrail_metrics(experiment_id)
        }

        # 7. 多重检验校正(对探索性指标)
        exploratory_results = self.bh_correction(
            [self.welch_t_test(data_cuped, metric=m)
             for m in self.get_exploratory_metrics(experiment_id)]
        )

        # 8. 时间趋势分析
        daily_effects = self.compute_daily_effects(data_cuped)
        novelty_check = self.check_novelty_effect(daily_effects)

        # 9. 生成报告
        return self.generate_report(
            primary_result,
            guardrail_results,
            daily_effects,
            novelty_check
        )

6.3 报告自动生成

分析引擎输出的标准报告结构:

## 实验报告:{实验名称}

### 数据质量
  - SRM: = x.xx, p = x.xxx 
  - 数据完整性:xx.x%
  - 异常值处理:P99 Winsorization

### 主指标结果(CUPED 修正后)
[效应量表格 + 置信区间图]

### 时间趋势
[效应量  实验天数的折线图]
[Novelty Effect 检测结果]

### 护栏指标
[每个护栏指标的效应量 + 状态]

### 决策建议
  - 统计显著:是/否
  - 效应量  MDE:是/否
  - 护栏正常:是/否
  - 建议:上线 / 迭代 / 放弃

### 探索性发现(仅供假设生成)
  - HTE 分组结果
  - 用户反馈要点

七、管理后台设计

7.1 管理后台的核心页面

页面 功能
实验列表 查看所有实验的状态、所有者、剩余时间
实验配置 创建/编辑实验:分流比例、指标配置、MDE 设定
实验详情 单个实验的实时看板、数据质量、分析报告
指标字典 浏览/搜索/管理所有标准化指标
层管理 查看各层的流量使用情况、冲突检测
知识库 搜索历史实验、查看复盘结论

7.2 实验配置的向导式流程

创建实验  4 步向导:

Step 1:基本信息
  - 实验名称、描述、假设
  - Owner、团队

Step 2:分流配置
  - 选择层(自动推荐空闲层)
  - 设置分流比例
  - 系统自动检查:该层是否有流量冲突?

Step 3:指标配置
  - 从指标字典中选择 OEC(可选复合指标)
  - 从指标字典中选择护栏指标(系统推荐默认护栏)
  - 设置 MDE、、

Step 4:审查与启动
  - 自动审查:
     SRM 预期检查
     流量冲突检测
     样本量估算
     护栏指标完整性
  - 审批流(关键实验需要审批)
  - 一键启动

八、监控与告警

8.1 四层监控体系

第一层:基础设施监控
  - 分流服务 CPU / 内存 / QPS / 延迟
  - 配置中心可用性
  - 数据管道 Kafka Lag
  - 离线计算任务完成时间

第二层:实验质量监控
  - SRM 自动检测(每 30 分钟一次)
  - AA 测试的 Type-I Error Rate(周级校验)
  - 数据完整性(事件到达率、缺失字段率)

第三层:业务指标监控
  - 护栏指标异常检测(实验组显著恶化)
  - 指标值异常波动(超出历史 3 范围)

第四层:平台运营监控
  - 活跃实验数
  - 实验平均时长
  - 实验成功率(显著正向比例)
  - 平台月活用户(使用实验平台的产品/运营人员数)

8.2 告警规则设计

告警级别 触发条件 通知方式 响应时间
P0 分流服务不可用 电话 + 企业微信 + Oncall 5 分钟内
P0 护栏指标反向显著恶化 电话 + 企业微信 + 实验 Owner 15 分钟内
P1 SRM 连续两次检测异常 企业微信 + 实验 Owner 30 分钟内
P1 数据管道延迟 > 2 小时 企业微信 + 数据团队 1 小时内
P2 护栏指标正向显著恶化 企业微信 + 实验 Owner 2 小时内
P3 数据质量指标轻度异常 邮件/企微群 当天处理

九、性能与 SLA 目标

9.1 系统性能指标

组件 指标 P50 P99 P999
分流服务 延迟 < 0.2ms < 1ms < 5ms
分流服务 吞吐(单实例) - > 50K QPS -
SDK 本地分流 延迟 < 0.05ms < 0.1ms < 0.5ms
配置变更生效 端到端延迟 < 10s < 60s < 120s
离线报告生成 完成时间 < 30min < 2h < 4h
实时看板 数据新鲜度 < 1min < 5min < 15min

9.2 可用性 SLA

组件 可用性目标 允许停机时间/年
分流服务 99.99% < 53 分钟
配置中心 99.99% < 53 分钟
数据管道(实时) 99.9% < 8.8 小时
数据管道(离线) 99.5% < 43.8 小时
管理后台 99.9% < 8.8 小时

十、技术选型建议

10.1 推荐技术栈

组件 推荐方案 替代方案
SDK 多语言客户端 Java / Go / Python / JS 自研各语言 SDK
分流服务 在线服务 Go(高性能)或 Rust Java + Spring
配置中心 配置存储与推送 Etcd Consul, Zookeeper, 自研 DB
事件总线 流数据接入 Kafka Pulsar, AWS Kinesis
实时计算 流处理 Apache Flink Spark Streaming, Kafka Streams
离线计算 批处理 Spark SQL Trino, Hive
指标存储 OLAP ClickHouse Druid, StarRocks, Doris
离线仓库 数据湖 Iceberg + Hive Metastore Delta Lake, Hudi
分析引擎 统计计算 Python (SciPy + Statsmodels) R
管理后台 Web 前端 React + TypeScript Vue
管理后台 后端 API Go / Python (FastAPI) Java (Spring Boot)

10.2 分阶段技术选型

Phase 1(MVP,0-3 个月)
  - 分流:Go 服务 + MySQL 存储配置(简单但够用)
  - 分析:Python 脚本手动跑
  - 看板:Grafana + ClickHouse
  - 目标:1 个人 1 个月内跑通第一个实验

Phase 2(平台化,3-6 个月)
  - 分流:Go 服务 + Etcd 配置中心(支持热更新)
  - 分析:Python 自动化分析引擎
  - 管理后台:React 前端
  - 数据管道:Kafka + Flink + ClickHouse
  - 目标:10 个实验并发,SRM 自动检测,自动报告

Phase 3(规模化,6-12 个月)
  - 分流:多机房部署,全球化
  - 分析:CUPED 内置、序贯检验、贝叶斯
  - 数据:Iceberg 数据湖 + 完整指标字典
  - 平台:实验知识库、HTE 分析、审批流
  - 目标:100+ 实验并发,全公司统一平台

附录:自研 vs 采购的最终决策树

你的团队是否具备以下条件?
 至少 3 名全职工程师可投入平台开发?
    否  采购商业平台(Statsig / Eppo / GrowthBook)

 实验并发量 > 100 个/天?
    否  采购或开源方案即可满足

 有特殊的合规/数据安全要求(数据不能出内网)?
    是  自研或 GrowthBook 自托管
    否  考虑 SaaS

 需要深度定制分流逻辑(如非标准实验单元、特殊哈希规则)?
     是  自研
     否  采购平台 + 轻量定制