2013年11月11日 星期一

ubuntu上安裝Hadoop

此時hadoop的最新版本有三個分支
  • 0.23.0
  • 1.2.1
  • 2.2.0
千萬記得這三種並非2.2.0是最新的,而是0, 1, 2這三個大版號的三種分支,所以三種最新的版本。
網路上能看到的教學文章大致上是針對1.x的,而這篇內容也是如此。只是修正一些做法。
我目前用的環境是:

  • Mac OS X 10.9
  • VirtualBox 4.3.0
  • Unbuntu 13.04 64bit
  • Hadoop 1.2.1
  • Oracle JDK 1.6.0_45(目前跑出來的結果,事實上不一定要用Sun JDK,Oracle JDK一樣跑得很順利)
整個過程的步驟大致如下:
  1. 具有Virtualbox的環境,包含已安裝ubuntu(這裡不談此安裝步驟,記得安裝順便選SSH deamon)
  2. 安裝JDK
  3. 建立Hadoop用的user account,並且設定不需輸入密碼的SSH連線
  4. 安裝Hadoop
  5. 設定Hadoop
  6. 啟動單一個node的cluster,並且run一個sample
以上是single-node,接著是建立multi-node:
  1. 複製現有的VM
  2. 修改部份的設定
  3. 啟動multi-node的cluster,並且run一個sample
