SparkSQL实战5——综合实战完成日志分析1

1、离线处理架构图:

2、数据处理流程:

  1. 数据采集:使用Flume采集web日志信息到HDFS中去
  2. 数据清洗:将脏数据清理,使用Spark、hive、MapReduce等分布式计算框架
  3. 数据处理:按照我们的需要进行相应业务的统计和分析,使用Spark、hive、MapReduce等分布式计算框架
  4. 处理结果入库:调用API,将结果存放到RDBMS,NoSQL中去;
  5. 数据可视化:通过图形化展示,Echarts或者DataV

项目所用数据集https://download.csdn.net/download/qq_21583077/11005273

数据集偏大,大约有5个G,考虑到机器配置等因素,在本案例中,我们只截取数据集的前10000条进行统计分析。

数据格式:

183.162.52.7 - - [10/Nov/2016:00:01:02 +0800] "POST /api3/getadv HTTP/1.1" 200 813 "www.imooc.com" "-" cid=0&timestamp=1478707261865&uid=2871142&marking=androidbanner&secrect=a6e8e14701ffe9f6063934780d9e2e6d&token=f51e97d1cb1a9caac669ea8acc162b96 "mukewang/5.0.0 (Android 5.1.1; Xiaomi Redmi 3 Build/LMY47V),Network 2G/3G" "-" 10.100.134.244:80 200 0.027 0.027

一条数据之间是以空格来分割没一个字段,以上面的一条数据为例:

第一个字段:183.162.52.7是访问者的IP;

第二个字段、第三个字段为空,用“-”代表;

第四、五个字段:访问时间,[10/Nov/2016:00:01:02 +0800]

第六个字段:HTTP协议,“POST”

第7个字段:/api3/getadv,URL地址

第8个字段:HTTP/1.1,HTTP版本号

第9个字段:200,状态码

第10个字段:813,访问所耗费的流量

第11个字段:"www.imooc.com",慕课网主站地址

...............后面的就不重要了,所以不一一介绍了(因为我也不知道了,嘿嘿)

3、数据清洗功能实现

我们本次课程需要的在原始访问信息中提取出:访问时间,访问URL,访问的IP,耗费的流量等几个字段。

import org.apache.spark.sql.SparkSession

/**
  * @author YuZhansheng
  * @desc   第一步清洗:抽取出我们所需要的指定列的数据
  * @create 2019-03-08 9:44
  */
object SparkStatFormatJob {

    def main(args: Array[String]): Unit = {

        val spark = SparkSession.builder().appName("SparkStatFormatJob").master("local[3]").getOrCreate()

        //textFile:从HDFS、本地文件系统(在所有节点上可用)或任何节点读取文本文件;支持hadoop的文件系统URI,并将其作为字符串的RDD返回。
        val access = spark.sparkContext.textFile("file:/root/DataSet/10000_access.log")

        //提取出IP,时间,url,流量,最后再把清洗后的数据写入到本地
        access.map(line => {
            //根据空格进行分割
            val splits = line.split(" ")
            //获取IP
            val ip = splits(0)
            //原始日志的第四个和第五个字段拼接起来就是完整的访问时间:[10/Nov/2016:00:01:02 +0800]
            //[10/Nov/2016:00:01:02 +0800] ==> yyyy-MM-dd HH:mm:ss,使用DateUtils工具类转换
            val time = splits(3) + " " + splits(4)

            //获取url,并把引号都给替换成空
            val url = splits(11).replace("\"","")

            //获取访问耗费的流量
            val traffic = splits(9)

            DateUtils.parse(time) + "\t" + url + "\t" + traffic + "\t" + ip
        }).saveAsTextFile("file:/root/DataSet/output/")

        spark.stop()
    }
}
import java.util.{Date, Locale}

import org.apache.commons.lang3.time.FastDateFormat


/**
  * @author YuZhansheng
  * @desc 日期时间解析工具类
  *      注意:SimpleDateFormat是线程不安全的,因此在这里我们要使用FastDateFormat.getInstance
  * @create 2019-02-26 15:22
  */
