1.四个需求

  需求一:求contentsize的平均值、最小值、最大值

  需求二:请各个不同返回值的出现的数据 ===> wordCount程序

  需求三:获取访问次数超过N次的IP地址

  需求四:获取访问次数最多的前K个endpoint的值 ==> TopN

 

2.主程序LogAnalyzer.scala



1 package com.ibeifeng.bigdata.spark.core.log
  2 
  3 import org.apache.spark.rdd.RDD
  4 import org.apache.spark.{SparkConf, SparkContext}
  5 
  6 /**
  7   * Apache日志分析
  8   * Created by ibf on 01/15.
  9   */
 10 object LogAnalyzer {
 11   def main(args: Array[String]): Unit = {
 12     val conf = new SparkConf()
 13       .setAppName("log-analyzer")
 14       .setMaster("local[*]")
 15       .set("spark.eventLog.enabled", "true")
 16       .set("spark.eventLog.dir", "hdfs://hadoop-senior01:8020/spark-history")
 17     val sc = SparkContext.getOrCreate(conf)
 18 
 19     // ================日志分析具体代码==================
 20     // HDFS上日志存储路径
 21     val path = "/beifeng/spark/access/access.log"
 22 
 23     // 创建rdd
 24     val rdd = sc.textFile(path)
 25 
 26     // rdd转换,返回进行后续操作
 27     val apacheAccessLog: RDD[ApacheAccessLog] = rdd
 28       // 过滤数据
 29       .filter(line => ApacheAccessLog.isValidateLogLine(line))
 30       .map(line => {
 31         // 对line数据进行转换操作
 32         ApacheAccessLog.parseLogLine(line)
 33       })
 34 
 35     // 对多次时候用的rdd进行cache
 36     apacheAccessLog.cache()
 37 
 38     // 需求一:求contentsize的平均值、最小值、最大值
 39     /*
 40     * The average, min, and max content size of responses returned from the server.
 41     * */
 42     val contentSizeRDD: RDD[Long] = apacheAccessLog
 43       // 提取计算需要的字段数据
 44       .map(log => (log.contentSize))
 45 
 46     // 对重复使用的RDD进行cache
 47     contentSizeRDD.cache()
 48 
 49     // 开始计算平均值、最小值、最大值
 50     val totalContentSize = contentSizeRDD.sum()
 51     val totalCount = contentSizeRDD.count()
 52     val avgSize = 1.0 * totalContentSize / totalCount
 53     val minSize = contentSizeRDD.min()
 54     val maxSize = contentSizeRDD.max()
 55 
 56     // 当RDD不使用的时候,进行unpersist
 57     contentSizeRDD.unpersist()
 58 
 59     // 结果输出
 60     println(s"ContentSize Avg:${avgSize}, Min: ${minSize}, Max: ${maxSize}")
 61 
 62     // 需求二:请各个不同返回值的出现的数据 ===> wordCount程序
 63     /*
 64     * A count of response code's returned.
 65     * */
 66     val responseCodeResultRDD = apacheAccessLog
 67       // 提取需要的字段数据, 转换为key/value键值对,方便进行reduceByKey操作
 68       // 当连续出现map或者flatMap的时候,将多个map/flatMap进行合并
 69       .map(log => (log.responseCode, 1))
 70       // 使用reduceByKey函数,按照key进行分组后,计算每个key出现的次数
 71       .reduceByKey(_ + _)
 72 
 73     // 结果输出
 74     println(s"""ResponseCode :${responseCodeResultRDD.collect().mkString(",")}""")
 75 
 76     // 需求三:获取访问次数超过N次的IP地址
 77     // 需求三额外:对IP地址进行限制,部分黑名单IP地址不统计
 78     /*
 79     * All IPAddresses that have accessed this server more than N times.
 80     * 1. 计算IP地址出现的次数 ===> WordCount程序
 81     * 2. 数据过滤
 82     * */
 83     val blackIP = Array("200-55-104-193.dsl.prima.net.ar", "10.0.0.153", "208-38-57-205.ip.cal.radiant.net")
 84     // 由于集合比较大,将集合的内容广播出去
 85     val broadCastIP = sc.broadcast(blackIP)
 86     val N = 10
 87     val ipAddressRDD = apacheAccessLog
 88       // 过滤IP地址在黑名单中的数据
 89       .filter(log => !broadCastIP.value.contains(log.ipAddress))
 90       // 获取计算需要的IP地址数据,并将返回值转换为Key/Value键值对类型
 91       .map(log => (log.ipAddress, 1L))
 92       // 使用reduceByKey函数进行聚合操作
 93       .reduceByKey(_ + _)
 94       // 过滤数据,要求IP地址必须出现N次以上
 95       .filter(tuple => tuple._2 > N)
 96     // 获取满足条件IP地址, 为了展示方便,将下面这行代码注释
 97     //      .map(tuple => tuple._1)
 98 
 99     // 结果输出
100     println(s"""IP Address :${ipAddressRDD.collect().mkString(",")}""")
101 
102     // 需求四:获取访问次数最多的前K个endpoint的值 ==> TopN
103     /*
104     * The top endpoints requested by count.
105     * 1. 先计算出每个endpoint的出现次数
106     * 2. 再进行topK的一个获取操作,获取出现次数最多的前K个值
107     * */
108     val K = 10
109     val topKValues = apacheAccessLog
110       // 获取计算需要的字段信息,并返回key/value键值对
111       .map(log => (log.endpoint, 1))
112       // 获取每个endpoint对应的出现次数
113       .reduceByKey(_ + _)
114       // 获取前10个元素, 而且使用我们自定义的排序类
115       .top(K)(LogSortingUtil.TupleOrdering)
116     // 如果只需要endpoint的值,不需要出现的次数,那么可以通过map函数进行转换
117     //      .map(_._1)
118 
119     // 结果输出
120     println(s"""TopK values:${topKValues.mkString(",")}""")
121 
122 
123     // 对不在使用的rdd,去除cache
124     apacheAccessLog.unpersist()
125 
126     // ================日志分析具体代码==================
127 
128     sc.stop()
129   }
130 }



 