========================================================================
  • 安裝JDK
    移除所有的open JDK
    $ sudo apt-get purge openjdk*
    

    裝上來源並安裝JDK 6
    $ sudo add-apt-repository pap:webupd8team/java
    $ sudo apt-get update
    $ sudo apt-get install oracle-java6-installer
    $ javac -version
    

    此時會顯示出"javac 1.6.XXXXX",就應該沒問題了。
  • 建立Hadoop用的user account,並且設定不需輸入密碼的SSH連線
    因為Hadoop會要透過user account來SSH連線至其它主機,目的是能啟動並管理其它的子節點。

    建立user account
    sudo addgroup hadoop
    sudo adduser --ingroup hadoop hduser

    新密碼填完後,其它的資訊可以按enter對它視而不見。
    設定SSH的觀念比較深,這裡僅直接顯示指令,帶過觀念部份。

    $ su - hduser
    $ ssh-keygen -t rsa -P ""
    

    切換為hduser後,我們要建立一個不需輸入密碼的RSA key。過程中會請你輸入儲存key的檔案,直接按enter表示使用預設值。接著把這新的key用來SSH access本機。

    $ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
    $ ssh localhost
    

    第一次透過SSH access本機,請先同意ssh的連線,然後會發現不再需要打密碼了。

    *額外補充,以下的文章中,若使用sudo指令時,表示身份為創建系統的帳號,並不是hduser,基於安全的理由,它並沒有sudo的使用權限。若為懶人或實驗者,請使用:
    $ sudo adduser hduser sudo
    
  • 安裝Hadoop
    請直接到官網下載最新版的hadoop。這裡我是選擇1.2.1後,選擇裡面是tar.gz的壓縮檔,檔名內有bin和沒bin的差別是,沒bin的檔案比較大,包含有source code。我是下載bin的版本(如果不覺得浪費空間,那把source code下載下來研究也是不錯啦~~~~)。以下wget後面的字串,請直接copy檔案的下載位置即可~~~

    $ cd /usr/local
    $ sudo wget xxxxxxxxxxxx
    $ sudo tar xzf hadoop-1.2.1-bin.tar.gz
    $ sudo mv hadoop-1.2.1 hadoop
    $ sudo chown -R hduser:hadoop hadoop
    


    到這裡我們已經把檔案準備好了,接下來就是開始一些簡單的設定檔,然後就大功告成了~~
    接著要設定bash shell的環境變數:

    $ export HADOOP_HOME=/usr/local/hadoop
    $ export JAVA_HOME=/usr/lib/jvm/java-6-oracle
    $ export PATH=$PATH:$HADOOP_HOME/bin
    

    以上是bash的shell專用,其它shell請自行轉換。

    上述完成之後,必須再做一件小事,這個步驟是reference裡的教學提到的,因為IPV6會產生某些問題,因此保險起見,我們還是將Hadoop設定為使用IPV4較好。因為某些因素的關係,請先關掉IPV6的設定,但這的影響只在於Hadoop上,所以不需要把整個server的IPV6都關閉。

    $ echo "export HADOOP_OPTS=-Djava.net.preferIPv4Stack=true" >> hadoop-env.sh
    


  • 設定Hadoop


    接著先為Hadoop的config檔設定JAVA的環境變數,我們先找到"$HADOOP_HOME/conf/hadoop-env.sh",接著找到裡面的這一行

    # The java implementation to use.  Required.
    # export JAVA_HOME=/usr/lib/j2sdk1.5-sun
    

    改為

    # The java implementation to use.  Required.
    export JAVA_HOME=/usr/lib/jvm/java-6-oracle
    

    接著就是設定三個site.xml檔,這三個檔案分別為


    • $HADOOP_HOME/conf/core-site.xml
    • $HADOOP_HOME/conf/mapred-site.xml
    • $HADOOP_HOME/conf/hdfs-site.xml


    簡單的解釋,mapred就是map-reduce的意思,所以一定是用來控制運算的單元;hdfs也就是Hadoop file system,也就是用來控制分散式檔案系統的單元。那麼core換言之一定是核心內容的設定囉。以下我們分別加一些設定值到這三個檔案中的<configuration></configuration>內:

    conf/core-site.xml

    <property>
      <name>hadoop.tmp.dir</name>
      <value>/app/hadoop/tmp</value>
      <description>A base for other temporary directories.</description>
    </property>
    
    <property>
      <name>fs.default.name</name>
      <value>hdfs://localhost:54310</value>
      <description>The name of the default file system.  A URI whose
      scheme and authority determine the FileSystem implementation.  The
      uri's scheme determines the config property (fs.SCHEME.impl) naming
      the FileSystem implementation class.  The uri's authority is used to
      determine the host, port, etc. for a filesystem.</description>
    </property>
    

    conf/mapred-site.xml

    <property>
      <name>mapred.job.tracker</name>
      <value>localhost:54311</value>
      <description>The host and port that the MapReduce job tracker runs
      at.  If "local", then jobs are run in-process as a single map
      and reduce task.
      </description>
    </property>
    

    conf/hdfs-site.xml

    <property>
      <name>dfs.replication</name>
      <value>1</value>
      <description>Default block replication.
      The actual number of replications can be specified when the file is created.
      The default is used if replication is not specified in create time.
      </description>
    </property>
    


    經過上述昏頭的設定後,我們再回來看一下,在core-site.xml裡面,有一串字是"/app/hadoop/tmp",這表示Hadoop的檔案系統路徑,其實觀念就是我們給它一個目錄,然後這個目錄會被視為Hadoop的檔案系統,所以相關的內容、暫存檔等等的,都在這裡面,所以它"超級"重要。在上述的操作過程中,我們還沒開出這個目錄,原因就是要說這非常重要!!!!!當我們做了設計的更動,有發現些怪問題,我都是把它刪了再重新格式化(並非真的把我們的硬碟格式化,而是像virtualbox操作虛擬目錄一般,對這目錄初始化一些內容;"當然格式化後,原有的資料都會不見,請小心使用"),就解決了,感覺它就像有暫存參數般@@總之筆者對Hadoop也不熟,所以也說不出個所以然,再請看到這裡的Hadoop高手幫忙補充囉~~~

    首先新增一個目錄,然後為它設定正確的權限(權限沒設定好,過程出錯時,新手根本不知道問題是卡在這)

    $ sudo mkdir -p /app/hadoop/tmp
    $ sudo chown hduser:hadoop /app/hadoop/tmp
    $ sudo chmod 750 /app/hadoop/tmp
    

    當一切完成後,就是將這個目錄格式化,讓它成為Hadoop認得的設置。

    $ $HADOOP_HOME/bin/hadoop namenode -format
    

    當指令下完後,會看到格式化的內容,一切完成後,就表示你的機器"真的"可以開始跑Hadoop了,後簡單吧@@

    *這裡額外一提,格式化指令hadoop是整個hadoop的核心指令,幾乎所有的功能都當它加上參數(至少我這新手大部份時間都是用它囉~~~)。而Hadoop它就是讀到core-site.xml裡的路徑,得知file system的目錄在哪,並且將它初始化的。一旦我們執行這個指令時,筆者的電腦上會出現"Warning: $HADOOP_HOME is deprecated."的提示,別怕,這只是一個警告,網路上google一下就知道改的方法了,因為它並不會影響操作,就不把fix的方法放進來讓內容更亂囉~~


  • 啟動單一個node的cluster,並且run一個sample
    啟動時,只要簡單的執行以下指令即可。

    $ $HADOOP_HOME/bin/start-all.sh
    

    我們也能用指令
    $ jps
    

    看一下是不是真的該有的process都run起來了。照理來說會出現以下這些(順序不重要):

    5694 Jps
    5573 TaskTracker
    5243 SecondaryNameNode
    5343 JobTracker
    4787 NameNode
    5010 DataNode
    

    至於測試方法,請參考原著
    Running Hadoop on Ubuntu Linux (Single-Node Cluster)
    這也是本文主要的參考對象,大部份的內容可以說是簡化加英翻中,因為有些小地方實在得花太多時間猜測了,故才有這篇文章的出現@@

    當然我也做過它的
    Running Hadoop on Ubuntu Linux (Multi-Node Cluster)
    也是眉角不少@@吃很多虧。


