jsonp为啥能跨域呢,我们来盗一下这个墓

不知道大家最近看没看《鬼吹灯之寻龙诀 》,感觉IMAX-3D效果真不错,虽然剧情不咋滴,但是效果是出来了。好了,今天我们也来一场盗墓,这次是挖jsonp的坟,挖一挖为啥jsonp就能跨域呢,它咋就那么牛X呢。如果看到了这篇文章,那么大家对于跨域一定不陌生了,通俗说就是不同域请求资源。不过怎么就构成跨域呢,这个问题貌似在《白帽子讲web安全》这本书里讲的比较全面,本文不去深究了,我们今天是来盗墓的,开始干活。

基本思路

大家都知道<script>标签可以通过src来引入js文件,当然它可不是仅仅可以引入js文件,它能引入很多资源。比如看下方:

jsonp-sina-home

这是新浪汽车首页引入了一个新浪汽车图库的php文件。如果你打开ip.php它返回如下信息(其实就是一段js代码,两个js方法get_city和get_ip_city):json-sina-ip

如果你开发或者调试过跨域的接口,也就知道基本上jsonp和json就差一个callback参数,学名叫回调函数。这个回调函数就为我们定了穴,接下来我们从这里开始挖。

接口

为了方便大家测试,我就不单独写接口了,我们找一个现成的:

http://news.auto.sina.com.cn/m/label/get_label_info.php?label=%E8%BD%A6%E8%81%94%E7%BD%91&length=1&page=1&callback=callback

这个接口是支持跨域的,为啥呢,接下来我们开始挖。

测试跨域

如果接口支持跨域并且没有做其他限制,那么也就意味着这个接口在任何地方都可以调用。所以我们在本地新建一个test.html文件,文件内容如下:

<html>
<head>
<script src="http://news.auto.sina.com.cn/m/label/get_label_info.php?label=%E8%BD%A6%E8%81%94%E7%BD%91&length=2&page=1&callback=func"></script>
</head>
<body>
</body>
</html>

用chrome打开上述文件,然后右键审查元素,你将会收获一个错误信息,点击错误信息,可以得到如下图:jsonp-error-func

其实这个报错信息提示我们func未定义,其实就是我们传入的callback的参数未定义,那这个需要在哪里定义呢?当然是在我们的html页面中,我们在html中定义一下,代码如下:

<html>
<head>
<script>
function func(data){
 console.log(data);
}
</script>
<script src="http://news.auto.sina.com.cn/m/label/get_label_info.php?label=%E8%BD%A6%E8%81%94%E7%BD%91&length=2&page=1&callback=func"></script>
</head>
<body>
</body>
</html>

上述代码我们添加了一个func方法,这个方法接收一个参数,并且将传入的值打印到console中。在chrome中运行上述代码,并审查源代码,效果如下图:

jsonp-success

这一次并没有报错,并且已经按照我们的预期将返回的值打印到了console中,其中的值其实就是json格式。

js的jsonp请求

这里我们用jQuery.ajax()方法去请求一下上述接口,来体验一下jsonp请求。当然你也可以用XMLHttpRequest/ActiveXObject,不过注意兼容性。代码如下:

<html>
<head>
<!-- 引入jquery包 -->
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script>
$(function(){
 $.ajax({
     url: 'http://news.auto.sina.com.cn/m/label/get_label_info.php?label=%E8%BD%A6%E8%81%94%E7%BD%91&length=2&page=1',
     dataType:'jsonp',
     success:function(res){
         console.log(res);
     }
 });
});
</script>
</head>
<body>
</body>
</html>

在chrome中打开上述文件,然后审查元素(记得要重新刷新页面哦),效果如下:

jsonp-jquery-succ

揭开谜底,发现宝藏

通过上边两种方式体验了一番,也许你已经知道了谜底。谜底就是js可以引入并运行跨域代码,而不能引入跨域的数据,jsonp通过回调函数来链接远程资源和本地js代码。回调函数在本地,而运行回调函数并传入参数是通过远程实现的。换句话说:jsonp返回来的是代码,而json返回来的是数据,浏览器为了安全起见不允许跨域请求数据,但是并没有限制不能运行远程代码。

那么如果你是一个后端开发人员,你写如下代码(PHP版本):

echo $callback."(".json_encode(['data']).")";

其实是相当于返回了一段调用js方法的代码,调用了js代码里的callback方法,而如果只是返回一串json,那么返回的是数据。如果你是js开发人员,需要注意jsonp返回的值是一段js代码,是有危险的,比如在callback前边加上一个while(1){}那你的代码就彻底歇菜了,不要轻易去使用别人的接口。此外还得提醒大家,jsonp只能通过GET方式传输,不能用POST等方式。

打完收工,今天我们从两个角度去思考了jsonp是怎么完成跨域的,其实本质是一样的,如果你想彻底弄明白这个过程,最好动手试一下。

