专栏首页程序猿的大杂烩使用Hadoop统计日志数据

使用Hadoop统计日志数据

用户行为日志概述

用户行为日志:

  • 用户每次访问网站时所有的行为数据
    • 访问、浏览、搜索、点击...
  • 用户行为轨迹、流量日志(用户行为日志的其他名称)

为什么要记录用户访问行为日志:

  • 进行网站页面的访问量的统计
  • 分析网站的黏性
  • 训练推荐系统

用户行为日志生成渠道:

  • web服务器记录的web访问日志
  • ajax记录的访问日志以及其他相关的日志

用户行为日志大致内容:

  • 访问时间
  • 访问者所使用的客户端(UserAgent)
  • 访问者的IP地址
  • 访问者账号
  • 某个页面的停留时间
  • 访问的时间与地点
  • 跳转的链接地址(referer)
  • 访问信息,例如:session_id
  • 模块AppID

用户行为日志分析的意义:

  • 网站的眼睛,能够看到用户的主要来源、喜好网站上的哪些内容,以及用户的忠诚度等
  • 网站的神经,通过分析用户行为日志,我们能对网站的布局、功能进一步的优化,以提高用户的体验等
  • 网站的大脑,通过分析结果,进行推广预算的划分,以及重点优化用户群体的倾向点等

离线数据处理架构

离线数据处理流程:

  • 数据采集
    • 例如可以使用Flume进行数据的采集:将web日志写入到HDFS
  • 数据清洗
    • 可以使用Spark、Hive、MapReduce等框架进行数据的清洗,清洗完之后的数据可以存放在HDFS或者Hive、Spark SQL里
  • 数据处理
    • 按照我们的需求进行相应业务的统计和分析
  • 数据处理结果入库
    • 结果可以存放到RDBMS、NoSQL数据库
  • 数据的可视化展示
    • 通过图形化展示的方式展现出来:饼图、柱状图、地图、折线图等等
    • 工具:ECharts、HUE、Zeppelin

流程示意图:


项目需求

需求:

  • 统计网站访问日志中每个浏览器的访问次数

日志片段如下:

183.162.52.7 - - [10/Nov/2016:00:01:02 +0800] "POST /api3/getadv HTTP/1.1" 200 813 "www.xxx.com" "-" cid=0×tamp=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
10.100.0.1 - - [10/Nov/2016:00:01:02 +0800] "HEAD / HTTP/1.1" 301 0 "117.121.101.40" "-" - "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.16.2.3 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-" - - - 0.000

功能实现之UserAgent解析类测试

首先我们需要根据日志信息抽取出浏览器信息,针对不同的浏览器进行统计操作。虽然可以自己实现这个功能,但是懒得再造轮子了,所以我在GitHub找到了一个小工具可以完成这个功能,GitHub地址如下:

https://github.com/LeeKemp/UserAgentParser

通过git clone或者浏览器下载到本地后,使用命令行进入到其主目录下,然后通过maven命令对其进行打包并安装到本地仓库里:

$ mvn clean package -DskipTest
$ mvn clean install -DskipTest

安装完成后,在工程中添加依赖以及插件:

<repositories>
    <repository>
      <id>cloudera</id>
      <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <hadoop.version>2.6.0-cdh5.7.0</hadoop.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.hadoop</groupId>
      <artifactId>hadoop-client</artifactId>
      <version>${hadoop.version}</version>
      <scope>provided</scope>
    </dependency>
    <!-- 添加UserAgent解析的依赖 -->
    <dependency>
      <groupId>com.kumkee</groupId>
      <artifactId>UserAgentParser</artifactId>
      <version>0.0.1</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <!-- mvn assembly:assembly -->
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <mainClass></mainClass>
            </manifest>
          </archive>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
      </plugin>
    </plugins>
  </build>

然后我们编写一个测试用例来测试一下这个解析类,因为之前并没有使用过这个工具,所以对于一个未使用过的工具,要养成在工程中使用之前对其进行测试的好习惯:

package org.zero01.project;

