Ubuntu下编译和部署Hadoop 2.2

  在Ubuntu下编译各种项目总会遇到不少的问题,尤其是缺少各种依赖,好友都推荐用CentOS。不过个人对Ubuntu这个品牌以及背后的Canonical公司比较喜欢,加之用过其桌面版多年,算是熟悉了。这里记录下编译和部署Hadoop 2.2的一些过程,以供快速参考。

0、虚拟机环境准备
  宿主机器配置i7 8g内存,我这里用VMware虚拟了4台Ubuntu Server 64bit,每台机器分配1g内存。主机名分别为h0,h1,h2,h3,配置好每台机器的/etc/hosts,将各主机名映射到私有ip。

127.0.0.1       localhost
192.168.10.33   h0
192.168.10.176  h1
192.168.10.207  h2
192.168.10.90   h3

  配置免密码ssh登录,这里只将h0作为namenode和secondarynamenode,h1~3作为datanode,应此只需要配置h0到h1~3的免密码登录,以及各虚拟机自己的免密码登录。

ssh-keygen -t rsa
cat id_rsa.pub >> authorized_keys
scp authorized_keys ubuntu@h1:~/.ssh/authorized_keys_from_h0
scp authorized_keys ubuntu@h2:~/.ssh/authorized_keys_from_h0
scp authorized_keys ubuntu@h3:~/.ssh/authorized_keys_from_h0

1、安装编译环境
  由于Hadoop官方提供的只有32位环境,所以需要自己编译64位的,以下步骤仅在h0上操作。首先准备编译需要的组件,这里随系统决定所需。当然Java运行环境也是必须的,apt-get安装OpenJDK即可。

sudo apt-get install subversion maven cmake libssl-dev

2、签出Hadoop 2.2源代码

svn checkout http://svn.apache.org/repos/asf/hadoop/common/tags/release-2.2.0/

3、添加jetty-util依赖
  编辑vim hadoop-common-project/hadoop-auth/pom.xml,添加:

<dependency>
   <groupId>org.mortbay.jetty</groupId>
   <artifactId>jetty-util</artifactId>
   <scope>test</scope>
</dependency>

4、编译安装protobuf 2.5

wget https://protobuf.googlecode.com/files/protobuf-2.5.0.tar.bz2
tar jxvf protobuf-2.5.0.tar.bz2
cd protobuf-2.5.0/
./configure
make
sudo make install

添加环境变量,或者重启一次

export LD_LIBRARY_PATH=/usr/local/lib/

5、执行编译
首次执行:

mvn package -Pdist,native -DskipTests -Dtar

如果有错误,根据错误原因处理,一般都是缺少依赖。装好后重复执行即可,也可以添加-e -X参数,打印更多的日志来帮助查找错误。成功后可以看到以下的输出:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 46:40.307s
[INFO] Finished at: Fri Jan 17 17:32:23 CST 2014
[INFO] Final Memory: 66M/188M
[INFO] ------------------------------------------------------------------------

6、安装配置
  建立以下文件夹:

~/hadoop/dfs/data
~/hadoop/dfs/name
~/hadoop/temp

  编译好的Hadoop可以在release-2.2.0/hadoop-dist/target/hadoop-2.2.0/找到,我这里将其复制到~/hadoop下,切换到目录:

cd ~/hadoop/hadoop-2.2.0/etc/hadoop/

配置以下几个文件,如果没有的,可以在复制改名对应的template。

hadoop-env.sh
修改JAVA_HOME 为系统Java Home环境,apt-get安装的OpenJDK一般是这个路径。

export JAVA_HOME=/usr/lib/jvm/default-java

core-site.xml
ubuntu为运行Hadoop的用户。

<configuration>
     <property>
          <name>fs.defaultFS</name>
          <value>hdfs://h0:9000</value>
     </property>
     <property>
          <name>io.file.buffer.size</name>
          <value>131072</value>
     </property>
     <property>
          <name>hadoop.tmp.dir</name>
          <value>file:/home/ubuntu/hadoop/temp</value>
          <description>Abase for other temporary directories.</description>
     </property>
     <property>
          <name>hadoop.proxyuser.ubuntu.hosts</name>
          <value>*</value>
     </property>
     <property>
          <name>hadoop.proxyuser.ubuntu.groups</name>
          <value>*</value>
     </property>