Chrome插件开发,只要你敢露我就敢抓

“只要你敢露我就敢抓。”多么霸气的一句话,是多少男人梦寐以求的事,特别是看到美女的时候,哈哈,开个玩笑。我们言归正传,目前抓站越来越难,特别是现在很多内容是通过js加载或者是乱起八糟的东西拼凑出来,酷炫是酷炫,但是用后端程序越来越难以抓取。经常有人搜索”如何等js执行完再抓取内容等等”(其实我也搜过)。下边我们来看看如何“以js之道还至js之身”。让你的js跑到任何一个你看到的网页,抓取任何你想要的内容。

直觉上打开网页的正确姿势是浏览器,没错,这次我们就是在浏览器上做文章,毕竟用其他方式打开网页都没有浏览器那么流行。也许你听说过浏览器插件这么个东西,如果你没听说过也没啥问题,毕竟下边要说的就是这个东西。下边我们先从开发一款chrome插件说起。

预备知识

写插件很简单,只需要你有一个chrome浏览器,你就可以写chrome插件了,当然如果你对js一点都不懂,那就学习一下吧, 毕竟也不难。当然如果你稍微懂点CSS就更好了,不过,不懂也无所谓。

起步

下边我们来开发我们的第一个插件。

  • 首先准备一个目录,用于存放插件。
  •  然后建配置文件manifest.json,这是唯一必须用的文件,内容如下。
{
  "manifest_version": 2,
  "name": "FOREVERNULL",
  "version": "1.0"
}

到此,不管你信不信,我们的插件写完了,哈哈。

加载运行

点击右上角【设置】按钮,就是那个三道杠,然后选择【更多工具】中的【扩展程序】,看到如下界面。而后点击【开发者模式】、【加载已解压的扩展程序】, 选择你的插件目录就OK了。看看是不是有了你的插件,虽然现在我们什么都没干。

chrome_extension_forevernull

如果你修改了扩展程序,只需要刷新这个页面或者点一下【重新加载】就OK了。接下来,我们需要给我们的插件添加个图标。

添加图标

如果你安装过chrome插件,可能你会知道chrome会在扩展程序页面和浏览器地址栏的右侧显示一些小的图标。下边我们为我们的插件添加图标。

  • 首先,我们下载一个图标文件,png或者ico,放到我们的插件目录。
  • 而后,在配置文件中添加这么一行:
   "icons": {
      "48": "icon.png"
   }

这里需要注意格式,目前你的配置文件应该长这样,特别注意icons前边的逗号,其实说白了就必须是符合JSON格式的规范:

{
  "manifest_version": 2,
  "name": "FOREVERNULL",
  "version": "1.0",
  "icons": {
      "48": "icon.png"
   }
}

现在我的就长这样了,是不是比刚才好看点了呢。
google_icon

这样我们的扩展程序在右上角还是没有图标,咋整呢?添加下边的配置,将会有惊喜:

    "browser_action": {
      "default_icon": "icon.png"
    }

变化请看浏览器的右上角,地址栏的右侧。图标OK了,你就知道在上述界面中如何添加属性了,你还可以添加描述、作者等,这些都是附属品,我们就不讲了。

做点小事

讲了半天,我们的chrome插件还是个摆设,只能玩一玩,怎么给它添加功能呢。接下来我们先写个简单的,先预热一下:

  • 首先, 在我们的插件目录创建一个js文件,比如helloworld.js
  • 然后,我们在配置文件中加载这个js文件,添加如下配置:
    "content_scripts":[{
        "js": [ "helloworld.js" ],
        "matches": [ "<all_urls>" ]
    }]
    

    这个配置稍微有点牛X,它是在所有页面都可以执行helloworld.js。这个match属性用于配置你插件作用的范围,这里我们配置为全部url都起效。

这里content_scripts的意思就是这段代码是插入到浏览的页面中的,chrome插件假设页面有这么几种,一种是background就是在后台执行的;另一种是popup弹出类型的,比如右上角的图标点击后可以弹出一个页面来,用于设置等;另一种是contents类型的,这种是在当前浏览的页面。并且他们直接可以互相通信,这里我们先不研究这种比较复杂,主要是还用不到的东西。我们只需要了解这个contents_script就是能够操纵当前浏览的页面的代码就好了。

下边我们在helloworld.js中稍微添加点js代码,比如:

alert("Hello, world!");

这句话的本意是打印一句话:Hello, world!。但是我们由于是一个插件,并且作用于任何url,那么无论打开哪个页面,都会弹出一个弹窗显示Hello, world!。

友情提示:修改完代码后,要去【扩展程序】重新刷新一下我们的插件,由于我们改动的是js文件,可能会报错或者需要重新勾选“启用”。

现在无论你打开什么页面或者刷新页面,都会看见一个弹窗了。到此为止,我们已经给我们的插件添加了行为,我们的chrome插件开发就到此为止了,其他的插件也不外乎这么写。

