想象你开了一家火锅店,生意红火,门口每天排着长队。你推出了一款新锅底,很想知道它是不是真的比老锅底更受欢迎。最科学的做法,是把顾客分成两拨:A桌尝新锅底,B桌吃老锅底,最后对比两拨人的满意度。可问题来了——谁去A桌、谁去B桌?如果凭迎宾员心情分,老顾客可能全被塞进同一桌;如果按排队号单双数分,恰好双号是同一批团购用户,结果照样失真。A/B测试里的“流量分桶”,就是解决“怎么分才公平”的技术。它像一位不动声色的迎宾员,用哈希算法这把“搅拌机”把顾客均匀地打散到各个实验组,还能让多个实验共享同一批顾客而不互相打扰。今天,我们就跟着这位迎宾员,把分桌的门道一次讲清楚。

岗位关联

流量分桶是 A/B 测试的地基:分桶不公平,实验结论再漂亮也是空中楼阁。掌握它,你才能为 AI 产品团队设计科学可信的在线实验,让每一个算法决策都有数据撑腰。

目录
  1. 可视化演示
  2. 分桶基础认知
  3. 哈希分桶原理
  4. 分层分桶设计
  5. 均衡性验证
  6. 桶间隔离策略
  7. 盐值设计
  8. 工程落地实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

分桶基础认知

火锅店门口,迎宾员小川手里攥着一沓号码牌。老板的要求很简单:每位顾客凭号码牌,被公平地安排到“新锅底桌”A 桌或“老锅底桌”B 桌。什么叫公平?不能凭顾客穿着判断,不能按来的早晚排序——最公平的办法,是号码牌一发放就定下一条铁规矩:什么号进 A 桌,什么号进 B 桌,人人适用、永不更改。

这条“铁规矩”,就是流量分桶的本质:把源源不断的用户请求,按某种确定的规则分配到不同的“桶”里,每个桶对应一个实验组。规则必须与用户特征无关,否则实验组和对照组从一开始就长得不一样,后面比任何指标都毫无意义。

落地时,分桶方案由三个要素定义:流量单元(通常取 user_id,也可用 device_id)、桶集合(0 到 B-1 号桶)以及分配函数 f。流量单元的选择直接影响统计精度——同一用户反复出现却不固定分桶,他的行为会同时污染两个组,因此“同一用户每次请求都进同一个桶”是分桶的底线。

回到火锅店:号码牌就是流量单元,A/B 桌就是桶,门口那条铁规矩就是分配函数。定下这三样,分桌这件事就有了章法。

f:U→{0,1,…,B−1}\displaystyle f: \mathcal{U} \to \{0, 1, \ldots, B-1\}
一句话:把每个用户 ID 当成“输入”,分配函数吐出一个 0 到 B-1 之间的“桶号”,这就是分桌的总规则。
  1. 用户集合 \mathcal{U}所有可能进入实验的流量单元(如 user_id、device_id)组成输入集合,分桶函数只接受其中的元素。
    U\displaystyle \mathcal{U}
  2. 桶号取值空间函数输出限定为 0 到 B-1 的整数,B 是预先设定的桶总数,通常取 100 的倍数。
    {0,1,…,B−1}\displaystyle \{0, 1, \ldots, B-1\}
  3. 分配函数 f这是一个确定性映射,同一个输入永远得到同一个桶号,这是“同用户不换组”的数学保证。
    f:U→{0,1,…,B−1}\displaystyle f: \mathcal{U} \to \{0, 1, \ldots, B-1\}

哈希分桶原理

如果铁规矩是“号码牌尾号为单数进 A 桌”,小川很快发现麻烦来了:发号机是连着发的,单号顾客几乎都是同一批人,早晚批次的客人全挤到一桌。为了让号码牌“看不出顺序”,小川搬来一台搅拌机——把号码牌丢进去哗啦啦一转,出来的序号完全打乱了原样。

这台搅拌机就是哈希函数(Hash)。哈希函数能把任意长度的输入(用户 ID)压缩成固定长度的整数“指纹”,输入稍有不同,指纹就面目全非。分桶时,把这个指纹对桶数 B 取模,就得到 0 到 B-1 的桶号。

