使用 Apache Hadoop 和 Dojo 降低商业智能成本,第 1 部分: 使用 Apache Hadoop 挖掘现有数据

供给一个基于 web 的报表应用程序

理解您的业务总是很重要。您的公司能够像您希望的那样敏捷,但如果您不知道该采取什么正确步骤,那么您就像是 “在闭着眼睛开车”。商业智能解决方案可能成本高昂,并且它们通常需要您改进您的数据以适应它们的系统。但是,开源技术使得创建您自己的商业智能报表比以前任何时候都更容易。本文是一个两部分系列文章的第一篇,介绍如何使用 Apache Hadoop 挖掘您的数据,并将数据转换为可以轻松供给一个基于 web 的报表应用程序的数据。

Michael Galpin, 软件架构师, eBay

Galpin 是 eBay 的一名架构师,经常为 developerWorks 撰写文章。他曾在各类技术会议上发表演说,这些会议包括 JavaOne、EclipseCon 和 AjaxWorld 等。要了解 Michael 的工作进展,请您在 Twitter 上跟随 @michaelg 。



2011 年 6 月 30 日

先决条件

在本文中,您将使用 Apache Hadoop 来处理大量难以处理的数据。本文将使用 Apache Hadoop 0.20。为使用 Hadoop,您需要一个 Java 开发包,本文使用 JDK 1.6.0_20。Hadoop 有一些自己的先决条件,比如必须安装 SSH 和 RSYNC。要获取完整的需求列表,请参阅 Hadoop 文档。参见 参考资料 获取这些工具的链接。

Apache Hadoop 和商业智能

不管您拥有什么样的业务,理解您的客户以及他们如何与您的软件交互的重要性无论如何强调都不过分。对于创业公司或年轻公司,您需要理解什么有效,什么无效,以便迅速重复并响应客户。这对于历史更长的公司同样适用,尽管对他们而言,调优业务或测试新理念可能更重要。无论是哪种情况,要理解您的用户的行为,都需要做几件事。

首先理解您的用户

有时候,理解您的用户是完全显而易见的;但有时候这是一个难以完成的任务。这可能是行为,比如您的用户正在点击您的 web 页面的哪个部分。或者,您可能需要了解,对于一个页面上的相同类型的数据,您的用户是喜欢多一些呢还是喜欢少一些呢?这种数据可能是一个页面上的搜索结果数量,或者是显示的关于一个项目的细节量。但是,这些并非是对理解您的用户来说惟一重要的事项。可能您想知道他们的位置在哪。在本文中,您将检查这样一种情况:您想知道您的用户正在使用哪种浏览器。即使没有其他用处,这种信息也可能对工程团队非常有用,因为这种信息有利于这些团队优化他们的开发工作。

发出必要数据

当您知道自己想衡量和分析的东西后,就需要确保您的应用程序正在发出量化您的用户的行为和/或信息所需的数据。现在,您可能比较幸运,您的应用程序正在发出这种数据。例如,也许您需要的一切已经正在作为系统中某种事务的一部分被处理并记录到数据库中。或者,也许这种数据正在写入一个应用程序或者系统日志。但通常不会是这两种情况,您需要修改您的系统配置或应用程序来记录需要的信息。

对于高度交互的 web 应用程序,这甚至包括编写 JavaScript 来进行 Ajax 调用或者动态丢弃信号(beacon) (通常是一些 1x1 图像,有一些特定事件被添加到它的 URL 的查询字符串),以捕获用户与您的 web 应用程序的交互方式。在本文的示例中,您想捕获正用于访问您的 web 应用程序的浏览器的用户代理。您可能正在捕获这个信息。如果不是,那么它通常是直观的。例如,如果您正在使用 Apache web 服务器,那么您只需添加两行到您的 httpd.conf 文件中。清单 1 展示了一个可能的示例。

清单 1. 使用 Apache web 服务器记录用户代理
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" common
CustomLog /dev/logs/apache_access_log common

清单 1 中的配置是一个相当典型的 Apache 配置。它记录大量有用信息,比如用户的 IP 地址、正在被请求的资源(页面、图像等)、引用者(用户发送请求时所处的页面)、当然还有用户的浏览器的用户代理。您可以调节 LogFormat 来记录更多或更少信息。