抓站

有了上一步小小的例子,我们就有了思路:通过js获取DOM元素并取其中的值,然后组织数据并提交到远程服务器即可。
下边我们稍微加强一下咱们的插件,引入jQuery帮我们处理DOM并提供方便的ajax调用。当然如果你不用jQuery也行,这里只是图方便。

  • 首先下载jquery到我们的插件目录。
  • 而后修改配置文件,给content_scripts添加jquery这个js。
    "content_scripts":[{
        "js": [ "jquery-1.11.3.min.js","helloworld.js" ],
        "matches": [ "<all_urls>" ]
      }]
    

下边我们假设我们的任务是抓取内推网的招聘数据(只用于做实验,你抓啥都行,看见了这个网址就打开了,就抓你来做实验吧,哈哈)网址:http://www.neitui.me/?name=neitui&handle=lists&kcity=&keyword=PHP。

我们开始写js代码,和平时写代码一样,这里我给出一个示例,大家稍微感受一下:

$(function(){
  jobs = [];
  $(".jobinfo").each(function(){
       jobs.push($(this).find('strong').text());//获取所有标题
  });
  //异步提交数据
  $.ajax({
      url : "http//<yoururl>/<path>",
      dataType:"jsonp",
      data:{
          jobs : jobs
      },
      success:function(){}
  });
});

这个代码唯一注意的问题就是提交数据的时候由于存在跨域问题,所以需要使用jsonp提交数据。

写在最后

看到这里,你基本上会开发你自己的插件了,现在你可以让浏览器帮你做你想做的事,所以机械的点击动作,都可以通过插件完成。并且我们展示了使用插件抓取数据的基本思路和示例。

用这种抓取数据,基本可以做到想要什么就抓什么,并且可以自动提交表单等等,最主要这种基本不会被屏蔽,还可以开多个窗口多进程抓。但也存在一些问题,比如网页加载缓慢会造成抓取效率不高等。但是我们已经实现了“只要你敢露我就敢抓”这个目标,另一种所见即所得,再放一句豪言壮语,“只有你想不到的办法,没有抓不到的数据”。

Hadoop Mapreduce之PHP实例

目前Hadoop是大家用的最普遍的大数据处理平台,但Hadoop本身是用JAVA开发的,作为PHP开发人员想要掺和大数据咋整呢?还好,Hadoop提供了大量的工具给其他语言的开发者。Hadoop Streaming就是Hadoop提供给其他语言开发者开发Mapreduce程序的工具。下边我们来体验一下PHP开发、测试和运行Mapreduce任务。

一、准备

二、原理

Hadoop Streaming是Hadoop工具集中的一个工具,说白了就是一个jar包,它负责从标准输出读取数据,并将中间结果输出到标准输出,同时负责创建、提交和监控mapreduce任务。那么这就为我们提供了一个思路,我们只需要和从标准输出读取和输出数据一样写代码即可。

上述jar包一般在Hadoop的安装目录下,文件位置和文件名根据版本号不同而不同,比如2.6.2版如下:

$HAOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.6.2.jar

可以在Hadoop的安装目录下搜索文件名即可:

find -name '*streaming*'

Hadoop Streaming基本用法:

hadoop jar hadoop-streaming-2.6.2.jar \
    -input myInputDirs \
    -output myOutputDir \
    -mapper /bin/cat \
    -reducer /usr/bin/wc

此命令不赘述,后边实际用到我们再解释,如果想学习具体细节请查看参考手册

三、Mapreduce简要介绍

Mapreduce是Hadoop处理数据的框架。Mapreduce主要包括两个过程,一个是Map过程,在集群内的机器中分节点处理数据;一个是Reduce过程,将Map过程产生的结果进行处理。见下图(图片来源)

ssd1

根据上述理解,我们就是要开发map程序和reduce程序,下边我们用PHP实现。

四、任务简述

接下来,我们实现如下功能:

使用PHP开发语言,利用Mapreduce处理用户数据,统计每个城市的注册用户数。

用户数据如下(users.txt):

1,张三,23,beijing,10086,
2,李四,34,shanghai,10000,
3,王五,20,beijing,10010,
……

基本结构为“ID,name, age, city, telphone,”,每条数据一行。

五、Mapper实现

mapper.php

#!/usr/bin/php
<?php
$count = 0;
while($line = fgets(STDIN)) {
    $line = trim($line);
    $user = explode(',', $line);
    echo $user[3]." 1\n";
}

这个脚本没啥特别的,其实就是CLI模式的PHP脚本,大家可能注意到了第一行不是&lt;?php 而是#!/usr/bin/php。这个需要稍微说明一下,这个是所谓的shebang,通常我们执行PHP脚本,可能这么执行php mapper.php,如果加了第一行,我们只需要这样执行即可:./mapper.sh(是不是很眼熟!)。如果你不知道PHP安装在哪里,可以通过which php来找到它的路径。

