供给一个基于 web 的报表应用程序
简介: 理解您的业务总是很重要。您的公司能够像您希望的那样敏捷,但如果您不知道该采取什么正确步骤,那么您就像是 “在闭着眼睛开车”。商业智能解决方案可能成本高昂,并且它们通常需要您改进您的数据以适应它们的系统。但是,开源技术使得创建您自己的商业智能报表比以前任何时候都更容易。本文是一个两部分系列文章的第一篇,介绍如何
Apache Hadoop 和商业智能
不管您拥有什么样的业务,理解您的客户以及他们如何与您的软件交互的重要性无论如何强调都不过分。对于创业公司或年轻公司,您需要理解什么有效,什么无效,以便迅速重复并响应客户。这对于历史更长的公司同样适用,尽管对他们而言,调优业务或测试新理念可能更重要。无论是哪种情况,要理解您的用户的行为,都需要做几件事。
首先理解您的用户
有时候,理解您的用户是完全显而易见的;但有时候这是一个难以完成的任务。这可能是行为,比如您的用户正在点击您的 web 页面的哪个部分。或者,您可能需要了解,对于一个页面上的相同类型的数据,您的用户是喜欢多一些呢还是喜欢少一些呢?这种数据可能是一个页面上的搜索结果数量,或者是显示的关于一个项目的细节量。但是,这些并非是对理解您的用户来说惟一重要的事项。可能您想知道他们的位置在哪。在本文中,您将检查这样一种情况:您想知道您的用户正在使用哪种浏览器。即使没有其他用处,这种信息也可能对工程团队非常有用,因为这种信息有利于这些团队优化他们的开发工作。
发出必要数据
当您知道自己想衡量和分析的东西后,就需要确保您的应用程序正在发出量化您的用户的行为和/或信息所需的数据。现在,您可能比较幸运,您的应用程序正在发出这种数据。例如,也许您需要的一切已经正在作为系统中某种事务的一部分被处理并记录到数据库中。或者,也许这种数据正在写入一个应用程序或者系统日志。但通常不会是这两种情况,您需要修改您的系统配置或应用程序来记录需要的信息。
对于高度交互的 web 应用程序,这甚至包括编写 JavaScript 来进行 Ajax 调用或者动态丢弃信号(beacon) (通常是一些 1x1 图像,有一些特定事件被添加到它的 URL 的查询字符串),以捕获用户与您的 web 应用程序的交互方式。在本文的示例中,您想捕获正用于访问您的 web 应用程序的浏览器的用户代理。您可能正在捕获这个信息。如果不是,那么它通常是直观的。例如,如果您正在使用 Apache web 服务器,那么您只需添加两行到您的 httpd.conf 文件中。清单 1 展示了一个可能的示例。
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 展示了一个输入内容样例。
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 展示了这个样例输出。
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 展示了一个最终输出样例。
{"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
函数。
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
对象。您针对这个对象运行一个正则表达式,提取用户代理字符串。然后,您使用字符串机器将这个用户代理字符串映射到一个名为 Agents
的 Enum
。最后,这个数据被写到 Context
对象(它的 write
方法基于映射生成的 (key,value)
对的类型进行参数化)。那个 write
语句将生成一行与 清单 3 中的那些行类似的代码。
步骤 2:reduce 阶段
Hadoop 框架将接收 map 阶段的输出,并将其写入将作为 reduce 阶段的输入的中间文件。reduce 阶段是数据被聚合为更有用的信息的地方。清单 6 展示如何指定 reduce
函数。
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 中,您可以看到如何进行输出格式指定。
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 应用程序使用的输出。
使用 Apache Hadoop 挖掘您的数据,并将数据转换为可以轻松供给一个基于 web 的报表应用程序的数据
回页首
下载
描述 | 名字 | 大小 | 下载方法 |
---|---|---|---|
本文源代码 | AccessLogAnalyzer.zip | 6KB | HTTP |
转载地址
http://www.ibm.com/developerworks/cn/web/wa-dojohadoop1/