上一篇,楼主介绍了使用 flume 集群来模拟网站产生的日志数据收集到 hdfs。但我们所采集的日志数据是不规则的,同时也包含了许多无用的日志。当需要分析一些核心指标来满足系统业务决策的时候,对日志的数据清洗在所难免,楼主本篇将介绍如何使用 mapreduce 程序对日志数据进行清洗,将清洗后的结构化数据存储到 hive,并进行相关指标的提取。
先明白几个概念:
1)PV(Page View)。页面浏览量即为 PV,是指所有用户浏览页面的总和,一个独立用户每打开一个页面就被记录 1 次。计算方式为:记录计数
2)注册用户数。对注册页面访问的次数。计算方式:对访问 member.php?mod=register 的 url,计数
3)IP 数。一天之内,访问网站的不同独立 IP 个数加和。其中同一 IP 无论访问了几个页面,独立 IP 数均为 1。这是我们最熟悉的一个概念,无论同一个 IP 上有多少台主机,或者其他用户,从某种程度上来说,独立 IP 的多少,是衡量网站推广活动好坏最直接的数据。计算方式:对不同 ip,计数
4)跳出率。只浏览了一个页面便离开了网站的访问次数占总的访问次数的百分比,即只浏览了一个页面的访问次数 / 全部的访问次数汇总。跳出率是非常重要的访客黏性指标,它显示了访客对网站的兴趣程度。跳出率越低说明流量质量越好,访客对网站的内容越感兴趣,这些访客越可能是网站的有效用户、忠实用户。该指标也可以衡量网络营销的效果,指出有多少访客被网络营销吸引到宣传产品页或网站上之后,又流失掉了,可以说就是煮熟的鸭子飞了。比如,网站在某媒体上打广告推广,分析从这个推广来源进入的访客指标,其跳出率可以反映出选择这个媒体是否合适,广告语的撰写是否优秀,以及网站入口页的设计是否用户体验良好。计算方式:(1) 统计一天内只出现一条记录的 ip,称为跳出数 (2) 跳出数 / PV 本次楼主只做以上几项简单指标的分析,各个网站的作用领域不一样,所涉及的分析指标也有很大差别,各位同学可以根据自己的需求尽情拓展。废话不多说,上干货。
1)hadoop 集群。楼主用的 6 个节点的 hadoop2.7.3 集群,各位同学可以根据自己的实际情况进行搭建,但至少需要 1 台伪分布式的。(参考 http://www.cnblogs.com/qq503665965/p/6790580.html)
2)hive。用于对各项核心指标进行分析(安装楼主不再介绍了)
3)mysql。存储分析后的数据指标。
4)sqoop。从 hive 到 mysql 的数据导入。
我们先看看从 flume 收集到 hdfs 中的源日志数据格式:
- 127.19.74.143 - - [30/4/2017:17:38:20 +0800] "GET /static/image/common/faq.gif HTTP/1.1" 200 11272211.97.15.179 - - [30/4/2017:17:38:22 +0800] "GET /home.php?mod=misc&ac=sendmail&rand=1369906181 HTTP/1.1" 200 -
上面包含条个静态资源日志和一条正常链接日志(楼主这里不做静态资源日志的分析),需要将以 /static 开头的日志文件过滤掉;时间格式需要转换为时间戳;去掉 IP 与时间之间的无用符号;过滤掉请求方式;"/" 分隔符、http 协议、请求状态及当次流量。效果如下:
- 1211.97.15.179 20170430173820 home.php?mod=misc&ac=sendmail&rand=1369906181
先写个日志解析类,测试是否能解析成功,我们再写 mapreduce 程序:
- 1 package mapreduce;
- 2 3 import java.text.ParseException;
- 4 import java.text.SimpleDateFormat;
- 5 import java.util.Date;
- 6 import java.util.Locale;
- 7 8 9 public class LogParser {
- 10 public static final SimpleDateFormat FORMAT = new SimpleDateFormat("d/MM/yyyy:HH:mm:ss", Locale.ENGLISH);
- 11 public static final SimpleDateFormat dateformat1 = new SimpleDateFormat("yyyyMMddHHmmss");
- 12 public static void main(String[] args) throws ParseException {
- 13 final String S1 = "27.19.74.143 - - [30/04/2017:17:38:20 +0800] \"GET /static/image/common/faq.gif HTTP/1.1\" 200 1127";
- 14 LogParser parser = new LogParser();
- 15 final String[] array = parser.parse(S1);
- 16 System.out.println("源数据: " + S1);
- 17 System.out.format("清洗结果数据: ip=%s, time=%s, url=%s, status=%s, traffic=%s", array[0], array[1], array[2], array[3], array[4]);
- 18
- }
- 19
- /**
- 20 * 解析英文时间字符串
- 21 * @param string
- 22 * @return
- 23 * @throws ParseException
- 24 */
- 25 private Date parseDateFormat(String string) {
- 26 Date parse = null;
- 27
- try {
- 28 parse = FORMAT.parse(string);
- 29
- } catch(ParseException e) {
- 30 e.printStackTrace();
- 31
- }
- 32
- return parse;
- 33
- }
- 34
- /**
- 35 * 解析日志的行记录
- 36 * @param line
- 37 * @return 数组含有5个元素,分别是ip、时间、url、状态、流量
- 38 */
- 39 public String[] parse(String line) {
- 40 String ip = parseIP(line);
- 41 String time = parseTime(line);
- 42 String url = parseURL(line);
- 43 String status = parseStatus(line);
- 44 String traffic = parseTraffic(line);
- 45 46
- return new String[] {
- ip,
- time,
- url,
- status,
- traffic
- };
- 47
- }
- 48 49 private String parseTraffic(String line) {
- 50 final String trim = line.substring(line.lastIndexOf("\"") + 1).trim();
- 51 String traffic = trim.split(" ")[1];
- 52
- return traffic;
- 53
- }
- 54 private String parseStatus(String line) {
- 55 final String trim = line.substring(line.lastIndexOf("\"") + 1).trim();
- 56 String status = trim.split(" ")[0];
- 57
- return status;
- 58
- }
- 59 private String parseURL(String line) {
- 60 final int first = line.indexOf("\"");
- 61 final int last = line.lastIndexOf("\"");
- 62 String url = line.substring(first + 1, last);
- 63
- return url;
- 64
- }
- 65 private String parseTime(String line) {
- 66 final int first = line.indexOf("[");
- 67 final int last = line.indexOf("+0800]");
- 68 String time = line.substring(first + 1, last).trim();
- 69 Date date = parseDateFormat(time);
- 70
- return dateformat1.format(date);
- 71
- }
- 72 private String parseIP(String line) {
- 73 String ip = line.split("- -")[0].trim();
- 74
- return ip;
- 75
- }
- 76
- }
输出结果:
- 1源数据:27.19.74.143 - -[30 / 04 / 2017 : 17 : 38 : 20 + 0800]"GET /static/image/common/faq.gif HTTP/1.1"200 1127 2清洗结果数据:ip = 27.19.74.143,
- time = 20170430173820,
- url = GET / static / image / common / faq.gif HTTP / 1.1,
- status = 200,
- traffic = 1127
再看 mapreduce 业务逻辑,在 map 中,我们需要拿出 ip、time、url 这三个属性的值,同时过滤掉静态资源日志。map 的 k1 用默认的 LongWritable 就 OK,v1 不用说 Text,k2、v2 与 k1、v1 类型对应就行:
- 1 static class MyMapper extends Mapper {
- 2 LogParser logParser = new LogParser();
- 3 Text v2 = new Text();
- 4@Override 5 protected void map(LongWritable key, Text value, Mapper.Context context) 6 throws IOException,
- InterruptedException {
- 7 final String[] parsed = logParser.parse(value.toString());
- 8 9 //过滤掉静态信息
- 10
- if (parsed[2].startsWith("GET /static/") || parsed[2].startsWith("GET /uc_server")) {
- 11
- return;
- 12
- }
- 13 //过掉开头的特定格式字符串
- 14
- if (parsed[2].startsWith("GET /")) {
- 15 parsed[2] = parsed[2].substring("GET /".length());
- 16
- }
- 17
- else if (parsed[2].startsWith("POST /")) {
- 18 parsed[2] = parsed[2].substring("POST /".length());
- 19
- }
- 20 //过滤结尾的特定格式字符串
- 21
- if (parsed[2].endsWith(" HTTP/1.1")) {
- 22 parsed[2] = parsed[2].substring(0, parsed[2].length() - " HTTP/1.1".length());
- 23
- }
- 24 v2.set(parsed[0] + "\t" + parsed[1] + "\t" + parsed[2]);
- 25 context.write(key, v2);
- 26
- }
reduce 相对来说就比较简单了,我们只需再讲 map 的输出写到一个文件中就 OK:
- 1 static class MyReducer extends Reducer {
- 2@Override 3 protected void reduce(LongWritable arg0, Iterable arg1, 4 Reducer.Context context) throws IOException,
- InterruptedException {
- 5
- for (Text v2: arg1) {
- 6 context.write(v2, NullWritable.get());
- 7
- }
- 8
- }
- 9
- }
最后,组装 JOB:
- 1 public static void main(String[] args) throws IllegalArgumentException,
- IOException,
- ClassNotFoundException,
- InterruptedException {
- 2 Job job = Job.getInstance(new Configuration());
- 3 job.setJarByClass(LogParser.class);
- 4 job.setMapperClass(MyMapper.class);
- 5 job.setMapOutputKeyClass(LongWritable.class);
- 6 job.setMapOutputValueClass(Text.class);
- 7 FileInputFormat.setInputPaths(job, new Path("/logs/20170430.log"));
- 8 job.setReducerClass(MyReducer.class);
- 9 job.setOutputKeyClass(Text.class);
- 10 job.setOutputValueClass(NullWritable.class);
- 11 FileOutputFormat.setOutputPath(job, new Path("/20170430"));
- 12 job.waitForCompletion(true);
- 13
- }
mapreduce 完成后就是运行 job 了:
1)打包,mapreduce 程序为 loger.jar
2) 上传 jar 包。运行 loger.jar hadoop jar loger.jar
运行结果:
hdfs 多了 20170430 目录:
我们下载下来看看清洗后的数据是否符合要求:
日志数据的清洗到此就完成了,接下来我们要在此之上使用 hive 提取核心指标数据。
1)构建一个外部分区表,sql 脚本如下:
- 1 CREATEEXTERNALTABLEsitelog(ip string, atime string, url string) PARTITIONEDBY(logdate string) ROW FORMAT DELIMITED FIELDS TERMINATEDBY '\t'LOCATION'/20170430';
2)增加分区,sql 脚本如下:
- ALTER TABLEsitelogADDPARTITION(logdate='20170430') LOCATION'/sitelog_cleaned/20170430';
3)统计每日 PV,sql 脚本如下:
- 1 CREATE TABLEsitelog_pv_20170430AS SELECT COUNT(1)ASPVFROMsitelogWHERElogdate='20170430';
4)统计每日注册用户数,sql 脚本如下:
- 1 CREATE TABLEsitelog_reguser_20170430AS SELECT COUNT(1)ASREGUSERFROMsitelogWHERElogdate=20170430' AND INSTR(url,'member.php?mod=register')>0;
5) 统计每日独立 IP,sql 脚本如下:
- 1 CREATE TABLEsite_ip_20170430AS SELECT COUNT(DISTINCTip)ASIPFROMsitelogWHERElogdate='20170430';
6)统计每日跳出的用户数,sql 脚本如下:
- CREATE TABLEsitelog_jumper_20170430AS SELECT COUNT(1)ASjumperFROM(SELECT COUNT(ip)AStimesFROMsitelogWHERElogdate='20170430' GROUP BYipHAVINGtimes=1) e;
7)把每天统计的数据放入一张表中,sql 脚本如下:
- 1 CREATE TABLE sitelog_20170430 AS SELECT '20170430',
- a.pv,
- b.reguser,
- c.ip,
- d.jumper FROM sitelog_pv_20170430 a JOIN sitelog_reguser_20170430 b ON 1 = 1 JOIN sitelog_ip_20170430 c ON 1 = 1 JOIN sitelog_jumper_20170430 d ON 1 = 1;
8)使用 sqoop 把数据导出到 mysql 中:
- sqoop export--connect jdbc: mysql: //hadoop02:3306/sitelog --username root --password root --table sitelog-result --fields-terminated-by '\001' --export-dir '/user/hive/warehouse/sitelog_20170430'
结果如下:
2017 年 4 月 30 日日志分析结果:PV 数为:169857;当日注册用户数:28;独立 IP 数:10411;跳出数:3749.
到此,一个简单的网站日志分析楼主就介绍完了,后面可视化的展示楼主就不写了,比较简单。相关代码地址:https://github.com/LJunChina/hadoop
来源: http://www.cnblogs.com/qq503665965/p/6882827.html