</configuration>

hdfs-site.xml

<configuration>
     <property>
          <name>dfs.namenode.secondary.http-address</name>
          <value>h0:9001</value>
     </property>
     <property>
          <name>dfs.namenode.name.dir</name>
          <value>file:/home/ubuntu/hadoop/dfs/name</value>
     </property>
     <property>
          <name>dfs.datanode.data.dir</name>
          <value>file:/home/ubuntu/hadoop/dfs/data</value>
     </property>
     <property>
          <name>dfs.replication</name>
          <value>3</value>
     </property>
     <property>
          <name>dfs.webhdfs.enabled</name>
          <value>true</value>
     </property>
</configuration>

mapred-site.xml

<configuration>
     <property>
          <name>dfs.namenode.secondary.http-address</name>
          <value>h0:9001</value>
     </property>
     <property>
          <name>dfs.namenode.name.dir</name>
          <value>file:/home/ubuntu/hadoop/dfs/name</value>
     </property>
     <property>
          <name>dfs.datanode.data.dir</name>
          <value>file:/home/ubuntu/hadoop/dfs/data</value>
     </property>
     <property>
          <name>dfs.replication</name>
          <value>3</value>
     </property>
     <property>
          <name>dfs.webhdfs.enabled</name>
          <value>true</value>
     </property>
</configuration>

yarn-site.xml

<configuration>
     <property>
          <name>yarn.nodemanager.aux-services</name>
          <value>mapreduce_shuffle</value>
     </property>
     <property>
          <name>yarn.nodemanager.aux-services.mapreduce.shuffle.class</name>
          <value>org.apache.hadoop.mapred.ShuffleHandler</value>
     </property>
     <property>
          <name>yarn.resourcemanager.address</name>
          <value>h0:8032</value>
     </property>
     <property>
          <name>yarn.resourcemanager.scheduler.address</name>
          <value>h0:8030</value>
     </property>
     <property>
          <name>yarn.resourcemanager.resource-tracker.address</name>
          <value>h0:8031</value>
     </property>
     <property>
          <name>yarn.resourcemanager.admin.address</name>
          <value>h0:8033</value>
     </property>
     <property>
          <name>yarn.resourcemanager.webapp.address</name>
          <value>h0:8088</value>
     </property>
</configuration>

slaves
定义节点的主机名:

h1
h2
h3

  至此,配置就算完成了,将Hadoop目录复制到另外三台机器上,放相同的目录下。

scp -r hadoop-2.2.0/ h1:~/hadoop/
scp -r hadoop-2.2.0/ h2:~/hadoop/
scp -r hadoop-2.2.0/ h3:~/hadoop/

7、启动Hadoop

在h0上执行:

./sbin/start-dfs.sh
./sbin/start-yarn.sh

  用jps可以查看Java进程所对应的服务。

ubuntu@h0:~/hadoop/hadoop-2.2.0$ jps
1902 NameNode
1604 SecondaryNameNode
2076 ResourceManager

ubuntu@h1:~$ jps
1354 DataNode
1123 NodeManager

ubuntu@h2:~$ jps
1146 DataNode
1378 NodeManager

ubuntu@h3:~$ jps
1139 DataNode
1371 NodeManager

  到此,namenode和datanode都启动完毕。

Java 内存区域分析

  Java虚拟机在执行的过程中会将物理内存划分为几个区域来进行管理,这些区域都有特定用途和生命周期。Java虚拟机规范中提到,有以下6个部分组成:

1:寄存器(PC Register)

  Java虚拟机的多线程是通过轮流切换分配处理器执行时间来实现的,在任何一个时间点上,一个处理器只能执行一个线程中的指令,其余线程中断状态。因此为了记录每个线程当前所执行的指令,每个线程都会拥有一个独立的PC寄存器,用于保存线程执行状态。而且寄存器互不影响,类似这样的内存空间也叫做“线程私有”内存。

  特别的,如果执行的方法是native方法,寄存器是不确定的(undefined),如果是Java方法,寄存器保存的是当前字节码指令地址。PC寄存器的容量至少应当能保存一个 returnAddress 类型的数据或者一个与平台相关的本地指针的值。所以在计算内存使用的时候,这一部分可以忽略不计。

