电商用户行为分析大数据平台之用户访问session分析
Easul Lv6

大数据项目开发流程

数据调研

分析底层的基础数据,分析表结构,弄清表之间的关系,表中数据的更新粒度(一小时更新一次还是一天更新一次),会不会有脏数据,每天什么时候数据可以进来

需求分析

需要跟产品经理(PM)沟通需求细节,根据产品经理的需求文档和原型图来研究如何实现功能。如果作为项目组leader,需要跟组内成员明确任务需求

技术方案设计

通过基础数据和PM提出的需求,进行技术方案设计
技术方案就是基于现有数据,针对提出的需求,实现所有需求的整个技术架构,关键的技术点等
需要考虑实现需求的所用技术点,技术选型(需要缓存时用redis还是什么)等
技术架构就是前台用什么,后台用什么,如何显示,流程如何实现等
技术方案需要列举出技术实现思路,需要实现的技术要点

数据表设计

根据基础数据,需求分析,技术方案设计,来规划数据设计
数据设计包含两方面

  1. 对上游数据(数据调研的基础数据)是否需要开发Hive ETL,来对数据进一步处理和转换,从而更方便和快速的执行spark作业
  2. 设计spark作业保存结果数据的业务表结构,用于前台显示任务执行结果

编码实现

也就是coding阶段,每开发完一个功能,都需要几个环节,即单元测试本地测试生产环境测试,最后是性能调优

模块分析

对用户访问session进行分析

  1. 可以根据使用者指定的条件,筛选出用户(如根据年龄,职业,城市来筛选用户),对这些用户在指定日期范围内发起的session,进行聚合统计,如
  2. 访问时长在0-3秒的session占总session的比例
  3. 按时间比例,12:00-13:00的session数量占当天总session的数量50%,当天总访问10000个,总共当天要抽取1000个,则抽取12:00-13:00的session数量为500(需随机抽取)
  4. 获取点击量,下单量和支付量都排名前10的商品种类
  5. 获取top10商品种类的点击数量排名前10的session
  6. 开发了上述功能,就需要进行大量,复杂的性能调优(十亿和百亿的资源分配是不同的)
  7. 十亿级数据量的troubleshooting经验总结
  8. 数据倾斜的完美解决方案(数据倾斜往往是大数据处理程序的性能杀手)
  9. 使用mock数据,来对模块进行调试,运行和演示

实际企业项目中的使用架构

  1. J2EE平台,通过前端页面提交给后台分析任务(如session分析模块的实现),前端可以提供各种筛选条件(职业,年龄,城市等)
  2. J2EE收到统计分析任务后,调用底层封装了spark-submit的shell脚本(通过Runtime,Process类),shell脚本提交spark作业
  3. spark作业获取使用者指定的筛选参数,进而运行相关作业逻辑,进行模块统计和分析
  4. Spark作业统计和分析的结果,会存入MySQL指定的表
  5. 最后J2EE前端可通过表格,图表等形式查看MySQL存储的统计分析任务的结果数据

用户访问session介绍

  1. 每个点击行为(如首页点击,商品点击,搜索关键词,商品加入购物车,购物车下订单,订单支付)都可以看成一个action
  2. 用户session就是从用户进入首页操作完(各种点击后)离开网站,关闭浏览器或长时间没操作而结束。
  3. session是电商网站中最基本的数据和大数据,面向C端(customer)

基础数据结构

一些相关的hive表
真实的表,数据字段可能有下边10倍之多
且实际中用户执行行为后,会给服务器发送日志,ETL工程师会对这些日志进行抽取转换,满足各种业务需要,形成大量结构的表

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
user_visit_action: 网站或app每天点击流的数据。用户点一下,表里就多一条数据,下边为相关字段
date: 行为发生的日期
user_id: 该点击行为的用户
session_id: 某个用户session会话的唯一标识
page_id: 商品页面的id
action_time: 行为发生的时间点
search_keyword: 搜索行为下的关键词
click_category: 点击的某个品类目录(美食,电脑等)
click_product_id: 点击的商品ID(xx手机,xx品牌电脑)
order_category_ids: 购物车中下单的商品的品类目录(食品品类,日用品品类等)
order_product_ids: 购物车中具体下单的商品(xx手机)
pay_category_ids: 支付的品类目录
pay_product_ids: 支付的具体商品
user_info: 用户信息表,主要是用户注册的信息
user_id: 用户唯一标识,通常是自增长的Long类型,Bigint类型
username: 用户登录名
name: 用户昵称
age: 用户年龄
professional: 用户职业
city: 用户城市

MySQL表

YAML
1
2
3
4
5
6
7
8
9
task: 保存平台使用者通过J2EE提交的基于特定参数的分析任务。使用MySQL表是因为任务要通过J2EE实现快速实时插入和查询。
task_id: 表的主键
task_name: 任务名称
create_time: 创建时间
start_time: 开始运行时间
finish_time: 结束运行时间
task_type: 任务类型,区分各种不同的统计分析任务,如用户访问session分析任务,页面单跳转化率任务
task_status: 任务状态,对应一次spark作业的运行,用于标记正在运行,运行完成还是未开始运行等
task_param: 使用JSON格式封装用户提交的特殊筛选参数