工程上常用 MD5、SHA-256、MurmurHash 等算法。哈希分桶满足三个性质:均匀性——指纹在值域上近似均匀分布,各桶容量天然趋于一致;确定性——同输入永远同指纹,用户重复访问不换组;雪崩效应——相邻 ID 的用户也会被甩到完全不同的桶里,规避了 ID 序列的天然规律。桶数通常取 100 的整数倍,便于按百分比切分流量做灰度放量。

搅拌机转完,顾客被均匀地扬到各个桌位——至于具体是哪桌,全看号码牌在搅拌机里的“缘分”,而这缘分稳定且可复现。

桶号=Hash(user_id) mod B\displaystyle \text{桶号} = \text{Hash}(\text{user\_id}) \bmod B
一句话:先把 user_id 丢进哈希“搅拌机”得到大整数指纹,再对桶数取余,就到了最终的桶号。
  1. 输入用户标识将 user_id 作为哈希函数的输入,要求该标识在用户生命周期内保持不变。
    user_id\displaystyle \text{user\_id}
  2. 计算哈希指纹用 MD5/SHA-256/MurmurHash 等算法把 user_id 压缩成固定长度的大整数,输入微变、输出剧变。
    Hash(user_id)\displaystyle \text{Hash}(\text{user\_id})
  3. 取模映射到桶对桶数 B 取余,把巨大的指纹压进 0 到 B-1 的小范围,得到最终桶号。
    Hash(user_id) mod B\displaystyle \text{Hash}(\text{user\_id}) \bmod B

分层分桶设计

新蘸料上线,老板要在同一批顾客身上同时测锅底和蘸料。小川若按“新锅底+新蘸料”开一桌,顾客满意了也分不清谁的功劳。他灵机一动:给大堂设计了两层“分桌逻辑”——顾客进门先按第一层规则分锅底桌,落座后再按第二层规则分蘸料桌。两轮分桌各算各的,互不牵连。

这就是分层分桶:每个“层”(Layer)都是一个独立的分桶空间,同一批流量进入每一层时都会被重新哈希、重新分配。第一层的桶服务于锅底实验,第二层的桶服务于蘸料实验,同一用户完全可以同时出现在两个实验里。

分层设计有两个关键词:层内正交——同一层内多个实验互斥,每个用户只能命中其中一个;层间独立——不同层的桶号各自随机、互不绑定。由此带来最宝贵的好处是流量复用:一份 100% 流量可以同时撑起多个实验,而不必为每个实验单独切一块流量。公式里的 salt_k 是第 k 层的盐值,我们会在后面专门展开。

所以小川的大堂看起来实验一个接一个,其实每位顾客都“一鱼多吃”——这正是分层分桶让实验团队省流量的秘诀。

bk=Hash(user_id⊕saltk) mod B\displaystyle b_k = \text{Hash}(\text{user\_id} \oplus \text{salt}_k) \bmod B
一句话:第 k 层的桶号由“用户 ID + 该层盐值”哈希取模得到,各层盐不同,所以同一用户在每层的桶号互不相关。
  1. 层号 kk 表示第几个实验层,每个层拥有一套独立的分桶配置与盐值。
    k=1,2,…,L\displaystyle k = 1, 2, \ldots, L
  2. 盐值 salt_k第 k 层专用的拼接字符串,不同层的盐值互不相同,是层间独立的关键。
    saltk\displaystyle \text{salt}_k
  3. 混合输入用拼接或异或把 user_id 与盐值混合后再哈希,相当于换了搅拌机的配方。
    user_id⊕saltk\displaystyle \text{user\_id} \oplus \text{salt}_k
  4. 取模得桶号哈希结果对 B 取余,得到第 k 层的桶号;每层独立计算一次,同一用户可以“一鱼多吃”。
    bk=Hash(user_id⊕saltk) mod B\displaystyle b_k = \text{Hash}(\text{user\_id} \oplus \text{salt}_k) \bmod B

均衡性验证

打烊后小川要交差:A 桌 82 人,B 桌 18 人,这实验还能比吗?当然不能。他得先确认各桌人数“差不多”,才敢让老板看满意度数据——否则实验组和对照组的“底子”就不一样。

统计学上,“差不多”不能靠肉眼。分桶均衡性验证用卡方检验(\chi² 检验)量化“观察人数”与“理想均匀人数”的偏差——偏差太大,说明分桶出了问题。

