离线数仓(四)2、点击流模型的生成

点击流模型

1、概述

点击流模型完全是业务模型,相关概念由业务指定而来。由于大量的指标统计从点击流模型中更容易得出,所以在预处理阶段,可以使用spark程序来生成点击流模型的数据。

在点击流模型中,存在着两种模型数据:

  • PageViews
  • Visits

(1) pageviews模型

Pageviews模型数据专注于用户每次会话(session)的识别,以及每次session内访问了几步和每一步的停留时间。

在日志数据分析中,通常把前后两条访问记录时间差在30分钟以内算成一次会话。如果超过30分钟,则把下次访问算成新的会话开始

大致步骤如下:

  • 在所有访问日志中找出该用户的所有访问记录
  • 把该用户所有访问记录按照时间正序排序
  • 计算前后两条记录时间差是否为30分钟
  • 如果小于30分钟,则是同一会话session的延续
  • 如果大于30分钟,则是下一会话session的开始
  • 用前后两条记录时间差算出上一步停留时间
  • 最后一步和只有一步的 业务默认指定页面停留时间60s

(2) visit模型

Visit模型专注于每次会话session内起始、结束的访问情况信息。比如用户在某一个会话session内,进入会话的起始页面和起始时间,会话结束是从哪个页面离开的,离开时间,本次session总共访问了几个页面等信息。

大致步骤如下:

  • 在pageviews模型上进行梳理

  • 对每一个session内所有访问记录按照时间正序排序

  • 第一天的时间页面就是起始时间页面

  • 业务指定最后一条记录的时间页面作为离开时间和离开页面

2、spark程序代码实现

(1) 创建日志数据hive-ods层表

执行资料中日志数据-ods层建表语句文件夹中的sql语句,分别创建三张表:
sql语句

--1.创建ODS层数据表
--1.1.原始日志清洗数据表
drop table if exists ods.weblog_origin;
create  table ods.weblog_origin(
valid Boolean,
remote_addr string,
remote_user string,
time_local string,
request string,
status string,
body_bytes_sent string,
http_referer string,
http_user_agent string,
guid string)
partitioned by (dt string)
STORED AS PARQUET TBLPROPERTIES('parquet.compression'='SNAPPY');


--注意事项:
--parquet中字段数据类型要与hive表字段类型保持一致!!
--1.2.点击流模型pageviews表
drop table if exists ods.click_pageviews;
create EXTERNAL table ods.click_pageviews(
session string,
remote_addr string,
time_local string,
request string,
visit_step int,
page_staylong string,
http_referer string,
http_user_agent string,
body_bytes_sent string,
status string,
guid string)
partitioned by (dt string)
STORED AS PARQUET TBLPROPERTIES('parquet.compression'='SNAPPY');


--1.3.点击流visit模型表

drop table if exists ods.click_stream_visit;
create table ods.click_stream_visit(
guid string,
session   string,
remote_addr string,
inTime    string,
outTime   string,
inPage    string,
outPage   string,
referal   string,
pageVisits  int)
partitioned by (dt string)
STORED AS PARQUET TBLPROPERTIES('parquet.compression'='SNAPPY');
日志数据说明

日志数据内容样例:

f5dd685d-6b83-4e7d-8c37-df8797812075 222.68.172.190 - - 2018-11-01 14:34:57 "GET /images/my.jpg HTTP/1.1" 200 19939 "http://www.angularjs.cn/A00n" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36" 

字段解析:

1、用户id信息-uid: f5dd685d-6b83-4e7d-8c37-df8797812075
2、访客ip地址:  222.68.172.190
3、访客用户信息:  - -
4、请求时间:2018-11-01 14:34:57
5、请求方式:GET
6、请求的url:/images/my.jpg
7、请求所用协议:HTTP/1.1
8、响应码:200
9、返回的数据流量:19939
10、访客的来源url:http://www.angularjs.cn/A00n
11、访客所用浏览器:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36


注意:分隔符为空格!

(2) 代码实现

使用spark rdd程序对数据进行预处理
注意:

  • 如果涉及多属性值数据传递 通常可建立与之对应的bean对象携带数据传递
  • 注意写出数据时要保存为parquet格式方便后续数据入hive映射方便。
  • 如涉及不符合本次分析的脏数据,往往采用逻辑删除,也就是自定义标记位,比如使用1或者0来表示数据是否有效,而不是直接物理删除