2:虚拟机栈(Java Virtual Machine Stacks)

  虚拟机栈也是线程私有的,与线程生命周期一致,创建的时候分配,终止的时候回收。在内存中以帧的形式存储,用于保存线程的局部变量表,局部计算结果,一个方法开始执行到返回结果,对于栈来说就是一个帧入栈和出栈的过程。其中局部变量表中包含了基本数据类型和引用对象(对象地址指针,字节码地址等等)

  在Java虚拟机规范第一版中,Java虚拟机栈也被直接称作Java栈,这个规范允许要么以固定尺寸来分配空间或者能动态扩展。如果栈大小是固定的,创建栈的时候,每一个Java虚拟机栈大小都可以独立选定。即通过参数决定栈大小或者最大最小值,内存可不要求连续。Java虚拟机的栈可以分配于更底层语言中的堆中。

  Java虚拟机栈上操作会有两个异常需要考虑,一是线程计算请求的栈深度(容量)超过虚拟机所允许的深度,将抛出StackOverflowError异常,如果虚拟机可以动态扩展,并试图扩展,但内存不足了,或者没有足够内存创建新线程的时候,将抛出OutOfMemoryError异常。

3:堆(Heap)

  堆是可供所有线程共享的区域,类实例和数组分配的区域。启动虚拟机的时候创建,堆中存储的对象受自动内存管理(GC),所以无需手动释放。空间分配也可以是固定大小或动态扩展,内存可不要求连续,这点和栈是一样的。如果需求的堆容量超过了自动内存管理能提供的最大容量,也会抛出OutOfMemoryError异常。

4:方法区(Method Area)

  方法区同样是线程共享的区域,有点类似于传统语言的“Text Segment”区。存储了类结构,例如:运行时常量池,字段和方法数据,以及构造函数和普通方法的字节码内容。还包括一些类,实例,接口初始化时用到的特殊方法。特殊方法例如init等等。

  方法区同样在虚拟机启动的时候创建,尽管方法区是堆的逻辑组成中的一部分,不过这个区可以实现垃圾回收,也可以不实现,规范不做强制要求,代码编译管理策略和物理内存分配都不做要求,具体得看虚拟机的实现了。

  当然,如果申请不到足够的内存,也会抛OutOfMemoryError。

5:运行时常量池(Run-Time Constant Pool)

  顾名思义,是每一个类或接口常量表现形式,包括若干种常量:从编译期可知的值到解析运行才知道的值。每一个常量池都保存在方法区中,在类被加载到虚拟机后创建出来。显然,构造常量池不能超过方法区大小,否则抛OutOfMemoryError。

6:本地方法栈(Native Method Stacks)

  这块区域是为Java调用非Java编写的代码而开辟的空间,实际上就是一块传统语言用到的栈。如果虚拟机不支持native方法,自身也不依赖传统栈,可以不分配本地方法栈空间,如果支持,一般都在线程创建的时候分配空间。本地方法栈的大小调整和前面几个一致。分配的栈容量不得超出本地方法栈最大容量,否则抛StackOverflowError异常,无法申请足够的内存扩展栈的话,将抛出OutOfMemoryError异常。

  官方参考:link

Java NIO 的一些细节

  最近项目中折腾到Java的新I/O接口,不得不说这个NIO的实现思路还是很不错的,相比传统的阻塞式I/O,NIO具有非阻塞,高效率。通过抽象出通道,缓冲器的概念来与数据打交道,减少了很多步骤,使用更为便捷。NIO支持文件I/O和网络I/O,高效性主要是设计结构更接近操作系统惯用模型。ByteBuffer作为最核心的东西,内容也十分的多,本文也主要问绕这个类来写。

  ByteBuffer继承自Buffer,Buffer中有四个比较关键的字段或者叫索引(mark, position, limit, capacity)。四个值有这样的关系:0 <= mark <= position <= limit <= capacity private int mark = -1;
  标记,设置一个标记位,调用mark()可以将mark的值设置为position。

private int position = 0;
  位置,当前数据起始位置,调用put()添加数据,position会自增。随时保持最新数据的最后一个字节位置。调用position()可以得到当前position值,position(int)可以设置position的值。

