本篇主要讲讲开发过程中可能会遇到的一些问题。主要包括脚本逻辑上的调优和spark开发调优。
为减少画像系统的调度作业时间,使各调度任务能够在更短的时间内,消耗更少的计算资源计算出结果,需要对画像调度系统的性能进行调优。调优的工作包括数据倾斜调优、合并小文件、开发中间表、缓存中间数据等方面。下面详细来看这些调优的场景和调优方式。
(一)数据倾斜调优
数据倾斜是开发中常遇到的问题,当任务执行一直卡在map 100%,reduce 99%,最后的1%花了几个小时都没执行完,这种情况一般是遇到了数据倾斜。
出现的原因是当分布式计算的时候,由于某些节点需要计算的数据较多,导致其他节点的reduce阶段任务执行完成时,该节点的任务还没有执行完成,造成其他节点等待该节点执行完成的情况。比如两张大表在join的时候大部分key对应10条数据,但是个别几个key对应了100万条数据,对应10条数据的task很快执行完了,但对应100万数据的key要执行好几个小时。
下图是一个典型的例子。

bb这个key在三个节点上有11条数据,aa和cc在三个节点上分别有2条和1条数据,这些数据都会被拉取到一个task上处理。处理bb这个task的运行时间可能是处理aa和cc的多倍,整体运行速度由最慢的task决定。
下面介绍两种解决数据倾斜的方案。
方案一:过滤掉倾斜数据
当少量key重复次数特别多,如果这种key不是业务需要的key,可以直接过滤掉。这里有一张埋点日志表ods.page_event_log需要和订单表dw.order_info_fact做join关联。在执行Hive的过程中发现任务卡在map 100%,reduce 99%,最后的1%一直运行不完。考虑应该是join的过程中出现了数据倾斜,下面进行排查。
对于ods.page_event_log表看出现次数最多的key:
select cookieid,
count(*) as num
from ods.page_event_log
where data_date = "20190101"
group by cookieid
distribute by cookieid
sort by num desc limit 10Key出现次数从多到少排序(见下图):

同样地对订单表dw.order_info_fact看出现次数最多的key:
select cookieid,
count(*) as num
from dw.order_info_fact
group by cookieid
distribute by cookieid
sort by num desc limit 10Key出现次数从多到少排序(见下图):

从上面可以看出,日志表和订单表通过cookieid进行join,当cookieid为0的时候,join操作将会产生142286×142286条数据,如此庞大的数量节点无法处理过来。同样当cookieid为NULL值和空值时也会出现这种情况,而且cookieid为这三个值时并没有实际业务意义。因此在两个表做关联时,排除掉这3个值以后,可以很快计算出结果。
方案二:引入随机数
数据按照类型group by的时候,会将相同的key所需数据拉取到一个节点进行汇聚,而当某组数据量过大时,会出现其他组计算已经完成而当前任务未完成的情况。可以考虑加入随机数,将原来一组key强制拆分为多组进行汇聚。下面通过一个案例来介绍。
有一个需求要统计用户的订单量,代码执行如下:
select t1.user_id,
t2.order_num
from (select user_id
from dim.user_info_fact # 用户维度表
where data_date = "20190101"
and user_status_id =1
) t1
join ( select user_id,
count(*) as order_num
from dw.order_info_fact # 订单表
where order_status_id in (1,2,3)
group by user_id
) t2
on t1.user_id = t2.user_id用户维度表有2000w条数据,订单表有10亿条数据,任务在未优化前执行了1个小时也没有跑出结果,判断可能出现了数据倾斜。
订单表中某些key值数量较多,在group by过程中拉取到一个task上执行时,会出现其他task执行完毕,等待该task执行的情况。
这里可以将原本相同的key通过添加随机前缀的方式,变成多个key,这样讲原本被一个task处理的key分散到多个task上先做一次汇聚,然后去掉前缀再进行一次汇聚得到最终结果。示意图如下图所示。

修改后代码执行如下:
select t1.user_id,
t2.order_num
from ( select user_id
from dim.user_info_fact
where data_date = "20190101"
and user_status_id=1
) t1
join ( select t.user_id,
sum(t.order_num) as order_num
from (select user_id,
round(rand()*1000) as rnd,
count(1) as order_num
from dw.order_info_fact
where order_status_id in (1,2,3)
group by user_id,round(rand()*1000)
) t
group by t.user_id
) t2
on t1.user_id = t2.user_id(二)开发中间表
在用户画像迭代开发的过程中,初期开发完标签后,通过对标签加工作业的血缘图整理,可以找到使用相同数据源的标签,对这部分标签通过加工中间表,可以缩减每日画像调度作业时间。
做中间层设计前需要明确几个重要的点:
1、这个中间层对应的业务场景、业务目标是什么;
2、业务方有了这份中间层数据以后可以进行哪些维度的分析,ETL时有了这份中间层
数据可以减少对哪些数据的重复开发计算;
3、这个业务场景分析中包含哪些分析维度和哪些指标;
4、同时面向很多业务场景的中间层不一定是好的中间层。
在开发中间表前,首先需要梳理清楚目前用户标签计算时依赖的上游数据仓库的表(见下图)和标签的血缘依赖(见下图)。

图 用户标签依赖上游数据仓库的表(示例表名)

图 用户标签血缘图梳理(示例)
例如在开发过程中,可以对dwd层的日分区存放当天日期对应的订单,而dws层作为服务层,其日分区用于存放当天日期对应的全量数据。这样,在日常调度计算的过程中,避免了dwd层重复计算历史数据,只需计算当天的新增数据。既节省了ETL时间,也对服务层的数据没有任何影响。
通过对用户标签的血缘图进行梳理,找到共同依赖的上游数据。
更多关于用户画像开发、分析和应用方案的内容详见 https://item.jd.com/12824930.html
