做者介绍git
杨兆辉,苏宁科技集团大数据中心高级架构师,ClickHouse Contributor。在OLAP领域、大规模分布式计算领域有着深厚的技术积累,目前负责数据中台、标签平台相关的架构工做。github
背景
想作营销活动,如何找到目标人群及用户特征?人群的筛选一般离不开用户画像。用户画像就是根据用户特征、业务场景和用户行为等信息,构建一个标签化的用户模型。sql
好比消费者用户画像分为属性和行为标签两类。这两类标签,一个是固定的,一个是可变的。数据库
固定的属性标签基本就是买家的性别,年龄段,会员等级,消费水平,购买力等。而可变的行为标签基本包括买家的浏览,加购物车,购买等行为。缓存
经过多年的建设,苏宁构建了完整的用户标签体系,覆盖零售、金融、体育等多个产业。服务器
同时搭建了标签服务平台,经过开放丰富的标签数据能力,为广告、推荐等提供智能化的标签中台服务能力。架构
随着数据的日益增多,如何对 6 亿+用户千亿级别的标签数据进行秒级用户画像?并发
本文将带来用户画像技术的新发展和架构实践,介绍基于 ClickHouse 定制开发的标签平台,真正作到海量标签数据的快速导入和秒级用户画像查询分析,提供一整套从前期人群筛选到后期的营销策略优化的标签体系。分布式
业务场景介绍
“双 11”到了,假设须要发放 1000 万张家电类优惠券,那咱们首先须要根据标签筛选出符合条件的人群,人数大约为 1000 万左右,而后对选择的人群进行画像分析,看是否符合预期的特征。函数
若是人群符合特征,系统将一键生成进行营销的人群包(userid 列表),自动化发布和营销。
图 1:业务流程
预估人数
用户选择标签及标签之间的交并差关系,圈选出符合条件的人群,实时预估出人群的数量。
好比选择:
图 2:建立人群
上图的标签选择的含义为:“用户年龄范围为 25-36 岁”而且为“智能家居特征”的人群,排除最近 30 天消费小于 10 元的人群。
表示为集合运算的公式为:
{ {用户年龄 25-36} ∩ {智能家居人群} } - {30天消费小于10元}
技术难点有:
人群包的个数多。
每一个人群包的用户基数较大。
系统实时输出计算结果,难度大。
画像分析
当筛选出用户数与规划的消费券的数量匹配时,须要对人群进行特征分析,看看人群是否符合特征要求。
用户画像表的结构举例以下:
将筛选出的人群包与用户画像表进行关联,详细分析关联出的画像特征。也能够进一步对画像特征进行一些历史数据分析。
咱们以前的解决方案是将用户标签存储在 ElasticSearch 的大宽表中的。大宽表的结构是:一个用户下挂一堆 tag 的表结构。
在向大宽表插入数据时,须要等待业务的数据都准备好后,才能跑关联表操做,而后将关联的结果插入到 ES。
常常遇到的状况是:某个业务方的任务延迟,致使插入 ES 的关联任务没法执行,运营人员没法及时使用最新的画像数据。
在 ES 中修改文档结构是比较重的操做,修改或者删除标签比较耗时,ES 的多维聚合性能比较差,ES 的 DSL 语法对研发人员不太友好,因此咱们将标签存储引擎从 ES 替换为 ClickHouse。
ClickHouse 是近年来备受关注的开源列式数据库,主要用于数据分析(OLAP)领域。凭借优异的查询性能,受到业界的青睐,各个大厂纷纷跟进大规模使用它。
苏宁大数据已将 ClickHouse 引入并改造,封装成丰富的 Bitmap 接口,用来支撑标签平台的存储及分析。
ClickHouse 集成 Bitmap
咱们在 ClickHouse 中集成了 RoaringBitmap,实现了 Bitmap 计算功能集,并贡献给开源社区。
对 userid 进行位图方式的压缩存储,将人群包的交并差计算交给高效率的位图函数,这样既省空间又能够提升查询速度。
图 3:ClickHouse 集成 Bitmap
围绕 Bitmap 对象实现了一套完善的计算函数。Bitmap 对象有两种构建方式,一种是从聚合函数 groupBitmap 构建,另外一种是从 Array 对象构建,也能够将 Bitmap 对象转换为 Array 对象。
ClickHouse 的 Array 类型有大量的函数集,这样能够更加方便的加工数据。
上图的中间部分是 Bitmap 的计算函数集,有位运算函数、聚合运算函数、求值类运算函数,比较丰富。
基于 ClickHouse 的新架构
架构介绍
架构图以下:
图 4:标签架构
ClickHouse Manager 是咱们自研的 ClickHouse 管理平台,负责集群管理、元数据管理和节点负载协调。
Spark 任务负责标签数据的生成和导入,当某个业务方的任务跑完后,会马上调用 tag-generate 生成标签数据文件,存放到 HDFS,而后在 ClickHouse 上执行从 HDFS 导入到 ClickHouse 的 SQL 语句,这样就完成了标签的生产工做。
标签生产是并发跑的,假设某个业务方的数据没有准备好,不影响其余业务的标签生产。
用户画像平台经过 Proxy 从 ClickHouse 集群查询标签数据。在查询前,须要将查询表达式转换为 SQL,咱们对这块逻辑作了一个封装,提供一个通用的转换模块,叫作:to-ch-sql。
业务层基本上不用修改就能够查询 ClickHouse 了。
标签数据表的基本结构
相对于 ElasticSearch 的存储结构,咱们将标签存储作了一个行转列存储。每一个标签对应一个 Bitmap 对象。
Bitmap 对象中存储 userid 集合:
CREATE TABLE ch_label_string
(
labelname String, --标签名称
labelvalue String, --标签值
uv AggregateFunction( groupBitmap, UInt64 ) --userid集合
)
ENGINE = AggregatingMergeTree()
PARTITION BY labelname
ORDER BY (labelname, labelvalue)
SETTINGS index_granularity = 128;
uv 字段为 Bitmap 类型的字段,将整形的 userid 存入,每一个 userid 用一个 bit 位表示。
主键索引(index_granularity)默认为 8192,修改成 128 或者其余数值,因为 Bitmap 占用的存储空间比较大,修改成小数值,以减小稀疏索引的读放大问题。
根据标签值的数据类型划分为四种类型的表:
-
String
-
Integer
-
Double
-
Date
标签名称做为 Partition。经过这样的设计,增长或者删除标签数据都比较方便,只须要修改 Partition 的数据就能够了。Partition 的管理有相应的 SQL 语句,操做比较方便。
Userid 分片存储
在标签数据导入时,按照 userid 分片导入,每台机器仅存储对应 userid 的标签数据。
每台机器分别导入分片后的标签数据,实现了数据并行导入。在咱们的环境上单机导入性能在 150 万条/秒左右。
在根据标签筛选人群时,SQL 仅须要在单个 shard 上执行,中间结果不须要返回给查询节点。
在执行“预估人数”计算时,优点特别明显:每一个 shard 仅须要返回符合条件的人数,在查询节点作 sum 操做,而后将 sum 结果返回给客户端。充分挖掘了 ClickHouse 分布式并行计算的能力。
查询流程
采用 with 语句进行计算出人群包的 Bitmap 对象,而后用 Bitmap 函数进行交并差的计算。
当须要计算的标签比较多时,标签查询的 SQL 比较复杂,将标签查询 SQL 包装到分布式代理表的 SQL 中,分布式代理表自己不存储数据,经过代理表标识到哪些节点上查询,分布式代理表所标识的节点上执行标签查询 SQL。
而后在分布式代理表上汇总查询结果。经过 ClickHouse 分布式表的自身特性,实现了标签查询的 colocate 机制。
图 5:查询流程
示例 SQL 以下:
-- 本地查询代理
CREATE TABLE ch_agent_user
(
agentname String
)
ENGINE = MergeTree()
PARTITION BY agentname
ORDER BY (agentname)
SETTINGS index_granularity = 8192;
-- 分布式代理表
CREATE TABLE ch_agent_dist_user AS ch_agent_user
ENGINE = Distributed('cluster_test', 'test', 'ch_agent_user', cityHash64(agentname))
-- 查询用户数
SELECT sum(user_number) AS user_number
FROM ch_agent_dist_user
RIGHT JOIN
(
WITH
(
SELECT groupBitmapState(userid) AS users0
FROM ch_label_string
WHERE labelname = 'T'
) AS users0
SELECT
'agent' AS agentname,
bitmapCardinality(users0) AS user_number
) USING (agentname) settings enable_scalar_subquery_optimization = 0;
ch_agent_user 表自己不存储数据,当与 with 语句进行 right join 关联查询时,因为是右关联查询,查询结果以 with 语句的结果集为准。
各个节点的查询结果返回给查询节点,查询节点进行汇总计算。参数 enable_scalar_subquery_optimization = 0 表示 with 语句的查询结果不作优化,每一个节点都须要执行。
默认状况,在 ClickHouse 中 with 语句的结果做为标量进行缓存,会将查询节点的标量分发到其余服务器,当发现已经存在标量时,就不会在本地节点执行 with 语句。
咱们指望 with 语句在每一个节点都执行,因此将这个参数设置为 0。
用户画像
用户画像对性能要求比较高,查询平均响应时间不能大于 5 秒。用户在界面上任意圈选人群,而后实时对圈选后的人群进行画像分析。
用户画像技术进行了三次架构重构:
1)V1:大宽表模式
最先的方案是建立一张 userid 为主键的画像表,表的其余字段为画像的特征字段,将圈选的人群与画像表进行 in 操做,而后 group by 操做。
这种设计带来两个比较严重的问题:
-
当增长或者删除特征字段时,画像表的表结构须要修改;
-
当圈选的人群数量比较大时,涉及到大记录集的 group by 运算,性能差。
2)V2:Bitmap 模式
将一个特征值下的 userid 集合作为 Bitmap 对象存储到一条记录中,一条记录的内容以下:
用户圈选的人群 Bitmap 对象与画像表的 Bitmap 对象进行与(AND)操做,返回圈选人群的画像信息。
经过这样设计,基本上知足了性能要求,平均时间小于 5 秒,可是一些大的人群包,画像的性能仍是差,在 10 秒左右。
画像表的记录数据量不大,但画像表的 Bitmap 字段在计算时须要从磁盘上反序列化出来。有的 Bitmap 对象占用几百兆的空间,致使了性能的降低。
3)V3:Join 表引擎模式
ClickHouse 的 Join 表引擎能够将数据常驻到内存。当插入数据时,数据先写入内存,而后刷到磁盘文件,系统重启时,自动把数据加载回内存。Join 表引擎能够说是常驻内存的带持久化功能的表。
咱们把画像表的数据保存到 Join 表引擎,画像表的 Bitmap 字段就常驻内存了,当圈选的人群 Bitmap 对象进行与(AND)操做时,两个内存中已经加载的 Bitmap 对象之间的计算就很是快。
经过此次优化平均查询时间优化到 1 到 2 秒,千万级人群画像分析不超过 5 秒。
总结
经过 ClickHouse 集成 Bitmap 功能,以及 Join 表引擎的应用,对架构进行了一系列优化后,极大的提高了标签平台的数据分析能力。
新的架构主要有如下优点:
-
标签数据能够并行构建,加快标签数据生产速度;
-
HDFS 文件并发导入 ClickHouse,加快标签数据的就绪速度;
-
查询请求平均响应时长在 2 秒如下,复杂查询在 5 秒如下;
-
支持标签数据准实时更新;
-
标签表达式和查询 SQL 对用户来讲比较友好,提高系统的易维护性;
-
相对于 ElasticSearch 的配置,能够节约一半硬件资源。
将来规划:
-
目前 ClickHouse 采用 RoaringBitmap 的 32 位版本,准备增长 64 位版本;
-
ClickHouse 查询的并发性较低,增长更加智能的 Cache 层;
-
支持 ClickHouse 数据文件离线生成,进一步提示标签的就绪速度。
>>>>
参考资料
-
ClickHouse 官网:https://clickhouse.tech/
-
ClickHouse 中文社区:http://www.clickhouse.com.cn/
-
Bitmap PR:https://github.com/ClickHouse/ClickHouse/pull/4207
做者丨杨兆辉 来源丨51CTO技术栈(ID:blog51cto) dbaplus社群欢迎广大技术人员投稿,投稿邮箱: editor@dbaplus.cn