目录
注:此博文为根据 赵宏田 老师的 用户画像·方法论与工程化解决方案 一书读后笔记而来,仅供学习使用
0. 相关文章链接
用户画像文章汇总
1. Hive存储
1.1. Hive数据仓库
建立用户画像首先需要建立数据仓库,用于存储用户标签数据。 Hive是基于Hadoop的数据仓库工具,依赖于HDFS存储数据,提供的SQL 语言可以查询存储在HDFS中的数据。开发时一般使用Hive作为数据仓 库,存储标签和用户特征库等相关数据。
“数据仓库之父”W.H.Inmon在《Building the Data Warehouse》一书中定义数据仓库是“一个面向主题的、集成的、非易失的、随时间变化的、用来支持管理人员决策的数据集合”。
数据抽取到数据仓库的流程如下图所示:
在数据仓库建模的过程中,主要涉及事实表和维度表的建模开发如下图所示:
维度表主要用于对事实属性的各个方面描述,例如,商品维度包 括商品的价格、折扣、品牌、原厂家、型号等方面信息。维度表开发 的过程中,经常会遇到维度缓慢变化的情况,对于缓慢变化维一般会 采用:①重写维度值,对历史数据进行覆盖;②保留多条记录,通过 插入维度列字段加以区分;③开发日期分区表,每日分区数据记录当 日维度的属性;④开发拉链表按时间变化进行全量存储等方式进行处 理。在画像系统中主要使用Hive作为数据仓库,开发相应的维度表和事实表来存储标签、人群、应用到服务层的相关数据。
1.2. 分区存储
如果将用户标签开发成一张大的宽表,在这张宽表下放几十种类 型标签,那么每天该画像宽表的ETL作业将会花费很长时间,而且不便 于向这张宽表中新增标签类型。
要解决这种ETL花费时间较长的问题,可以从以下几个方面着手:
- 将数据分区存储,分别执行作业;
- 标签脚本性能调优;
- 基于一些标签共同的数据来源开发中间表。
根据标签指标体系的人口属性、行为属性、用户消费、风险控制、社交属性等维度分别建立对应的标签表进行分表存储对应的标签数据。
- 人口属性表:dw.userprofile_attritube_all;
- 行为属性表:dw.userprofile_action_all;
- 用户消费表:dw.userprofile_consume_all;
- 风险控制表:dw.userprofile_riskmanage_all;
在上面的创建中通过设立人口属性维度的宽表开发相关的用户标签,为了提高数据的插入和查询效率,在Hive中可以使用分区表的方式,将数据存储在不同的目录中。在Hive使用select查询时一般会扫描整个表中所有数据,将会花费很多时间扫描不是当前要查询的数 据,为了扫描表中关心的一部分数据,在建表时引入了partition的概 念。在查询时,可以通过Hive的分区机制来控制一次遍历的数据量。
1.3. 标签汇聚
在上述介绍中,用户的每个标签都插入到相应的分区下面, 但是对一个用户来说,打在他身上的全部标签存储在不同的分区下面。为了方便分析和查询,需要将用户身上的标签做聚合处理。
汇聚后用户标签的存储格式如下图所示:
将用户身上的标签进行聚合便于查询和计算。例如,在画像产品中,输入用户id后通过直接查询该表,解析标签id和对应的标签权重 后,即可在前端展示该用户的相关信息(如下图所示):
1.4. ID-Mapping
开发用户标签的时候,有项非常重要的内容一一ID-Mapping,即把用户不同来源的身份标识通过数据手段识别为同一个主体。用户的属性、行为相关数据分散在不同的数据来源中,通过ID-Mapping能够把用户在不同场景下的行为串联起来,消除数据孤岛。如下图1展示了用户与设备间的多对多关系。图2展示了同一用户在不同平台间的行为示意图。

