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 }