数仓数据预处理整体流程
整体点的思路:(精简版)
一.创建spark环境
二.加载当日的app埋点数据
三.解析json为rdd[AppLogBesn],
1.解析json数据,解析成扁平格式化
2.抽取有用字段
3.清洗过滤不符合规则的数据
4.构造一个case calss对象,装入数据
四.将解析好的数据进行清洗.过滤.拼接,
五.数据集成
1.先将geo字段个ibmap字典收集到Driver端并广播
2.现根据收据解析后的数据取出经纬度,调用geohash算法转成geohash编码,去geo字典中进行对比查找,填充省市区
3.查找idmap字典填充guid,过滤掉guid不存在的用户
六.将结果返回parquet格式进行保存.
创建一个spark环境
加载App埋点数据
解析json
抽取json的各个字段
在整个数据中其他的字段都是写死的,只有event(事件)这一个字段是活的,因为有的人只是浏览了网页,有的人却下单,购买,邮寄了,
每个人都不一样所以事件不能写死,
最好的办法就是用Map类型将所有点的事件包含进去,是(k,v)形式的其中(k就是event,v就是所有的事件本身)
数据类型主要有String,Map,Double,类型
将时间戳改成Long格式
进行数据清洗
将日志中uid|imei|uuid|mac|androidId|全部为空的数据过滤掉因为全部为空整理出来没有意义
val sb = new StringBuilder//创建一个将数据列名放在一起判断的条件
val flagFields =sb.append(uid).append(imei).append(uuid).append(mac).append(androidId).toString().replaceAll("null,")
将日志中cvent|eventid|sessionid缺少任一列的数据过滤掉
//定义一个bean的AppLogBean(标准类)
同时在beans文件夹下建立一个case class AppLogBean(所有的 列名:列名对应的属性)
返回结果:如果数据符合要求,则返回一个AppLogBean,如果不符合要求就返回null
var bean:AppLogBean = null
//&&是并且的意思.只有event为map类型map类型可以识别!=null.其余都为String.String类型判断不为空就必要要用到StringUtils.isNotBlank(列名)这种办法
if(StringUtils.isNotBlank(flagFields)&&event!=null && StringUtils.isNotBlank(event)&& StringUtils.isNotBlank(sessionid)){
//如果数据符合要求,则返回一个AppLogBean,如果不符合要求就返回null
AppLogBean(所有的列名)//要与case中的定义完全吻合除非case class中有 var 列名:属性="未知" 就可以不用填
}
这个if结束之后返回一个bean,然后从解析json到返回bean之间的代码try{}catch{case e :Exception => null}
//保留不为空的内容
下面已经将两个字典解析好了就剩下数据集成了 数据集成–省市区
.map(bean =>{
//从广播里面将地理字典取出
val geoDict =bc_geo.value
//将表格中的经度和纬度返回值,当成参数装到这个算法中(lat,lng,5)返回一个geo然后再将给传入到这个字典中!
val lng = bean.longtitude
val lat = bean.latitude
//查字典 GeoHash是一个工具,geoHashStringWithCharacterPrecision就是一个方法,
//用这个工具加这个方法传入参数就能把这个表中的经纬度,变成字典中的对应点的索引的 K
val geo = GeoHash.geoHashStringWithCharacterPrecision(lat,lng,5)
//当字典Get出来的类型是option,查询时如果为空就报异常了
val maybetuple :=geoDict.get(geo)
//所以要做个if判断
if(maybetuple.isDefined){
val area :(省,市,区)=maybetuple.get
//字典中的值对应上你的表中你假设"未知"的值
bean.province = area._1
bean.city = area._2
bean.district = area._3
}
})
下面已经将两个字典解析好了就剩下数据集成了 数据集成–guid
val ids = Array(bean.imei,bean.imsi,bean.mac,bean.uid,bean.androidId,bean.uuid)
var find = false
for (elem <- ids if !find) {
val maybeLong = idmpDict.get(elem.hashCode.toLong)
if(maybeLong.isDefined){
bean.guid = maybeLong.get
find = true
}
}
bean
//最后将得到的数据表格遍历 整个框架完成!!!
.filter(bean=> (bean.guid!=Long.MinValue))
.toDF()
过滤掉不符合要求的数据
.filter(_!=null)//括号里面的数据是要留下来的数据标准(filter()保留器!)
将日志中脏数据过滤掉
这个时候.todf已经可以遍历出来了,但是两个字段看不懂一个是经纬度,
一个是全局的唯一标识
接下来就是将这两个看不懂的字段查字典,翻译成通俗易懂的表格
数据解析成map–省市区
加载geo地理位置字典,并收集到driver端,然后广播出去
//读取
spark.read.parquet("字典所在的路径")
//收集称为一个HashMap方便以后的查询
加载guid地理位置字典,并收集到driver端,然后广播出去
收集为HashMap格式首先要搞成map (k,v)格式
.rdd
.map({
case Row(geo:String,province:String,city:String,district:String)
=>
(geo,(province,city,district))
}).collectAsMap()
val bc_geo = spark.sparkContext.broadcast(字典名)
数据解析成map–guid映射字典
加载映射字典,收集到driver端,然后广播出去
spark.read.parquet(guid的路径)
.rdd
.map ({
case Reward(bioshi_hashcode,guid)
=>
(guid,(biaoshi_hashcode,guid ))
}).collectAsMap()
val bc_idmp =spark.sparkContext.broadcast(字典名)
数据写出
关流