《大型綜合項目-基於大數據平臺的數據倉庫》學習筆記(09):日誌預處理篇

本項目教程筆記源自多易教育《Titan綜合數據倉庫與數據運營系統》,在CSDN學院有相關視頻教程購買鏈接,大數據企業級項目實戰–Titan大型數據運營系統
本項目課程是一門極具綜合性和完整性的大型大數據項目實戰課程,課程項目的業務背景源自各類互聯網公司對海量用戶瀏覽行爲數據和業務數據分析的需求及企業數據管理、數據運營需求。
學完本課程,你將很容易就拿到大數據數倉建設或用戶畫像建設等崗位的OFFER

本課程項目涵蓋數據採集與預處理數據倉庫體系建設、用戶畫像系統建設、數據治理(元數據管理、數據質量管理)、任務調度系統、數據服務層建設、OLAP即席分析系統建設等大量模塊,力求原汁原味重現一個完備的企業級大型數據運營系統。

跟隨項目課程,歷經接近100+小時的時間,從需求分析開始,到數據埋點採集,到預處理程序代碼編寫,到數倉體系搭建…逐漸展開整個項目的宏大視圖,構建起整個項目的摩天大廈。


一、需求說明

1、清洗過濾

        去除json數據體中的廢棄字段(這是前端開發人員在埋點設計方案變更後遺留的無用字段):

"email"
"phoneNbr"
"birthday"
"isRegistered"
"isLogin"
"addr"
"gender"

        過濾掉日誌中: uid | imei | uuid | mac |androidId | ip全爲空的記錄!
過濾掉日誌中缺少關鍵字段(event/eventid/sessionid 缺任何一個都不行)的記錄!
        過濾掉json格式不正確的(髒數據)!

2、數據解析

將json打平: 解析成扁平格式;

uid imei mac imsi deviceType resolution eventid event lng lat appver

注: event字段不用扁平化;轉成Map類型存儲即可

3、數據集成

        將日誌中的GPS經緯度座標解析成省、市、縣(區)信息;(爲了方便後續的地域維度分析)
        集成商圈信息;(爲了方便後續的地域維度分析)

4、數據修正

guid回補

字段名稱規範化
        比如app日誌中pgid,wxapp中這個字段叫pageid,和web端日誌中的page,統一成pageid

字段度量規範化
        比如時間戳統一用秒級

字段類型規範化
        比如時間戳統一用長整型

5、保存結果

最後,將數據輸出爲parquet格式,壓縮編碼用snappy
在這裏插入圖片描述

二、id-mapping技術手段1:預處理開發實現

1、整體流程

1)json解析,解析成功的返回LogBean對象,解析失敗的返回null
(這樣一來,json格式不對、不完整的髒數據就被識別出來了)
2)對上一步結果RDD[LogBean]進行過濾(清掉json不完整的髒數據,清掉不符合規則的數據)
3)數據修正(回補uid,統一命名規範、度量單位規範等)
4)對數據進行字典知識集成
5)從集成後的結果中跳出無法解析的gps,寫入一個待解析目錄
6)輸出最終結果保存爲parquet(或ORC)文件

2、完整代碼

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()

  }

}
3、打包提交線上運行

步驟1:
       將代碼中寫死的路徑換成參數形式
步驟2:
       在pom中加入打包插件

<build>
    <plugins>
        <!-- 指定編譯java的插件 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>

        <!-- 指定編譯scala的插件 -->
        <plugin>
            <groupId>net.alchim31.maven</groupId>
            <artifactId>scala-maven-plugin</artifactId>
            <version>3.2.2</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>testCompile</goal>
                    </goals>
                    <configuration>
                        <args>
                            <arg>-dependencyfile</arg>
                            <arg>${project.build.directory}/.scala_dependencies</arg>
                        </args>
                    </configuration>
                </execution>
            </executions>
        </plugin>

    </plugins>
</build>

步驟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+小時的時間,從需求分析開始,到數據埋點採集,到預處理程序代碼編寫,到數倉體系搭建…逐漸展開整個項目的宏大視圖,構建起整個項目的摩天大廈。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章