举例来说,用户在未登录App的状态下,在App站内访问、搜索相关内容时,记录的是设备id (即cookieid)相关的行为数据。而用户在登录App后,访问、收藏、下单等相关的行为记录的是账号id (即 userid)相关行为数据。虽然是同一个用户,但其在登录和未登录设 备时记录的行为数据之间是未打通的。通过ID-Mapping打通userid和 cookieid的对应关系,可以在用户登录、未登录设备时都能捕获其行为轨迹。
下面通过一个案例介绍如何通过Hive的ETL工作完成ID-Mapping的 数据清洗工作。
缓慢变化维是在维表设计中常见的一种方式,维度并不是不变的,随时间也会发生缓慢变化。如用户的手机号、邮箱等信息可能会 随用户的状态变化而改变,再如商品的价格也会随时间变化而调整上 架的价格。因此在设计用户、商品等维表时会考虑用缓慢变化维来开 发。同样,在设计ID-Mapping表时,由于一个用户可以在多个设备上 登录,一个设备也能被多个用户登录,所以考虑用缓慢变化维表来记 录这种不同时间点的状态变化(如下图)。

在上图中,通过拉链表记录了userid每一次关联到不同cookieid 的情况。如userid为44463729的用户,在20190101这天登录某设备, 在6号那天变换了另一个设备登录。其中start_date表示该记录的开始 日期,end_date表示该记录的结束日期,当end_date为99991231时, 表示该条记录当前仍然有效。
首先需要从埋点表和访问日志表里面获取到cookieid和userid同 时出现的访问记录。下面案例中,ods.page_event_log是埋点日志 表,ods.page_view_log是访问日志表,将获取到的userid和cookieid 信息插入cookieid-userid关系表(ods.cookie_user_signin)中。代 码执行如下:
INSERT OVERWRITE TABLE ods.cookie_user_signin PARTITION (data_date = '${data_date}')
SELECT
t.*
FROM (
SELECT
userid,
cookieid,
from_unixtime(eventtime,'yyyyMMdd') as signdate
FROM ods.page_event_10g --埋点表
WHERE data_date = '${data_date}'
UNION ALL
SELECT
userid,
cookieid,
from_unixtime(viewtime,'yyyyMMdd') as signdate
FROM ods.page_view_10g -- 访问日志表
WHERE data_date = '${data_date}'
) as t
创建ID-Map的拉链表,将每天新增到0ds.cookie_user_signin表中的数据与拉链表历史数据做比较,如果有变化或新增数据则进行更新。
CREATE TABLE 'dw.cookie_user_zippertable' (
'userid' string COMMENT '账号ID',
'cookieid' string COMMENT '设备ID',
'start_date' string COMMENT 'start_date',
'end_date' string COMMENT 'end_date'
) COMMENT 'id-map 拉链表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t
创建完成后,每天ETL调度将数据更新到ID-Mapping拉链表中,任务执行如下。
INSERT OVERWRITE TABLE dw.cookie_user_zippertable
SELECT
t.*
FROM (
SELECT
t1.user_num,
tl.mobile,
t1.reg_date,
t1.start_date,
CASE
WHEN t1.end_date = '99991231' AND t2.userid IS NOT NULL THEN '${data_date}'
ELSE t1.end_date
END AS end_date
FROM dw.cookie_user_zippertable t1
LEFT JOIN (
SELECT
*
FROM ods.cookie_user_signin
WHERE datadate='${datadate}'
) as t2
ON tl.userid = t2.userid
UNION
SELECT
userid,
cookieid,
'${data_date}' AS start_date,
'99991231' AS end_date
FROM ods.cookie_user_signin
WHERE datadate = '${datadate}'
) as t
数据写入表中,如上图所示。
对于该拉链表,可查看某日(如20190801)的快照数据。
select *
from dw.cookie_user_zippertable
where startdate<='20190801' and enddate>='20190801'
例如,目前存在一个记录userid和cookieid关联关系的表,但是 为多对多的记录(即一个userid对应多条cookieid记录,以及一条 cookieid对应多条userid记录)。这里可以通过拉链表的日期来查看 某个时间点userid对应的cookieid。查看某个用户(如32101029)在 某天(如20190801)关联到的设备id (如下图所示)。
select cookieid
from dw.cookie_user_zippertable
where userid='32101029' and startdate<='20190801' and enddate>='20190801
上图可看出用户‘32101029,在历史中曾登录过3个设备,通过限 定时间段可找到特定时间下用户的登录设备。
在开发中需要注意关于userid与cookieid的多对多关联,如果不 加条件限制就做关联,很可能引起数据膨胀问题。
在实际应用中,会遇到许多需要将userid和cookieid做关联的情况。例如,需要在userid维度开发出该用户近30日的购买次数、购买金额、登录时长、登录天数等标签。前两个标签可以很容易地从相应的业务数据表中根据算法加工出来,而登录时长、登录天数的数据存 储在相关日志数据中,日志数据表记录的userid与cookieid为多对多 关系。因此在结合业务需求开发标签时,要确定好标签口径定义。
在这里只介绍了将userid和cookieid打通的一种解决方案,实践中还存在需要将用户在不同平台间(如Web端和App端)行为打通的应用场景。
2. MySQL存储
MySQL作为关系型数据库,在用户画像中可用于元数据管理、监控预警数据、结果集存储等应用中。
3.1. 元数据管理
Hive适合于大数据量的批处理作业,对于量级较小的数据,MySQL 具有更快的读写速度。Web端产品读写MySQL数据库会有更快的速度, 方便标签的定义、管理。
其中MySQL主要是元数据录入和查询功能,将相应的数据存储在MySQL中。下面给出了平台标签视图(如图1所示)和元数据管理页面(如图2所示)。

