ITPub博客

首页 > 数据库 > 数据库开发技术 > 大数据实战项目第3步—数仓建模

大数据实战项目第3步—数仓建模

原创 数据库开发技术 作者:安瑞哥是码农 时间:2022-09-02 11:35:00 0 删除 编辑


经过之前讲解过的第1步模拟实时数据写kafka,第2步演示了spark structured streaming如何从kafka实时消费数据并写入Elasticsearch,以及写hive动态分区表,并对比了flink实时Elasticsearch的区别,知道了用flink的一些坑,目前不支持最新版本的Elasticsearch。

接下来,咱们来玩第3步:构建数仓的ODS和DWD层写入。


|为什么要做这一步
我看讨论群里很多小伙伴都在讨论目前很多大数据开发岗位都跟数据仓库有关,而很多刚毕业或者毕业不久的小伙伴对这块怎么玩并不十分很清楚,所以想着针对这一块来聊聊我的一些看法。

数据仓库是一个更偏向业务层面的概率,最初的目的是企业用来汇集各个业务系统数据,然后对其进行集中式存储、计算、离线分析,最终用来做BI展现,为企业提供商业决策,这个是数据仓库最初的目的和职能。

以前传统的数据仓库因为都是基于RDBMS来展开,所有的数据存储都是在传统数据库之上,我们知道,数据仓库建设的一大特点就是对数据类型以及业务需要进行分层。

但是随着大数据技术的引入,以及开源生态的蓬勃发展,虽然传统数据仓库的数据分层思想依然得以保留,但是其实现形式,却发生了翻天覆地的变化。

因为大数据技术的飞速发展,以及数据应用场景的更加灵活化,如今的数据仓库建设,不再拘泥于把数据全部放到一个数据库软件中,而是会根据其数据量、数据查询特点、使用便利性、存储规模、查询响应时间等诸多维度来考量一份数据到底应该放到哪个存储组件中,可以给整个数据仓库系统带来最高的收益。

那么接下来,就根据我给大家提供的这份数据集(如果大家有兴趣,后面我会增加数据的多样性),来模拟下数仓ODS层和DWD层数据的玩法。

具体的数据处理流程图如下:

其中彩色部分就是这次要讲的内容。


|构建ODS层数据
之前视频说到,这层叫贴源层,所以除了清除一些业务无法使用的脏数据外,其他所有数据都按照原始格式进行保留,几乎不做任何字段转换和处理。

因为数据源在kafka中,因此考虑用流计算引擎structured streaming将数据写入ODS。

废话不多说,上代码:
package com.anryg.bigdata.streaming.ods

import com.alibaba.fastjson.JSON
import com.anryg.bigdata.streaming.StreamingProcessHelper
import org.apache.spark.SparkConf
import org.apache.spark.sql.streaming.{OutputMode, StreamingQuery}
import org.apache.spark.sql.{DataFrame, Dataset, SparkSession}


/**
  * @DESC:
  * @Auther: Anryg
  * @Date: 2022/8/31 19:03
  */

object StreamingSource2HiveOds extends StreamingProcessHelper[Any]{


    /**
      * @DESC: 主函数,应用运行入口
      * */

    def main(args: Array[String]): Unit = {
        val conf = new SparkConf().setAppName("StreamingSource2HiveOds").setMaster("local[*]")
        val spark = SparkSession.builder().config(conf)
                .config("spark.sql.hive.hiveserver2.jdbc.url","jdbc:hive2://server1:2181,server2:2181,server3:2181/;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2")
                .config("spark.datasource.hive.warehouse.metastoreUri","thrift://serverXXX:9083")
                .enableHiveSupport() //打开hive支持功能,可以与hive共享catalog
                .getOrCreate()

        clickProcess(spark)

    }

    /**
      *@DESC: 将kafka数据源数据读取出来成为DataFrame
      * */

    def readKafka2DF(sparkSession: SparkSession): DataFrame ={
        val config = Map(("kafka.bootstrap.servers", "xxxx:6667"),("subscribe","test"),
            ("failOnDataLoss","false"),("fetchOffset.numRetries","3"),("startingOffsets","earliest"))

        getStreamingReader(sparkSession,"kafka",config).load()
    }

    /**
      *@DESC: 将原始DF进行业务逻辑处理
      * */


    def handleData(sparkSession: SparkSession, rawDF:DataFrame): DataFrame ={
        import sparkSession.implicits._
        val targetDS = rawDF.selectExpr("CAST(value AS STRING)")  //将kafka中的数据的value转为为string,原始为binary类型
                .map(row => {
            val line = row.getAs[String]("value") //获取row对象中的field,其实也只有一个field
            val rawJson = JSON.parseObject(line)      //原始string是一个json,对其进行解析
            val message = rawJson.getString("message")  //获取业务数据部分
            val msgArray = message.split(",")  //指定分隔符进行字段切分
            msgArray
        }).filter(_.length == 9).filter(array => array(2).length >= 8)//确保日期字段符合规范
                .map(array =>(array(0),array(1),array(2),array(3), array(4),array(5),array(6),array(7),array(8),
                array(2).substring(0,4),array(2).substring(4,6),array(2).substring(6,8)))
                .toDF("client_ip","domain","time","target_ip","rcode","query_type","authority_record","add_msg","dns_ip","year","month","day") //给裸数据添加字段名

        targetDS
    }

    /**
      *@DESC: 将目标数据集进行写入hive的ODS
      * */

    def sinkData(targetDS:DataFrame): StreamingQuery ={
        val config = Map(("checkpointLocation","hdfs://XXXX:8020/tmp/offset/test/StreamingSource2HiveOds"),
            ("format","append"))
        getStreamingWriter(targetDS, OutputMode.Append(),"orc",config)
                .partitionBy("year","month","day")
                .toTable("ods.ods_kafka_internetlog")
    }

