本项目教程笔记源自多易教育《Titan综合数据仓库与数据运营系统》,在CSDN学院有相关视频教程购买链接,大数据企业级项目实战–Titan大型数据运营系统
本项目课程是一门极具综合性和完整性的大型大数据项目实战课程,课程项目的业务背景源自各类互联网公司对海量用户浏览行为数据和业务数据分析的需求及企业数据管理、数据运营需求。
学完本课程,你将很容易就拿到大数据数仓建设或用户画像建设等岗位的OFFER
本课程项目涵盖数据采集与预处理、数据仓库体系建设、用户画像系统建设、数据治理(元数据管理、数据质量管理)、任务调度系统、数据服务层建设、OLAP即席分析系统建设等大量模块,力求原汁原味重现一个完备的企业级大型数据运营系统。
跟随项目课程,历经接近100+小时的时间,从需求分析开始,到数据埋点采集,到预处理程序代码编写,到数仓体系搭建…逐渐展开整个项目的宏大视图,构建起整个项目的摩天大厦。
去除json数据体中的废弃字段(这是前端开发人员在埋点设计方案变更后遗留的无用字段):
"email"
"phoneNbr"
"birthday"
"isRegistered"
"isLogin"
"addr"
"gender"
过滤掉日志中: uid | imei | uuid | mac |androidId | ip全为空的记录!
过滤掉日志中缺少关键字段(event/eventid/sessionid 缺任何一个都不行)的记录!
过滤掉json格式不正确的(脏数据)!
将json打平: 解析成扁平格式;
uid | imei | mac | imsi | deviceType | resolution | eventid | event | lng | lat | appver | … | … |
---|---|---|---|---|---|---|---|---|---|---|---|---|
注: event字段不用扁平化;转成Map类型存储即可
将日志中的GPS经纬度坐标解析成省、市、县(区)信息;(为了方便后续的地域维度分析)
集成商圈信息;(为了方便后续的地域维度分析)
guid回补
字段名称规范化
比如app日志中pgid,wxapp中这个字段叫pageid,和web端日志中的page,统一成pageid
字段度量规范化
比如时间戳统一用秒级
字段类型规范化
比如时间戳统一用长整型
最后,将数据输出为parquet格式,压缩编码用snappy
1)json解析,解析成功的返回LogBean对象,解析失败的返回null
(这样一来,json格式不对、不完整的脏数据就被识别出来了)
2)对上一步结果RDD[LogBean]进行过滤(清掉json不完整的脏数据,清掉不符合规则的数据)
3)数据修正(回补uid,统一命名规范、度量单位规范等)
4)对数据进行字典知识集成
5)从集成后的结果中跳出无法解析的gps,写入一个待解析目录
6)输出最终结果保存为parquet(或ORC)文件
case class定义:
/**
* @date: 2020/1/12
* @site: www.doitedu.cn
* @author: hunter.d 涛哥
* @qq: 657270652
* @description: 封装app埋点日志的case class
*/
case class AppLogBean(
var guid:Long,
eventid: String,
event: Map[String, String],
uid: String,
imei: String,
mac: String,
imsi: String,
osName: String,
osVer: String,
androidId: String,
resolution: String,
deviceType: String,
deviceId: String,
uuid: String,
appid: String,
appVer: String,
release_ch: String,
promotion_ch: String,
longtitude: Double,
latitude: Double,
carrier: String,
netType: String,
cid_sn: String,
ip: String,
sessionId: String,
timestamp: Long,
var province:String="未知",
var city:String="未知",
var district:String="未知"
)
预处理流程如下:
package cn.doitedu.dw.pre
import java.util
import ch.hsr.geohash.GeoHash
import cn.doitedu.commons.util.SparkUtil
import cn.doitedu.dw.beans.AppLogBean
import com.alibaba.fastjson.{JSON, JSONObject}
import org.apache.commons.lang3.StringUtils
import org.apache.spark.sql.{Dataset, Row}
/**
* @date: 2020/1/12
* @site: www.doitedu.cn
* @author: hunter.d 涛哥
* @qq: 657270652
* @description: app埋点日志预处理
*/
object AppLogDataPreprocess {
def main(args: Array[String]): Unit = {
// 构造sparksessiong
val spark = SparkUtil.getSparkSession(this.getClass.getSimpleName)
import spark.implicits._
// 加载当日的app埋点日志文件,成为一个dataset[String]
val appDs: Dataset[String] = spark.read.textFile("G:\\yiee_logs\\2020-01-12\\app")
// 加载geo地域字典数据
/**
* -----|---------|------|----------|
* geo |province | city| district|
* -----|---------|------|----------|
* 39eu |河北省 | 石家庄| 裕华区 | Row
* y67u |河南省 | 郑州市| 金水区 | Row
*/
val geodf = spark.read.parquet("data/dict/geo_dict/output")
val geoMap: collection.Map[String, (String, String, String)] = geodf.rdd.map(row=>{
val geo = row.getAs[String]("geo")
val province = row.getAs[String]("province")
val city = row.getAs[String]("city")
val district = row.getAs[String]("district")
(geo,(province,city,district))
}).collectAsMap()
// 广播地域字典
// Map{ 39eu -> (河北省,石家庄,裕华区)
// y67u -> (河南省,郑州市,金水区)
// }
val bc_geo = spark.sparkContext.broadcast(geoMap)
// 加载id映射字典
/**
* ---------------|------|
* biaoshi_hashcode | guid|
* ---------------|------|
* 8238574359 | 62375|row
* ---------------|------|
* 3285943259 | 62375|row
* ---------------|------|
* 62375 | 62375|row
* -----------------|------|
*/
val idmpdf = spark.read.parquet("data/idmp/2020-01-12")
val idMap = idmpdf.rdd.map(row=>{
val id = row.getAs[Long]("biaoshi_hashcode")
val guid = row.getAs[Long]("guid")
(id,guid)
}).collectAsMap()
val bc_id = spark.sparkContext.broadcast(idMap)
// 对日志ds集合中的每一条记录(json)进行解析
appDs.map(line => {
var bean: AppLogBean = null
try {
val jsonobj = JSON.parseObject(line)
val eventid = jsonobj.getString("eventid")
val timestamp = jsonobj.getString("timestamp").toLong
val eventobj: JSONObject = jsonobj.getJSONObject("event")
import scala.collection.JavaConversions._
val javaMap: util.Map[String, String] = eventobj.getInnerMap.asInstanceOf[util.Map[String, String]]
val event: Map[String, String] = javaMap.toMap
val userobj = jsonobj.getJSONObject("user")
val uid = userobj.getString("uid")
val sessionId = userobj.getString("sessionId")
val phoneobj = userobj.getJSONObject("phone")
val imei = phoneobj.getString("imei")
val mac = phoneobj.getString("mac")
val imsi = phoneobj.getString("imsi")
val osName = phoneobj.getString("osName")
val osVer = phoneobj.getString("osVer")
val androidId = phoneobj.getString("androidId")
val resolution = phoneobj.getString("resolution")
val deviceType = phoneobj.getString("deviceType")
val deviceId = phoneobj.getString("deviceId")
val uuid = phoneobj.getString("uuid")
val appobj = jsonobj.getJSONObject("app")
val appid = appobj.getString("appid")
val appVer = appobj.getString("appVer")
val release_ch = appobj.getString("release_ch") // 下载渠道
val promotion_ch = appobj.getString("promotion_ch") // 推广渠道
val locobj = jsonobj.getJSONObject("loc")
var lng = 0.0
var lat = -90.0
try {
lng = locobj.getDouble("longtitude")
lat = locobj.getDouble("latitude")
} catch {
case e: Exception =>
}
val carrier = locobj.getString("carrier")
val netType = locobj.getString("netType")
val cid_sn = locobj.getString("cid_sn")
val ip = locobj.getString("ip")
// 判断数据合法规则
val tmp = (imei + imsi + mac + uid + uuid + androidId).replaceAll("null", "")
if (StringUtils.isNotBlank(tmp) && event != null && StringUtils.isNotBlank(eventid) && StringUtils.isNotBlank(sessionId)) {
// 将提取出来的各个字段,封装到AppLogBean中
bean = AppLogBean(
Long.MinValue,
eventid,
event,
uid,
imei,
mac,
imsi,
osName,
osVer,
androidId,
resolution,
deviceType,
deviceId,
uuid,
appid,
appVer,
release_ch,
promotion_ch,
lng,
lat,
carrier,
netType,
cid_sn,
ip,
sessionId,
timestamp
)
}
} catch {
case e: Exception => null
}
bean
})
.filter(_ != null)
.map(bean=>{
val geoDict = bc_geo.value
val idmpDict = bc_id.value
// 查geo地域字典,填充省市区
val lat = bean.latitude
val lng = bean.longtitude
val mygeo = GeoHash.geoHashStringWithCharacterPrecision(lat,lng,5)
val maybeTuple: Option[(String, String, String)] = geoDict.get(mygeo)
if(maybeTuple.isDefined){
val areaNames = maybeTuple.get
// 填充省市区
bean.province = areaNames._1
bean.city = areaNames._2
bean.district = areaNames._3
}
// 查id映射字典,填充guid
val ids = Array(bean.imei,bean.imsi,bean.mac,bean.androidId,bean.uuid,bean.uid)
val mouId = ids.filter(StringUtils.isNotBlank(_))(0)
val maybeLong = idmpDict.get(mouId.hashCode.toLong)
if(maybeLong.isDefined){
val guid = maybeLong.get
bean.guid = guid
}
bean
})
.filter(bean=>bean.guid != Long.MinValue)
.toDF()
.write
.parquet("data/applog_processed/2020-01-12")
spark.close()
}
}
步骤1:
将代码中写死的路径换成参数形式
步骤2:
在pom中加入打包插件
org.apache.maven.plugins
maven-compiler-plugin
3.5.1
1.8
net.alchim31.maven
scala-maven-plugin
3.2.2
compile
testCompile
-dependencyfile
${project.build.directory}/.scala_dependencies
步骤3:
在idea的maven侧边栏卡中,选父工程,点击install,对整个工程进行打包和本地库安装
步骤4:
拷贝预处理程序的jar包上传到集群,用命令提交
bin/spark-submit \
--master yarn \
--deploy-mode cluster \
--num-executors 3 \
--executor-memory 1g \
--executor-cores 1 \
--class cn.doitedu.titan.dw.pre.AppEventLogPreprocess \
/root/dw.jar /titan/applog/2019-10-29 /titan/areadict /titan/output/applog/2019-10-29 yarn
本项目教程笔记源自多易教育《Titan综合数据仓库与数据运营系统》,在CSDN学院有相关视频教程购买链接,大数据企业级项目实战–Titan大型数据运营系统
本项目课程是一门极具综合性和完整性的大型大数据项目实战课程,课程项目的业务背景源自各类互联网公司对海量用户浏览行为数据和业务数据分析的需求及企业数据管理、数据运营需求。
学完本课程,你将很容易就拿到大数据数仓建设或用户画像建设等岗位的OFFER
本课程项目涵盖数据采集与预处理、数据仓库体系建设、用户画像系统建设、数据治理(元数据管理、数据质量管理)、任务调度系统、数据服务层建设、OLAP即席分析系统建设等大量模块,力求原汁原味重现一个完备的企业级大型数据运营系统。
跟随项目课程,历经接近100+小时的时间,从需求分析开始,到数据埋点采集,到预处理程序代码编写,到数仓体系搭建…逐渐展开整个项目的宏大视图,构建起整个项目的摩天大厦。