平台标签视图中的标签元数据可以维护在MySQL关系数据库中,便于标签的编辑、查询和管理。
2.2. 监控预警数据
MySQL还可用于存储每天对ETL结果的监控信息。从整个画像调度流的关键节点来看,需要监控的环节主要包括对每天标签的产出量、 服务层数据同步情况的监控等主要场景。如下图所示是用户画像调度流主要模块,下面详细介绍。
2.2.1. 标签计算数据监控
主要用于监控每天标签ETL的数据量是否出现异常,如果有异常情 况则发出告警邮件,同时暂停后面的ETL任务。
2.2.2. 服务层同步数据监控
服务层一般采用HBase、Elasticsearch等作为数据库存储标签数 据供线上调用,将标签相关数据从Hive数仓向服务层同步的过程中, 有出现差错的可能,因此需要记录相关数据在Hive中的数量及同步到 对应服务层后的数量,如果数量不一致则触发告警。
在对画像的数据监控中,调度流每跑完相应的模块,就将该模块的监控数据插入MySQL中,当校验任务判断达到触发告警阈值时,发送告警邮件,同时中断后续的调度任务。待开发人员解决问题后,可重启后续调度。
2.2.3. 结果集存储
·结果集可以用来存储多维透视分析用的标签、圈人服务用的用户 标签、当日记录各标签数量,用于校验标签数据是否出现异常。
有的线上业务系统使用MySQL、Oracle等关系型数据库存储数据, 如短信系统、消息推送系统等。在打通画像数据与线上业务系统时, 需要考虑将存储在Hive中的用户标签相关数据同步到各业务系统,此 时MySQL可用于存储结果集。
Sqoop是一个用来将Hadoop和关系型数据库中的数据相互迁移的工 具。它可以将一个关系型数据库(如MySQL、Oracle、PostgreSQL等) 中的数据导入Hadoop的HDFS中,也可以将HDFS中的数据导入关系型数 据库中。
下面通过一个案例来讲解如何使用Sqoop将Hive中的标签数据迁移 到MySQL中。
电商、保险、金融等公司的客服部门的日常工作内容之一是对目 标用户群(如已流失用户、高价值用户等)进行主动外呼,以此召回 用户来平台进行购买或复购。这里可以借助用户画像系统实现该功 能。
将Hive中存储的与用户身份相关的数据同步到客服系统中,首先 在Hive中建立一张记录用户身份相关信息的表(dw.userprofile_userservice_all)。设置日期分区以满足按日期 选取当前人群的需要。
3. HBase存储
3.1. HBase简介
HBase是一个高性能、列存储、可伸缩、实时读写的分布式存储系 统,同样运行在HDFS之上。与Hive不同的是,HBase能够在数据库上实 时运行,而不是跑MapReduce任务,适合进行大数据的实时查询。
画像系统中每天在Hive里跑出的结果集数据可同步到HBase数据库,用于线上实时应用的场景。
由于HBase通过rowkey对数据进行检索,而rowkey由于长度限制的因素不能将很多查询条件拼接在rowkey中,因此HBase无法像关系数据 库那样根据多种条件对数据进行筛选。一般地,HBase需建立二级索引 来满足根据复杂条件查询数据的需求。
3.2. 应用场景
某渠道运营人员为促进未注册的新安装用户注册、下单,计划通 过App首页弹窗(如图3-15所示)发放红包或优惠券的方式进行引导。 在该场景中可通过画像系统实现对应功能。
业务逻辑上,渠道运营人员通过组合用户标签(如“未注册用 户”和“安装距今天数”小于XX天)筛选出对应的用户群,然后选 择将对应人群推送到“广告系统”(产品功能详见7.4节),这样每天 画像系统的ETL调度完成后对应人群数据就被推送到HBase数据库进行 存储。满足条件的新用户来访App时,由在线接口读取HBase数据库, 在查询到该用户时为其推送该弹窗。
下面通过某工程案例来讲解HBase在该触达用户场景中的应用方式。
3.3. 工程化案例
运营人员在画像系统中根据业务规则定义组合用户标签筛选出用户群,并将该人群上线到广告系统中(如下图所示)。
在业务人员配置好规则后,下面我们来看在数据调度层面是如何运行的。
用户标签数据经过ETL将每个用户身上的标签聚合后插入到目标表 中,如dw.userprofile_userlabel_map_all。聚合后数据存储为每个用户id,以及他身上对应的标签集合,数据格式如下图所示。
接下来需要将Hive中的数据导入血256,便于线上接口实时调用库中数据。
HBase的服务器体系结构遵循主从服务器架构(如下图所示), 同一时刻只有一个HMaster处于活跃状态,当活跃的Master挂掉后, Backup HMaster自动接管整个HBase集群。在同步数据前,首先需要判 断HBase的当前活跃节点是哪台机器。
在写入数据时为避免数据都写入一个region,造成HBase的数据倾斜问题。在当前HMaster活跃的节点上,创建预分区表:
create 'userprofile_labels', { NAME => "f", BLOCKCACHE => "true" , BLOOMFILTER => "ROWCOL" , COMPRESSION => 'snappy', IN_MEMORY => 'true' }, {NUMREGIONS => 10,SPLITALGO => 'HexStringSplit'}
将待同步的数据写入HFile,HFile中的数据以key-value键值对方 式存储,然后将HFile数据使用BulkLoad批量写入HBase集群中。具体脚本可以自行百度,使用的是批量加载方式将HFile中数据bulkload到HBase中。
在线接口在查询HBase中数据时,由于HBase无法像关系数据库那 样根据多种条件对数据进行筛选(类似SQL语言中的where筛选条 件)。一般地HBase需建立二级索引来满足根据复杂条件查询数据的需求,可以选择使用Elasticsearch存储HBase索引数据(如下图)。
在组合标签查询对应的用户人群场景中,首先通过组合标签的条 件在Elasticsearch中查询对应的索引数据,然后通过索引数据去 HBase中批量获取rowkey对应的数据(Elasticsearch中的documentid 和HBase中的rowkey都设计为用户id)。
为了避免从Hive向HBase灌入数据时缺失,在向HBase数据同步完成后,还需要校验HBase和Hive中数据量是否一致,如出现较大的波动则发送告警信息。
在将userid作为rowkey存入HBase中时,一方面在组合标签的场景中可以支持条件查询多用户人群,另一方面可以支持单个用户标签 的查询,例如查看某id用户身上的标签,以便运营人员决定是否对其 进行运营操作。HBase在离线数仓环境的服务架构如下图所示。
4. Elasticsearch存储
4.1. Elasticsearch简介
Elasticsearch是一个开源的分布式全文检索引擎,可以近乎实时地存储、检索数据。而且可扩展性很好,可以扩展到上百台服务器, 处理PB级别的数据。对于用户标签查询、用户人群计算、用户群多维 透视分析这类对叩应时间要求较高的场景,也可以考虑选用 Elasticsearch进行存储。
Elasticsearch是面向文档型数据库,一条数据在这里就是一个文 档,用json作为文档格式。为了更清晰地理解Elasticsearch查询的一 些概念,将其和关系数据库的类型进行对照,如下图所示。
在关系型数据库中查询数据时可通过选中数据库、表、行、列来 定位所查找的内容,在Elasticsearch中通过索引(index)、类型 (type)、文档(document)、字段来定位查找内容。一个Elasticsearch集群可以包括多个索引(数据库),也就是说,其中包 含了很多类型(表),这些类型中包含了很多的文档(行),然后每 个文档中又包含了很多的字段(列).Elasticsearch的交互可以使用 Java API,也可以使用HTTP的RESTful API方式。
4.2. 应用场景
基于HBase的存储方案并没有解决数据的高效检索问题。在实际应用中,经常有根据特定的几个字段进行组合后检索的应用场景,而 HBase采用rowkey作为一级索引,不支持多条件查询,如果要对库里的 非rowkey进行数据检索和查询,往往需要通过MapReduce等分布式框架 进行计算,时间延迟上会比较高,难以同时满足用户对于复杂条件查 询和高效率响应这两方面的需求。
为了既能支持对数据的高效查询,同时也能支持通过条件筛选进 行复杂查询,需要在HBase上构建二级索引,以满足对应的需要。我们可以采用Elasticsearch存储HBase的索引信息,以支持复杂高效的查询功能。
HBase数据存储数据的索引放在Elasticsearch中,实现了数据和 索引的分离。在Elasticsearch中documentid是文档的唯一标识,在 HBase中rowkey是记出的唯一标识。在工程实践中,两者可同时选用用 户在平台上的唯一标识(如userid或deviceid)作为rowkey或 documentid,进而解决HBase和Elasticsearch索引关联的问题。
下面通过使用Elasticsearch解决用户人群计算和分析应用场景的 案例来了解这一过程。
对汇聚后的用户标签表 dw.userprofile_userlabel_map_all中的数据进行清洗, 过滤掉一些无效字符,达到导入Elasticsearch的条件,如下图所示。
然后将dw.userprofile_userlabel_map_all数据写入 Elasticsearch中,之后需要数据就可以直接从Elasticsearch中获取数据了,对比使用Impala从Hive中进行计算,效果可以从原先的几十秒到几分钟优化到现在的秒级响应。
4.3. 工程化案例
下面通过一个工程案例来讲解实现画像产品中“用户人 群”和“人群分析”功能对用户群计算秒级响应的一种解决方案。
在每天的ETL调度中,需要将Hive计算的标签数据导入 Elasticsearch中。如图3-28所示,在标签调度完成且通过校验后(如下图中的“标签监控预警”任务执行完成后),将标签数据同步到 Elasticsearch中。
在与Elasticsearch数据同步完成并通过校验后,向在MySQL中维 护的状态表中插入一条状态记录,表示当前日期的Elasticsearch数据 可用,线上计算用户人群的接口则读取最近日期对应的数据。如果某 天因为调度延迟等方面的原因,没有及时将当日数据导入 Elasticsearch中,接口也能读取最近一天对应的数据,是一种可行的灾备方案。
例如,数据同步完成后向MySQL状态表“elasticsearch_state”中插入记录(如下图所示),当日数据产出正常时,state字段为“0”,产出异常时为“1”。图3-29中1月 20日导入的数据出现异常,则“state”状态字段置1,线上接口扫描 该状态记录位后不读取1月20日数据,而是取用最近的1月19日数据。
为了避免从Hive向Elasticsearch中灌入数据时发生数据缺失,在向状态表更新状态位前需要校验Elasticsearch和Hive中的数据量是否 一致。还需要通过其他脚本来看数据校验逻辑:
如上所示就是在工程化调度流中何时将Hive中的用户标签数据灌入 Elasticsearch中,之后业务人员在画像产品端计算人群或透视分析人群时(如下图1所示),通过RESTful API访问Elasticsearch进行计算(如下图2所示)。
5. 总结
注:再次声明,此博文为根据 赵宏田 老师的 用户画像·方法论与工程化解决方案 一书读后笔记而来,仅供学习使用
注:其他相关文章链接由此进 -> 用户画像文章汇总