假设 10 个桶、总共 N 个用户,理想情况下每桶 E_i = N/10 人。卡方统计量把每个桶的偏差平方后归一化再累加,得到的值对照自由度 B-1 的卡方分布查临界值。若统计量超过临界值,说明各桶人数失衡,需要排查哈希函数、盐值或数据源是否异常。反过来,小样本下出现一定波动是正常的——样本量越大,桶间比例会越收敛到均匀。

就像小川数人头不必要求每个人精确相等、只要别出现“一桌挤爆一桌冷清”的悬殊即可,卡方检验给了我们一把客观的尺子。

χ2=∑i=1B(Oi−Ei)2Ei,Ei=NB\displaystyle \chi^2 = \sum_{i=1}^{B} \frac{(O_i - E_i)^2}{E_i}, \quad E_i = \frac{N}{B}
一句话:把每个桶的实际人数与均匀期望值比一比,偏差大到一定程度就说明分桶失去均衡。
  1. 观察人数 O_i实际运行中第 i 个桶分到的用户数量,来自分桶日志的统计。
    Oi\displaystyle O_i
  2. 期望人数 E_i完全均匀时每桶应有的人数,用总人数 N 除以桶数 B 得到。
    Ei=NB\displaystyle E_i = \frac{N}{B}
  3. 逐桶计算偏差每个桶的偏差平方后除以期望值,把“绝对偏差”归一化为“相对偏差”。
    (Oi−Ei)2Ei\displaystyle \frac{(O_i - E_i)^2}{E_i}
  4. 累加并与临界值对比所有桶的偏差求和得到卡方统计量,再对照自由度 B-1 的卡方分布判定是否失衡。
    χ2=∑i=1B(Oi−Ei)2Ei\displaystyle \chi^2 = \sum_{i=1}^{B} \frac{(O_i - E_i)^2}{E_i}

桶间隔离策略

老板又出幺蛾子:新锅底桌顺便把餐具也换成新款式?小川坚决摇头——如果 A 桌同时换了锅底和餐具,满意度涨了,你说是锅底好吃还是餐具好用?两件事的效果“串味”了,谁也说不清。

这种“串味”在统计学里叫交互效应:两个改动同时作用于同一批用户时,无法归因。桶间隔离就是为了避免串味——把可能互相影响的实验安排在同一层内互斥,让每个用户只能进入其中一个;而互不干扰的实验放在不同层,允许重叠。

隔离的数学表达是用户集合不相交:实验 A 命中的用户集合与实验 B 命中的用户集合交集为空。工程上,同一层的多个实验各占一段桶号区间,比如 0-49 号桶给实验 A,50-99 号桶给实验 B,同一时间只有一个在生效。而不同层的实验因为盐值不同、独立重分,天然互不干扰。

一句话总结:该隔离的是“会串味的改动”,而不是“所有实验”。小川的规矩是——同一张桌子上,一次只换一样新东西。

GA∩GB=∅\displaystyle G_A \cap G_B = \varnothing
一句话:互斥实验的用户集合不能有交集——一张桌子一次只做一种改动。
  1. 实验 A 的用户集合 G_A分桶后命中实验 A 的全部用户构成集合,用分配函数与桶号区间描述。
    GA={u∣桶号(u)∈RA}\displaystyle G_A = \{ u \mid \text{桶号}(u) \in R_A \}
  2. 实验 B 的用户集合 G_B同理,命中实验 B 的用户集合,区间 R_B 与 R_A 不重叠。
    GB={u∣桶号(u)∈RB}\displaystyle G_B = \{ u \mid \text{桶号}(u) \in R_B \}
  3. 交集为空两个集合没有公共元素,即任何用户都不可能同时进入两个互斥实验。
    GA∩GB=∅\displaystyle G_A \cap G_B = \varnothing

盐值设计

小川发现一个新隐患:两层都“搅拌”,可如果两台搅拌机的配方完全相同,顾客在两层会被分到一模一样的桌号——层间独立就名存实亡了。他的对策是:每台搅拌机加一勺不同的“调味料”,转出来的花样自然不同。

