【Spark数仓项目】需求二:DWD层会话分隔构建-高德地图API解析经纬度位置

写在前面

本项目需求运行在Hadoop10单机环境:

  • Spark3.2.0
  • Flink1.13.6
  • Hadoop3.1.4
  • jdk1.8
  • Sqoop1.4.6
  • MySQL5.7
  • Hive3.1.2
  • Kafka0.11
  • Flume1.9.0
  • Zookeeper3.4.6
  • Hbase2.4
  • Redis6.2.0
  • Dlink0.7.3

Windows11 开发环境:

  • Idea 2020
  • Moba
  • DBeaver7.0.0
  • Scala2.12.17

一、Session会话分隔切割

1.1 会话分隔是为了什么?好处

图1
由需求一清洗后的临时表中可得会话id和会话的时间戳,我们需要将单一设备的会话进一步细化分隔为新的会话。如上图查询结果所示。

Chatgpt:
将用户行为数据的会话分隔成细粒度的时间片可以带来以下好处:

  • 分析用户行为模式:细粒度的时间片可以更好地了解用户在不同时间段内的行为模式和趋势。通过分析用户在不同时间片内的行为,可以发现用户活动的高峰期、低谷期和变化趋势,为业务决策提供更准确的数据支持。

  • 个性化推荐和营销:通过了解用户在不同时间片内的兴趣和需求变化,可以为用户提供更加个性化的推荐内容和营销策略。例如,根据用户在特定时间片内的购买习惯推荐相似商品,或者在用户活跃时间段展示相关的促销活动。

  • 用户行为分析和异常检测:细粒度的时间片可以帮助进行更精细的用户行为分析和异常检测。通过比较用户在不同时间片内的行为特征,可以发现异常行为,如频繁登录、异常购买行为等,及时采取措施防范风险。

  • 优化产品和服务策略:通过时间片分析,可以了解用户在不同时间段内对产品和服务的使用情况。这有助于优化产品的功能、性能和用户体验,以满足用户在不同时间片的需求和期望。

综上所述,将用户行为数据的会话分隔成细粒度的时间片可以提供更详细、准确的用户行为分析和个性化服务,为企业决策和用户体验提供更有价值的数据支持。

1.2 分隔测试SQL代码Demo

该案例是本章节分隔会话需求的拆解测试。
需求是:会话差超过4,定义为一个新的会话。

* sesssionid  newsessionid
 *   abc         abc-0
 *   abc         abc-0
 *   abc         abc-1
 *   abc         abc-1
 *   abc         abc-2
 *   qwe         qwe-0
 *   qwe         qwe-0
 *   qwe         qwe-1

1.2.1 HSQL建表语句

create table tmp.test1(
  sesssionid  string,
  ts          int
)
insert into tmp.test1 
values('abc',2),('abc',3),('abc',8),('abc',10),('abc',18),
      ('qwe',2),('qwe',3),('qwe',9)
select * from tmp.test1
-- 开启本地模式
set hive.exec.mode.local.auto=true;

select sesssionid ,ts,CONCAT(sesssionid,'-',new_row) AS newsessionid
FROM (
	SELECT sesssionid ,ts,before_sid,diff,flag
			,sum(flag) over(PARTITION by sesssionid order by ts) new_row
	from (
		select sesssionid ,ts,before_sid,diff,if(diff>4,1,0) as flag
		from (
			select sesssionid ,ts,before_sid,diff
			from (
				select 	sesssionid ,ts,before_sid,ts-before_sid as diff
				from (
					 select sesssionid ,ts
					 		,lag(ts,1,ts) over (PARTITION by sesssionid order by ts ) as before_sid
			
					 from tmp.test1 
				) t1
			)t2
		)t3
	)t4
)T5	

1.2.2 测试Demo查询结果

在这里插入图片描述
总结一下上述代码,需要用lag开窗计算会话的时间差,按照会话分隔的粒度计算出一个差值flag标志,flag是为了判断当前会话是否为重新开始的新会话。关键是掌握sum开窗函数的几种用法,sum可以按照给定分区进行累加,也可以进行分区内按照flag分别累加。具体步骤请按照上述HSQL中子查询一步一步验证该思路。
在这里插入图片描述
以上测试DEMO即为本章节DWD层需求,在开始DWD层会话分割前,先完成该测试,即可应用该思路在项目需求。

1.3 开始分隔Session前的建表工作

首先是创建tmp临时表event_log_splited,因为后续需求仍然需要不断完善。

create table tmp.event_log_splited(
  account           string,
  appid             string,
  appversion        string,
  carrier           string,
  deviceid          string,
  devicetype        string,
  eventid           string,
  ip                string,
  latitude          double,
  longitude         double,
  nettype           string,
  osname            string,
  osversion         string,
  properties        Map<String,String>,
  releasechannel    string,
  resolution        string,  
  sessionid         string,
  `timestamp`       bigint,
  newsessionid      string
)
partitioned by(dt string)
STORED as ORC
TBLPROPERTIES ('orc.compress'='SNAPPY')