此外因为Hadoop认为脚本都是可执行的程序,那么我们需要给这个mapper.php赋予可执行权限:

chmod +x mapper.php

上述程序很简单,只是从标准输出(STDIN)读取数据,然后取出城市信息,并标记一个1,每行一个输出出来。

六、Reducer实现

#!/usr/bin/php
<?php
$result = array();
while($line = fgets(STDIN)) {
    list($city, $count) = explode(' ', $line);
    if(!isset($result[$city])) $result[$city] = 0; 
    $result[$city] += $count;
}
foreach($result as $key=>$value){
    echo "$key $value\n";
}

这段程序也比较简单,就是从标准输出(STDIN)按行读取数据,然后统计各城市的人数而已。同样我们需要shebang,并给reducer.php赋予可执行权限。

七、调试代码

目前我们得到了三个原材料文件:

  • user.txt 存储用户数据信息,每行代表一条用户信息(可填充少量数据进行测试使用)。
  • mapper.php 可执行的mapper脚本,具有shabang,并已赋予可执行权限。
  • reducer.php 可执行的reducer脚本,具有shabang,并已赋予可执行权限。

调试mapper程序:

$ cat user.txt|./mapper.php
shandong 1
beijing 1
beijing 1

将少量数据放入user.txt中,使用Linux命令行工具进行调试。

调试reducer程序:

$ cat user.txt | ./mapper.php | ./reducer.php 
beijing 2
shandong 1

实际上,reducer负责处理多个mapper产生的结果,此处我们仅模拟一个mapper输出然后用reducer处理即可,就能够完成任务目标,通过测试。

八、实际运行

8.1 启动Hadoop伪分布式集群
$HADOOP_HOME/sbin/start-dfs.sh

$HADOOP_HOME/sbin/start-yar.sh

运行完上述两条命令,使用jps命令验证一下,如果有DataNode、 NodeManager、NameNode、ResourceManager、SecondaryNameNode说明启动成功。

8.2 创建目录并将文件存储到HDFS中
$HADOOP_HOME/bin/hdfs dfs -mkdir /user
$HADOOP_HOME/bin/hdfs dfs -mkdi /user/<username>
$HADOOP_HOME/bin/hdfs dfs -mkdi /user/<username>/input

$HADOOP_HOME/bin/hdfs dfs -put user.txt /user/<username>/input

此处一定要注意<username>就是你当前登陆Linux系统的用用户名,比如我的是’hadoop’。最后一条命令一定要在user.txt所在目录执行,你懂的。

8.3 执行MAPpreduce命令
$HADOOP_HOME/bin/hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.6.2.jar \
-input input/user.txt \
-output output2 \
-mapper /home/hadoop/Documents/src/php/mapper.php \
-reducer /home/hadoop/Documents/src/php/reducer.php

Hadoop可以运行jar文件,用java写代码就是用了这个原理,我们没辙只能通过streaming来搞,所以传入的是文章开头提到的那个hadoop-streaming文件。主要参数:

  • input 输入的文件路径,这里是指HDFS中的路径。
  • output Mapreduce运行结果输出目录,也是指HDFS中的目录,这个目录必须是不存在,Hadoop会自动重建,否则报路径已存在错误。
  • mapper 我们的Mapper脚本,即mapper.php, 但必须写绝对路径,这里指本地文件路径,如果写相对路径或者脚本执行错误,会报配置文件配置错误。
  • reducer 我们的Reducer脚本,即reducer.php, 同样需要绝对路径。

执行结束输出:

packageJobJar: [/tmp/hadoop-unjar2712470127417447142/] [] /tmp/streamjob561393005597605928.jar tmpDir=null
15/11/28 00:04:34 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032
15/11/28 00:04:34 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032
15/11/28 00:04:35 INFO mapred.FileInputFormat: Total input paths to process : 1
15/11/28 00:04:35 INFO mapreduce.JobSubmitter: number of splits:2
15/11/28 00:04:36 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1448639746810_0003
15/11/28 00:04:36 INFO impl.YarnClientImpl: Submitted application application_1448639746810_0003
15/11/28 00:04:36 INFO mapreduce.Job: The url to track the job: http://localhost:8088/proxy/application_1448639746810_0003/
15/11/28 00:04:36 INFO mapreduce.Job: Running job: job_1448639746810_0003
15/11/28 00:04:42 INFO mapreduce.Job: Job job_1448639746810_0003 running in uber mode : false
15/11/28 00:04:42 INFO mapreduce.Job:  map 0% reduce 0%
15/11/28 00:04:51 INFO mapreduce.Job:  map 100% reduce 0%
15/11/28 00:04:56 INFO mapreduce.Job:  map 100% reduce 100%
15/11/28 00:04:57 INFO mapreduce.Job: Job job_1448639746810_0003 completed successfully
15/11/28 00:04:57 INFO mapreduce.Job: Counters: 49
    File System Counters
        FILE: Number of bytes read=46
        FILE: Number of bytes written=324442
        FILE: Number of read operations=0
        FILE: Number of large read operations=0
        FILE: Number of write operations=0
        HDFS: Number of bytes read=338
        HDFS: Number of bytes written=23
        HDFS: Number of read operations=9
        HDFS: Number of large read operations=0
        HDFS: Number of write operations=2
    Job Counters 
        Launched map tasks=2
        Launched reduce tasks=1
        Data-local map tasks=2
        Total time spent by all maps in occupied slots (ms)=12457
        Total time spent by all reduces in occupied slots (ms)=3178
        Total time spent by all map tasks (ms)=12457
        Total time spent by all reduce tasks (ms)=3178
        Total vcore-seconds taken by all map tasks=12457
        Total vcore-seconds taken by all reduce tasks=3178
        Total megabyte-seconds taken by all map tasks=12755968
        Total megabyte-seconds taken by all reduce tasks=3254272
    Map-Reduce Framework
        Map input records=3
        Map output records=3
        Map output bytes=34
        Map output materialized bytes=52
        Input split bytes=200
        Combine input records=0
        Combine output records=0
        Reduce input groups=2
        Reduce shuffle bytes=52
        Reduce input records=3
        Reduce output records=2
        Spilled Records=6
        Shuffled Maps =2
        Failed Shuffles=0
        Merged Map outputs=2
        GC time elapsed (ms)=668
        CPU time spent (ms)=2320
        Physical memory (bytes) snapshot=674447360
        Virtual memory (bytes) snapshot=6298329088
        Total committed heap usage (bytes)=491257856
    Shuffle Errors
        BAD_ID=0
        CONNECTION=0
        IO_ERROR=0
        WRONG_LENGTH=0
        WRONG_MAP=0
        WRONG_REDUCE=0
    File Input Format Counters 
        Bytes Read=138
    File Output Format Counters 
        Bytes Written=23