相关流程

  • 使用者可以通过J2EE平台提交统计分析任务,查看任务列表,查看任务执行结果的图表展示
  • J2EE平台接收到请求后,会先将任务信息保存到MySQL的task表(顺便设置create_time)
  • 然后J2EE通过Runtime,Process等API执行封装了spark-submit命令的linux脚本(顺便设置start_time)
  • 脚本会顺带提交Spark作业的jar包(自己编写的spark作业)
  • 作业提交后会在Spark Standalone集群或在yarn集群运行
  • Spark作业运行时,会先从MySQL的task表读取任务使用的筛选参数,然后读取Hive中需要的表数据
  • Spark作业运行过程中,或运行完毕后,会将统计分析结果数据插入到MySQL对应的表(后边用于分析展示)
  • Runtime,Process执行的spark-submit脚本结束后,会设置task表中该任务的finish_time
  • 使用者可以在任务结束后,查看统计分析结果的报表(表格或图表)

需求分析

  1. 按条件筛选session
    如条件为:搜索过某些关键词的用户,访问时间在某时间段的用户,年龄在某范围的用户等。然后找到对应session数据
    这个功能可以获取指定用户群体数据去进行分析
  2. 统计符合条件的session中,访问时长与访问步长在1-3S,4-6S,7-9S,10-30S,30-60S,1-3m,3-10m,10-30m,30m以上各范围的session占比
    访问时长为session的开始action到结束action的时间范围。访问步长为一个session在执行期间内依次点击过多少页面
    一个session维持了一分钟,点了10个页面,访问时长为1m,访问步长为10
    若筛选出1000万数据,1-3S访问时长有100万,则其占比为10%
    这个功能可以从全局看符合某些条件的用户使用该产品的习惯
  3. 在符合条件的session中,按时间比例随机抽取1000个session
    如在一天1000万session中随机抽取1000个session,需要满足以下几点
    如果这天12-13点有100万数据,则12-13点抽取100个数据即可,即总量10%(从100万里随机抽100个)
    这个功能可以让使用者按时间比例均匀随机采样数据(样本更符合实际),然后就可以观察每个session流(先点击首页,再点击商品等)
  4. 在符合条件的session中,获取点击,下单,支付数量排名前十的品类
    这个功能可以分析出符合条件的用户最感兴趣的商品是什么品类,从而了解不同类型用户的心理和喜好
  5. 对于排名前十的品类,分别获取其点击次数排名前十的session
    这个功能可以获取某个用户群体最感兴趣的品类中最典型用户的session行为

技术方案设计

需求分析内容来设计技术方案

  1. 按条件筛选session的相关问题
    1. 筛选粒度不统一:session或action粒度(搜索词,访问时间等),用户粒度(年龄,性别,职业等)
    2. 用户数据量巨大:user_visit_action一行就是一个行为,如果日活用户有千万,则日数据量可能有5-10亿
    3. 筛选粒度不统一数据量大带来的问题:
      1. 筛选粒度不统一,就需要对所有数据进行全量扫描
      2. 全量扫描,量太大,spark作业的运行速度就会大幅降低,影响用户体验
    4. 解决方法:
      1. 对原始数据进行聚合(session粒度的聚合),如从hive表筛选某时间范围的数据,再根据session_id进行聚合
      2. 这样一条记录就是一个用户某session内的访问记录,可有所有搜索关键词点击的所有商品id
      3. 聚合后针对session粒度的数据,按使用者指定的筛选条件筛选出符合的session粒度的数据
  2. 聚合统计
    因为spark作业是分布式的,所以每个spark task执行统计逻辑时,可能需要全局变量来累加操作。
    spark实现分布式的安全累加操作,需要使用Accumulator变量。
    如果使用基础的Accumulator变量,多个统计范围,就需要很多个Accumulator变量
    但这样代码就会有大量Accumulator变量,维护困难,修改代码可能会导致错误(如要给1-3s的Accumulator累加,但是加到了4-6s上的Accumulator)。
    所以可以使用自定义Accumulator,实现复杂的分布式计算(用一个Accumulator计算所有指标)。
  3. 在符合条件的session中,按时间比例抽取1000个session
    主要使用Spark的countByKeygroupByKeymapToPair等算子开发复杂的按时间比例随机均匀采样抽取的算法。
  4. 在符合条件的session中,获取点击,下单和支付数量排名前10的品类
    对每个品类的点击,下单和支付数量都进行计算,然后使用Spark的自定义key二次排序技术,对所有品类按这三个字段依次排序
    先比较点击数量,相同则比较下单数量,再相同则比较支付数量
  5. 对排名前10的品类,获取其点击次数排名前10的session
    需要使用spark的分组取TopN的算法实现
    先对排名前十的品类数据按品类ID分组,然后获取每组点击数量排名前十的session
  6. 相关技能点总结
    1. 通过底层数据聚合,减少spark作业处理数据量,提升spark作业性能(从根本提升spark性能的技巧)
    2. 自定义Accumulator,实现复杂分布式计算的技术
    3. Spark按时间比例随机抽取算法
    4. Spark自定义key二次排序技术
    5. Spark分组取TopN算法
    6. 通过Spark各种功能与技术点,实现聚合,采样,排序,取TopN的实现

