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

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

一.引言

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

二.实现原理

1. id 加盐

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

saltNum: Int + id: String => String

image.gif

2.MD5 编码

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

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

image.gif

3.字节转16进制

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

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

image.gif

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

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

image.gif

4.16进制转10进制

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

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

image.gif

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

5.Hash 获取新分组

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

6.完整实现

A.MD5 Hash

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

image.gif

B.Common Hash

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

image.gif

三.效果评估

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

-> id 分组均匀程度

-> id 分组AB效果程度

-> 分组速度

三个方面进行评估。

1.分组均匀程度

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

image.gif编辑

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

2.分组效果均匀程度

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

image.gif编辑

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

3.分组速度

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

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

image.gif

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

四.总结

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

目录
相关文章
|
4月前
|
分布式计算 大数据 Java
大数据-86 Spark 集群 WordCount 用 Scala & Java 调用Spark 编译并打包上传运行 梦开始的地方
大数据-86 Spark 集群 WordCount 用 Scala & Java 调用Spark 编译并打包上传运行 梦开始的地方
73 1
大数据-86 Spark 集群 WordCount 用 Scala & Java 调用Spark 编译并打包上传运行 梦开始的地方
|
5月前
|
Java Go PHP
Java分组匹配
Java分组匹配
37 5
|
6月前
|
存储 算法 Java
在Java中使用MD5对用户输入密码进行加密存储、同时登录验证。
这篇文章详细介绍了在Java项目中如何使用MD5算法对用户密码进行加密存储和登录验证,包括加入依赖、编写MD5工具类、注册时的密码加密和登录时的密码验证等步骤,并通过示例代码和数据库存储信息展示了测试效果。
在Java中使用MD5对用户输入密码进行加密存储、同时登录验证。
|
6月前
|
算法 Java
【Java集合类面试十八】、ConcurrentHashMap是怎么分段分组的?
ConcurrentHashMap通过分段锁(Segment)实现高效并发访问,get操作无需加锁,而put操作首先判断是否需要扩容,然后通过两次hash定位并尝试使用CAS和锁机制安全地添加元素。
|
6月前
|
安全 Java 数据库连接
|
6月前
|
Java C# 数据安全/隐私保护
如何 使 Java、C# md5 加密的值保持一致
如何 使 Java、C# md5 加密的值保持一致
96 0
|
7月前
|
算法 安全 Java
Java中MD5加密算法的原理与实现详解
Java中MD5加密算法的原理与实现详解
|
7月前
|
SQL Java 数据处理
实时计算 Flink版产品使用问题之使用MavenShadePlugin进行relocation并遇到只包含了Java代码而未包含Scala代码,该怎么办
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
7月前
|
存储 算法 Java
分布式自增ID算法---雪花算法(SnowFlake)Java实现
分布式自增ID算法---雪花算法(SnowFlake)Java实现
358 0
### Cause: java.sql.SQLException: Field ‘id‘ doesn‘t have a default value; Field ‘id‘ doesn‘t have
### Cause: java.sql.SQLException: Field ‘id‘ doesn‘t have a default value; Field ‘id‘ doesn‘t have