1、背景
1.1 黑马论坛日志,数据分为两部分组成,原来是一个大文件,是56GB;以后每天生成一个文件,大约是150-200MB之间;
1.2 日志格式是apache common日志格式;
1.3 分析一些核心指标,供运营决策者使用;
1.4 开发该系统的目的是分了获取一些业务相关的指标,这些指标在第三方工具中无法获得的;
2、开发大致流程:
2.1 把日志数据上传到HDFS中进行处理
如果是日志服务器数据较小、压力较小,可以直接使用shell命令把数据上传到HDFS中;
如果是日志服务器数据较大、压力较大,使用NFS在另一台服务器上上传数据;
如果日志服务器非常多、数据量大,使用flume进行数据处理;
2.2 使用MapReduce对HDFS中的原始数据进行清洗;
2.3 使用Hive对清洗后的数据进行统计分析;
2.4 使用Sqoop把Hive产生的统计结果导出到mysql中;
2.5 如果用户需要查看详细数据的话,可以使用HBase进行展现;
3、数据准备:
每行记录有5部分组成:
1.访问ip
2.访问时间
3.访问资源【跟着两个访问的Url】
4.访问状态
5.本次流量
截取部分数据如下:
27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/shy.gif HTTP/1.1" 200 2663 8.35.201.163 - - [30/May/2013:17:38:21 +0800] "GET /static/image/common/nv_a.png HTTP/1.1" 200 2076 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/titter.gif HTTP/1.1" 200 1398 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/sweat.gif HTTP/1.1" 200 1879 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/mad.gif HTTP/1.1" 200 2423 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/hug.gif HTTP/1.1" 200 1054 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/lol.gif HTTP/1.1" 200 1443 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/victory.gif HTTP/1.1" 200 1275 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/time.gif HTTP/1.1" 200 687 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/kiss.gif HTTP/1.1" 200 987 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/handshake.gif HTTP/1.1" 200 1322 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/loveliness.gif HTTP/1.1" 200 1579 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/call.gif HTTP/1.1" 200 603 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/funk.gif HTTP/1.1" 200 2928 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/curse.gif HTTP/1.1" 200 1543 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/dizzy.gif HTTP/1.1" 200 1859 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/shutup.gif HTTP/1.1" 200 2500 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/smiley/default/sleepy.gif HTTP/1.1" 200 2375 8.35.201.164 - - [30/May/2013:17:38:21 +0800] "GET /static/image/common/pn.png HTTP/1.1" 200 592 8.35.201.165 - - [30/May/2013:17:38:21 +0800] "GET /uc_server/avatar.php?uid=56212&size=middle HTTP/1.1" 301 - 27.19.74.143 - - [30/May/2013:17:38:21 +0800] "GET /static/image/common/uploadbutton_small.png HTTP/1.1" 200 690 8.35.201.160 - - [30/May/2013:17:38:21 +0800] "GET /static/image/common/fastreply.gif HTTP/1.1" 200 608 8.35.201.160 - - [30/May/2013:17:38:21 +0800] "GET /uc_server/avatar.php?uid=21212&size=middle HTTP/1.1" 301 |
4、相关关键指标及计算逻辑
⊙浏览量PV
定义:页面浏览量即为PV(Page View),是指所有用户浏览页面的总和,一个独立用户每打开一个页面就被记录1 次。
分析:网站总浏览量,可以考核用户对于网站的兴趣,就像收视率对于电视剧一样。但是对于网站运营者来说,更重要的是,每个栏目下的浏览量。
计算公式:记录计数
⊙注册用户数
计算公式:对访问member.php?mod=register的url,计数
⊙IP数
定义:一天之内,访问网站的不同独立IP 个数加和。其中同一IP无论访问了几个页面,独立IP 数均为1。
分析:这是我们最熟悉的一个概念,无论同一个IP上有多少电脑,或者其他用户,从某种程度上来说,独立IP的多少,是衡量网站推广活动好坏最直接的数据。
公式:对不同ip,计数
⊙跳出率
定义:只浏览了一个页面便离开了网站的访问次数占总的访问次数的百分比,即只浏览了一个页面的访问次数 / 全部的访问次数汇总。
分析:跳出率是非常重要的访客黏性指标,它显示了访客对网站的兴趣程度:跳出率越低说明流量质量越好,访客对网站的内容越感兴趣,这些访客越可能是网站的有效用户、忠实用户。
该指标也可以衡量网络营销的效果,指出有多少访客被网络营销吸引到宣传产品页或网站上之后,又流失掉了,可以说就是煮熟的鸭子飞了。比如,网站在某媒体上打广告推广,分析从这个推广来源进入的访客指标,其跳出率可以反映出选择这个媒体是否合适,广告语的撰写是否优秀,以及网站入口页的设计是否用户体验良好。
计算公式:(1)统计一天内只出现一条记录的ip,称为跳出数
(2)跳出数/PV
⊙版块热度排行榜
定义:版块的访问情况排行。
分析:巩固热点版块成绩,加强冷清版块建设。同时对学科建设也有影响。
计算公式:按访问次数统计排序
5、具体开发实现:
5-1、把日志数据上传到HDFS中进行处理
如果是日志服务器数据较小、压力较小,可以直接使用shell命令把数据上传到HDFS中;
如果是日志服务器数据较大、压力较答,使用NFS在另一台服务器上上传数据;
如果日志服务器数据非常多、数据量大,使用flume进行数据处理;
[root@hadoop11 mydata]# hadoop fs -put access_2013_05_30.log /hmbbs_logs/ [root@hadoop11 mydata]# hadoop fs -lsr /hmbbs_logs/ lsr: DEPRECATED: Please use 'ls -R' instead. -rw-r--r-- 3 root supergroup 61084192 2016-07-22 14:42 /hmbbs_logs/access_2013_05_30.log |
5-2、使用MapReduce对数据进行清洗,清洗后的数据易于我们的使用
清洗标准:将Apache log日志清洗成我们熟悉的日志格式。
①
访问状态和本次流量字段去掉
②
过滤掉静态记录,只要动态记录;
GET /static 或者 GET /uc_server 开头的行文本舍弃
过滤掉了开头和结尾的标志信息;
GET / POST /
HTTP/1.1 HTTP/1.0
清洗日志的MapReduce源代码:
package Hmbbs; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.input.TextInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; import org.apache.hadoop.mapreduce.lib.partition.HashPartitioner; ; //本程序的目的是通过MapReduce对Hmbbs中的日志数据进行清洗时 public class HmbbsCleaner { public static String path1="";//指定文件的输入路径 public static String path2="";//指定日志的输出路径 public static void main(String[] args) throws Exception { path1 = args[0]; path2 = args[1]; Configuration conf = new Configuration(); conf.set("fs.defaultFS", "hdfs://hadoop11:9000/"); FileSystem fileSystem = FileSystem.get(conf); if(fileSystem.exists(new Path(path2))) { fileSystem.delete(new Path(path2), true); } Job job = Job.getInstance(conf, "HmbbsCleaner"); job.setJarByClass(HmbbsCleaner.class);//jar包 //编写驱动 FileInputFormat.setInputPaths(job, new Path(path1)); job.setInputFormatClass(TextInputFormat.class); job.setMapperClass(MyMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(NullWritable.class); job.setNumReduceTasks(1);//指定Reducer的任务数量为1 job.setPartitionerClass(HashPartitioner.class); job.setReducerClass(MyReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(NullWritable.class); FileOutputFormat.setOutputPath(job, new Path(path2)); job.setOutputFormatClass(TextOutputFormat.class); //提交任务 job.waitForCompletion(true); } public static class MyMapper extends Mapper { protected void map(LongWritable k1, Text v1,Context context)throws IOException, InterruptedException { String string = v1.toString();//获取待记录 Parselogs parselogs = new Parselogs(); try { String[] sub = parselogs.parseString(string); if(sub[2].startsWith("GET /static")||sub[2].startsWith("GET /uc_server")) return ;//对于静态的记录直接过滤掉,不进行任何处理 if(sub[2].startsWith("GET /")) { sub[2] = sub[2].substring("GET /".length()); } if(sub[2].startsWith("POST /")) { sub[2] = sub[2].substring("POST /".length()); }//过滤掉了开头和结尾的标志信息 if(sub[2].endsWith(" HTTP/1.1")) { sub[2] = sub[2].substring(0, sub[2].length()-" HTTP/1.1".length()); } if(sub[2].endsWith(" HTTP/1.0")) { sub[2] = sub[2].substring(0, sub[2].length()-" HTTP/1.0".length()); } Text k2 = new Text(); k2.set(sub[0]+"\t"+sub[1]+"\t"+sub[2]);//三个字段之间以制表符进行分开 context.write(k2, NullWritable.get()); } catch (ParseException e) { e.printStackTrace(); } } } public static class MyReducer extends Reducer { protected void reduce(Text k2, Iterable { for (NullWritable v2 : v2s) { Text k3 = k2; context.write(k3, NullWritable.get()); } } } } class Parselogs //Parselogs这个类用来对字符串进行解析 { public String[] parseString(String str) throws ParseException { String str1 = parseIp(str); String str2 = parseDate(str); String str3 = parseUrl(str); //String str4 = parseStatus(str); //String str5 = parseFlow(str); //String[] str66 = new String[]{str1,str2,str3,str4,str5}; String[] str66 = new String[]{str1,str2,str3};//在这里只获取与本次项目有关的数据 return str66; } public String parseIp(String str)//对ip地址进行解析的方法 { String[] splited = str.split(" - - ");//用指定的正则表达式进行切分,获取我们需要的字段 return splited[0]; } public String parseDate(String str) throws ParseException { String[] splited = str.split(" - - ");//用指定的正则表达式进行切分,获取我们需要的字段 int index1 = splited[1].indexOf("["); int index2 = splited[1].indexOf("]"); String substring = splited[1].substring(index1+1, index2);//到此获取了时间字段30/May/2013:17:38:20 +0800 SimpleDateFormat simple1 = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss", Locale.ENGLISH);//匹配我们给定的字符串,并将其解析成对应的时间 SimpleDateFormat simple2 = new SimpleDateFormat("yyyyMMddHHmmss");//匹配我们给定的字符串 Date parse = simple1.parse(substring); String format = simple2.format(parse); return format; } public String parseUrl(String str)//获取访问的url { int index1 = str.indexOf("]"); int index2= str.lastIndexOf("\""); String substring = str.substring(index1+3, index2); return substring; } public String parseStatus(String str)//获取访问的状态 { int index1= str.lastIndexOf("\""); String str2 = str.substring(index1+1).trim(); String[] splited = str2.split(" "); return splited[0]; } public String parseFlow(String str)//获取访问的状态 { int index1= str.lastIndexOf("\""); String str2 = str.substring(index1+1).trim(); String[] splited = str2.split(" "); return splited[1]; } } |
5-3、使用hive对清洗后的数据进行多维分析
(1)统计每日的pv(浏览量)
hive> create table hmbbs_pv as select count(1) as pv from hmbbs_table; |
(2)统计每日的register(注册用户数)
hive> create table hmbbs_register as select count(1) as register from hmbbs_table where instr(urllog,'member.php?mod=register') > 0; |
(3)统计每日的独立的ip
hive> create table hmbbs_ip as select count(distinct iplog) as ip from hmbbs_table; |
(4)统计每日的独立的跳出率
hive> CREATE TABLE hmbbs_jumper AS SELECT COUNT(1) AS jumper FROM (SELECT COUNT(iplog) AS times FROM hmbbs_table GROUP BY iplog HAVING times=1) e ; |
到此获得了各个参数的结果:
hive> show tables; OK hmbbs_ip hmbbs_jumper hmbbs_pv hmbbs_register hmbbs_table Time taken: 0.081 seconds hive> select * from hmbbs_ip; OK 10411 Time taken: 0.111 seconds hive> select * from hmbbs_jumper; OK 3749 Time taken: 0.107 seconds hive> select * from hmbbs_pv; OK 169857 Time taken: 0.108 seconds hive> select * from hmbbs_register; OK 28 Time taken: 0.107 seconds |
(5) 将结果汇总:
CREATE TABLE hmbbs_2013_05_30 AS SELECT '2013_05_30', a.pv, b.reguser, c.ip, d.jumper FROM hmbbs_pv a JOIN hmbbs_register b ON 1=1 JOIN hmbbs_ip c ON 1=1 JOIN hmbbs_jumper d ON 1=1 ; |
4、将hive分析的结果使用sqoop导出到mysql中
sqoop export --connect jdbc:mysql://hadoop0:3306/mydata --username root --password admin --table hmresult --fields-terminated-by '\001' --export-dir '/hive/hmbbs_2013_05_30' |
接下来我们在mysql中查看数据: