交互式项目
公司中的PM(产品经理)、数据分析师以及管理人员进入平台,输入搜索条件,了解特殊用户群体的各种业务逻辑的统计和分析结果。从而辅助高管进行公司战略上的决策制定。从长远看,交互式平台的出现 大大减少了业务管理人员和开发人员的沟通成本,提升了业务管理人员制定决策的效率。
需求
获取平台前端传来的搜索条件,从数仓的用户行为表中筛选出符合条件的记录,然后在此基础上进行以下需求的开发:
- 聚合统计 各个范围的时长、步长 的 session数量占比。
这个功能的作用,可以让人从全局的角度了解这些用户使用我们的产品的一些习惯。比如大多数人,到底是会在产品中停留多长时间,大多数人,会在一次使用产品的过程中,访问多少个页面。这样可以让平台使用者有一个清晰的全局认识。 - 按时间比例随机抽取一定数量的session.
这个功能的作用,可以让平台使用者看到特殊用户群体的行为明细,观察每个session具体的点击流/行为,比如先进入了首页、然后点击了床品品类、然后点击了凉席商品、然后搜索了冰丝席的关键词、接着对某一款冰丝席下了订单、最后对订单做了支付。之所以要做到按时间比例随机抽取,就是要做到,观察样本的公平性。 - 获取热度最高的商品类别,即 点击量、下单量、支付量 排名前十的 品类
这个功能的作用,就可以让平台使用者明白符合条件的用户最感兴趣的商品是什么种类,清晰地了解到不同层次、不同类型的用户的心理和喜好。 - 对于排名前十的类别,获取其点击次数排名前十的 session。
这个功能的作用,是让平台使用者观察,对于某个用户群体最感兴趣的品类,各个品类最感兴趣最典型的用户的session的行为。
数据来源
- 前端传来的搜索条件保存在MySQL的param表中。
- 按用户条件筛选hive的user_info表。
- 按时间范围筛选hive的user_visitor_actor表。
- 两表交联获得符合条件的session记录。
- user_info是用户维度表
- user_visitor_actor是用户访问事实表
每日访问量八九百万。以一周的访问数据为分析对象,user_visitor_actor表有六千万条记录,十台服务器的集群规模有八个NodeManager,每台服务器是16G内存,可以制定120G内存跑spark作业,一般的作业可以在两三分钟跑完。
业务功能开发技术点
- 底层数据聚合。
以session为粒度聚合用户行为事实表数据。
减少spark作业处理数据量,从而提升spark作业的性能,这是从根本上提升spark性能的技巧。
- 自定义Accumulator实现复杂分布式计算的技术。
基础的Accumulator只能对一个指标进行计数,多个指标的计数会导致代码中充斥大量Accumulator对象,难以维护。自定义的Accumulator可以实现一个计数器对象即可进行多个指标的计算。
//自定义计数器
class SessionAggrInfoAccumulator extends AccumulatorParam[String] {
/**
* 累加器的初始化值
* @param initialValue
*/
override def zero(initialValue: String) = {...}
/**
* 调用累加器的add方法,最终就是调用该addInPlace方法
* @param old 累加器中已经有的值
* @param newFiled 需要累加的字段
*/
override def addInPlace(old: String, newFiled: String) = {...}
}
//使用计数器
val sAggAccu = sc.accumulator("")(new SessionAggrInfoAccumulator())
sAggrAccu.add(Constants.FIELD_SESSION_COUNT) //总的session个数+1
val aggrAccu = sAggrAccu.value //获取累加器的值
- Spark按时间比例随机抽取算法。
一方面统计各个时间范围的session数量,制作索引列表并广播;
一方面以时间范围为粒度聚合数据,然后按照索引列表抽取session。
//使用索引列表抽取session
val extractSessionInfoRDD:RDD[Iterable[String]] = dateHour2AggrInfos.map{case (dateHour, aggrInfos) => {
val indexList = extractIndexListMapBC.value(dateHour)
val extractAggrInfo = ArrayBuffer[String]()
val infos = Random.shuffle(aggrInfos).toList
for(index <- indexList) {
extractAggrInfo.append(infos(index))
}
extractAggrInfo
}}
- Spark自定义key二次排序技术
- Spark分组取TopN算法
val categoryId2CountTop:Array[(Long, CategorySort)] = categoryId2CountsRDD.sortBy(cc => cc._2,true,1)(
new Ordering[CategorySort](){
override def compare(x: CategorySort, y: CategorySort) = {
var ret = y.getClickCount.compareTo(x.getClickCount)
if(ret == 0) {
ret = y.getOrderCount.compareTo(x.getOrderCount)
if(ret == 0) {
ret = y.getPayCount.compareTo(x.getPayCount)
}
}ret
}
},ClassTag.Object.asInstanceOf[ClassTag[CategorySort]]
).take(10)
Spark调优技术点
参见 《Spark调优技术点》