import com.kumkee.userAgent.UserAgent;
import com.kumkee.userAgent.UserAgentParser;

/**
 * @program: hadoop-train
 * @description: UserAgent解析测试类
 * @author: 01
 * @create: 2018-04-01 22:43
 **/
public class UserAgentTest {

    public static void main(String[] args) {
        String source = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36";
        UserAgentParser userAgentParser = new UserAgentParser();
        UserAgent agent = userAgentParser.parse(source);

        String browser = agent.getBrowser();
        String engine = agent.getEngine();
        String engineVersion = agent.getEngineVersion();
        String os = agent.getOs();
        String platform = agent.getPlatform();
        boolean isMobile = agent.isMobile();

        System.out.println("浏览器:" + browser);
        System.out.println("引擎:" + engine);
        System.out.println("引擎版本:" + engineVersion);
        System.out.println("操作系统:" + os);
        System.out.println("平台:" + platform);
        System.out.println("是否是移动设备:" + isMobile);
    }
}

控制台输出结果如下:

浏览器:Chrome
引擎:Webkit
引擎版本:537.36
操作系统:Windows 7
平台:Windows
是否是移动设备:false

从打印结果可以看到,UserAgent的相关信息都正常获取到了,我们就可以在工程中进行使用这个工具了。


使用MapReduce完成需求统计

创建一个类,编写代码如下:

package org.zero01.hadoop.project;

import com.kumkee.userAgent.UserAgent;
import com.kumkee.userAgent.UserAgentParser;
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.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.output.FileOutputFormat;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: hadoop-train
 * @description: 使用MapReduce来完成统计浏览器的访问次数
 * @author: 01
 * @create: 2018-04-02 14:20
 **/
public class LogApp {

    /**
     * Map: 读取输入的文件内容
     */
    public static class MyMapper extends Mapper<LongWritable, Text, Text, LongWritable> {

        LongWritable one = new LongWritable(1);
        private UserAgentParser userAgentParser;

        protected void setup(Context context) throws IOException, InterruptedException {
            userAgentParser = new UserAgentParser();
        }

        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            // 接收到的每一行日志信息
            String line = value.toString();

            String source = line.substring(getCharacterPosition(line, "\"", 7) + 1);
            UserAgent agent = userAgentParser.parse(source);
            String browser = agent.getBrowser();

            // 通过上下文把map的处理结果输出
            context.write(new Text(browser), one);
        }