注意:发出必要数据看起来比较直观,但是也有一些陷阱。例如,如果您需要捕获大量用户行为,您最终可能会向您的应用程序添加大量日志记录语句。这可能会产生一些不想要的副作用。它可能会增加处理请求的时间。对于一个 web 应用程序而言,那意味着页面变慢或响应性降低,那可不是一件好事情。它还可能意味着您需要更多应用程序服务器来处理相同数量的请求,因此显然会增加成本。您可能会想考虑使用一个非阻塞的日志系统,以防止您的终端用户等待您记录数据。您还可能会想只对请求的一个随机部分执行详细的日志记录,然后从中进行普遍性推断。但增加后的日志记录也可能需要多得多的存储空间来存储那些日志。您可能想购买单独的硬件来进行日志记录。同样,这显然会增加成本,但增加的业务洞察将带来巨大的价值。

挖掘数据

在这个过程中,仅仅把所有数据持久化到某个有用的地方只是相对简单的一步。接着,您需要处理这些数据并将数据转换为更有用的信息。要很好地理解这个需求在以前有多重要,只需对 business intelligence 进行一个 web 搜索。搜索结果不仅包含大量条目,而且有大量付费条目。那里有许多非常昂贵的软件包,旨在帮助您完成这个步骤(以及最后的步骤:报表)。

甚至这些昂贵的软件套件也通常需要一些可能非常复杂的集成工作(当然,供应商通常有一个专业的服务部门来帮助您完成集成,只是要请您带上支票簿)。这正是 Hadoop 发挥作用的地方。它非常适合挖掘大量数据。只需抓取一些 TB 级日志文件,一个商品服务器集群,然后准备好开始工作。这就是本文重点关注的步骤。在这个示例中,您将编写一个 Hadoop map/reduce 作业,将一些 Apache web 服务器访问日志文件转换为一个小型的有用信息数据集,以便轻松地在一个创建交互式报表的 web 应用程序中使用。

创建报表

挖掘所有数据后,您将得到一些精简格式的非常有价值的信息,但这些信息可能位于一个数据库中,也可能作为一个 XML 文件位于某个文件夹中。这对于您而言无关紧要,但这些数据需要交给您公司内的业务分析师和执行官。他们期望得到某种形式的报表,这种报表应该是交互式的,且在视觉上具有吸引力。如果您的报表允许终端用户以多种不同的方式分解并检查这些数据,那么这不仅意味着您的报表对用户更有用,而且您不必返工创建另一个基于相同数据的报表。当然,每个人都喜欢美观的事物。在本系列第二部分中,我将展示如何使用 Dojo 工具包来美化您的报表。但现在,我们将主要关注使用 Hadoop 挖掘数据。我们首先来处理这些 Apache 日志。


分析访问日志

Hadoop 基于 map/reduce 范式,其背后的理念是获取一些难以处理的数据集,将其转换为您感兴趣的数据(这是 map 步骤),然后聚合结果(这是 reduce 步骤)。在本例中,您想从 Apache 访问日志开始,将其转换为一个数据集,这个数据集包含您正从各种浏览器收到的那些请求。因此,这个流程的输入是一个日志文件(也可能是多个日志文件)。清单 2 展示了一个输入内容样例。

清单 2. 输入内容样例
127.0.0.1 - - [11/Jul/2010:15:25:29 -0700] "GET /MAMP/images/pro_timeEdition.jpg 
HTTP/1.1" 304 - "http://localhost:8888/MAMP/?language=English" "Mozilla/5.0 (Windows; U;
Windows NT 6.1; it; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)"
127.0.0.1 - - [11/Jul/2010:15:25:29 -0700] "GET /MAMP/images/welogo.gif HTTP/1.1" 304 -
 "http://localhost:8888/MAMP/?language=English" "Mozilla/5.0 (Macintosh; U; Intel Mac 
OS X 10_6_4; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3"
127.0.0.1 - - [11/Jul/2010:15:25:29 -0700] "GET /MAMP/images/madeonamac.gif HTTP/1.1" 
304 - "http://localhost:8888/MAMP/?language=English" "Mozilla/4.0 (compatible; MSIE 
8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; Media Center PC 6.0; InfoPath.2; 
MS-RTC LM 8)"
127.0.0.1 - - [11/Jul/2010:15:25:29 -0700] "GET /MAMP/images/bullet.gif HTTP/1.1" 304 -
 "http://localhost:8888/MAMP/?language=English" "Mozilla/5.0 (Macintosh; U; Intel 
 Mac OS X 10_6_3; en-us) AppleWebKit/534.1+ (KHTML, like Gecko) Version/5.0 
 Safari/533.16"
127.0.0.1 - - [11/Jul/2010:15:25:29 -0700] "GET /MAMP/images/valid-xhtml10.png HTTP/1.1"
304 - "http://localhost:8888/MAMP/?language=English" "Mozilla/5.0 (Macintosh; U; PPC 
Mac OS X 10.5; en-US; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.0"

这些内容可能在一个非常大的日志文件中,也可能在为系统输入的多个日志文件中。Hadoop 将运行这个数据挖掘流程的 map 步骤。其结果将是一个中间文件集,这个文件集包含 map 步骤的输出,通常要比源文件小得多。清单 3 展示了这个样例输出。

清单 3. map 步骤创建的中间输出
FIREFOX 1
CHROME 1
IE 1
SAFARI 1
FIREFOX 1

从清单 3 可以看出,在这个样例中,这个问题得到了极大简化,您只需关注 4 种浏览器:Microsoft Internet Explorer®、Mozilla Firefox®、Apple Safari® 和 Google Chrome®。您甚至不必担心浏览器版本(在实践中不要这样做,特别是对 Internet Explorer)。然后,基于这些文件执行 reduce 步骤,将这些结果聚合为一个最终输出。清单 4 展示了一个最终输出样例。

清单 4. reduce 步骤创建的最终输出
{"IE":"8678891",
"FIREFOX":"4677201",
"CHROME":"2011558",
"SAFARI":"733549"}

清单 4 中的数据不仅仅被 reduce 步骤聚合,它还被格式化为 JOSN 格式。这将有利于您的基于 web 的报表应用程序使用。当然,您也可以输出更原始 的数据,进行一个服务器端应用程序访问,并将它提供给您的 web 应用程序。Hadoop 和 Dojo 都没有对这个步骤进行限制和要求。

我希望这个过程看起来简单直观。但是,Hadoop 不仅仅是一个封装 map/reduce 范式的框架。它还基于一个分布式基础实现这个范式。那些日志文件可能很大,但是挖掘工作将在您的 Hadoop 集群中的多个机器(节点)之间分配。这些集群通常水平伸缩,因此,如果您想处理更多数据或提高处理速度,只需添加更多机器。配置您的集群并优化您的配置(比如分割数据以便将其发送到各个机器)本身就是一个大课题。本文不会详细介绍这个主题,相反,我们主要关注 map、reduce、以及格式化输出部分。我们从 map 阶段开始。

步骤 1:map 阶段

Hadoop 运行时将分割需要处理的数据(一些日志文件)并向您的集群中的每个节点分配一个数据块。这些数据上需要应用 map 函数。清单 5 展示了如何为您的日志分析器样例指定 map 函数。

清单 5. 用于访问日志样例的 Mapper
public class LogMapper extends Mapper<Object, Text, Text, IntWritable> {
    private final static IntWritable one = new IntWritable(1);
      private static final Pattern regex = 
          Pattern.compile("(.*?)\"(.*?)\"(.*?)\"(.*?)\"(.*?)\"(.*?)\"");    
    @Override
    protected void map(Object key, Text value, Context context)
            throws IOException, InterruptedException {
        Matcher m = regex.matcher(value.toString());
        if (m.matches()){
            String ua = m.group(6).toLowerCase();
            Agents agent = IE; // default
            if (ua.contains("chrome")){
                agent = CHROME;
            } else if (ua.contains("safari")){
                agent = SAFARI;
            } else if (ua.contains("firefox")){
                agent = FIREFOX;
            }
            Text agentStr = new Text(agent.name());
            context.write(agentStr, one);
        }
    }
}

Hadoop 大量使用 Java™ 泛型,以提供一种类型安全的方法来编写 map 和 reduce 函数。清单 5 就是这样一个示例。注意,您扩展了 Hadoop 框架 class org.apache.hadoop.mapreduce.Mapper。这个类接受一个 (key,value) 对作为输入,然后将该输入映射到一个新的 (key,value) 对。它基于输入和输出 (key,value) 对的类型进行参数化。在本例中,它接受一个类型为 (Object, Text)(key,value) 对,然后将其映射到类型为 (Text,IntWritable)(key,value) 对。这里有一个名为 map 的简单实现方法,该方法基于输入 (key,value) 对的类型进行参数化。

对于 清单 5 中的样例,日志文件中的一行(如 清单 2 所示)将作为传递到 map 方法中的输入 Text 对象。您针对这个对象运行一个正则表达式,提取用户代理字符串。然后,您使用字符串机器将这个用户代理字符串映射到一个名为 AgentsEnum。最后,这个数据被写到 Context 对象(它的 write 方法基于映射生成的 (key,value) 对的类型进行参数化)。那个 write 语句将生成一行与 清单 3 中的那些行类似的代码。

步骤 2:reduce 阶段

Hadoop 框架将接收 map 阶段的输出,并将其写入将作为 reduce 阶段的输入的中间文件。reduce 阶段是数据被聚合为更有用的信息的地方。清单 6 展示如何指定 reduce 函数。

