• Scala / Java - 采用 MD5 加盐 实现 id 均匀分组


    一.引言

    大量 id 场景下经常需要通过 id 进行 AB Test,最常见的就是使用尾号 hash 进行分组,但是由于 id 生成规则以及其他因素,按照尾号分组往往会造成 id 不匀,从而导致 AB Test 效果受影响,所以下文采用 md5 加盐 Hash 的方式,得到更均匀的分组与 AB Test 效果。

    二.实现原理

    1. id 加盐

    id 即为用户 uid 或商品 pid,加盐中盐代表盐值,可以指定为任一质数,id 加盐可以理解:

    saltNum: Int + id: String => String 

    2.MD5 编码

    通过 MD5 编码将上述加盐的 id 进行编码处理,获取加密后的字节形式,md5 包采用 java 自带的:

    1. import java.security.MessageDigest
    2. val md5 = MessageDigest.getInstance("MD5")
    3. val encoded = md5.digest(saltNum + id)

    3.字节转16进制

    将加密后的每个字节转16进制,转换16进制采用 org.apache.commons 自带工具包:

    1. import org.apache.commons.codec.binary.Hex
    2. val encodeStr = Hex.encodeHexString(encoded)

    也可以直接使用 String 自带的 format 方法实现转换16进制:

    val encodeStr = encoded.map("%02x".format(_)).mkString("")
    

    4.16进制转10进制

    将16进制数字截取 TopN,然后将16进制转换为10进制

    val num = java.lang.Long.parseLong(encodeStr.slice(0, N), 16).toString
    

    直接取 TopN 并通过 parseLong 得到新的 10 进制数字。

    5.Hash 获取新分组

    通过新的十进制数字取尾号 hash,获取新的分组,上面得到 10 进制数字 num,可以再使用尾号划分,例如对倒数两位取 mod,即可得到 100 个分组,对倒数三位取 mod,即可得到 1000 个分组,依次类推。

    6.完整实现

    A.MD5 Hash

    1. def md5Encode(id: String, saltNum: Int, N: Int): String = {
    2. val input = saltNum + id // 加盐
    3. val md5 = MessageDigest.getInstance("md5")
    4. val encoded = md5.digest(input.getBytes) // md5 编码
    5. val encodeStr = Hex.encodeHexString(encoded) // 转16进制
    6. val num = java.lang.Long.parseLong(encodeStr.slice(0, N), 16).toString // 转10进制
    7. val group = num.slice(num.length - 2, num.length).toInt % 100 // hash
    8. group.toString
    9. }

    B.Common Hash

    1. def commonHash(num: String): String = {
    2. val group = num.slice(num.length - 2, num.length).toInt % 100 // hash
    3. group.toString
    4. }

    三.效果评估

    对 uid、pid 重新分组主要是为了提高 AB Test 的置信度,而且涉及到工程实现即每个 id 都需要获取对应的 group,所以下面从:

    -> id 分组均匀程度

    -> id 分组AB效果程度

    -> 分组速度

    三个方面进行评估。

    1.分组均匀程度

    由于 uid、pid 为系统生成,一定程度上不能做到完美的 hash 均分,所以需要重 hash 解决,下面分别使用 MD5 Hash 与 Common Hash 做 id 数的分析,指标: [分组 id 数 - 分组 id 平均数]

    绿线为 MD5,红线为 CommonHash,可以看到 MD5 得到的 100 个分组 id 数相对 CommonHash 分组均匀很多,前者 Std 为 1100+,后者 Std 达到 4000+。

    2.分组效果均匀程度

    分组均分后,还要验证下效果是否一致,如果 id 数相同但是同组的 id 表现差异很大,对 AB Test 也会造成很大影响,这里采用 Pid 的销售额作图,指标: [pid 销售额 - pid 销售额均值]

    绿线为 MD5,红线为 CommonHash,可以看到 pid 在 MD5 hash 后整体表现均匀,而原始的 CommonHash 则存在个别组出现极端坏数据的情况,影响 AB Test。

    3.分组速度

    构造 10000 个 id 模拟 Pid,打印执行时间比较:

    1. val random = scala.util.Random
    2. val testId = (0 to 10000).map(x => random.nextLong()).toArray
    3. val st = System.currentTimeMillis()
    4. testId.foreach(num => {
    5. // md5Encode(num.toString, saltNum, N)
    6. commonHash(num.toString)
    7. })
    8. println(s"cost: ${System.currentTimeMillis() - st}")

    MD5 耗时 220ms / 10000,CommonHash 耗时 45ms / 10000,前者大约是后者的 5 倍,但是均匀到 id 上 0.022 ms  / id 的耗时也是可以接受的,所以耗时虽然比 CommonHash 慢5倍,但是工业场景下也基本不受影响。

    四.总结

    经过上面的分析,该使用什么分组 AB Test 不用我说了吧。

  • 相关阅读:
    ROS-serial串口包的数据原始模式使用方法
    聚星文社工具【聚星文社】Ai小说推文一键生成工具大更新:一键出图、一键改文、一键关键帧、MJ+SD推文神器
    Linux c++ 中文字符转十六进制 UTF-8 编码
    【玩转 Cloud Studio】简单谈一下用户体验
    【无标题】
    【C语言进阶】自定义类型——结构体的位段&&枚举&&联合体
    Python旅游门票收费问题
    win11不兼容,咋办啊大家,我的世界下载16位不兼容我试了好多种办法不管用,希望有懂得,可以帮助一下
    【AUTOSAR】【以太网】SomeIpXf
    2.4 PE结构:节表详细解析
  • 原文地址:https://blog.csdn.net/BIT_666/article/details/126170625