15/11/28 00:04:57 INFO streaming.StreamJob: Output directory: output2
8.4 查看结果
$ $HADOOP_HOME/bin/hdfs dfs -cat output2/*
beijing 2    
shandong 1

由于运算结果是保存在HDFS中的,所以我们需要使用hdfs的cat命令去查看。

九、趟过的坑

如下遇到的问题,均为复现而得到。

9.1 文件夹已存在
packageJobJar: [/tmp/hadoop-unjar986578381948307717/] [] /tmp/streamjob9098881249462859146.jar tmpDir=null
15/11/28 23:46:00 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032
15/11/28 23:46:01 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032
15/11/28 23:46:01 ERROR streaming.StreamJob: Error Launching job : Output directory hdfs://localhost:9000/user/hadoop/output2 already exists
Streaming Command Failed!

从错误的内容可以看出是目录已经存在,解决办法就是删除它:

$HADOOP_HOME/bin/hdfs dfs -rm -r -f output2
9.2 等待输出错误
packageJobJar: [/tmp/hadoop-unjar7696480168906810677/] [] /tmp/streamjob226646231265106849.jar tmpDir=null
15/11/28 23:52:02 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032
15/11/28 23:52:02 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032
15/11/28 23:52:03 INFO mapred.FileInputFormat: Total input paths to process : 1
15/11/28 23:52:03 INFO mapreduce.JobSubmitter: number of splits:2
15/11/28 23:52:04 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1448639746810_0004
15/11/28 23:52:04 INFO impl.YarnClientImpl: Submitted application application_1448639746810_0004
15/11/28 23:52:04 INFO mapreduce.Job: The url to track the job: http://localhost:8088/proxy/application_1448639746810_0004/
15/11/28 23:52:04 INFO mapreduce.Job: Running job: job_1448639746810_0004
15/11/28 23:52:12 INFO mapreduce.Job: Job job_1448639746810_0004 running in uber mode : false
15/11/28 23:52:12 INFO mapreduce.Job:  map 0% reduce 0%
15/11/28 23:52:28 INFO mapreduce.Job:  map 100% reduce 0%
15/11/28 23:52:31 INFO mapreduce.Job: Task Id : attempt_1448639746810_0004_m_000001_0, Status : FAILED
Error: java.lang.RuntimeException: PipeMapRed.waitOutputThreads(): subprocess failed with code 2
    at org.apache.hadoop.streaming.PipeMapRed.waitOutputThreads(PipeMapRed.java:322)
    at org.apache.hadoop.streaming.PipeMapRed.mapRedFinished(PipeMapRed.java:535)
    at org.apache.hadoop.streaming.PipeMapper.close(PipeMapper.java:130)
    at org.apache.hadoop.mapred.MapRunner.run(MapRunner.java:61)
    at org.apache.hadoop.streaming.PipeMapRunner.run(PipeMapRunner.java:34)
    at org.apache.hadoop.mapred.MapTask.runOldMapper(MapTask.java:450)
    at org.apache.hadoop.mapred.MapTask.run(MapTask.java:343)
    at org.apache.hadoop.mapred.YarnChild$2.run(YarnChild.java:163)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.Subject.doAs(Subject.java:422)
    at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1656)
    at org.apache.hadoop.mapred.YarnChild.main(YarnChild.java:158)

15/11/28 23:52:31 INFO mapreduce.Job: Task Id : attempt_1448639746810_0004_m_000000_0, Status : FAILED
Error: java.lang.RuntimeException: PipeMapRed.waitOutputThreads(): subprocess failed with code 2
    at org.apache.hadoop.streaming.PipeMapRed.waitOutputThreads(PipeMapRed.java:322)
    at org.apache.hadoop.streaming.PipeMapRed.mapRedFinished(PipeMapRed.java:535)
    at org.apache.hadoop.streaming.PipeMapper.close(PipeMapper.java:130)
    at org.apache.hadoop.mapred.MapRunner.run(MapRunner.java:61)
    at org.apache.hadoop.streaming.PipeMapRunner.run(PipeMapRunner.java:34)
    at org.apache.hadoop.mapred.MapTask.runOldMapper(MapTask.java:450)
    at org.apache.hadoop.mapred.MapTask.run(MapTask.java:343)
    at org.apache.hadoop.mapred.YarnChild$2.run(YarnChild.java:163)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.Subject.doAs(Subject.java:422)
    at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1656)
    at org.apache.hadoop.mapred.YarnChild.main(YarnChild.java:158)

这个错误是因为Hadoop无法执行你的程序,原因是你的脚本中没有添加shabang。

9.3 配置错误
packageJobJar: [/tmp/hadoop-unjar5507801207525106156/] [] /tmp/streamjob5584049820660820845.jar tmpDir=null
15/11/28 23:55:27 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032
15/11/28 23:55:28 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032
15/11/28 23:55:28 INFO mapred.FileInputFormat: Total input paths to process : 1
15/11/28 23:55:28 INFO mapreduce.JobSubmitter: number of splits:2
15/11/28 23:55:29 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1448639746810_0005
15/11/28 23:55:29 INFO impl.YarnClientImpl: Submitted application application_1448639746810_0005
15/11/28 23:55:30 INFO mapreduce.Job: The url to track the job: http://localhost:8088/proxy/application_1448639746810_0005/
15/11/28 23:55:30 INFO mapreduce.Job: Running job: job_1448639746810_0005
15/11/28 23:55:37 INFO mapreduce.Job: Job job_1448639746810_0005 running in uber mode : false
15/11/28 23:55:37 INFO mapreduce.Job:  map 0% reduce 0%
15/11/28 23:55:45 INFO mapreduce.Job: Task Id : attempt_1448639746810_0005_m_000001_0, Status : FAILED
Error: java.lang.RuntimeException: Error in configuring object
    at org.apache.hadoop.util.ReflectionUtils.setJobConf(ReflectionUtils.java:109)
    at org.apache.hadoop.util.ReflectionUtils.setConf(ReflectionUtils.java:75)
    at org.apache.hadoop.util.ReflectionUtils.newInstance(ReflectionUtils.java:133)
    at org.apache.hadoop.mapred.MapTask.runOldMapper(MapTask.java:446)
    at org.apache.hadoop.mapred.MapTask.run(MapTask.java:343)
    at org.apache.hadoop.mapred.YarnChild$2.run(YarnChild.java:163)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.Subject.doAs(Subject.java:422)
    at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1656)
    at org.apache.hadoop.mapred.YarnChild.main(YarnChild.java:158)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.apache.hadoop.util.ReflectionUtils.setJobConf(ReflectionUtils.java:106)
    ... 9 more
Caused by: java.lang.RuntimeException: Error in configuring object
    at org.apache.hadoop.util.ReflectionUtils.setJobConf(ReflectionUtils.java:109)
    at org.apache.hadoop.util.ReflectionUtils.setConf(ReflectionUtils.java:75)
    at org.apache.hadoop.util.ReflectionUtils.newInstance(ReflectionUtils.java:133)
    at org.apache.hadoop.mapred.MapRunner.configure(MapRunner.java:38)
    ... 14 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.apache.hadoop.util.ReflectionUtils.setJobConf(ReflectionUtils.java:106)
    ... 17 more
Caused by: java.lang.RuntimeException: configuration exception
    at org.apache.hadoop.streaming.PipeMapRed.configure(PipeMapRed.java:222)
    at org.apache.hadoop.streaming.PipeMapper.configure(PipeMapper.java:66)
    ... 22 more
Caused by: java.io.IOException: Cannot run program "/home/hadoop/Documents/src/php/mapper.php": error=13, Permission denied
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
    at org.apache.hadoop.streaming.PipeMapRed.configure(PipeMapRed.java:209)
    ... 23 more
Caused by: java.io.IOException: error=13, Permission denied
    at java.lang.UNIXProcess.forkAndExec(Native Method)
    at java.lang.UNIXProcess.(UNIXProcess.java:248)
    at java.lang.ProcessImpl.start(ProcessImpl.java:134)
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
    ... 24 more

这个错误比较隐晦,只提醒配置错误,起初以为是配置文件找不到,经过“苦心钻研”才发现,这里的配置就是指我们的input/output/mapper/reducer这几个参数的值,触发这个错误原因有很多,包括但不限于如下几种情况:

  • mapper或者reducer程序有错误
  • mapper或者reducer程序路径找不到,所以要写绝对路径
  • mapper或者reducer程序没有赋予可执行权限,`chmod +x mapper.php`

十、总结

经过上述艰难的若干步,我们就完成了第一个Mapreduce程序的PHP版本,其实PHP在处理文本上具有先天性的优势,比如处理json或者XML等,感觉上PHP还是很适合开发Mapreduce程序的。

上述例子,我们完成了根据城市统计注册用户数,这个例子如果用SQL表示大约是这样:

SELCT city, count(*) _cnt
FROM user
GROUP BY city

本文仅用于展示使用PHP如何开发、测试和运行Mapreduce任务,关于上述程序仅用于参考。

 

 

使用cURL调试REST接口

最近在学习Restful API,今天看到一篇很好的文章,决定将其翻译出来,虽然是很久以前的文章,但感觉受益匪浅。文章来源

–译文开始–

在过去的几个月一直在做Restful网页应用,并使用cURL快速测试功能。

测试REST资源的基本命令
POST数据到REST资源

curl -i -H "Accept: application/json" -X POST -d "firstName=james" http://192.168.0.165/persons/person

参数如下:
i – 显示响应头
H – 向资源请求头部
X – 传递HTTP名称
d – 传入双引号中的参数,多个参数用’&’分割
上述命令posts名字(firstName)”james”到person资源。假设服务器使用名字James创建一个新的person,我们同时告诉服务器以json格式返回这个新创建的资源。
PUT资源

curl -i -H "Accept: application/json" -X PUT -d "phone=1-800-999-9999" http://192.168.0.165/persons/person/1curl -i -H "Accept: application/json" -X PUT -d "phone=1-800-999-9999" http://192.168.0.165/persons/person/1

这里puts一个电话号码到上面创建的person资源。
GET资源

curl -i -H "Accept: application/json" http://192.168.0.165/persons/person/1

对于GET请求,-X参数为可选参数。(译者注:curl默认为GET模式请求数据,译者就会废话。)

curl -i -H "Accept: application/json" http://192.168.0.165/persons?zipcode=93031

你可以在url后边添加请求参数。

curl -i -H "Accept: application/json" "http://192.168.0.165/persons?firstName=james&lastName=wallis"

传入多个用&链接的参数时,资源uri需要加引号。如果参数值中带空格,需要转义,使用+或者%20代替空格。
DELETE资源

curl -i -H "Accept: application/json" -X DELETE http://192.168.0.165/persons/person/1

删除一条资源, -X参数支持DELETE选项
使用POST来PUT资源

curl -i -H "Accept: application/json" -H "X-HTTP-Method-Override: PUT" -X POST -d "phone=1-800-999-9999" http://192.168.0.165/persons/person/1

有些客户端不支持PUT或者发送PUT请求比较困难。出于这些原因,你可以将POST请求头中X-HTTP-Method-Override设置为PUT,告诉服务器端想要的是PUT请求。大多数服务器端(你也可以自己编码)支持X-HTTP-Method-Override,转换请求为期望的请求方法(X-HTTP-Method-Override的值)。本例puts一个电话号码(通过POST)到标识为1的person资源。
使用POST来DELETE资源

curl -i -H "Accept: application/json" -H "X-HTTP-Method-Override: DELETE" -X POST http://192.168.0.3:8090/persons/person/1

和前一个命令非常相似,这个例子删除了标识为1的person资源,这里使用POST HTTP方法来告诉服务器重写为DELETE。

另一个不错的REST测试工具Poster Firefox add-on如果你不想鸟cURL或者你用Widows系统(当然你可以安装Cygwin然后安装使用cURL)测试,这是一个不错的GUI工具。不过与Poster相比,使用cURL生产效率更高。
使用cURL来PUT/GET文件,看这里

–翻译完毕–

Centos6 64位编译安装hadoop

预编译的hadoop基本能够满足需求了,之所以自己动手编译安装hadoop也是迫不得已。如果你的系统是Centos6,并且是64位,那么很不幸,运行start-dfs.sh就会有如下提醒,咱们必须自己编译一下hadoop。

一、问题描述

15/11/03 22:50:32 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

请参考官方文档:官方文档 编译README

根据官方文档,运行:

hadoop checknative -a

我们会得到一堆false

Native library checking:
hadoop:  false
zlib:    false
snappy:  false
lz4:     false
bzip2:   false
openssl: false

目前网上的解决方法基本不可用,下边我们通过编译hadoop,得到lib/native/*下文件,来解决这个问题。

二、编译安装

1、下载源代码包,结尾为src.tar.gz文件。

http://www.apache.org/dyn/closer.cgi/hadoop/common/

下载好源代码包后,只需要解压就好了,`tar xvf *.src.tar.gz`.

2、编译安装之前需要完成的工作。

2.1、升级软件包,升级java以及安装’Development Tools’(可选,强烈建议执行)

yum update
yum install java-1.8.0-openjdk java-1.8.0-openjdk-devel -y
yum groupinstall 'Development Tools'

2.2、安装maven(如果已安装可以忽略)
下载并解压缩maven:

wget http://www.us.apache.org/dist/maven/maven-3/3.3.3/binaries/apache-maven-3.3.3-bin.tar.gz
tar zxvf apache-maven-3.3.3-bin.tar.gz

添加到环境变量,将如下语句写入~/.bash_profile

export PATH=/(path)/apache-maven-3.3.3/bin:$PATH

使环境变量生效即可。

source ~/.bash_profile

测试一下效果,如果运行如下命令可以看到版本信息即可。

mvn --version

2.3、安装编译中使用到的其他程序包(至关重要)

yum install openssl openssl-devel zlib zlib-devel snappy snappy-devel lz4 lz4-devel bzip2 bzip2-devel cmake

2.4、安装protobuf2.5.0,这个是比较恶心的,必须是2.5.0否则maven一直报错,此地儿有坑儿。

git clone https://github.com/google/protobuf.git
cd protobuf
git checkout v2.5.0#切换到tag v2.5.0,非常关键

源代码也可以在这里下载:protobuf v2.5.0
编译安装

./configure --prefix=/usr/local
make
make install

添加环境变量,在/etc/profile最后,添加如下内容

#protobuf
export PATH=$PATH:/usr/local/protobuf/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib

使环境变量生效

source /etc/profile

运行如下命令如果,你看到2.5.0即成功,是其他数字全部不行。

protoc --version

如果上述全部准备妥当,那么下边我们就开始编译了。

3、编译hadoop
进入第1步中解压的hadoop目录,执行如下命令:

mvn package -Pdist,native -DskipTests -Dtar -Dmaven.javadoc.skip=true

请注意最后一个参数-Dmaven.javadoc.skip=true,忽略生成doc文件,主要原因是部分机器配置比较低,可能造成编译报错:无法分配内存。由于我们关注的是native目录下的可执行文件,所以忽略掉doc即可。
如果上述步骤顺利执行完毕,那么所有的都应该是SUCCESS状态,那么我们就可以在如下目录找到我们的native文件了:

hadoop-dist/target/hadoop-2.7.1/lib/native

到此,你有多个选择,可以使用我们自己编译的hadoop也可以使用预编译好的hadoop,如果使用预编译好的hadoop,我们需要将上述native文件夹中的所有文件拷贝到预编译的hadoop的native目录下。
最后验证一下我们的成功,执行命令:

hadoop checknative -a

我们将得到如下结果:

Native library checking:
hadoop:  true /data/Applicaion/hadoop-2.7.1/lib/native/libhadoop.so
zlib:    true /lib64/libz.so.1
snappy:  true /usr/lib64/libsnappy.so.1
lz4:     true revision:99
bzip2:   true /lib64/libbz2.so.1
openssl: true /usr/lib64/libcrypto.so

Centos搭建LAMPP环境(极速)

在Centos上安装Apache、PHP、MySQL是每个PHP程序员的基本技能,下边的方法可以快速便捷地帮你完成搭建:

sudo yum install httpd mysql mysql-devel mysql-server php php-devel php-mysql php-gd libjpeg* php-imap php-ldap php-odbc php-pear php-xml php-xmlrpc php-mbstring php-mcrypt php-bcmath php-mhash libmcrypt -y

执行完上述命令,我们的环境就搭建完成了。
执行启动命令即可让apache、MySQL、PHP跑起来:

sudo service mysqld start
sudo service httpd start

此时在浏览器访问:http://localhost,就可以看到It works!了。大功告成。

关于这个博客

开通这个博客,只为了记录自己在“搬砖”过程中的点滴收获。

作为一个码农,需要摸索很多东西,如果不记录下来,很快就会忘记,过后找起来也比较费劲,这个博客就是为了帮助我记录这些东西,然后我就能拿着这个博客吹牛逼了。

当然,如果自己的点滴经验能够分享或者帮助到别人,那就更好不过了。