场景
[2.0.0]已经对用户session分析模块的统计session访问时长占比功能点的需求分析进行了完美的解说。本文旨在对统计session访问时长占比的代码进行解读,并抽取核心spark知识点单独说明
分析
1、由action粒度的数据 map 得到 session粒度的数据;进一步group,得到聚合后的session粒度数据
2、join用户表数据得到,包含用户信息的session粒度数据
3、按照请求参数过滤session粒度数据的数据
代码架构
/**
* 0、session粒度的聚合聚合:对用户session表信息进行session粒度的聚合,并且关联上用户相关信息
*/
JavaRDD<Row> actionRDD = getActionRDDByDateRange(sqlContext,taskParam);
JavaPairRDD<String, Row> session2actionRDD = generateSess2ActionRDD(actionRDD);
// session->Row
// JavaPairRDD<String, Row> sessionRDD = getSessionRDD(actionRDD);
//Group(session) -> Iterable<Row>
JavaPairRDD<String, Iterable<Row>> session2actionsRDD = session2actionRDD.groupByKey();
// Key: userId -> Value: String(partSessionInfo)
JavaPairRDD<Long, String> userid2partAggSesionInfoRDD = getUserid2partAggSessionInfoRDD(session2actionsRDD);
//Key: userId -> Value: userInfoRow
JavaPairRDD<Long,Row> userid2userInfoRDD = getUserid2userInfoRDD(sqlContext);
//Key : sessionid -> Value : fullAggSessionInfo
JavaPairRDD<String, String> sessionid2fullAggSessionInfoRDD=
getSessionid2fullAggSessionInfoRDD(userid2partAggSesionInfoRDD, userid2userInfoRDD);
/**
* 1、统计过滤后session访问时长占比
* 过滤:按照用户提交任务的其他查询参数,对聚合后的session信息进行过滤
*/
//自定义 Accumulator 进行计数
Accumulator<String> sessionAggStatisticAccumulator = sc.accumulator("", new SessionAggStatisticAccumulator());
JavaPairRDD<String, String> filteredSessionid2fullAggSessionInfoRDD=
getFilteredSessionid2fullAggSessionInfo(
taskParam,sessionid2fullAggSessionInfoRDD,sessionAggStatisticAccumulator);
//将计算出的session访问占比写入mysql表中
calculateAndPersistAggrStat(sessionAggStatisticAccumulator.value(),taskId);
解读
1、按日期对hive仓库中use_visit_action表数据进行筛选,得到 JavaRDD actionRDD
获取用户指定日期的action数据(注:行为数据,行为={搜索、点击、下单与支付})。‘日期’ 这个参数是j2ee前端必传的,在于大幅度过滤hive中的数据。use_visit_action表中的数据量可能达到PB级别,按日期过滤后的数据量可能就只有TB级别的数据量。
JavaRDD<Row> actionRDD = getActionRDDByDateRange(sqlContext,taskParam);
内部实现:getActionRDDByDateRange方法内部,通过HiveContext对象直接查询并过滤use_visit_action表中数据
String sql =
"select * "
+ " from user_visit_action "
+ " where date >='" + startDate +"'"
+ "and date <='"+ endDate +"'";
DataFrame actionDF = sqlContext.sql(sql);
2、获取 session 级别的数据:JavaPairRDD
JavaPairRDD<String, Row> session2actionRDD = generateSess2ActionRDD(actionRDD);
JavaPairRDD<String, Iterable<Row>> session2actionsRDD = session2actionRDD.groupByKey();
其中,session2actionsRDD的 Key:sessionid Value : actions (对应use_visit_action表中的多条记录)。
3、获取包含用户详情的session聚合信息:JavaPairRDD[String,String] sessionid2fullAggSessionInfoRDD
// Key: userId -> Value: String(partSessionInfo)
JavaPairRDD<Long, String> userid2partAggSesionInfoRDD = getUserid2partAggSessionInfoRDD(session2actionsRDD);
//Key: userId -> Value: userInfoRow
JavaPairRDD<Long,Row> userid2userInfoRDD = getUserid2userInfoRDD(sqlContext);
//Key : sessionid -> Value : fullAggSessionInfo
JavaPairRDD<String, String> sessionid2fullAggSessionInfoRDD=
getSessionid2fullAggSessionInfoRDD(userid2partAggSesionInfoRDD, userid2userInfoRDD);
其中,sessionid2fullAggSessionInfoRDD中的一条数据内容大致如下:
[123456]=[sessionid=123456|searchKeywords=music|clickCategoryIds=10,25,08|visitTimeLength=60s|visitStepLength=10|age=23|professional=教师|city=深圳|sex=woman]
注释:遍历聚合session的时候,随便计算出访问时长与访问步长。
4、按照用户提交的其他参数进一步过滤得到:JavaPairRDD[String, String] filteredSessionid2fullAggSessionInfoRDD
//自定义 Accumulator 进行计数
Accumulator<String> sessionAggStatisticAccumulator = sc.accumulator("", new SessionAggStatisticAccumulator());
JavaPairRDD<String, String> filteredSessionid2fullAggSessionInfoRDD= getFilteredSessionid2fullAggSessionInfo(
taskParam,sessionid2fullAggSessionInfoRDD,sessionAggStatisticAccumulator);
//将计算出的session访问占比写入mysql表中 calculateAndPersistAggrStat(sessionAggStatisticAccumulator.value(),taskId);
知识点
自定义累加器(accumulator)的使用
如何保存0-3s、3-5s … 1-3m … 10-30m等session访问时长占比呢?
当然,你可以为每个访问时长单独定义一个累加器,但是这样就以为着得定义几十个累加器 - 这样极容易出问题:本来session的访问时长为 4s,此时3-5s对应的accumulator值应该加一,由于人为的失误你将 0-3s的accumulator值加一了。更重要的是这样写的话,代码的可扩展性、可维护性极差!怎么解决这个问题呢?自定义累加器!
- 自定义累加器
自己写一个类实现AccumulatorParam(Helper object defining how to accumulate values of a particular type)接口,定义如何 ‘累加’ - (这里传递的泛型参数为String,当然也可以是其他 Object:Object如何自增呢?就在这个类里定义)
package cool.pengych.sparker.session;
import org.apache.spark.AccumulatorParam;
import cool.pengych.sparker.constant.Constants;
import cool.pengych.sparker.util.StringUtils;
/**
* 自定义累加器
* 求各session访问时长占比
* @author pengyucheng
*/
public class SessionAggStatisticAccumulator implements AccumulatorParam<String>
{
private static final long serialVersionUID = 1L;
/**
* Merge two accumulated values together. Is allowed to modify and return the first value
* for efficiency (to avoid allocating objects).
*
* @param v1 one set of accumulated data
* @param v2 another set of accumulated data
* @return both data sets merged together
*/
@Override
public String addInPlace(String v1, String v2)
{
return add( v1, v2);
}
/**
* Return the "zero" (identity) value for an accumulator type, given its initial value. For
* example, if R was a vector of N dimensions, this would return a vector of N zeroes. 这里只定义了一个 变量来存储所有的session访问时长
*/
@Override
public String zero(String R)
{
return Constants.SESSION_COUNT+"=0|"
+ Constants.TIME_PERIOD_1s_3s + "=0|"
+ Constants.TIME_PERIOD_4s_6s + "=0|"
+ Constants.TIME_PERIOD_7s_9s + "=0|"
+ Constants.TIME_PERIOD_10s_30s + "=0|"
+ Constants.TIME_PERIOD_30s_60s + "=0|"
+ Constants.TIME_PERIOD_1m_3m + "=0|"
+ Constants.TIME_PERIOD_3m_10m + "=0|"
+ Constants.TIME_PERIOD_10m_30m + "=0|"
+ Constants.TIME_PERIOD_30m + "=0|"
+ Constants.STEP_PERIOD_1_3 + "=0|"
+ Constants.STEP_PERIOD_4_6 + "=0|"
+ Constants.STEP_PERIOD_7_9 + "=0|"
+ Constants.STEP_PERIOD_10_30 + "=0|"
+ Constants.STEP_PERIOD_30_60 + "=0|"
+ Constants.STEP_PERIOD_60 + "=0";
}
/**
* Add additional data to the accumulator value. Is allowed to modify and return `r`
* for efficiency (to avoid allocating objects).
*
* @param v1 the current value of the accumulator
* @param v2 the data to be added to the accumulator
* @return the new value of the accumulator
*/
@Override
public String addAccumulator(String v1, String v2)
{
return add( v1, v2);
}
/**
* session累加计算逻辑
* @param v1 连接串
* @param v2 范围区间 Key
* @return 更新以后的连接串
*/
private String add(String v1, String v2)
{
// 当前值为空,直接返回 增加的值
if(StringUtils.isEmpty(v1))
{
return v2;
}
// 使用StringUtils工具类,从v1中,提取v2对应的值,并累加1
String oldValue = StringUtils.getFieldFromConcatString(v1, "\\|", v2);
if(oldValue != null)
{
// 将范围区间原有的值,累加1
int newValue = Integer.valueOf(oldValue) + 1;
// 使用StringUtils工具类,将v1中,v2对应的值,设置成新的累加后的值
return StringUtils.setFieldInConcatString(v1, "\\|", v2, String.valueOf(newValue));
}
return v1;
}
}
- 用法
同普通累加器一样:先申明,再使用
Accumulator<String> sessionAggStatisticAccumulator = sc.accumulator("", new SessionAggStatisticAccumulator());
sessionAggStatisticAccumulator.add(Constants.SESSION_COUNT);
sessionAggStatisticAccumulator.value() // 获取累加器中的值
总结
- RDD的命名一定要规范:filteredSessionid2fullAggSessionInfoRDD
- 自定义累加器的使用有待后续进一步实战体验