用于测试的语句:

select * from tmp.event_log_splited  where dt = '2023-06-22'

alter table tmp.event_log_splited drop partition(dt='2023-06-22')

1.4 编写Spark程序代码

1.4.1 Local测试

以下scala代码完成了从tmp.event_log_washed中的sessionid到 tmp.event_log_splited表中的newsessionid的需求。需求思路即为1.2小节内容。其中工具类已在本项目需求一中给出。

package com.yh.ods_etl

import com.yh.utils.SparkUtils

object AppLogSessionSplit_02 {

  def main(args: Array[String]): Unit = {
    if(args.length == 0){
      println("缺失参数")
      System.exit(0)
    }

    val spark = SparkUtils.getSparkSession("AppLogSessionSplit_02")

    val dt = args(0)

    spark.sql(
      s"""
        |
        |insert overwrite table tmp.event_log_splited
        |partition(dt='${dt}')
        |select
        |   account
        |   ,appid
        |   ,appversion
        |   ,carrier
        |   ,deviceid
        |   ,devicetype
        |   ,eventid
        |   ,ip
        |   ,latitude
        |   ,longitude
        |   ,nettype
        |   ,osname
        |   ,osversion
        |   ,properties
        |   ,releasechannel
        |   ,resolution
        |   ,sessionid
        |   ,`timestamp`
        |   ,concat(sessionid,'-',sum(c1) over(partition by sessionid order by `timestamp`)) newsessionid
        |from(
        |	select
        |     account
        |     ,appid
        |     ,appversion
        |     ,carrier
        |     ,deviceid
        |     ,devicetype
        |     ,eventid
        |     ,ip
        |     ,latitude
        |     ,longitude
        |     ,nettype
        |     ,osname
        |     ,osversion
        |     ,properties
        |     ,releasechannel
        |     ,resolution
        |     ,sessionid
        |     ,`timestamp`
        |	    ,if( (`timestamp`-lag(`timestamp`,1,`timestamp`) over(partition by sessionid order by `timestamp`))/1000/60 > 10 ,1,0) c1
        |	from tmp.event_log_washed where dt = '${dt}'
        |)t2
        |
        |""".stripMargin)

    spark.stop()
  }

}

Local本地测试运行成功hive截图:在这里插入图片描述

1.4.2 提交到Yarn服务器运行

编写一个shell03.DataSplit.sh

#! /bin/bash


dt=$1
if [ "x"$1 == "x" ]
then
  dt=$(date -d "1 days ago" +%Y-%m-%d)
fi

echo " 执行日期 ---------- $dt --------------- "