object DateUtils {

    //输入的日期格式
    val YYYYMMDDHHMM_TIME_FORMAT = FastDateFormat.getInstance("dd/MMM/yyyy:HH:mm:ss Z",Locale.ENGLISH)

    //目标日期格式
    val TARGE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss")

    //获取时间:yyyy-MM-dd HH:mm:ss
    def parse(time:String) = {
        TARGE_FORMAT.format(new Date(getTime(time)))
    }

    //获取输入日志时间:返回long类型
    //time : [10/Nov/2016:00:01:02 +0800]
    def getTime(time:String) = {
        try{
            YYYYMMDDHHMM_TIME_FORMAT.parse(time.substring(time.indexOf("[") + 1,time.lastIndexOf("]"))).getTime
        }catch {
            case e : Exception => {
                0l
            }
        }
    }
}

清洗过后,最后输出到本地的日志信息如下:

2016-11-10 00:01:53     http://www.imooc.com/video/2314 54      117.181.194.121
2016-11-10 00:01:53     http://www.imooc.com/code/4336  67      120.198.231.150
2016-11-10 00:01:53     -       0       10.100.0.1
2016-11-10 00:01:53     -       0       218.26.76.143
2016-11-10 00:01:53     -       0       10.100.0.1
2016-11-10 00:01:53     http://www.imooc.com/   2340    120.197.199.74

4、数据清洗之日志解析

一般日志处理方式,我们是需要进行分区的,按照日志中的访问时间进行相应的分区,比如按天分区、小时分区等。

经过上一步的日志清洗,现在得到的日志信息是:访问时间、访问URL、耗费的流量、访问IP地址信息;现在我们进行进一步解析,得到更详细的信息:URL、cmsType(video/article)、cmsId(编号)、流量、IP、城市信息、访问时间等。

上一步我们把输出文件给存放到:saveAsTextFile("file:/root/DataSet/output/")这个路径了,里面会有两个文件块,part00000和part00001,我们把两个文件里的内容提取出来,整合成一个文件,命名为access.log,下面的操作就以access.log这个文件为基础来进行解析和清洗。

import org.apache.spark.sql.Row
import org.apache.spark.sql.types.{LongType, StringType, StructField, StructType}

/**
  * @author YuZhansheng
  * @desc   访问日志转换工具类
  * @create 2019-03-08 14:57
  */
object AccessConvertUtil {

    //定义的输出的字段
    val struct = StructType(
        Array(
            StructField("url",StringType),
            StructField("cmsType",StringType),
            StructField("cmsId",LongType),
            StructField("traffic",LongType),
            StructField("ip",StringType),
            StructField("city",StringType),
            StructField("time",StringType),
            StructField("day",StringType)
        )
    )

    //根据输入的每一行记录信息转换成输出样式
    def parseLog(log:String) = {

        try{
            val splits = log.split("\t")

            val url = splits(1)
            val traffic = splits(2).toLong
            val ip = splits(3)

            val domain = "http://www.imooc.com/"
            val cms = url.substring(url.indexOf(domain) + domain.length).split("/")

            var cmsType = ""
            var cmsId = 0l

            if (cms.length > 1){
                cmsType = cms(0)
                cmsId = cms(1).toLong
            }

            val city = ""
            val time = splits(0)
            val day = time.substring(0,10).replace("-","")

            //这个row里面的字段要和struct中的字段对应上
            Row(url,cmsType,cmsId,traffic,ip,city,time,day)
        }catch {
            case e : Exception => Row(0)
        }
    }
}
package com.xidian.spark.log

import org.apache.spark.sql.SparkSession

/**
  * @author YuZhansheng
  * @desc  使用Spark完成数据解析操作
  * @create 2019-03-08 14:27
  */
object SparkStatCleanJob {
    def main(args: Array[String]): Unit = {
        //Spark主程序入口,无需多说
        val spark = SparkSession.builder().appName("SparkStatCleanJob").master("local[2]").getOrCreate()

        //读取本地文件,创建RDD
        val accessRDD = spark.sparkContext.textFile("file:/root/DataSet/access.log")

        //将RDD转换为DataFrame
        val accessDF = spark.createDataFrame(accessRDD.map(x => AccessConvertUtil.parseLog(x)),AccessConvertUtil.struct)

        accessDF.show()
        accessDF.printSchema()

        spark.stop()
    }
}

