人群创建的基础:画像标签BitMap

2023-10-17 21:38:43 浏览数 (1)

​上文提到了使用画像宽表可以便捷的创建人群,本文介绍人群创建所依赖的另外一种数据组织形式:标签BitMap。

使用画像宽表圈人的逻辑是从明细数据中找到满足条件的用户并最终构建人群,而使用BitMap进行圈人会对用户进行预聚合,在人群圈选时直接使用聚合后的结果进行计算。首先将指定标签值下的所有用户聚合后生成BitMap,然后基于这些BitMap执行交、并、差操作实现人群筛选。图5-8展示了基于宽表和BitMap进行人群圈选的功能示意图,两种方式最终产出的人群相同。

图5-8 使用宽表和BitMap进行人群圈选示意图图5-8 使用宽表和BitMap进行人群圈选示意图

BitMap特殊的数据结构决定了其适合做用户聚合并应用到人群圈选场景下。BitMap底层构建了一个bit数组,bit每一位只能存储1或者0,其中数组的索引值映射到UserId,当前索引上的数字是1的时候代表对应的UserId存在,是0的时候代表UserId不存在。图5-9展示了BitMap存储UserId的基本逻辑,UserId不再是一个具体数字而是映射到位数组的索引值上面,借助这一特点可以实现大量UserId数字的压缩、去重、排序和判存。

图5-9 BitMap存储UserId基本原理图5-9 BitMap存储UserId基本原理

将大量的UserId写入BitMap时,因为相同的UserId所对应的索引位置一样,可以自动实现人群UserId的去重;bit数组索引天然有序,人群UserId写入BitMap可以实现便捷排序;判存是判断UserId是否在人群中,通过判断bit数组指定索引位置的数值是否为1便可以快速判断出UserId是否存在。BitMap以上特点都非常适合存储人群数据,也决定了其在画像平台的广泛使用。

基于Hive标签数据表可以生成BitMap,图5-10展示了性别和常住省标签生成BitMap的示意图。首先基于标签明细数据聚合生成标签值BitMap数据,其执行结果会存储在Hive表中;其次将已经生成的标签值BitMap的Hive表数据写入到ClickHouse表中,该操作可以提高后续查询BitMap的效率;最后在人群创建过程中从数据表中查询出BitMap并计算出人群数据。

图5-10 通过标签Hive表数据生成BitMap图5-10 通过标签Hive表数据生成BitMap

BitMap是一种位图映射方案,其具体实现方式有多种,在Java语言中可以使用RoaringBitMap进行工程开发。图5-11展示了由标签Hive表生成标签BitMap表的过程中所使用的主要的技术,不同环节涉及BitMap不同数据形式的转换。

图5-11 标签BitMap生成流程图图5-11 标签BitMap生成流程图

Hive表数据转为RoaringBitMap依赖开源工具包hive-bitmap-udf.jar,其中UDF函数to_bitmap可以将UserId列表转换为RoaringBitMap对象并以binary格式存储到Hive表中。工具包中还包含常用的UDF函数:bitmap_count、bitmap_and和bitmap_or等,可以便捷地对BitMap进行各类操作。Hive表中的BitMap数据经由Spark等大数据引擎批量处理后写入ClickHouse表中。ClickHouse中没有binary数据类型,一般通过string类型承接Hive中的binary数据。使用byteToString函数可以将Hive表的bitmap数据转换为string类型,其实现原理是将binary数据转换为byte[],然后通过BASE64编码成string。从ClickHouse中读取到string类型的bitmap数据,借助bytesToBitMap函数可以实现string到RoaringBitMap的转换。多个RoaringBitMap可以在内存中直接进行交、并、差操作,最终实现人群的创建。

Hive表数据生成BitMap 的SQL代码如下所示,通过引入工具包并调用其中的to_bitmap函数将gender下的所有UserId转换为binary格式,并将数据并写入Hive数据表中。

代码语言:javascript复制
-- 引入UDF工具包 --
ADD JAR hdfs://userprofile-master:9000/hive-bitmap-udf.jar;
CREATE TEMPORARY FUNCTION to_bitmap AS 'com.hive.bitmap.udf.ToBitmapUDAF';
-- 将数据写入BitMap数据表 --
INSERT OVERWRITE TABLE userprofile_demo.gender_label_bitmap PARTITION(p_date = '2022-08-01')
SELECT
gender,
to_bitmap(user_id)
FROM
userprofile_demo.gender_label
WHERE
p_date = '2022-08-01'
GROUP BY
gender

byte[],string以及RoaringBitMap之间核心转换代码如下所示,通过不同函数的灵活搭配使用可以实现Hive与ClickHouse、数据存储与内存之间数据类型的转换。

代码语言:javascript复制
// 字节码数组转string
public static String bytesToString(byte[] bytes) throws IOException {
return Base64.getEncoder().encodeToString(bytes);
}
// 字符串转字节数组
public static byte[] stringToBytes(String str) throws IOException {
return Base64.getDecoder().decode(str);
}
// 字节数组转Roaring64Bitmap 
public static Roaring64Bitmap bytesToBitMap(byte[] bytes) throws IOException {
Roaring64Bitmap bitmapValue = new Roaring64Bitmap();
DataInputStream in = new DataInputStream(new ByteArrayInputStream(bytes));
bitmapValue.deserialize(in);
in.close();
return bitmapValue;
}
// Roaring64Bitmap 转字节数组
public static byte[] bitMapToBytes(Roaring64Bitmap bitmap) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
bitmap.serialize(dos);
dos.close();
return bos.toByteArray();
}

并不是所有的画像标签都适合转换为BitMap,只有标签值可枚举且数量有限的标签才适合转换为BitMap来支持人群圈选。性别标签有男、女、未知三个标签值,标签值之间有明显的区分度,生成BitMap之后每个BitMap被使用的概率也较高,其比较适合构建标签BitMap。对于在线时长、粉丝数等数值型标签,其标签值不可枚举或者数量庞大,标签值之间没有明显的区分度,此类标签不适合构建BitMap。生成BitMap会消耗大量的计算和存储资源,如果标签值区分度较小,生成的BitMap数据被使用到的概率较低,是对计算和存储资源的浪费。

使用画像宽表还是BitMap要根据业务特点来决定。基于宽表中全量用户的明细数据可以实现所有的人群圈选功能,但是采用BitMap方案的人群创建速度相比宽表模式可以提升50%以上。BitMap适用的标签类型和业务场景有限,要结合实际的数据进行判断。业界一般使用混合模式,优先通过BitMap进行人群创建,不适用的场景下兜底使用画像宽表进行人群圈选。采用混合模式要考虑对齐画像宽表和BitMap的标签时间,这增加了工程的实现复杂度。


本文节选自《用户画像:平台构建与业务实践》,转载请注明出处。

0 人点赞