3.需要的辅助类一(返回匹配的日志)



1 package com.ibeifeng.bigdata.spark.core.log
 2 
 3 import scala.util.matching.Regex
 4 
 5 /**
 6   * 64.242.88.10 - - [07/Mar/2004:16:05:49 -0800] "GET /twiki/bin/edit/Main/Double_bounce_sender?topicparent=Main.ConfigurationVariables HTTP/1.1" 401 12846
 7   * Created by ibf on 01/15.
 8   */
 9 case class ApacheAccessLog(
10                             ipAddress: String, // IP地址
11                             clientId: String, // 客户端唯一标识符
12                             userId: String, // 用户唯一标识符
13                             serverTime: String, // 服务器时间
14                             method: String, // 请求类型/方式
15                             endpoint: String, // 请求的资源
16                             protocol: String, // 请求的协议名称
17                             responseCode: Int, // 请求返回值:比如:200、401
18                             contentSize: Long // 返回的结果数据大小
19                           )
20 
21 /**
22   * 64.242.88.10 - - [07/Mar/2004:16:05:49 -0800] "GET /twiki/bin/edit/Main/Double_bounce_sender?topicparent=Main.ConfigurationVariables HTTP/1.1" 401 12846
23   * on 01/15.
24   * 提供一些操作Apache Log的工具类供SparkCore使用
25   */
26 object ApacheAccessLog {
27   // Apache日志的正则
28   val PARTTERN: Regex =
29   """^(\S+) (\S+) (\S+) \[([\w:/]+\s[+\-]\d{4})\] "(\S+) (\S+) (\S+)" (\d{3}) (\d+)""".r
30 
31   /**
32     * 验证一下输入的数据是否符合给定的日志正则,如果符合返回true;否则返回false
33     *
34     * @param line
35     * @return
36     */
37   def isValidateLogLine(line: String): Boolean = {
38     val options = PARTTERN.findFirstMatchIn(line)
39 
40     if (options.isEmpty) {
41       false
42     } else {
43       true
44     }
45   }
46 
47   /**
48     * 解析输入的日志数据
49     *
50     * @param line
51     * @return
52     */
53   def parseLogLine(line: String): ApacheAccessLog = {
54     if (!isValidateLogLine(line)) {
55       throw new IllegalArgumentException("参数格式异常")
56     }
57 
58     // 从line中获取匹配的数据
59     val options = PARTTERN.findFirstMatchIn(line)
60 
61     // 获取matcher
62     val matcher = options.get
63 
64     // 构建返回值
65     ApacheAccessLog(
66       matcher.group(1), // 获取匹配字符串中第一个小括号中的值
67       matcher.group(2),
68       matcher.group(3),
69       matcher.group(4),
70       matcher.group(5),
71       matcher.group(6),
72       matcher.group(7),
73       matcher.group(8).toInt,
74       matcher.group(9).toLong
75     )
76   }
77 }



 

4.需要的辅助类二(自定义的一个二元组的比较器,方便进行TopN)



1 package com.ibeifeng.bigdata.spark.core.log
 2 
 3 /**
 4   * Created by ibf on 01/15.
 5   */
 6 object LogSortingUtil {
 7 
 8   /**
 9     * 自定义的一个二元组的比较器
10     */
11   object TupleOrdering extends scala.math.Ordering[(String, Int)] {
12     override def compare(x: (String, Int), y: (String, Int)): Int = {
13       // 按照出现的次数进行比较,也就是按照二元组的第二个元素进行比较
14       x._2.compare(y._2)
15     }
16   }
17 
18 }