spark-submit                           \
--master yarn                          \
--class com.yh.ods_etl.AppLogSessionSplit_02   \
--conf spark.yarn.jars=local:/opt/installs/spark3.2.0/jars/*   \
--driver-memory   1G                   \
--driver-cores 	  2                    \
--executor-memory 2G                   \
--num-executors   3                    \
--executor-cores  2                    \
--queue           abc                  \
--jars /opt/app/spark-dw-jar-with-dependencies.jar      \
/opt/app/spark-dw.jar $dt

相比较于需求一种的两个提交Shell,本次的shell只是更换了全类名和新上传的依赖jar包,需要注意是否有新的依赖加入。运行shell如下截图:
在这里插入图片描述

二、高德地图webAPI解析经纬度位置应用

2.1 为什么要解析?

在这里插入图片描述

在我们前面需求处理的数据中存储的位置信息是经纬度,现在我们需要获取具体到省市区县的数据,因此我们就需要调用某地图的api来帮助我们解析字段。

2.2 某德地图api获取

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

获取api方式也较为简单,在高德开放平台申请即可,官方文档有详细的使用说明。

2.3 Json工具类hutool依赖
由于高德的返回位置信息是json,所以我们使用了hutool工具类解析json,请加入相关版本的依赖在pom文件。

    <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.20</version>
        </dependency>

2.3 建库建表

以下是dwd层数据库的创建,以及dwd.event_log_detail表的创建,数据将从前一章节的临时表中插入。

create database dwd;
create table dwd.event_log_detail(
  account           string,
  appid             string,
  appversion        string,
  carrier           string,
  deviceid          string,
  devicetype        string,
  eventid           string,
  ip                string,
  latitude          double,
  longitude         double,
  nettype           string,
  osname            string,
  osversion         string,
  properties        Map<String,String>,
  releasechannel    string,
  resolution        string,  
  sessionid         string,
  `timestamp`       bigint,
  newsessionid      string,
  province          string,   
  city              string,   
  district          string
)
partitioned by(dt string)     
STORED as orc 
TBLPROPERTIES ('orc.compress'='SNAPPY')
      
select count(*)
from tmp.event_log_splited where dt = '2023-06-22' and account = '毕导'
      
alter table dwd.event_log_detail drop partition(dt='2023-06-22')

select *
from dwd.event_log_detail

2.4 编写高德API解析位置代码

这里自定义了udf函数,将api返回字段解析到表格字段中,并且,每一次省市县的解析只调用一次api的解析查询。我们暂时先指定一位叫毕导的用户测试。

package com.yh.ods_etl

import cn.hutool.json.{JSONObject, JSONUtil}
import com.yh.utils.SparkUtils
import org.apache.commons.lang3.StringUtils
import org.apache.spark.sql.DataFrame
import scalaj.http.{Http, HttpRequest}

object AppLogToDWD_03 {

  def main(args: Array[String]): Unit = {
    if(args.length == 0){
      println("缺失参数")
      System.exit(0)
    }

    val spark = SparkUtils.getSparkSession("AppLogToDWD_03")
    spark.sparkContext.setLogLevel("WARN")

    val dt = args(0)

    spark.udf.register("parse_city",(latitude:Double,longitude:Double) => {
      val request: HttpRequest = Http("https://restapi.amap.com/v3/geocode/regeo")
        .param("key", "1ee98f687f76d5428f279cd0dbc5ad85")
        .param("location", longitude+","+latitude)

      val str: String = request.asString.body
      print(str)

      if(!StringUtils.isBlank(str)){
        val jSONObject: JSONObject = JSONUtil.parseObj(str)
        val jSONObject2: JSONObject = jSONObject.getJSONObject("regeocode").getJSONObject("addressComponent")
        val province = jSONObject2.getStr("province")
        val city = jSONObject2.getStr("city").replace("\\[]","")
        val district = jSONObject2.getStr("district")
        province+","+city+","+district
      }else{
        "null,null,null"
      }
    })



    val df: DataFrame = spark.sql(
      s"""
         |	select
         |	   *
         |	  ,split(parse_city(round(latitude,6),round(longitude,6)),',') c1
         |	from tmp.event_log_splited where dt = '${dt}' and account = '毕导'
         |
         |""".stripMargin)

    df.cache()
    df.createTempView("t2")

    spark.sql(
      s"""
        |
        |insert overwrite table dwd.event_log_detail
        |partition(dt='${dt}')
        |select
        |  account
        |  ,appid
        |  ,appversion
        |  ,carrier
        |  ,deviceid
        |  ,devicetype
        |  ,eventid
        |  ,ip
        |  ,latitude
        |  ,longitude
        |  ,nettype
        |  ,osname
        |  ,osversion
        |  ,properties
        |  ,releasechannel
        |  ,resolution
        |  ,sessionid
        |  ,`timestamp`
        |  ,newsessionid
        |  ,c1[0]  prov
        |  ,c1[1]  city
        |  ,c1[2]  district
        |from t2
        |
        |""".stripMargin)

    spark.stop()
  }
}

本地测试运行界面:在这里插入图片描述

hive中查询dwd层:在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: DWD(Data Warehouse Detail)是数据仓库中的一个重要级,主要用于存储数据仓库中的明细数据。DWD的数据来源通常是来自于数据源系统,与数据源系统的数据模型保持一致,不做任何业务逻辑的处理。在DWD中,数据以原子粒度存储,即每个数据记录对应一个具体的事实或事件。 DWD的主要功能是将数据仓库中的原始数据进行抽取、清洗、转换和加载,保证数据的准确性和完整性,同时为后续的数据处理提供基础数据。DWD还可以作为数据仓库中各之间的过渡,方便数据在不同级之间的传递和转换。 ### 回答2: DWD数仓(Data Warehouse)基本架构中的一部分,它是数据仓库中的数据明细,全称为数据明细(Detail Data Warehouse)。DWD的主要功能是存储原始、完整、精细的源数据,即数据仓库的原子数据。 DWD的设计原则是将所有的源数据按照原汇报系统中的数据结构进行存储,不对数据进行任何业务规则转换和聚合计算。因此,DWD保留了源系统中的所有业务细节和原始数据。 DWD的重要性在于为数据仓库的后续处理(如DWS、DDS等)提供了可靠的数据基础。在DWD,数据质量和精确性得到了高度保证,同时保留了源系统中的所有业务细节,使得后续处理和分析可以进行更高次的数据探索和深入分析。 通过DWD,可以实现以下数据处理操作和功能: 1. 数据集成和清洗:将来自各个源系统的数据进行整合和清洗,确保数据准确性和完整性。 2. 数据存储:将原始数据按照适当的存储结构进行存储,提供高性能的数据访问。 3. 数据加工:对原始数据进行简单的加工操作,如数据类型转换、字段拆分等。 4. 数据备份和恢复:保障数据安全性,提供数据恢复功能,防止数据丢失或损坏。 5. 数据血缘追踪:记录数据来源和变动历史,方便数据溯源和追踪。 总之,DWD数仓架构中扮演着重要角色,通过存储原始、完整的业务数据,为后续数据处理和分析提供了坚实的基础,同时保证了数据的质量和准确性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大数据程序终结者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值