运行时候打印出如下说明解析成功了:

+--------------------+-------+-----+-------+---------------+----+-------------------+--------+
|                 url|cmsType|cmsId|traffic|             ip|city|               time|     day|
+--------------------+-------+-----+-------+---------------+----+-------------------+--------+
|http://www.imooc....|  video| 4500|    304|  218.75.35.226|    |2017-05-11 14:09:14|20170511|
|http://www.imooc....|  video|14623|     69| 202.96.134.133|    |2017-05-11 15:25:05|20170511|
|http://www.imooc....|article|17894|    115| 202.96.134.133|    |2017-05-11 07:50:01|20170511|
|http://www.imooc....|article|17896|    804|  218.75.35.226|    |2017-05-11 02:46:43|20170511|
|http://www.imooc....|article|17893|    893|222.129.235.182|    |2017-05-11 09:30:25|20170511|
|http://www.imooc....|article|17891|    407|  218.75.35.226|    |2017-05-11 08:07:35|20170511|
|http://www.imooc....|article|17897|     78| 202.96.134.133|    |2017-05-11 19:08:13|20170511|
|http://www.imooc....|article|17894|    658|222.129.235.182|    |2017-05-11 04:18:47|20170511|
|http://www.imooc....|article|17893|    161|   58.32.19.255|    |2017-05-11 01:25:21|20170511|
|http://www.imooc....|article|17895|    701|    218.22.9.56|    |2017-05-11 13:37:22|20170511|
|http://www.imooc....|article|17892|    986|  218.75.35.226|    |2017-05-11 05:53:47|20170511|
|http://www.imooc....|  video|14540|    987|   58.32.19.255|    |2017-05-11 18:44:56|20170511|
|http://www.imooc....|article|17892|    610|  218.75.35.226|    |2017-05-11 17:48:51|20170511|
|http://www.imooc....|article|17893|      0|    218.22.9.56|    |2017-05-11 16:20:03|20170511|
|http://www.imooc....|article|17891|    262|   58.32.19.255|    |2017-05-11 00:38:01|20170511|
|http://www.imooc....|  video| 4600|    465|  218.75.35.226|    |2017-05-11 17:38:16|20170511|
|http://www.imooc....|  video| 4600|    833|222.129.235.182|    |2017-05-11 07:11:36|20170511|
|http://www.imooc....|article|17895|    320|222.129.235.182|    |2017-05-11 19:25:04|20170511|
|http://www.imooc....|article|17898|    460| 202.96.134.133|    |2017-05-11 15:14:28|20170511|
|http://www.imooc....|article|17899|    389|222.129.235.182|    |2017-05-11 02:43:15|20170511|
+--------------------+-------+-----+-------+---------------+----+-------------------+--------+
only showing top 20 rows

root
 |-- url: string (nullable = true)
 |-- cmsType: string (nullable = true)
 |-- cmsId: long (nullable = true)
 |-- traffic: long (nullable = true)
 |-- ip: string (nullable = true)
 |-- city: string (nullable = true)
 |-- time: string (nullable = true)
 |-- day: string (nullable = true)

5、数据清洗之IP地址解析

根据IP解析地址需要借助GitHub上的一个开源项目:https://github.com/wzhe06/ipdatabase

利用二叉树实现IP查询,首先将10进制IPV4地址转化为二进制构建二叉树,利用二叉树搜索进行搜索,查询时间复杂度log2n,比传统IP库n的查询速度高出一个量级。数据源采用2015年广告协会制定的IP地址标准数据库,中国互联网广告行业统一采用的标准IP库。

首先需要先克隆或者下载这个项目,然后使用maven命令编译这个项目,安装到maven仓库之后,就可以项目中的pom文件中添加groupId、artifactId、version来使用这个jar包了。

