- 实验系统本质上是为了在不直接全量上线的情况下, 用随机分流去验证某个改动到底有没有提升业务指标
- 这套系统需要同时解决几个问题:
- 随机性: 用户要被尽可能均匀地打散
- 稳定性: 同一个用户在实验周期内不能反复漂移到不同组
- 可复用性: 不同流量层可以并行做实验, 尽量互不干扰
- 可归因性: 最终要能把用户行为准确归到某个实验组上
- Libra 大致可以拆成 4 块:
Experiment Service: 管实验、版本、配置等元数据
Traffic Allocation Service: 在线分流, 决定一个 request 命中哪些实验组
Metrics Service / Libra Gallery: 管指标元信息, 生成离线计算任务
Report / Data Portal: 聚合实验结果, 给报表侧查询
- 整体主链路就是: 平台配置实验 -> 分流服务命中 -> 命中日志/埋点落表 -> 离线算指标 -> 报表展示
- 业务请求会带
token, token 对应一个 Product, 一个 Product 下会挂多个 Layer
- 分流不是给用户只算一次哈希, 而是会把这个
token 下的所有 Layer 都遍历一遍
- 每个
Layer 都会独立执行下面的判断:
- 测试用户检查
- 流量分桶
- 过滤条件
- 用户分群
- 流量分桶的本质是:
按 uid 分流: hash("uid_type:uid:layer_name") % 1000
按 did 分流: hash("did:layer_name") % 1000
- 这里的
hash 一般用的是 MurmurHash, 它本身会先输出一个很大的整数, 最后通过 % 1000 才映射到 0-999 的桶号
1000 个桶不是哈希函数决定的, 而是系统设计上的选择, 好处是最小流量粒度是 0.1%, 同时分配和管理成本也比较可控
- 如果当前桶被分配给某个实验, 才算进入这个实验; 进入实验之后还会再做一次版本分组计算, 决定命中
v0/v1/v2 哪个组
hash("id:flight_name") % version_num
- 同一个
Layer 内一个用户最多命中一个实验, 所以同层天然互斥
- 不同
Layer 之间不会复用同一个桶位, 而是每一层都重新计算一次哈希
- 同一个
did 在不同层会进不同桶, 关键是哈希输入里带了 layer_name
MurmurHash 的雪崩效应保证输入哪怕只差一个字符, 输出结果也会明显不同, 这就是层间正交的基础
层A: hash("device_123:layer_A") % 1000 -> 347
层B: hash("device_123:layer_B") % 1000 -> 891
层C: hash("device_123:layer_C") % 1000 -> 203
- 所有
Layer 都判断完之后, 分流服务会把命中的实验配置 merge 后返回
- 所以一个 request 可以同时命中多个
Layer, 但每个 Layer 内最多只会命中一个实验
- 调试用户本质上就是 Libra 里的测试用户, 它是配在“某个实验的某个实验组”上的, 不是直接配在
Layer 上
- 但由于分流流程是逐层执行的, 某一层一旦通过测试用户命中了某个实验组, 这一层就不需要再继续做正常的哈希分桶
- 测试用户检查的优先级高于正常分桶, 通常顺序是:
- 测试用户
- 哈希分桶
- 过滤条件
- 用户分群
- 常见口径里会支持
uid/did/uuid 这类标识做测试命中, 一般优先级是 uid > did > uuid
- 测试用户通常还有两种模式:
- 只要命中标识就直接生效
- 命中标识后还要继续满足过滤条件才生效
- 测试用户只影响当前命中的那一层, 其他
Layer 还是正常走各自的哈希和分桶逻辑
- 实验组和对照组的数据不是分开建两套链路, 核心做法是先记录“用户命中了哪个实验组”, 后续再按实验组字段聚合
- 核心区分字段是
vid/version_id, 也就是实验组 ID
- 服务端实验:
- 业务服务调用分流接口时, 分流服务会记录命中结果
- 后续把命中日志和业务行为表 join, 再按
flight_id + vid 统计指标
- 客户端实验:
- 端上主动上报命中曝光埋点到
eventlog
- 再从埋点日志抽到实验命中表, 后续和业务表 join
- 统计流程可以简单理解成:
分流命中 -> 记录 uid/did + flight_id + vid -> join 业务行为表 -> 按 vid 分组统计
- 从实验语义上看, 同一个用户在同一个实验里同一时刻只会对应一个有效
vid
- 但底层日志表里可能会因为多次请求、按天落表、曝光链路等出现多条记录, 真正做统计时还是要按
flight_id/vid/date 这类口径聚合去看
- 指标计算本身一般不是同步完成的,
Libra Gallery 会给实验指标生成周期任务, 最终再汇总到报表侧