待續。
這幾天會再補上"設定第二節點的過程"以及"running mahout"。


2013年11月7日 星期四

Spring MVC Restful JSON and XML

從Spring MVC 3.1開始,設定純Json或XML的response變得很簡單了。
  1. 以下是Maven pom file該加入的dependency
    (這是指選完Spring MVC template後,該額外加入的library)。
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-oxm</artifactId>
        <version>${org.springframework-version}</version>
    </dependency>
    
    <!-- Jackson -->
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-core-asl</artifactId>
        <version>1.9.12</version>
    </dependency>
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-mapper-asl</artifactId>
        <version>1.9.12</version>
    </dependency>
    
    <!-- marshaling and unmarshaling of XML data -->
    <dependency>
        <groupId>org.codehaus.castor</groupId>
        <artifactId>castor-xml</artifactId>
        <version>1.3.2</version>
    </dependency>
    

  2. 在Controller的method上加入@responsebody,並且把return type改成自已要的。

        @RequestMapping(value="/", method = RequestMethod.GET)
    @ResponseBody
    public Posts getAll() {     
        
        List posts = new ArrayList();
        
        Post p1 = new Post();
        p1.setId(1);
        p1.setValue(11);
        
        Post p2 = new Post();
        p2.setId(2);
        p2.setValue(22);
        
        posts.add(p1);
        posts.add(p2);
        
        Posts ps = new Posts();
        ps.setPosts(posts);
    
        return ps;
    }
    

    以下是Post POJO的內容。
    加上@XmlRootElement(name = "post")的用意是讓Spring MVC也能回應XML,而JSON是什麼都不用加即可運作。
    Mark掉的@XmlElement是讓你選擇要的property,如果都不加,預設是全部使用;一加上去後,就只會認有mark這個annotation的property。
    @XmlRootElement(name = "post")
    public class Post {
    //  @XmlElement
        private int id;
    //  @XmlElement
        private int value;
        
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public int getValue() {
            return value;
        }
        public void setValue(int value) {
            this.value = value;
        }
        
        @Override
        public String toString() {
            return "Post [id=" + id + ", value=" + value + "]";
        }
    }
    
    

    當我們需要傳List的時候,也可以把它封裝在POJO裡,這樣遇見的問題會比較少。
    @XmlRootElement(name = "posts")
    public class Posts {
        private List posts;
        
        public List getPosts() {
            return posts;
        }
    //  @XmlElement
        public void setPosts(List posts) {
            this.posts = posts;
        }
        
        @Override
        public String toString() {
            return "Posts [posts=" + posts + "]";
        }
    }
    
    
  3. 因為我從頭到尾都是用annotation來設定,因此如果你是從XML based的環境上來的,記得在servlet-context.html上加入一些內容。
    
    <?xml version="1.0" encoding="UTF-8"?>
    <beans:beans xmlns="http://www.springframework.org/schema/mvc"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:beans="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
    
        <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
        
        <!-- Enables the Spring MVC @Controller programming model -->
        <annotation-driven/>
        
        <context:component-scan base-package="com.test.restful.web" />
    </beans:beans>
    
其實3.1版的環境就真的這麼簡單就好了.....

另外說明一下,上述的code已經足以啟動XML或JSON的回傳內容。
基本上它會回傳哪一種格式,完全看Request Header的屬性"Accept"決定。
如果是"application/json"則回傳JSON格式,反之"application/xml"則回傳XML格式。
若同時出現,則看哪一個比較前面,便會以那個為預設值。

Reference: http://www.mkyong.com/spring-mvc/spring-3-mvc-and-xml-example/