        protected void cleanup(Context context) throws IOException, InterruptedException {
            userAgentParser = null;
        }
    }

    /**
     * Reduce: 归并操作
     */
    public static class MyReducer extends Reducer<Text, LongWritable, Text, LongWritable> {

        protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
            long sum = 0;
            for (LongWritable value : values) {
                // 求key出现的次数总和
                sum += value.get();
            }
            // 将最终的统计结果输出
            context.write(key, new LongWritable(sum));
        }
    }

    /**
     * 获取指定字符串中指定标识的字符串出现的索引位置
     *
     * @param value
     * @param operator
     * @param index
     * @return
     */
    private static int getCharacterPosition(String value, String operator, int index) {
        Matcher slashMatcher = Pattern.compile(operator).matcher(value);
        int mIdex = 0;
        while (slashMatcher.find()) {
            mIdex++;

            if (mIdex == index) {
                break;
            }
        }
        return slashMatcher.start();
    }

    /**
     * 定义Driver:封装了MapReduce作业的所有信息
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration configuration = new Configuration();

        // 准备清理已存在的输出目录
        Path outputPath = new Path(args[1]);
        FileSystem fileSystem = FileSystem.get(configuration);
        if (fileSystem.exists(outputPath)) {
            fileSystem.delete(outputPath, true);
            System.out.println("output file exists, but is has deleted");
        }

        // 创建Job,通过参数设置Job的名称
        Job job = Job.getInstance(configuration, "LogApp");

        // 设置Job的处理类
        job.setJarByClass(LogApp.class);

        // 设置作业处理的输入路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));

        // 设置map相关参数
        job.setMapperClass(LogApp.MyMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);

        // 设置reduce相关参数
        job.setReducerClass(LogApp.MyReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);

        // 设置作业处理完成后的输出路径
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}

在工程目录下打开控制台,输入如下命令进行打包:

mvn assembly:assembly

打包成功:

将这个jar包上传到服务器上:

[root@localhost ~]# rz  # 使用的是Xshell工具,所以直接使用rz命令即可上传文件
[root@localhost ~]# ls |grep hadoop-train-1.0-jar-with-dependencies.jar   # 查看是否上传成功
hadoop-train-1.0-jar-with-dependencies.jar
[root@localhost ~]#

把事先准备好的日志文件上传到HDFS文件系统中:

[root@localhost ~]# hdfs dfs -put ./10000_access.log /
[root@localhost ~]# hdfs dfs -ls /10000_access.log
-rw-r--r--   1 root supergroup    2769741 2018-04-02 22:33 /10000_access.log
[root@localhost ~]#

执行如下命令

[root@localhost ~]# hadoop jar ./hadoop-train-1.0-jar-with-dependencies.jar org.zero01.hadoop.project.LogApp /10000_access.log /browserout

执行成功:

查看处理结果:

[root@localhost ~]# hdfs dfs -ls /browserout
Found 2 items
-rw-r--r--   1 root supergroup          0 2018-04-02 22:42 /browserout/_SUCCESS
-rw-r--r--   1 root supergroup         56 2018-04-02 22:42 /browserout/part-r-00000
[root@localhost ~]# hdfs dfs -text /browserout/part-r-00000
Chrome  2775
Firefox 327
MSIE    78
Safari  115
Unknown 6705
[root@localhost ~]# 

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 配置防盗链,访问控制Directory,访问控制FilesMatch

    防盗链能限制不认识的referer的访问,能够禁止别人的服务器引用或转发我服务器上的内容,这样可以防止别人盗用我服务器上的资源,服务器的资源被盗用会导致网络带宽...

    端碗吹水
  • 分布式计算框架MapReduce

    MapReduce源自Google的MapReduce论文,论文发表于2004年12月。Hadoop MapReduce可以说是Google MapReduce...

    端碗吹水
  • 微信公众号开发者模式介绍及接入

    编辑模式和开发模式是互斥的关系,也就是说,当我们使用开发模式时,编辑模式下的操作就会失效。反之,使用编辑模式时,开发模式下的操作就会失效,所以只能使用其中一个模...

    端碗吹水
  • 有关 Spring 异步的有效建议

    根据目前的趋势,我发现初级到高级的开发者都使用 Spring Boot 作为他们构建软件的首选务器。事实上,它对开发人员友好,它“约定优于配置”的风格有助于开发...

    用户1516716
  • day58_BOS项目_10

    之前的请假流程,是没有实际意义的,我们要使得我们流程变得有意义(有实际意义),需要在流程向下推进的过程中带着数据推进才有意义。如下图所示:

    黑泽君
  • 【小家Spring】Spring MVC容器的web九大组件之---ViewResolver源码详解---视图解析器ViewResolver详解

    Spring的一个优秀之处在于,把view层技术与MVC框架的其他部分离开来。 例如,选择使用Velocity或者XSLT来代替已有的JSP方式只需要修改配置就...

    YourBatman
  • SpringBoot---(20)核心原理:自动化配置1

    摘要:本文通过断点追溯源码的方式,来逐步展示SpringBoot的核心功能实现原理:自动化配置;

    IT云清
  • drf框架中所有视图及用法

    from rest_framework import views, generics, mixins, viewsets

    小小咸鱼YwY
  • leetcode:141 环形链表

    思路;一快一慢两个指针在同一起点,快的走两步,慢的一步。 直到快的快慢的一圈正好形成一个圆圈就是环形链表了.

    用户7873631
  • Spring(4)——面向切面编程(AOP模块)

    Spring AOP 简介 如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了,在数据库事务中切面编程被广泛使用...

    我没有三颗心脏

扫码关注云+社区

领取腾讯云代金券