G. B.

个人博客

且将新火试新茶,诗酒趁年华


ABTest灰度上线

我们都知道ABTest主要是围绕用户进行的实验,从统计意义上观察用户对不同的产品设计、交互体验、业务流程的反馈,从而指导产品的改进方向。那么很重要的一点就是如何进行分桶。与已知样本总量去切分比例不同,灰度上线时我们不知道用户总量会是多少,因此在线上如何实现具体的切分算法十分关键。

一种错误做法

通过UserID的尾数进行划分。这种设计只能进行单层实验,如果考虑长期交叉、连续的实验,这样做有很大的问题。

如果在同一分桶中同时进行了X和Y两个实验,那么实验结果就会相互干涉,结果变得不好解释。

一种正确做法

目前业界使用最多的就是可重叠分层分桶方法。将流量分成可重叠的多个层,保证流量在不同层中是正交的,即一个用户在每个层中应该分到哪个桶中是独立不相关的。具体来说,如果是二分桶的实验,上一层在一个桶中的用户,理论上应该均匀分布在下一层的两个桶里。这样一份流量通过N个层可以同时进行N个实验,而且实验之间互不干扰,可以提升流量使用率。

graph LR id1([userid]) id2(prefix + userid) id3(hash为byte) id4(取前8字节) id5(转为整数) id6(8字节能表示的最大整数为18446744073709551615) id7(求除法将结果与阈值比较得出灰度分组) id1 --> id2 --> id3 --> id4 --> id5 --> id7 id6 --> id7

如此的伪随机分配算法,可以实现实验的复现,同一个用户,无论多少次进入同一个实验,都被会分配进同一个组别。同时,通过给用户名前添加前缀,可以保证不同实验在切分时,同一个用户不会都被分进测试组中,造成分桶有偏的问题。

Python实现

from hashlib import sha256


def gray_release(prefix: str, key: str, low: float, high: float) -> bool:
    return low <= int(sha256((prefix + key).encode('ascii')).digest()[:8].hex(), 16) / (0xffffffffffffffff + 1) < high

下面模拟数据对该函数进行验证:

from collections import Counter


x = ['A' if gray_release('test', str(x), 0, .5) else 'B' for x in range(10000)]
Counter(x)
# Counter({'A': 5025, 'B': 4975})
pie title 切分结果 "A": 5025 "B": 4975

可以观察到切分结果还算均衡。

Java实现

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;


public class App {

    public static boolean gray_release(String prefix, String key, double low, double high) {
        String input = prefix + key;
        double result = 0;
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("SHA-256");
            byte[] messageDigest = md.digest(input.getBytes());
            byte[] firstUnsignedLong = new byte[8];
            for (int i = 0; i < 8; i++) {
                firstUnsignedLong[7 - i] = messageDigest[i];
            }
            BigInteger numerator = new BigInteger(1, firstUnsignedLong);
            BigInteger denominator = new BigInteger("18446744073709551616", 10);
            result = numerator.doubleValue() / denominator.doubleValue();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return result >= low && result < high;
    }

    public static void main(String[] args) {
        System.out.println(gray_release("test", "326357626", 0, .5));
    }
}

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