    /**
      * @DESC: 将所有数据步骤串起来
      * */

    def clickProcess(sparkSession: SparkSession): Unit ={
        val rawDF = readKafka2DF(sparkSession)
        val targetDS = handleData(sparkSession, rawDF)
        sinkData(targetDS).awaitTermination()
    }

}

这个是主体代码部分,项目的全部代码,我已经放到GitHub上,有需要的同学可以自取,地址在文末。


|构建DWD层数据
该层属于数据的明显层,会在ODS的基础上进行字段扩充,以及一些简单的字段转换操作,因此DWD层的数据直接来源于ODS。

考虑现在很多数据仓库系统对时效性要求越来越高,为了保证该层的数据在时效上能够尽可能跟ODS保持同步,因此也考虑采用流式的同步方式,用structured streaming读取ODS,然后经过ETL到DWD,在实现上做到同步更新。

核心代码如下:
package com.anryg.bigdata.streaming.dwd

import com.anryg.bigdata.{IpSearch, RedisClientUtils}
import com.anryg.bigdata.streaming.StreamingProcessHelper
import org.apache.spark.SparkConf
import org.apache.spark.sql.streaming.{OutputMode, StreamingQuery}
import org.apache.spark.sql.{DataFrame, SparkSession}


/**
  * @DESC: 读取ods层数据,并加工写入到dwd
  * @Auther: Anryg
  * @Date: 2022/9/1 09:53
  */

object StreamingFromOds2Dwd  extends StreamingProcessHelper[Any]{

    def main(args: Array[String]): Unit = {
        val conf = new SparkConf().setAppName("StreamingFromOds2Dwd").setMaster("local")
        val spark = SparkSession.builder().config(conf)
                .config("spark.sql.hive.hiveserver2.jdbc.url","jdbc:hive2://server1:2181,server2:2181,server3:2181/;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2")
                .config("spark.datasource.hive.warehouse.metastoreUri","thrift://XXXX:9083")
                .enableHiveSupport() //打开hive支持功能,可以与hive共享catalog
                .getOrCreate()

        clickProcess(spark,"ods.ods_kafka_internetlog","dwd.dwd_internetlog_detail")
    }

    /**
      *@DESC: 流方式读取hive数据源
      * */

    def readHive2DF(sparkSession: SparkSession, sourceTable:String): DataFrame ={
        sparkSession.readStream.table(sourceTable)
    }

    /**
      *@DESC: 对ods数据进行字段补齐等处理
      * */

    def handleData(sparkSession: SparkSession, dataFrame: DataFrame, tableName:String): DataFrame ={
        import sparkSession.implicits._
        dataFrame.printSchema()
        dataFrame.map(row => {
            val clientIP = row.getAs[String]("client_ip")
            val ipAndAddr = IpSearch.getAddrByIP(RedisClientUtils.getSingleRedisClient,clientIP).split("-")
            val country = ipAndAddr(2)
            val province = ipAndAddr(3)
            val city = ipAndAddr(4)
            val operator = ipAndAddr(5)
            val domain = row.getAs[String]("domain").toLowerCase//将域名转成小写
            val time = row.getAs[String]("time")
            val targetIP = row.getAs[String]("target_ip")
            val rcode = row.getAs[String]("rcode")
            val queryType = row.getAs[String]("query_type")
            val authRecord = row.getAs[String]("authority_record").toLowerCase
            val addMsg = row.getAs[String]("add_msg")
            val dnsIP = row.getAs[String]("dns_ip")
            val year = row.getAs[String]("year")
            val month = row.getAs[String]("month")
            val day = row.getAs[String]("day")
            (clientIP,country,province,city,operator,domain,time,targetIP,rcode,queryType,authRecord,addMsg,dnsIP,year,month,day)
        }).toDF("client_ip","country","province","city","operator","domain","time","target_ip","rcode","query_type","authority_record","add_msg","dns_ip","year","month","day")
    }

    /**
      *@DESC: 将处理好的数据sink到dwd表中
      * */

    def sinkData(targetDS:DataFrame, tableName:String): StreamingQuery ={
        val config = Map(("checkpointLocation","hdfs://XXXX:8020/tmp/offset/test/StreamingFromOds2Dwd"),
            ("format","append"))
        getStreamingWriter(targetDS,OutputMode.Append(),"orc",config)
                .partitionBy("year","month","day")
                .toTable(tableName)
    }

    /**
      * @DESC: 将所有数据步骤串起来
      * */

    def clickProcess(sparkSession: SparkSession,sourceTable:String, sinkTable:String): Unit ={
        val rawDF = readHive2DF(sparkSession, sourceTable)
        val targetDS = handleData(sparkSession, rawDF, sourceTable)
        sinkData(targetDS, sinkTable).awaitTermination()
    }

}


还是一样,这个部分也在GitHub上,有需要的小伙伴可以自取。



|最后

这部分代码都是我这几天现写的,整个流程也都实现了,当然,过程中不免遇到很多坑,具体有哪些坑、如何避坑,以及该部分内容的讲解,周日晚八点会在B站直播给大家唠唠,有兴趣的小伙伴,欢迎来捧场。

最后,该项目的github地址: https://github.com/Anryg/internet_behavior_project


有任何问题,欢迎找我交流哈。



我这有一个高质量的大数据学习讨论群,不怎么扯淡、偶尔活跃气氛、大部分时间都在讨论技术,你在群里的几乎任何大数据相关的问题,都可以得到解答,要不要进来试试呢?拉你进群啊...


原文作者:安瑞哥是码农;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2022-02-16

  • 博文量
    228
  • 访问量
    115431