NingG +

实验平台:设计--分桶

实验平台,具体落地过程,涉及到一些通用问题,当前 blog 将进行详细的讨论:

分流路由策略(分桶)

分桶的作用?

分桶的实现细节:

从应用角度,几个考虑点:

  1. 同一个 userId,在不同的访问时间,是否命中相同的实验和分组?
    1. 用户体验的一致性

针对所有的流量,常见分流路由策略:

分流路由参数介绍:

  1. 流量完全随机:random
    1. 最简单的分流方式,流量随机命中各个实验和分组;
  2. 用户标识id 的哈希:hash(userId)
    1. 同一个用户会一直命中同一实验,从而保证了用户体验的一致性;
    2. 对同一用户具有累积效应的策略的实验需求;
  3. 用户标识id+日期的哈希:hash(userId_day)
    1. 流量划分在跨时间维度上更为均匀;
    2. 用户请求,跨时间区间时,用户体验不一致;
  4. 用户id尾号划分:userId 尾号
    1. 常用、很简单的分流方式,保证了用户体验的一致性;(用户的累计效应
    2. 可能会由于尾号分布的不均匀性而造成流量分布的不够均匀,影响实验的可对比性;
    3. 一种折衷方案是,根据用户标识 id「中间若干位」来进行流量的划分;

Note:

常用 Hash 算法:

分桶实现细节

分桶的初始化、分配、调整、释放,都是在 Experiment Server 侧进行,核心过程,参考下文示意图:

在 Experiment Client 侧,「使用分桶」的示意图:

分层 & 分桶的核心算法(判断流量命中哪个分桶):

/**
 * 分流策略(计算流量对应分桶的编号)
 *
 * @param paramId 参数Id: 流量的标识
 * @param layerId 分层Id
 * @return 流量对应分桶编号.
 */
public static int getBucketNo(String paramId, Integer layerId) {
    // 计算 MD5
    String destKey = (StringUtils.isNotBlank(paramId) ? paramId : "") + String.valueOf(layerId);
    String md5Hex = DigestUtils.md5Hex(destKey);
 
    long hash = Long.parseUnsignedLong(md5Hex.substring(md5Hex.length() - 16, md5Hex.length() - 1), 16);
    if (hash < 0) {
        hash = hash * (-1);
    }
 
    // 取模
    return (int) (hash % BUCKET_TOTAL_NUM);
}

参考资料

Top