初步清洗
package com.yyds.main

import java.util.UUID

import com.itheima.bean.{PageViewsBeanCase, VisitBeanCase, WebLogBean, WeblogBeanCase}
import com.itheima.util.DateUtil
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{Dataset, SparkSession}
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable
import scala.collection.mutable.ListBuffer

object ETLApp {
  def main(args: Array[String]): Unit = {
    //准备sparksession
    val conf = new SparkConf()
    val spark = SparkSession.builder()
      .master("local[*]")
      .config(conf).appName(ETLApp.getClass.getName).getOrCreate()
    //获取上下文
    val sc: SparkContext = spark.sparkContext
    /*
    1:读取日志文件,解析封装为weblogbean对象
    2:过滤掉静态请求资源路径
  
     */
    //flume采集输出的路径
    val textRdd: RDD[String] = sc.textFile("/spark_etl/data/input2/")
    val webLogBeanRdd: RDD[WebLogBean] = textRdd.map(WebLogBean(_))
    //过滤掉不合法的请求
    val filterWeblogBeanRdd: RDD[WebLogBean] = webLogBeanRdd.filter(
      x => {
        x != null && x.valid
      }
    )
    //2:过滤掉静态请求资源路径,哪些是静态的资源路径,准备一个初始规则文件,初始的集合装有规则的路径
    initlizePages
    //使用广播变量广播规则
    val pagesBroadCast: Broadcast[mutable.HashSet[String]] = sc.broadcast(pages)
    val filterStaticWeblogRdd: RDD[WebLogBean] = filterWeblogBeanRdd.filter(
      bean => {
        val request: String = bean.request
        val res: Boolean = pagesBroadCast.value.contains(request) //pages直接如此使用是否好?是driver端还是executor端的?所以为了性能要考虑使用广播变量广播pages规则
        //如果被规则文件包含则过滤掉这个请求
        if (res) {
          false
        } else {
          true
        }
      }
    )
    
    //过滤和清洗已经完成

    import spark.implicits._
    val weblogBeanCaseDataset: Dataset[WeblogBeanCase] = filterStaticWeblogRdd.map(bean => WeblogBeanCase(bean.valid, bean.remote_addr, bean.remote_user, bean.time_local,
      bean.request, bean.status, bean.body_bytes_sent, bean.http_referer, bean.http_user_agent, bean.guid)).toDS()

    //保存为parquet文件
    weblogBeanCaseDataset.write.mode("overwrite")
      .parquet("/user/hive/warehouse/ods.db/weblog_origin/dt=20191101/")



    //停止程序
    sc.stop()
  }

  //我们准备一个静态资源的集合
  // 用来存储网站url分类数据
  val pages = new mutable.HashSet[String]()