依赖:

   <dependency>
     <groupId>com.ggstar</groupId>
     <artifactId>ipdatabase</artifactId>
     <version>1.0-SNAPSHOT</version>
   </dependency>

除此之外,还需要将ipdatabase 这个项目中的两个文件:ipDatabase.csv、ipRegion.xlsx给复制到spark项目的resources目录下,不然会报错。

写一个IPUtiles测试类:

import com.ggstar.util.ip.IpHelper

/**
  * @author YuZhansheng
  * @desc IP地址解析工具类
  * @create 2019-03-11 10:23
  */
object IPUtiles {

    def getCity(ip:String) = {
        IpHelper.findRegionByIp(ip)
    }

    def main(args: Array[String]): Unit = {
        println(getCity("39.105.104.164"))
    }
}

 

输出:北京市;说明这个工具类可以使用。

修改AccessConvertUtil访问日志转换工具类程序,解析出城市,将city给填充上去:   val city = ""  ==> val city = IpUtils.getCity(ip)

再次运行,城市已经被填充上去了,如下:

+--------------------+-------+-----+-------+---------------+----+-------------------+--------+
|                 url|cmsType|cmsId|traffic|             ip|city|               time|     day|
+--------------------+-------+-----+-------+---------------+----+-------------------+--------+
|http://www.imooc....|  video| 4500|    304|  218.75.35.226| 浙江省|2017-05-11 14:09:14|20170511|
|http://www.imooc....|  video|14623|     69| 202.96.134.133| 广东省|2017-05-11 15:25:05|20170511|
|http://www.imooc....|article|17894|    115| 202.96.134.133| 广东省|2017-05-11 07:50:01|20170511|
|http://www.imooc....|article|17896|    804|  218.75.35.226| 浙江省|2017-05-11 02:46:43|20170511|


6、将清洗后的数据保存到指定目录

//accessDF.printSchema()
//accessDF.show()

//将清理后的数据保存到指定目录
accessDF.write.format("parquet").partitionBy("day").save("file:/root/DataSet/clean")

生成如下文件:

[root@hadoop0 clean]# ls
day=20170511  _SUCCESS
[root@hadoop0 clean]# cd day\=20170511/
[root@hadoop0 day=20170511]# ls
part-00000-ba848fef-ee72-4151-90c7-6aa2644a3eef.c000.snappy.parquet  part-00002-ba848fef-ee72-4151-90c7-6aa2644a3eef.c000.snappy.parquet
part-00001-ba848fef-ee72-4151-90c7-6aa2644a3eef.c000.snappy.parquet
[root@hadoop0 day=20170511]# ls -lh
总用量 9.5M
-rw-r--r-- 1 root root 4.5M 3月  11 11:11 part-00000-ba848fef-ee72-4151-90c7-6aa2644a3eef.c000.snappy.parquet
-rw-r--r-- 1 root root 4.5M 3月  11 11:11 part-00001-ba848fef-ee72-4151-90c7-6aa2644a3eef.c000.snappy.parquet
-rw-r--r-- 1 root root 626K 3月  11 11:11 part-00002-ba848fef-ee72-4151-90c7-6aa2644a3eef.c000.snappy.parquet

生成了三个文件,感觉有点多,能不能指定生成的文件块数呢?当然可以啦!

//accessDF.printSchema()
//accessDF.show()

//将清理后的数据保存到指定目录           
accessDF.coalesce(1).write.format("parquet").mode("overwrite").partitionBy("day").save("file:/root/DataSet/clean")

coalesce(1):指定生成的文件块数

mode("SaveMode.Overwrite"):保存模式,覆盖掉原有的clean文件

这次运行程序,会生成如下的文件结构:

[root@hadoop0 DataSet]# cd clean/
[root@hadoop0 clean]# ls
day=20170511  _SUCCESS
[root@hadoop0 clean]# cd day\=20170511/
[root@hadoop0 day=20170511]# ls
part-00000-bb0e3fd6-dab9-4ece-a01f-00d0b3b002af.c000.snappy.parquet

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值