第3章 标签数据存储
在画像系统搭建的过程中,数据存储的技术选型是非常重要的一项内容,不同的存储方式适用于不同的应用场景。本章主要介绍使用Hive、MySQL、HBase、Elasticsearch存储画像相关数据的应用场景及对应的解决方案。
3.1 Hive存储
3.1.1 Hive数据仓库
建立用户画像首先需要建立数据仓库,用于存储用户标签数据。Hive是基于Hadoop的数据仓库工具,依赖于HDFS存储数据,提供的SQL语言可以查询存储在HDFS中的数据。开发时一般使用Hive作为数据仓库,存储标签和用户特征库等相关数据。
“数据仓库之父”W.H.Inmon在《Building the Data Warehouse》一书中定义数据仓库是“一个面向主题的、集成的、非易失的、随时间变化的、用来支持管理人员决策的数据集合”。
数据抽取到数据仓库的流程如图3-1所示。
在数据仓库建模的过程中,主要涉及事实表和维度表的建模开发(图3-2)。
事实表主要围绕业务过程设计,就应用场景来看主要包括事务事实表,周期快照事实表和累计快照事实表:
❑事务事实表:用于描述业务过程,按业务过程的单一性或多业务过程可进一步分为单事务事实表和多事务事实表。其中单事务事实表分别记录每个业务过程,如下单业务记入下单事实表,支付业务记入支付事实表。多事务事实表在同一个表中包含了不同业务过程,如下单、支付、签收等业务过程记录在一张表中,通过新增字段来判断属于哪一个业务过程。当不同业务过程有着相似性时可考虑将多业务过程放到多事务事实表中。
❑周期快照事实表:在一个确定的时间间隔内对业务状态进行度量。例如查看一个用户的近1年付款金额、近1年购物次数、近30日登录天数等。
❑累计快照事实表:用于查看不同事件之间的时间间隔,例如分析用户从购买到支付的时长、从下单到订单完结的时长等。一般适用于有明确时间周期的业务过程。
维度表主要用于对事实属性的各个方面描述,例如,商品维度包括商品的价格、折扣、品牌、原厂家、型号等方面信息。维度表开发的过程中,经常会遇到维度缓慢变化的情况,对于缓慢变化维一般会采用:
①重写维度值,对历史数据进行覆盖;
②保留多条记录,通过插入维度列字段加以区分;
③开发日期分区表,每日分区数据记录当日维度的属性;
④开发拉链表按时间变化进行全量存储等方式进行处理。
在画像系统中主要使用Hive作为数据仓库,开发相应的维度表和事实表来存储标签、人群、应用到服务层的相关数据。
拉链表说明:
拉链表 - 维护历史状态,以及最新状态数据的一种表,拉链表根据拉链粒度的不同,实际上相当于快照,只不过做了优化,去除了一部分不变的记录而已,通过拉链表可以很方便的还原出拉链时点的客户记录。拉链表通常是对账户信息的历史变动进行处理保留的结果。
3.1.2 分区存储
如果将用户标签开发成一张大的宽表,在这张宽表下放几十种类型标签,那么每天该画像宽表的ETL作业将会花费很长时间,而且不便于向这张宽表中新增标签类型。要解决这种ETL花费时间较长的问题,可以从以下几个方面着手:
❑将数据分区存储,分别执行作业;
❑标签脚本性能调优;
❑基于一些标签共同的数据来源开发中间表。
下面介绍一种用户标签分表、分区存储的解决方案。根据标签指标体系的人口属性、行为属性、用户消费、风险控制、社交属性等维度分别建立对应的标签表进行分表存储对应的标签数据。如图3-3所示。
❑人口属性表:dw.userprofile_attritube_all;
❑行为属性表:dw.userprofile_action_all;
❑用户消费表:dw.userprofile_consume_all;
❑风险控制表:dw.userprofile_riskmanage_all;
❑社交属性表:dw.userprofile_social_all
例如创建用户的人口属性宽表:
同样的,用户其他id维度(如cookieid、deviceid、registerid等)的标签数据存储,也可以使用上面案例中的表结构。
在上面的创建中通过设立人口属性维度的宽表开发相关的用户标签,为了提高数据的插入和查询效率,在Hive中可以使用分区表的方式,将数据存储在不同的目录中。在Hive使用select查询时一般会扫描整个表中所有数据,将会花费很多时间扫描不是当前要查询的数据,为了扫描表中关心的一部分数据,在建表时引入了partition的概念。在查询时,可以通过Hive的分区机制来控制一次遍历的数据量。
3.1.3 标签汇聚
在3.1.2节的案例中,用户的每个标签都插入到相应的分区下面,但是对一个用户来说,打在他身上的全部标签存储在不同的分区下面。为了方便分析和查询,需要将用户身上的标签做聚合处理。紧接3.1.2节的案例,下面讲解标签汇聚的开发案例(见图3-4)。
标签汇聚后将一个每个用户身上的全量标签汇聚到一个字段中,表结构设计如下:
开发udf函数“cast_to_json”将用户身上的标签汇聚成json字符串,执行命令将按分区存储的标签进行汇聚:
汇聚后用户标签的存储格式如图3-5所示
将用户身上的标签进行聚合便于查询和计算。例如,在画像产品中,输入用户id后通过直接查询该表,解析标签id和对应的标签权重后,即可在前端展示该用户的相关信息(如图3-6所示)。
3.1.4 ID-MAP
开发用户标签的时候,有项非常重要的内容——ID-MApping,即把用户不同来源的身份标识通过数据手段识别为同一个主体。用户的属性、行为相关数据分散在不同的数据来源中,通过ID-MApping能够把用户在不同场景下的行为串联起来,消除数据孤岛。图3-7展示了用户与设备间的多对多关系。图3-8展示了同一用户在不同平台间的行为示意图。
举例来说,用户在未登录App的状态下,在App站内访问、搜索相关内容时,记录的是设备id(即cookieid)相关的行为数据。而用户在登录App后,访问、收藏、下单等相关的行为记录的是账号id(即userid)相关行为数据。虽然是同一个用户,但其在登录和未登录设备时记录的行为数据之间是未打通的。通过ID-MApping打通userid和cookieid的对应关系,可以在用户登录、未登录设备时都能捕获其行为轨迹。
下面通过一个案例介绍如何通过Hive的ETL工作完成ID-Mapping的数据清洗工作。
缓慢变化维是在维表设计中常见的一种方式,维度并不是不变的,随时间也会发生缓慢变化。如用户的手机号、邮箱等信息可能会随用户的状态变化而改变,再如商品的价格也会随时间变化而调整上架的价格。因此在设计用户、商品等维表时会考虑用缓慢变化维来开发。同样,在设计ID-Mapping表时,由于一个用户可以在多个设备上登录,一个设备也能被多个用户登录,所以考虑用缓慢变化维表来记录这种不同时间点的状态变化(图3-9)。
拉链表是针对缓慢变化维表的一种设计方式,记录一个事物从开始到当前状态的全部状态变化信息。
在上图中,通过拉链表记录了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)中。代码执行如下:
创建ID-Map的拉链表,将每天新增到ods.cookie_user_signin表中的数据与拉链表历史数据做比较,如果有变化或新增数据则进行更新。
数据写入表中,如图3-9所示。对于该拉链表,可查看某日(如20190801)的快照数据。
例如,目前存在一个记录userid和cookieid关联关系的表,但是为多对多的记录(即一个userid对应多条cookieid记录,以及一条cookieid对应多条userid记录)。这里可以通过拉链表的日期来查看某个时间点userid对应的cookieid。查看某个用户(如32101029)在某天(如20190801)关联到的设备id(图3-10)。
上图可看出用户‘32101029’在历史中曾登录过3个设备,通过限定时间段可找到特定时间下用户的登录设备。
在开发中需要注意关于userid与cookieid的多对多关联,如果不加条件限制就做关联,很可能引起数据膨胀问题。
在实际应用中,会遇到许多需要将userid和cookieid做关联的情况。例如,需要在userid维度开发出该用户近30日的购买次数、购买金额、登录时长、登录天数等标签。前两个标签可以很容易地从相应的业务数据表中根据算法加工出来,而登录时长、登录天数的数据存储在相关日志数据中,日志数据表记录的userid与cookieid为多对多关系。因此在结合业务需求开发标签时,要确定好标签口径定义。
本节中通过案例介绍了将userid和cookieid打通的一种解决方案,实践中还存在需要将用户在不同平台间(如Web端和App端)行为打通的应用场景。
3.2 MySQL存储
MySQL作为关系型数据库,在用户画像中可用于元数据管理、监控预警数据、结果集存储等应用中。下面详细介绍这3个应用场景。
3.2.1 元数据管理
Hive适合于大数据量的批处理作业,对于量级较小的数据,MySQL具有更快的读写速度。Web端产品读写MySQL数据库会有更快的速度,方便标签的定义、管理。在7.2节和7.3节中,我们会介绍元数据录入和查询功能,将相应的数据存储在MySQL中。用户标签的元数据表结构设计会在7.3节进行详细的介绍。这里给出了平台标签视图(如图3-11所示)和元数据管理页面(如图3-12所示)。
平台标签视图中的标签元数据可以维护在MySQL关系数据库中,便于标签的编辑、查询和管理。
3.2.2 监控预警数据
MySQL还可用于存储每天对ETL结果的监控信息。从整个画像调度流的关键节点来看,需要监控的环节主要包括对每天标签的产出量、服务层数据同步情况的监控等主要场景。图3-13所示是用户画像调度流主要模块,下面详细介绍。
1.标签计算数据监控
主要用于监控每天标签ETL的数据量是否出现异常,如果有异常情况则发出告警邮件,同时暂停后面的ETL任务。
2.服务层同步数据监控
服务层一般采用HBase、Elasticsearch等作为数据库存储标签数据供线上调用,将标签相关数据从Hive数仓向服务层同步的过程中,有出现差错的可能,因此需要记录相关数据在Hive中的数量及同步到对应服务层后的数量,如果数量不一致则触发告警。
在对画像的数据监控中,调度流每跑完相应的模块,就将该模块的监控数据插入MySQL中,当校验任务判断达到触发告警阈值时,发送告警邮件,同时中断后续的调度任务。待开发人员解决问题后,可重启后续调度。
3.2.3 结果集存储
结果集可以用来存储多维透视分析用的标签、圈人服务用的用户标签、当日记录各标签数量,用于校验标签数据是否出现异常。
有的线上业务系统使用MySQL、Oracle等关系型数据库存储数据,如短信系统、消息推送系统等。在打通画像数据与线上业务系统时,需要考虑将存储在Hive中的用户标签相关数据同步到各业务系统,此时MySQL可用于存储结果集。
Sqoop是一个用来将Hadoop和关系型数据库中的数据相互迁移的工具。它可以将一个关系型数据库(如MySQL、Oracle、PostgreSQL等)中的数据导入Hadoop的HDFS中,也可以将HDFS中的数据导入关系型数据库中。
下面通过一个案例来讲解如何使用Sqoop将Hive中的标签数据迁移到MySQL中。
电商、保险、金融等公司的客服部门的日常工作内容之一是对目标用户群(如已流失用户、高价值用户等)进行主动外呼,以此召回用户来平台进行购买或复购。这里可以借助用户画像系统实现该功能。
将Hive中存储的与用户身份相关的数据同步到客服系统中,首先在Hive中建立一张记录用户身份相关信息的表(dw.userprofile_userservice_all)。设置日期分区以满足按日期选取当前人群的需要。
在MySQL中建立一张用于接收同步数据的表(userservice_data)。
通过Python脚本调用shell命令,将Hive中的数据同步到MySQL中。执行如下脚本:
其中用到了sqoop从Hive导出数据到MySQL的命令:
同步后MySQL中的数据如图3-14所示。
3.3 HBase存储
3.3.1 HBase简介
HBase是一个高性能、列存储、可伸缩、实时读写的分布式存储系统,同样运行在HDFS之上。与Hive不同的是,HBase能够在数据库上实时运行,而不是跑MapReduce任务,适合进行大数据的实时查询。
画像系统中每天在Hive里跑出的结果集数据可同步到HBase数据库,用于线上实时应用的场景。下面介绍几个基本概念:
❑row key:用来表示唯一一行记录的主键,HBase的数据是按照row key的字典顺序进行全局排列的。
访问HBase中的行只有3种方式:
○通过单个row key访问;
○通过row key的正则访问;
○全表扫描。
由于HBase通过rowkey对数据进行检索,而rowkey由于长度限制的因素不能将很多查询条件拼接在rowkey中,因此HBase无法像关系数据库那样根据多种条件对数据进行筛选。一般地,HBase需建立二级索引来满足根据复杂条件查询数据的需求。
Rowkey设计时需要遵循三大原则:
○唯一性原则:rowkey需要保证唯一性,不存在重复的情况。在画像中一般使用用户id作为rowkey。
○长度原则:rowkey的长度一般为10-100bytes。
○散列原则:rowkey的散列分布有利于数据均衡分布在每个RegionServer,可实现负载均衡。
❑columns family:指列簇,HBase中的每个列都归属于某个列簇。列簇是表的schema的一部分,必须在使用表之前定义。划分columns family的原则如下:
○是否具有相似的数据格式;
○是否具有相似的访问类型。
常用的增删改查命令如下。
1)创建一个表,指定表名和列簇名:[插图]
create '<table name>','<column family>'
2)扫描表中数据,并显示其中的10条记录:[插图]
scan 'table name',{LIMIT =>10}
3)使用get命令读取数据:[插图]
get '<table name>','row1'
4)插入数据:[插图]
put '<table name>','row ','colfamily:colname','<value>'
5)更新数据:[插图]
put '<table name>','row','Column family:column name','new value'
6)在删除表之前先将其禁用,然后删除:[插图]
disable '<table name>'
drop '<table name>'
下面通过一个案例来介绍HBase在画像系统中的应用场景和工程化实现方式。
3.3.2 应用场景
某渠道运营人员为促进未注册的新安装用户注册、下单,计划通过App首页弹窗(如图3-15所示)发放红包或优惠券的方式进行引导。在该场景中可通过画像系统实现对应功能。
业务逻辑上,渠道运营人员通过组合用户标签(如“未注册用户”和“安装距今天数”小于××天)筛选出对应的用户群,然后选择将对应人群推送到“广告系统”(产品功能详见7.4节),这样每天画像系统的ETL调度完成后对应人群数据就被推送到HBase数据库进行存储。满足条件的新用户来访App时,由在线接口读取HBase数据库,在查询到该用户时为其推送该弹窗。
下面通过某工程案例来讲解HBase在该触达用户场景中的应用方式。
3.3.3 工程化案例
运营人员在画像系统(详见第7章)中根据业务规则定义组合用户标签筛选出用户群,并将该人群上线到广告系统中(如图3-16所示)。
在业务人员配置好规则后,下面我们来看在数据调度层面是如何运行的。
用户标签数据经过ETL将每个用户身上的标签聚合后插入到目标表中,如dw.userprofile_userlabel_map_all(详见3.1.3节)。聚合后数据存储为每个用户id,以及他身上对应的标签集合,数据格式如图3-17所示。
接下来需要将Hive中的数据导入HBase,便于线上接口实时调用库中数据。
HBase的服务器体系结构遵循主从服务器架构(如图3-18所示),同一时刻只有一个HMaster处于活跃状态,当活跃的Master挂掉后,Backup HMaster自动接管整个HBase集群。在同步数据前,首先需要判断HBase的当前活跃节点是哪台机器。
执行如下脚本:
执行完毕后,可通过返回的“State”字段判断当前节点状态(活跃为“active”,不活跃为“standby”),如图3-19所示。
为避免数据都写入一个region,造成HBase的数据倾斜问题。在当前HMaster活跃的节点上,创建预分区表:
将待同步的数据写入HFile,HFile中的数据以key-value键值对方式存储,然后将HFile数据使用BulkLoad批量写入HBase集群中。Scala脚本执行如下:
......
提交Spark任务,将HFile中数据bulkload到HBase中。执行完成后,可以在HBase中看到该数据已经写入“userprofile_labels”中(图3-20)。
在线接口在查询HBase中数据时,由于HBase无法像关系数据库那样根据多种条件对数据进行筛选(类似SQL语言中的where筛选条件)。一般地HBase需建立二级索引来满足根据复杂条件查询数据的需求,本案中选用Elasticsearch存储HBase索引数据(图3-21)。
在组合标签查询对应的用户人群场景中,首先通过组合标签的条件在Elasticsearch中查询对应的索引数据,然后通过索引数据去HBase中批量获取rowkey对应的数据(Elasticsearch中的documentid和HBase中的rowkey都设计为用户id)。
为了避免从Hive向HBase灌入数据时缺失,在向HBase数据同步完成后,还需要校验HBase和Hive中数据量是否一致,如出现较大的波动则发送告警信息。下面通过Python脚本来看该HBase状态表数据校验逻辑:
本案例中将userid作为rowkey存入HBase,一方面在组合标签的场景中可以支持条件查询多用户人群,另一方面可以支持单个用户标签的查询,例如查看某id用户身上的标签,以便运营人员决定是否对其进行运营操作。HBase在离线数仓环境的服务架构如图3-22所示。
3.4 Elasticsearch存储
3.4.1 Elasticsearch简介
Elasticsearch是一个开源的分布式全文检索引擎,可以近乎实时地存储、检索数据。而且可扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。对于用户标签查询、用户人群计算、用户群多维透视分析这类对响应时间要求较高的场景,也可以考虑选用Elasticsearch进行存储。
Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档,用json作为文档格式。为了更清晰地理解Elasticsearch查询的一些概念,将其和关系数据库的类型进行对照,如图3-23所示。
在关系型数据库中查询数据时可通过选中数据库、表、行、列来定位所查找的内容,在Elasticsearch中通过索引(index)、类型(type)、文档(document)、字段来定位查找内容。一个Elasticsearch集群可以包括多个索引(数据库),也就是说,其中包含了很多类型(表),这些类型中包含了很多的文档(行),然后每个文档中又包含了很多的字段(列)。Elasticsearch的交互可以使用Java API,也可以使用HTTP的RESTful API方式。
3.4.2 应用场景
基于HBase的存储方案并没有解决数据的高效检索问题。在实际应用中,经常有根据特定的几个字段进行组合后检索的应用场景,而HBase采用rowkey作为一级索引,不支持多条件查询,如果要对库里的非rowkey进行数据检索和查询,往往需要通过MapReduce等分布式框架进行计算,时间延迟上会比较高,难以同时满足用户对于复杂条件查询和高效率响应这两方面的需求。
为了既能支持对数据的高效查询,同时也能支持通过条件筛选进行复杂查询,需要在HBase上构建二级索引,以满足对应的需要。在本案中我们采用Elasticsearch存储HBase的索引信息,以支持复杂高效的查询功能。
主要查询过程包括:
1)在Elasticsearch中存放用于检索条件的数据,并将rowkey也存储进去;
2)使用Elasticsearch的API根据组合标签的条件查询出rowkey的集合;
3)使用上一步得到的rowkey去HBase数据库查询对应的结果(见图3-24)。
HBase数据存储数据的索引放在Elasticsearch中,实现了数据和索引的分离。在Elasticsearch中documentid是文档的唯一标识,在HBase中rowkey是记录的唯一标识。在工程实践中,两者可同时选用用户在平台上的唯一标识(如userid或deviceid)作为rowkey或documentid,进而解决HBase和Elasticsearch索引关联的问题。
下面通过使用Elasticsearch解决用户人群计算和分析应用场景的案例来了解这一过程。
对汇聚后的用户标签表dw.userprofile_userlabel_map_all(3.1.3节)中的数据进行清洗,过滤掉一些无效字符,达到导入Elasticsearch的条件,如图3-25所示。
然后将dw.userprofile_userlabel_map_all数据写入Elasticsearch中,Scala代码如下:
.......
工程依赖如下:
将该工程打包之后提交任务,传入日期分区参数“20190101”执行。提交命令“spark-submit--class com.example.HiveDataToEs--master yarn--deploy-mode client--executor-memory 2g--num-executors 50--driver-memory 3g--executor-cores 2 spark-hive-to-es.jar 20190101”。任务执行完毕后,当日userid维度的用户标签数据全部导入Elasticsearch中。使用RESTfulAPI查询包含某个标签的用户量,可实时得到返回结果,如图3-26所示。
。。。
3.5 本章小结
本章讲解了使用Hive、MySQL、HBase和Elasticsearch存储标签数据的解决方案,包括:Hive存储数据相关标签表、人群计算表的表结构设计以及ID-Mapping的一种实现方式;MySQL存储标签元数据、监控数据及结果集数据;HBase存储线上接口实时调用的数据;Elasticsearch存储标签用于人群计算和人群多维透视分析。存储过程中涉及如下相关表。
❑dw.userprofile_attritube_all:存储人口属性维度的标签表;
❑dw.userprofile_action_all:存储行为属性维度的标签表;
❑dw.userprofile_consume_all:存储用户消费维度的标签表;
❑dw.userprofile_riskmanage_all:存储风险控制维度的标签表;
❑dw.userprofile_social_all:存储社交属性维度的标签表;
❑dw.userprofile_userlabel_map_all:汇聚用户各维度标签的表;
❑dw.userprofile_usergroup_labels_all:存储计算后人群数据的表。
面向不同的工程场景使用不同的存储方案,本章通过“工程场景+案例”的形式介绍了一种可实现的用户标签存储解决方案。
本文为读书笔记,更多精彩内容请自行购买书籍。