  //初始化静态资源路径集合
  def initlizePages(): Unit = {
    pages.add("/about")
    pages.add("/black-ip-list/")
    pages.add("/cassandra-clustor/")
    pages.add("/finance-rhive-repurchase/")
    pages.add("/hadoop-family-roadmap/")
    pages.add("/hadoop-hive-intro/")
    pages.add("/hadoop-zookeeper-intro/")
    pages.add("/hadoop-mahout-roadmap/")
  }
}
pageview模型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//pageview模型生成
    //按照用户id分组(uid,iterator(用户的所有访问记录))
    val uidWeblogRdd: RDD[(String, Iterable[WebLogBean])] = filterStaticWeblogRdd.groupBy(x => x.guid)


    //排序
    val pageviewBeanCaseRdd: RDD[PageViewsBeanCase] = uidWeblogRdd.flatMap(
      item => {
        val uid = item._1
        //按照时间排序
        val sortWeblogBeanList: List[WebLogBean] = item._2.toList.sortBy(_.time_local)
        //两两比较,生成sessionid,step,staylong(停留时长),如何计算停留时长:遍历的时候都是计算上一条的停留时长,sessionid,step信息
        //只有一条记录:sessionid,step,staylong:60s
        val pageViewBeanCaseList: ListBuffer[PageViewsBeanCase] = new ListBuffer[PageViewsBeanCase]()
        import scala.util.control.Breaks._
        var sessionid: String = UUID.randomUUID().toString
        var step = 1
        var page_staylong = 60000
        breakable {
          for (num <- 0 until (sortWeblogBeanList.size)) {
            //一条访问记录
            val bean: WebLogBean = sortWeblogBeanList(num)
            //如果只由一条记录
            if (sortWeblogBeanList.size == 1) {
              val pageViewsBeanCase = PageViewsBeanCase(sessionid, bean.remote_addr, bean.time_local,
                bean.request, step, page_staylong,
                bean.http_referer, bean.http_user_agent, bean.body_bytes_sent, bean.status, bean.guid)
              //之前输出,现在则需要保存起来最后一起输出
              pageViewBeanCaseList += pageViewsBeanCase
              //重新生成sessionid
              sessionid = UUID.randomUUID().toString
              //跳出循环
              break
            }
            //数量不止一条,本条来计算上一条的时长
            breakable {
              if (num == 0) {
                //continue:中止本次,进入下次循环
                break()
              }
              //不是第一条数据,获取到上一条记录
              val lastBean: WebLogBean = sortWeblogBeanList(num - 1)
              //判断的是两个bean对象的差值
              val timeDiff: Long = DateUtil.getTimeDiff(lastBean.time_local, bean.time_local)
              //毫秒值是否小于30分钟
              if (timeDiff <= 30 * 60 * 1000) {
                //属于同个session,你们俩共用一个sessionid,输出上一条
                val pageViewsBeanCase = PageViewsBeanCase(sessionid, lastBean.remote_addr, lastBean.time_local, lastBean.request,
                  step, timeDiff, lastBean.http_referer, lastBean.http_user_agent, lastBean.body_bytes_sent,
                  lastBean.status, lastBean.guid
                )
                //添加到集合中
                pageViewBeanCaseList += pageViewsBeanCase
                //sessionid是否重新生成:不需要,step如何处理?
                step += 1

              } else {
                //不属于一个sessionid,如何处理?不管是否属于同个会话,我们输出的都是上一条记录
                //属于同个session,你们俩共用一个sessionid,输出上一条
                val pageViewsBeanCase = PageViewsBeanCase(sessionid, lastBean.remote_addr, lastBean.time_local, lastBean.request,
                  step, page_staylong, lastBean.http_referer,
                  lastBean.http_user_agent, lastBean.body_bytes_sent,
                  lastBean.status, lastBean.guid
                )
                //添加到集合中
                pageViewBeanCaseList += pageViewsBeanCase
                //sessionid是否重新生成:需要,step如何处理?
                sessionid = UUID.randomUUID().toString
                //step重置为1
                step = 1
              }

              //最后一条需要我们控制输出
              if (num == sortWeblogBeanList.size - 1) {

                val pageViewsBeanCase = PageViewsBeanCase(sessionid, bean.remote_addr, bean.time_local,
                  bean.request, step, page_staylong,
                  bean.http_referer, bean.http_user_agent, bean.body_bytes_sent, bean.status, bean.guid)
                //之前输出,现在则需要保存起来最后一起输出
                pageViewBeanCaseList += pageViewsBeanCase
                //重新生成sessionid
                sessionid = UUID.randomUUID().toString
              }
            }

          }
        }

        pageViewBeanCaseList
      }
    )

    pageviewBeanCaseRdd.toDS().write.mode("overwrite")
      .parquet("/user/hive/warehouse/ods.db/click_pageviews/dt=20191101/")
visit模型

在这里插入图片描述

//生成visit模型,汇总每个会话的总步长,时长等信息
    //按照sessionid分组,(sessionid,iterable(pageviewbeancase))
    val sessionidRdd: RDD[(String, Iterable[PageViewsBeanCase])] = pageviewBeanCaseRdd.groupBy(_.session)

    val visitBeanCaseRdd: RDD[VisitBeanCase] = sessionidRdd.map(
      item => {
        //sessionid
        val sessionid: String = item._1
        //sessionid对应的访问记录
        val cases: List[PageViewsBeanCase] = item._2.toList.sortBy(_.time_local)
        VisitBeanCase(cases(0).guid, sessionid, cases(0).remote_addr, cases(0).time_local, cases(cases.size - 1).time_local,
          cases(0).request, cases(cases.size - 1).request, cases(0).htp_referer, cases.size
        )
      }
    )

    visitBeanCaseRdd.toDS().write.mode("overwrite")
      .parquet("/user/hive/warehouse/ods.db/click_stream_visit/dt=20191101/")

注意:
如果某个用户访问记录过多,则生成page view模型的时候,会产生数据倾斜,此时该怎样处理呢?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值