- actionRDD做映射成<sessionid,Row>格式
- 按Sessionid聚合,计算出每个Session的访问时长和访问步长
- 遍历新生成的RDD,将每个Session的访问时长和访问步长去更新自定义Accumulator中自定义的值
- 使用自定义Accumulator中的统计值,去计算各个区间的比例
- 将最后计算出来的结果写入MySQL对应的表中
- actionRDD在之前Session聚合的时候已经映射过,这里显得多余。
- 将最后计算出来的结果写入MySQL对应的表中
优点: 解耦,易维护,重代码设计
缺点: 性能稍低
- 不要去生成新的RDD(可能要处理上亿的数据)
- 不要去单独遍历一遍Session的数据(处理上千万的数据)
- 可以在进行Session聚合时,就直接计算出每个Session的访问时长和访问步长
- 在进行过滤的时候,本来就要遍历所有的聚合Session信息,此时,就可以在某个Session通过筛选条件后,将其访问时长和访问步长累加到自定义Accumulator上
- 在这两种截然不同的实现方式上,面对上亿,上千万数据时,甚至可以节省时间长达半小时甚至几小时
优点: 性能好
缺点: 高度耦合,代码不易维护
- 尽量少生成RDD
- 尽量少对RDD进行算子操作,如果有可能,尽量在一个算子里面,实现多个算子功能
- 尽量少对RDD进行shuffle算子操作,groupByKey,sortByKey,reduceByKey,shuffle操作会导致大量磁盘读写,导致严重性能下降
- shuffle算子很容易导致数据倾斜,一旦倾斜,就是性能杀手
- 无论什么功能,性能第一
在聚合过程中直接计算起始时间和结束时间,并计算步长
具体细节看源码
JavaPairRDD<Long,String> sessionId2PartAggrInfoRDD = sessionId2ActionsRDD.mapToPair(t -> {
//Session起始时间
Date startTime = null;
Date endTime = null;
//Session访问步长
int stepLength = 0;
//遍历session所有用户行为
while (iterator.hasNext()) {
//计算Session开始和结束时间
Date actionTime = DateUtils.parseTime(row.getString(4));
if (startTime == null) {
startTime = actionTime;
}
if (endTime == null) {
endTime = actionTime;
}
if (actionTime.before(startTime)) {
startTime = actionTime;
}
if (actionTime.after(endTime)) {
endTime = actionTime;
}
//计算访问步长
stepLength++;
}
//计算Session访问时长,单位:秒
long visitLength = (endTime.getTime() - startTime.getTime()) / 1000;
//聚合数据拼接:key=value|key=value
String partAggrInfo = Constants.FIELD_SESSION_ID+"="+sessionId+"|"
+Constants.FIELD_SEARCH_KEYWORDS+"="+searchKeyWords+"|"
+Constants.FIELD_CLICK_CATEGORY_IDS+"="+clickCategoryIds+"|"
//重构添加新字段:访问时长和访问步长
+Constants.FIELD_VISIT_LENGTH+"="+visitLength+"|"
+Constants.FIELD_STEP_LENGTH+"="+stepLength;