这勺调味料就是盐值(Salt):一个拼接在用户 ID 后面的固定字符串。第 k 层的分桶写成 b_k = Hash(user_id ⊕ salt_k) mod B。salt_1 = 'layer_exp1',salt_2 = 'layer_exp2',两层虽然用同一个哈希函数,但因为盐不同,分配结果彼此独立。

盐值的设计有三个要点:唯一性——每个实验/层用独一无二的盐,这是层间正交的前提;固定性——同一个实验的盐一旦确定就不能变,否则所有用户会被重新分桶,已进入实验的用户身份全部作废;可复现——盐写死在配置里,任何人任何时候重算都能得到同样的桶号。层间正交用概率表达:两层分桶独立均匀时,同一用户恰好落入同号桶的概率是 1/B,B=100 时只有 1%。

所以,“加盐”不是为了让菜更咸,而是让每一层的搅拌机都转出自己的节奏——这正是分层分桶能落地的秘密武器。

P(bi=bj)=1B\displaystyle P(b_i = b_j) = \frac{1}{B}
一句话:两层独立均匀分桶时,同一用户恰好撞进同号桶的概率只有 1/B,这是层间正交的量化体现。
  1. 第 i 层桶号 b_i用户在第 i 层分到的桶号,由该层盐值决定。
    bi=Hash(user_id⊕salti) mod B\displaystyle b_i = \text{Hash}(\text{user\_id} \oplus \text{salt}_i) \bmod B
  2. 第 j 层桶号 b_j同一位用户在第 j 层分到的桶号,盐值不同,结果独立。
    bj=Hash(user_id⊕saltj) mod B\displaystyle b_j = \text{Hash}(\text{user\_id} \oplus \text{salt}_j) \bmod B
  3. 相等概率两层独立均匀时,两号恰好相等的概率为 1/B;B=100 时仅有 1%,说明两层几乎不会“撞桶”。
    P(bi=bj)=1B\displaystyle P(b_i = b_j) = \frac{1}{B}

工程落地实践

店越开越大,小川一个人记不住所有规矩了。他干脆把分桌规则写成一本手册:顾客进门→扫码取号→系统算号→LED 屏亮桌号。碰上周年庆要临时加开 A 桌,他不再手忙脚乱,改一行配置就能把 A 桌比例从 10% 调到 20%。

工程上的分桶系统也是同样一条流水线:请求进入分桶引擎→取流量单元→调哈希函数→按当前实验配置查表→返回桶号。配置中心统一管理每个实验的桶号区间和盐值,改配置即时生效,不需要重新发版。

两个工程要点值得强调:一是动态配比——通过“桶号 < p×B”把前 p 比例的桶划给实验组,灰度放量只需调 p;二是一致性哈希——当桶数 B 变化(扩容或缩容)时,传统取模会让几乎所有用户的桶号跳变,而一致性哈希让大多数用户保持原桶,大幅减少用户被卷入或踢出实验的震荡。最后,健康监控要盯着各桶人数、进出桶日志、异常波动,确保分桶在线上不出幺蛾子。

手册越写越厚,但小川心里有数:有了标准流程、稳定哈希和随时可调的配置,无论来多少顾客、开多少实验,分桌这件事都井井有条。