清单 6. 访问日志样例的 Reducer
public class AgentSumReducer extends 
    Reducer<Text,IntWritable,Text,IntWritable> {
    private IntWritable result = new IntWritable();
    @Override
    public void reduce(Text key, Iterable<IntWritable> values, 
          Context context) throws IOException, InterruptedException {
        int sum = 0;
        for (IntWritable val : values) {
            sum += val.get();
        }
        result.set(sum);
        context.write(key, result);
    }
}

这一次,您将扩展 org.apache.hadoop.mapreduce.Reducer 类并实现其 reduce 方法。从 清单 6 中可以看出,我们再次利用泛型来使所有这些代码类型安全。但是,您必须确保 Mapper 生成的类型与 Reducer 使用的类型相同。对 reduce 方法传递一个 Iterable 值,它的类型将是输入它的 (key, value) 对的类型。您在这里需要做的只是添加那些值。然后,您将它们写入 Context 对象,正如您在 Mapper 中所做的一样。现在,您准备好格式化输出了。

步骤 3:格式化输出

这个步骤实际上是可选的。您可以只是使用来自 Hadoop 的原始输出(每行上有一个名称和值,用空格分隔)。但是,您想将这个数据提供给一个 web 应用程序以创建报表,因此,您将把它格式化为 JSON,如 清单 4 所示。在 清单 7 中,您可以看到如何进行输出格式指定。

清单 7. 访问日志样例的 OutputFormat
public class JsonOutputFormat extends TextOutputFormat<Text, IntWritable> {
    @Override
    public RecordWriter<Text, IntWritable> getRecordWriter(
            TaskAttemptContext context) throws IOException, 
                  InterruptedException {
        Configuration conf = context.getConfiguration();
        Path path = getOutputPath(context);
        FileSystem fs = path.getFileSystem(conf);
        FSDataOutputStream out = 
                fs.create(new Path(path,context.getJobName()));
        return new JsonRecordWriter(out);
    }

    private static class JsonRecordWriter extends 
          LineRecordWriter<Text,IntWritable>{
        boolean firstRecord = true;
        @Override
        public synchronized void close(TaskAttemptContext context)
                throws IOException {
            out.writeChar('{');
            super.close(null);
        }

        @Override
        public synchronized void write(Text key, IntWritable value)
                throws IOException {
            if (!firstRecord){
                out.writeChars(",\r\n");
                firstRecord = false;
            }
            out.writeChars("\"" + key.toString() + "\":\""+
                    value.toString()+"\"");
        }

        public JsonRecordWriter(DataOutputStream out) 
                throws IOException{
            super(out);
            out.writeChar('}');
        }
    }
}

清单 7 中的代码看起来可能有点复杂,但它实际上很直观。您正在扩展 class org.apache.hadoop.mapreduce.lib.output.TextOutputFormat,这是一个将输出格式化为文本的工具类(注意,我们又一次使用了泛型 — 以确保它们匹配 Reducer 类生成的 (key,value) 对的类型)。您只需实现 getRecordWriter 方法,该方法返回一个 org.apache.hadoop.mapreduce.RecordWriter(同样经过参数化)实例。您正在返回一个 JsonRecordWriter 实例,这是一个内部类,用于从 清单 3 获取数据并生成 清单 4 中的一行数据。这将生成 JSON 数据,以便报表应用程序中基于 Dojo 的代码使用。

请继续关注本系列第 2 部分,了解如何使用 Dojo 制作交互式报表,该报表将使用 Hadoop 在这里生成的智能数据。


结束语

本文展示了使用 Hadoop 挖掘大数据的一个简单示例。这是处理大量数据的一种简单方法,可以向您提供宝贵的业务洞察。在本文中,您直接使用了 Hadoop 的 map/reduce 功能。如果您要将 Hadoop 用于更多用例,您肯定想看看构建于 Hadoop 之上的高级框架,以便简化 map/reduce 作业的编写。Pig 和 Hive 是两个这样的优秀开源框架,它们也都是 Apache 项目,都使用更多声明性语法和更少的编程。Pig 是一种数据流语言,由 Yahoo® 的核心 Hadoop 团队成员开发,而 Facebook 开发的 Facebook 更像 SQL。您可以使用其中一个,或者使用更多基础 map/reduce 作业,来生成供 web 应用程序使用的输出。


下载

描述名字大小
本文源代码AccessLogAnalyzer.zip6KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=696546
ArticleTitle=使用 Apache Hadoop 和 Dojo 降低商业智能成本,第 1 部分: 使用 Apache Hadoop 挖掘现有数据
publish-date=06302011