private int limit;
  界限,不能使用的数据位,即指向一段数据流末尾。调用get()方法返回的数据就是在position和limit之间的数据。调用limit()可以获取limit值,limit(int)设置limit值。

private int capacity;
  容量,即该Buffer的大小,分配空间时候决定的,一直指向最后一个数据地址。

字节缓冲区的几个重要方法:
allocate(int):新建一个Buffer,分配指定size的空间。此时position = 0,limit = capacity,mark = null,所有元素将初始化为0。

allocateDirect(int):这个功能如上,比较牛的是与系统耦合性较高,因此速度更快,但是分配开支也会增大,数据位于常规垃圾回收管理之外。

get()/get(int):get()获取当前position的值,并且对position做自增,表示移动到下个位置。get(int)只取出指定位置的数据,不移动指针。

put(byte)/put(int, byte):put(byte)在position位置存入byte,对position做自增,移动到下个位置。put(int, byte)替换int指定位置的值为byte,不移动指针。

flip():在准备取缓冲区内所有数据的时候必须调用一次,进行这些操作:limit = position,position = 0,mark = -1,意味着之前标记将丢失,从0到limit进行遍历即可得到所有数据,写入也时同样的道理。

mark():对缓冲区的位置做标记,进行这个操作:mark = position。一般会配合reset()来使用,前者将当前位置记住,后者将当前位置设置为记住的位置,这有点像录音机中的A-B复读的意思。对于需要取一段特殊数据是有用的。

reset():重置缓冲区的position为先前mark的位置,进行这个操作:position = mark。如果mark < 0会抛InvalidMarkException异常,也就是没有调用mark()之前,不可以reset。 clear():重置缓冲区指针,进行这些操作:position = 0,limit = capacity,mark = -1。这个操作并不会删除实际的数据,但是指针位置被重置了,和flip()接近。

limit()/limit(int):分别是获取limit和设置limit。需要注意的是设置的时候不能超过capacity,不能小于0,如果position > limit,会将position也设置为limit,相当于缩小了范围。如果mark > limit,则mark = -1,作废。

position()/position(int):分别是获取和设置position,设置的时候不得大于limit,不得小于0,如果mark > position,则mark = -1,作废。

rewind():重绕缓冲区,进行这些操作:position = 0,mark = -1。可见也是类似flip(),可以为存取数据做准备,并且使mark作废。

remaining()/hasRemaining():前者得到剩余数,即position – limit的值。后者是判断position是否小于limit,小于返回true,可以用在取数据时判断是否还有数据。

  等等,还有些就不一一介绍了。

屏蔽Android广告

  广告这东西可爱又可恨,尤其“积分墙”可恶。眼不见为净,屏蔽之。要么改hosts,要么加iptables规则,无论那种都需要一个屏蔽列表。看到rom toolbox中有这么一个屏蔽广告功能,试用了下,效果不错。它也是改了hosts实现的,于是取出来做备用,手机root后用re覆盖到/etc/hosts即可。
  一共23542条记录,国内外都有覆盖,绿色环保免安装,谁用谁知道。

  link:download

struts中使用FormFile文件上传

用贯了spring mvc的注入式文件上传,回到struts中都忘了怎么写,翻了翻老项目,记录下。
struts config中,定义formBean,action中用name指定formBean。

<struts-config>
    <form-beans>
        <form-bean name="fileManagerForm" type="com.dorole.FileManagerForm" />
    </form-beans>
    <action path="..." type="..." parameter="method" name="fileManagerForm">
        <forward name="..." path="..."></forward>
    </action>
</struts-config>

FileManagerForm如下

public class FileManagerForm extends ActionForm {
    private FormFile file;
    public void setFile(FormFile file) {
        this.file = file;
    }
    public FormFile getFile() {
        return file;
    }
}

FileManagerAction如下

public ActionForward upload(ActionMapping mapping, ActionForm form,
            HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        FileManagerForm fmf = (FileManagerForm) form;
        FormFile formFile = fmf.getFile();
        if (formFile.getFileData().length != 0) {
            ...
        }
        return null;
}

jsp如下

<form action="..." method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit" value="upload" />
</form>