数据表设计

这里的表用于存储spark作业执行结果的数据

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
-- 存储session聚合统计的结果表
-- task_id与任务表中的主键相同,便于找到该结果对应的任务
-- 1s_3s这种是访问时长的统计,1_3是访问步长的统计
CREATE TABLE `session_aggr_stat` (
`task_id` int(11) NOT NULL,
`session_count` int(11) DEFAULT NULL,
`1s_3s` double DEFAULT NULL,
`4s_6s` double DEFAULT NULL,
`7s_9s` double DEFAULT NULL,
`10s_30s` double DEFAULT NULL,
`30s_60s` double DEFAULT NULL,
`1m_3m` double DEFAULT NULL,
`3m_10m` double DEFAULT NULL,
`10m_30m` double DEFAULT NULL,
`30m` double DEFAULT NULL,
`1_3` double DEFAULT NULL,
`4_6` double DEFAULT NULL,
`7_9` double DEFAULT NULL,
`10_30` double DEFAULT NULL,
`30_60` double DEFAULT NULL,
`60` double DEFAULT NULL,
PRIMARY KEY (`task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

-- 存储按时间比例随机抽取1000个session的结果表
-- start_time是session开始时间
-- end_time是session结束时间
-- search_keywords是查询操作时的关键字
CREATE TABLE `session_random_extract` (
`task_id` int(11) NOT NULL,
`session_id` varchar(255) DEFAULT NULL,
`start_time` varchar(50) DEFAULT NULL,
`end_time` varchar(50) DEFAULT NULL,
`search_keywords` varchar(255) DEFAULT NULL,
PRIMARY KEY (`task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

-- 存储按点击,下单,支付,排序的top10品类数据
-- 只有10个数据,而不是点击,支付,排序各十个数据
CREATE TABLE `top10_category` (
`task_id` int(11) NOT NULL,
`category_id` int(11) DEFAULT NULL,
`click_count` int(11) DEFAULT NULL,
`order_count` int(11) DEFAULT NULL,
`pay_count` int(11) DEFAULT NULL,
PRIMARY KEY (`task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

-- 存储top10每个品类点击top10的session
CREATE TABLE `top10_category_session` (
`task_id` int(11) NO NULL,
`category_id` int(11) DEFAULT NULL,
`session_id` varchar(255) DEFAULT NULL,
`click_count` int(11) DEFAULT NULL,
PRIMARY KEY (`task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

-- 存储随机抽取出来的session明细,top10品类的session明细
CREATE TABLE `session_detail` (
`task_id` int(11) NOT NULL,
`user_id` int(11) DEFAULT NULL,
`session_id` varchar(255) DEFAULT NULL,
`page_id` int(11) DEFAULT NULL,
`action_time` varchar(255) DEFAULT NULL,
`search_keyword` varchar(255) DEFAULT NULL,
`click_category_id` int(11) DEFAULT NULL,
`click_product_id` int(11) DEFAULT NULL,
`order_category_ids` varchar(255) DEFAULT NULL,
`order_product_ids` varchar(255) DEFAULT NULL,
`pay_category_ids` varchar(255) DEFAULT NULL,
`pay_product_ids` varchar(255) DEFAULT NULL,
PRIMARY KEY (`task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

-- 任务表,
CREATE TABLE `task` (
`task_id` int(11) NOT NULL AUTO_INCREMENT,
`task_name` varchar(255) DEFAULT NULL,
`create_time` varchar(255) DEFAULT NULL,
`start_time` varchar(255) DEFAULT NULL,
`finish_time` varchar(255) DEFAULT NULL,
`task_type` varchar(255) DEFAULT NULL,
`task_status` varchar(255) DEFAULT NULL,
`task_param` text,
PRIMARY KEY (`task_id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8

编码阶段

如果项目比较大,可以设计一些组件。如配置管理组件,常量类常量接口JDBC辅助组件
配置管理组件可用于读取配置文件中的配置项。如果设计复杂的配置管理组件,可能需要一些设计模式(单例模式,解释器模式等),管理多个propertiesxml文件。

小知识

JAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ConfigurationManager {
// 私有化Properties,可以防止外界不小心更改了里边的值,影响其他的项目
private static Properties prop = new Properties();

/*
每个类第一次使用,jvm的类加载器会从磁盘的.class加载该字节码文件,并为其构建Class对象
该类第一次加载先加载静态成员,再加载静态代码块
*/
static {
InputStream in = ConfigurationManager.class.getClassLoader().getResourceAsStream("my.properties");
try {
prop.load(in);
} catch (IOException e) {
e.printStackTrace();
}
}

public static String get(String key) {
return prop.getProperty(key);
}
}

public class Test {
public static void main(String[] args) {
// 第一次使用该类,JVM发现该类不在内存中,就用类加载器从该类所在的磁盘文件加载该类
ConfigurationManager.get("test");
}
}
 评论