组={实验组b<p×B对照组b≥p×B,0≤p≤1\displaystyle \text{组} = \begin{cases} \text{实验组} & b < p \times B \\ \text{对照组} & b \geq p \times B \end{cases}, \quad 0 \le p \le 1
一句话:桶号小于 p×B 的用户进实验组,其余进对照组——调 p 就能控制灰度流量比例。
  1. 流量比例 p实验组要占的流量比例,灰度放量时从 0.01 逐步调大到 1。
    0≤p≤1\displaystyle 0 \le p \le 1
  2. 分界桶号p 乘以桶数 B 得到分界值,小于它的桶归实验组。
    p×B\displaystyle p \times B
  3. 实验组条件桶号小于分界值的用户进入实验组,接受新策略。
    b<p×B\displaystyle b < p \times B
  4. 对照组条件其余用户留在对照组,维持旧策略,两组按比例共存。
    b≥p×B\displaystyle b \geq p \times B

AI 应用场景

推荐系统新模型灰度放量

阶段:模型评估与灰度上线

解决的问题:传统做法是全体用户直接切换新排序模型,出问题才回滚,风险大且无法量化提升。用哈希分桶按 user_id 将流量按 1%→5%→50% 逐步放给新模型,对照组永远保留老模型,两组公平对比点击率与时长指标。

具体案例:某资讯 App 的推荐团队给新召回模型配置盐值 'rec_v2_2024',桶数 100,先把 p=0.05 的桶划给实验组,观察 7 天指标与桶均衡,确认无异常后逐步放量到 100%。

电商大促双实验分层并行

阶段:在线实验设计

解决的问题:首页改版和优惠券策略两个实验都想在大促前验证,若各占 50% 流量就需要两倍用户;用分层分桶让同一批用户同时参与两个实验,层间独立、互不干扰。

具体案例:增长团队开两个层:层 1 跑首页新布局实验(盐 'home_v3'),层 2 跑券门槛实验(盐 'coupon_v2')。同一用户可能在层 1 进实验组、层 2 进对照组,两层独立对比,互不污染。

LLM 新版本回复质量评测

阶段:模型评测与回归

解决的问题:大模型版本升级无法离线完全验证,需要在线抽样对比新旧版本回复质量;直接用时间切分会因时段流量差异带来偏差,用一致性哈希让同一用户稳定命中同一版本,保证评测环境一致。

具体案例:客服机器人团队给 'gpt-4o-mini' 与 'gpt-4.1-mini' 两个版本各配 50% 桶,扩容时用一致性哈希保持用户不换版本,配合满意度打分对比两版效果。

Python 演示

python40 行
import hashlibfrom collections import Counter def hash_bucket(user_id, salt, bucket_count=100):    # 哈希分桶: user_id 拼上盐值, MD5 指纹对桶数取模    key = f"{user_id}:{salt}".encode("utf-8")    digest = int(hashlib.md5(key).hexdigest(), 16)    return digest % bucket_count def assign_group(user_id, salt, experiment_ratio=0.5, bucket_count=100):    # 前 p 比例的桶给实验组, 其余给对照组    bucket = hash_bucket(user_id, salt, bucket_count)    return "experiment" if bucket < experiment_ratio * bucket_count else "control" def chi_square(bucket_counts):    # 卡方检验: 观察人数与均匀期望人数的偏差    n = sum(bucket_counts)    expected = n / len(bucket_counts)    return sum((c - expected) ** 2 / expected for c in bucket_counts) def main():    users = list(range(10000))    buckets = [hash_bucket(u, "main", 10) for u in users]    counts = [0] * 10    for b in buckets:        counts[b] += 1    print("10 buckets:", counts)    print("chi2:", round(chi_square(counts), 2))     groups = Counter(assign_group(u, "main") for u in users)    print("groups:", dict(groups))     # 分层分桶: 同一批用户, 两个层用不同盐值独立分桶    layer1 = [hash_bucket(u, "exp1") for u in users[:1000]]    layer2 = [hash_bucket(u, "exp2") for u in users[:1000]]    same = sum(a == b for a, b in zip(layer1, layer2))    print("same bucket ratio:", round(same / 1000, 4)) if __name__ == "__main__":    main()
目的:用 30 行代码演示完整的流量分桶链路——哈希分桶、按比例分组、卡方均衡性验证与分层分桶的层间正交。
  1. def hash_bucket(user_id, salt, bucket_count=100):定义哈希分桶函数,任何 ID 与盐值组合都能映射到一个稳定的桶号。
  2. key = f"{user_id}:{salt}".encode("utf-8")把用户 ID 和盐值拼成一个字符串再编码,盐值由此参与哈希。
  3. digest = int(hashlib.md5(key).hexdigest(), 16)用 MD5 生成 128 位十六进制指纹并转成整数,这就是“搅拌机”的输出。
  4. return digest % bucket_count取模把大整数压到 0 到 bucket_count-1 的桶范围。
  5. return "experiment" if bucket < experiment_ratio * bucket_count else "control"按比例把前 p 的桶划给实验组,对应动态配比公式。
  6. expected = n / len(bucket_counts)计算均匀假设下的期望人数 N/B,是卡方检验的基准。
  7. return sum((c - expected) ** 2 / expected for c in bucket_counts)卡方统计量:逐桶累加相对偏差,值越大说明越失衡。
  8. layer1 = [hash_bucket(u, "exp1") for u in users[:1000]]同一批用户用不同盐值 'exp1' 测第一层分桶。
  9. same = sum(a == b for a, b in zip(layer1, layer2))统计两层撞桶次数,验证层间正交(约 1% 为正常)。

常见误区

误区“我直接用 user_id % 10 分桶,看起来每个桶都有 10% 的人,这不就是公平的随机分配吗?还需要哈希干什么?”

正解user_id 往往不是随机数:注册时间递增、批量导入、连续分配等规律会让相邻 ID 挤进同一个桶。比如某天导入了 1 万个连续 ID,它们会全部落在同一个桶里,实验组和对照组的用户构成立刻失衡。哈希函数的作用是把这种“天然顺序”打散,让相邻 ID 也落到不同的桶,再用取模缩放到桶范围。先哈希再取模,才是可靠的随机化。

为何常见:取模“看起来”均匀,且不引入任何额外计算,初学者最容易跳过哈希这一步。

误区“哈希函数的数学性质保证了输出均匀,各桶人数天然相等,均衡性验证是多余的。”

正解哈希保证的是“长期、大量”样本下的统计均匀,不是每一小批都均匀。短时段内流量可能受推广活动、数据导入影响而偏斜;而且如果盐值配错、流量单元字段取错,哈希再均匀也白搭。上线前和实验期间都要用卡方检验等手段盯着各桶人数,发现偏差及时排查。

为何常见:把概率上的“最终会均匀”误解成“每一刻都均匀”,忽视了样本量和监控的作用。

误区“分层嘛,就是把 100% 流量切成几块,比如 30% 给实验 A、40% 给实验 B,各玩各的。”

正解真正的分层分桶恰恰相反:每一层都拿到 100% 流量,再独立重分一次。同一用户既在层 1 参与锅底实验,又可以在层 2 参与蘸料实验——这是“复用”而不是“切分”。切分流量每个实验只能分到一部分用户,统计功效低;分层复用让每个实验都能用全量流量。

为何常见:“分层”这个中文词容易让人联想到“分蛋糕”,而实际模型是“多套分桌规则叠在同一批顾客身上”。

误区“盐值就是个拼字符串的随机数,取什么、换不换都无所谓,反正哈希函数会把它打乱。”

正解盐值直接决定层间正交和实验可复现性。两个层用了相同盐值,两层分桶结果强相关,实验效果串味;实验进行中更换盐值,等于把用户重新洗牌,之前分配好的实验组全部作废,统计结果无法延续。盐值选取要唯一、固定,并写进配置中心管理。

为何常见:盐值在公式里只是个不起眼的参数,初学者低估了它对正交性和稳定性的决定性作用。

误区“两个实验如果要隔离,就分别抽取互不重叠的用户群,各测各的。”

正解隔离针对的是“同层内会互相影响的实验”,把它们放在同一层互斥:同一用户在一层里只能命中一个实验。而不同层的实验正是希望重叠的——同一个用户同时参与两个实验,才能高效复用流量。若把人群物理隔开,每个实验的用户量骤减,统计功效下降,也违背了分层分桶的初衷。

为何常见:把“隔离”理解成了物理隔离,忽略了实验设计里“同层互斥、跨层重叠”的精确含义。

面试题

Q1. 哈希分桶中,桶号的计算方式是?

单选题 · easy

  • A.用户ID直接除以10取余
  • B.哈希值对桶数取模← 正确答案
  • C.随机数对桶数取模
  • D.时间戳对桶数取模

答案:B。正确的分桶是“先哈希后取模”:用哈希函数把 user_id 打散成均匀的大整数,再对桶数 B 取模得到 0 到 B-1 的桶号。直接取模无法规避 ID 序列规律,随机数和时间戳不稳定,都会导致同一用户反复换桶。好比火锅店迎宾员用“搅拌机”(哈希)打散号码牌,而不是按号码顺序硬分。

Q2. 分桶时最常用的流量单元是?

单选题 · easy

  • A.用户唯一标识ID← 正确答案
  • B.设备的IP地址
  • C.浏览器的版本号
  • D.操作系统的类型

答案:A。流量单元是分桶的“号码牌”,最常见的是 user_id(或 device_id)。IP 地址会随网络变化、同一局域网共享出口,浏览器和系统版本更是与用户身份无关。选错流量单元会让同一用户反复横跳,实验数据失去独立性。

Q3. 分桶结果应当满足哪种性质?

单选题 · easy

  • A.每次请求都不同
  • B.同一用户保持稳定← 正确答案
  • C.同桶人数差异大
  • D.桶号随时间增长

答案:B。分桶的底线是“同一用户每次请求都进同一个桶”,否则用户行为会同时污染两个实验组,统计结论不可信。稳定性本质上来自哈希的确定性与盐值固定,缺一不可。

Q4. 分层分桶中“层间正交”指的是?

单选题 · mid

  • A.各层用户互不重叠
  • B.各层分桶结果相互独立← 正确答案
  • C.各层实验必须互斥
  • D.各层桶数必须相同

答案:B。层间正交 = 每一层用不同盐值独立重分,同一用户在各层的桶号互不相关,因此可以同时参与不同层的实验。用户互不重叠是错误理解(层间正是要重叠复用);同层内实验才互斥;桶数也不必相同。

Q5. 均衡性验证中,各桶期望人数如何计算?

单选题 · mid

  • A.总人数除以桶数← 正确答案
  • B.桶数除以总人数
  • C.人数最多的桶的值
  • D.人数最少的桶的值

答案:A。卡方检验中的期望值 E_i = N / B,即假设完全均匀时每桶应有的人数。它用“实际人数 O_i 偏离期望值 E_i 的程度”来量化不均衡。B 除以 N 是倒数,取最大或最小桶都不符合均匀假设。

Q6. 盐值(Salt)在分桶中的作用是?

单选题 · mid

  • A.增加分桶的桶数量
  • B.区分不同实验的分配← 正确答案
  • C.加密用户的敏感信息
  • D.加速哈希计算的效率

答案:B。盐值是拼在 user_id 后面的固定字符串,让不同实验或层的哈希结果各不相同,从而保证层间正交与实验间互不干扰。它不改变桶数,不是加密,也不参与性能优化。

Q7. 同一层内两个互斥实验意味着?

单选题 · mid

  • A.同一用户只能进入其中一个← 正确答案
  • B.同一用户可同时进入两个
  • C.两个实验必须使用相同策略
  • D.两个实验共享全部用户流量

答案:A。层内互斥是隔离的核心:同一层里,一个用户只能命中一个实验,避免两个改动在同一用户身上“串味”、无法归因。可同时进入是跨层重叠的特征,共享全部用户则会同时施加两个改动,恰恰是互斥要避免的。

Q8. 一致性哈希主要解决分桶的什么问题?

单选题 · hard

  • A.桶数变化时分配稳定← 正确答案
  • B.加快哈希运算速度
  • C.增加可用的桶数量
  • D.减少存储的空间开销

答案:A。传统取模哈希在桶数变化(扩缩容)时会让几乎所有用户桶号跳变,用户被大规模卷入或踢出实验。一致性哈希让大多数用户保持原桶归属,只有少数受影响,适合灰度放量等需要动态调整桶数的场景。

Q9. 分桶均衡性验证的常用手段有?(多选)

多选题 · hard

  • A.用卡方检验量化偏离← 正确答案
  • B.对比各桶的实际人数← 正确答案
  • C.让所有桶人数完全相同
  • D.检查桶号是否连续递增

答案:AB。均衡性验证通过“观察人数 vs 期望人数”来把关:卡方检验给出客观判据,直接对比各桶人数是最直观的检查。要求所有桶人数完全一致不现实也不必要,小样本波动正常;桶号连续递增与均衡无关。

Q10. 关于盐值设计与层间正交,正确的有?(多选)

多选题 · extreme

  • A.两层使用相同盐值会破坏正交← 正确答案
  • B.盐值固定则分桶结果可复现← 正确答案
  • C.实验中途更换盐值会重新分桶← 正确答案
  • D.盐值越长分桶结果越均匀

答案:ABC。相同盐值会让两层分桶强相关,正交性被破坏;盐值固定时哈希结果确定,这是可复现的前提;换盐等于重新洗牌,实验中途绝不能换。均匀性由哈希函数的性质决定,与盐值长度无关,D 错误。