HadoopのCDH4で完全分散環境構築した

基本的には、Clouderaの
http://www.cloudera.com/content/support/en/documentation/cdh4-documentation/cdh4-documentation-v4-latest.html
ここにある、ドキュメントにそってやっただけ。
意外とすんなりできました。

英語のドキュメントしかねぇから、英語の勉強もかねて
和訳したのをどっかで公開するのもありかと思ったけど
著作権的にアウトだった。。。

閑話休題

HDFSMapReduce、HBaseが動くまでの手順をまとめてみる。
この記事に全部詰め込む予定だから、だいぶ長くなるかも。

マシン構成は、マスタ1台、スレーブ2台。
NameNodeHA試すために、この後、マスタを1台追加する予定だけど
それはまた別のお話。

まずはネットワーク周りの設定。
ホスト名の設定

# hostname master10

/etc/hostsにクラスタを構成するマシンのホスト名とIPを記述

192.168.1.10 master10 master10
192.168.1.11 slave11 slave11
192.168.1.12 slave12 slave12

的な感じ。

# uname -a

コマンドで、設定したホスト名が含まれてたらOK?

ファイヤウォール停止
本当は、ポート番号指定して、許可する設定をするべきなんだろうけど
ひとまずは・・・。

# /etc/init.d/iptables stop

次に、NameNode、DataNode、JobTracker、TaskTrackerとかのインストール。
あぁ、いまさらだけど、OSはCentOS6.4です。

・各ノード共通

# cd /etc/yum.repos.d/
# wget http://archive.cloudera.com/cdh4/redhat/6/x86_64/cdh/cloudera-cdh4.repo

・NameNodeにて

# yum install hadoop-hdfs-namenode
# yum install hadoop-0.20-mapreduce-jobtracker
# yum install hbase-master
# yum install zookeeper-server

・DataNodeにて

# yum install hadoop-hdfs-datanode
# yum install hadoop-0.20-mapreduce-tasktracker
# yum install hbase-regionserver

次、設定ファイル周り。

# cd /etc/hadoop
# cp -r conf.empty conf.my_cluster
# alternatives --verbose --install /etc/hadoop/conf hadoop-conf /etc/hadoop/conf.my_cluster 50
# alternatives --set hadoo-conf /etc/hadoop/conf.my_cluster

# alternatives --display hadoop-conf

で、確認。

で、今コピーして、向き先を設定したmy_clusterの中の
core-site.xmlに、以下の記述を追加

    
        fs.defaultFS
        hdfs://master10:8020/
    
    
        hadoop.tmp.dir
        /tmmp/hadop-${user.name}
    

次、NameNodeのhdfs-site.xmlに以下の記述を追加。

    
        dfs.permissions.superusergroup
        hadoop
    
    
        dfs.name.dir
        /data/1/dfs/nn
    

DataNodeのhdfs-site.xmlには以下の記述を追加。

    
        dfs.permissions.superusergroup
        hadoop
    
    
        dfs.name.dir
        /data/1/dfs/dn,/data/2/dfs/dn,/data/3/dfs/dn
    

hdfs-site.xml中で指定したディレクトリを作成、所有者、パーミッションを修正。
NameNodeにて

# mkdir -p /data/1/dfs/nn
# chown -R hdfs:hdfs /data/1/dfs/nn
# chmod 700 /data/1/dfs/nn

DataNodeにて

# mkdir -p /data/1/dfs/dn /data/2/dfs/dn /data/3/dfs/dn
# chown -R hdfs:hdfs /data/1/dfs/dn /data/2/dfs/dn /data/3/dfs/dn
# chmod 700 /data/1/dfs/dn /data/2/dfs/dn /data/3/dfs/dn

HDFSは、とりあえずこれで、準備が整ったので
NameNodeとDataNodeを起動する。

まずは、NameNodeの初期化

# su - hdfs
$ hadoop namenode -format

NameNode起動

# /etc/init.d/hadoop-hdfs-namenode start

DataNode起動y

# /etc/init.d/hadoo-hdfs-datanode start

/var/log/hadoo-hdfs/配下のログにエラーとか出てなければ

# jps

で、NameNodeとDataNodeがいるはず。
jpsコマンドがない人は
# yum install openjdk-1.7.0-devel(だいたいこんな感じ・・・)
で、インストール。

動作確認は、まず

# su - hdfs
$ hadoop fs -mkdir /test

ディレクトリを作成できるか。
次に、適当なファイルを用意して

$ hadoop fs -put test.txt /test
$ hadoop fs -ls -R /

で、ファイルをHDFSにおけるか確認。おければOK。


次、MapReduce
conf配下にmapreduce-site.xmlを作成して、以下の記述をする。

<?xml version="1.0" ?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl" ?>

<configuration>
    <property>
        <name>mapred.job.tracker</name>
        <value>master10:8021</value>
    </property>
    <property>
        <name>mapred.local.dir</name>
        <value>/data/1/mapred/local,/data/2/mapred/local,/data/3/mapred/local</value>
    </property>
</configuration>

ただし、mapred.local.dirはDataNodeのmapred-site.xmlにのみ記述。
(NameNodeのmapred-site.xmlはmapred.job.trackerだけ)

DataNodeにて、mapred.local.dirで指定したディレクトリを作成、オーナーを変更。

# mkdir -p /data/1/mapred/local /data/2/mapred/local /data/3/mapred/local
# chown -R mapred:hadoop /data/1/mapred/local /data/2/mapred/local /data/3/mapred/local

HDFS上にtmpディレクトリを作成。

# su - hdfs
$ hadoop fs -mkdir /tmp
$ hadoop fs -chmod -R 1777 /tmp

/varディレクトリ作成

$ hadoop fs -mkdir /var/lib/hadoop-hdfs/cache/mapred/mapred/staging
$ hadoop fs -chmod -R 1777 /var/lib/hadoop-hdfs/cache/mapred/mapred/staging
$ hadoop fs -chown -R mapred /var/lib/hadoop-hdfs/cache/mapred

mapred.system.dirディレクトリ作成

$ hadoop fs -mkdir /tmp/mapred/system
$ hadoop fs -chown mapred:hadoop /tmp/mapred/system

これで、MapReduceの準備が整ったので、JobTrackerとTaskTrackerを起動
NameNodeにて

# /etc/init.d/hadoop-0.20-mapreduce-jobtracker start

DataNodeにて

# /etc/init.d/hadoop-0.20-mapreduce-tasktracker start

まずは、/var/log/hadoop-0.20-mapreduce配下のログファイルにエラーが
出てないことを確認。

その後、以下のコマンドで、エラーが出なければOK

# cd /etc/hadoop-0.20-mapreduce
# hadoop jar hadoop-example.jar pi 4 1000


次、zookeeper

設定ファイル、/etc/zookeeper/confo/zoo.cfg
に以下の記述を。

dataDir=/tmp/zookeeper
server.1=master10:2888:3888

dataDirは修正する必要ないかも。初期設定の/var/lib/zookeeperはもうあるよって
怒られたんだけど、後でする、初期化時に、--forceオプションをつければ問題ない?
正直、よくわからない。

以下のコマンドで、初期化と起動

# /etc/init.d/zookeeper-server init --myid=1
# /etc/init.d/zookeeper-server start

最後、HBase
各ノードのhbase-site.xmlに以下の記述を追加。

    
        hbase.cluster.distributed
        true
    
    
        hbase.rootdir
        hdfs://master10:8020/hbase
    
    
        hbase.zookeeper.quorum
        master10
    

hbase-site.xmlで指定したディレクトリをHDFS上に作成

# su - hdfs
$ hadoop fs -mkdir /hbase
$ hadoop fs -chown hbase /hbase

これでHBaseの準備が整ったので起動するだけ。
NameNodeにて

# /etc/init.d/hbase-master start

DataNodeにて

# /etc/init.d/hbase-regionserver start

# hbase shell

で、テーブル作成したりなんなりができればOK


ふぅ・・・長かった。

インタフェースって便利だね!

ついったーで軽くしたやり取りをメモ。

発端は、forの中にifを書くか、ifの中にforを書くか、どっちがいいの!?ってお話。
具体的には

boolean bool = true;

if (bool) {
    for (略) {
        〜〜
    }
} else {
    for (略) {
        〜〜
    }
}

こっちがいいのか

boolean bool = true;

for (略) {
    if (bool) {
        〜〜
    } else {
        〜〜
    }
}

こっちがいいのか。
同じ条件のforを何個も書くのも馬鹿らしいけど
ループのたびに変わらない条件判定も馬鹿らしい。

この疑問に対する一つの回答を教えてもらいました。
delegate使えばいいんじゃね。」

javaではdelegateは使えないので、
インタフェース使って、同じような仕組みを書いてみた。

まず、インタフェース。

public interface Delegate {
    public void invoke(int num);
}

で、メインクラス。

public class DelegateTestMain {

    public static void main(String[] args) {

        boolean branch = true;

        Delegate delegate = null;

        if (branch) {
            delegate = new Delegate() {
                int sum = 0;
                @Override
                public void invoke(int num) {
                    this.sum += num;
                    System.out.println(this.sum);
                }
            };
        } else {
            delegate = new Delegate() {
                int prod = 1;
                @Override
                public void invoke(int num) {
                    this.prod *= num;
                    System.out.println(this.prod);
                }
            };
        }

        for (int i = 1; i <= 10; i++) {
            delegate.invoke(i);
        }
    }
}

branchがtrueだったら、1〜10総和を一つずつ出力
falseだったら、1〜10総乗を一つずつ出力するよ。

例として適当かどうかは置いておいて
ifの条件判定も1回だし、forも1回で済んでるNE!

他にも、インタフェース使うと、メソッドを引数に渡すみたいなことができて便利ネ。

public interface Delegate<R> {
    public R invoke(String arg);
}

こんなインタフェースと

public class DelegateTestMain {

    public static void main(String[] args) {

        Delegate<List<String>> delegateList = null;
        Delegate<Map<String, String>> delegateMap = null;

        delegateList = new Delegate<List<String>>() {
            @Override
            public List<String> invoke(String arg) {
                List<String> retList = new ArrayList<String>();
                retList.add(arg);
                return retList;
            }
        };

        delegateMap = new Delegate<Map<String, String>>() {
            @Override
            public Map<String, String> invoke(String arg) {
                Map<String, String> retMap = new HashMap<String, String>();
                retMap.put(arg, arg);
                return retMap;
            }
        };

        System.out.println(getData(delegateList));
        System.out.println(getData(delegateMap));

    }

    private static <R> R getData(Delegate<R> delegate) {
        return delegate.invoke("test");
    }
}

こんなメインクラスとかで、やらせたい処理で、戻り値の型も変えられるし。

Solrクエリのエスケープ

いくつか、Solrクエリのエスケープについて
この文字をエスケープする必要があるよ!って情報が載ってるサイト、というかブログは
あったけれど、↓のメソッド呼べば一発で解決じゃないですかー!
org.apache.solr.client.solrj.util.ClientUtils#escapeQueryChars()

そう、クライアントのライブラリ提供してるんだから
エスケープするメソッドぐらい用意してて当たり前だよね・・・
どうしてそこに気付かなかったのだろうか・・・。

札幌Ruby会議2012に行ってみたYO

9月14〜16日に札幌で開催された、Ruby会議に行ってみたよ。
参加できたのは16日だけだったのだけども・・・

Rubyは触ったことがある程度で、書いたコードの行数を
数えることができるほどだと思うのだけど、
Rubyでバリバリコードを書いてないとわからない、なんてことも全然なくて
俺は普段仕事でJAVA(たまにJavascript)しか書いてないけど
興味深い話はたくさんあって、面白かった。
初日、は無理としても、せめて二日目と懇親会にはでたかった・・・。

一日会場にいて、一番感じたのは、自分から行動しないと、ってことかなー
こういうイベントに参加するのもそうだし
参加したうえでコミュニケーション取っていくこともそうだし
コードを書いていくこともそうだし
きれいなコードを他の人に伝えるのもそうだし
英語の勉強もそうだし

残念ながら特に2番目は全然できなくて、セッションを聞くだけになってしまったのだけど
だれかの感想にもあったけど、セッションを聞くだけならUstでもいいわけで
せっかく会場に行くからには、何かしらアクションを起こさないと
もったいなかったかなぁ・・・と思う。

あと、英語だよ、英語。せっかく、薦めてもらったセッションも
半分も理解できなくてすごくもったいなかった。
せっかく理解できたところも、本筋とは関係ない余談だったりとかしてw

ちょっと前にOSCに行ってみたときも思ったけど、
こういうイベントに参加している人って本当に生き生きしてて、楽しそうだから
次はその中に入れたらいいなぁ、と思うね!

一日だけだったけど、本当に有意義な時間だったと思う。
誘ってくれた@snoozer05さんには感謝です。

jettyでテスト用簡易サーバ 

微妙にはまったのでメモ
すげー頭の悪いはまり方だったから、自戒の意味もこめて・・・orz

まずは結論を簡単に
・jettyの8はservlet3、7は2.5だ。バージョンの違いに注意。
・7.3.1⇒7.6.1のどこかで、HttpConnectionが
 AsyncHttpConnectionとBlockingHttpConnectionに分かれた。
 そりゃ、HttpConnectionが解決できません、って怒られるわ・・・。
 
以下経緯。

JUnitでWebサービスのモックを作る
これを参考にして、簡易サーバを立てようとしたわけだ

なるほど、最新版は8.1.1か・・・とりあえずこれでいいか、とダウンロードして
ビルドパスに追加。

で、↑のソースをほぼコピペして、とりあえず動かしてみて
一週間くらいまえにHttpClient4でmixiにつないでみたで触ったHttpClientで
つないでみた・・・が、AsyncContextがみつからねぇぞ、と怒られてしまう。
ここみると、Servletのバージョンのせいらしい。なるほど確かに、jettyのDLページを見ると、8ってServlet3じゃないですか
今作ってるもので使ってるのって、2.5じゃないですか。8じゃだめじゃないですか。

ってことで、7の最新、7.6.1をDLして、ビルドパス修正、再実行。

おぉ、繋がった。が、Responseに設定した値が取れない。
↓のようにコメントアウトしてる部分か・・・。

    private static class RequestHandlerImpl extends AbstractHandler {

        @Override
        public void handle(String target, Request baseRequest,
                HttpServletRequest request, HttpServletResponse response)
                throws IOException, ServletException {
            
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json");
            response.setStatus(HttpServletResponse.SC_OK);
            
            Writer writer = response.getWriter();
            writer.write(TEST_RESULT);
            
            // HttpConnectionが解決できないのでとりあえずコメントアウト
            //HttpConnection connection =
            //    HttpConnection.getCurrentConnection();
            //Request req = connection.getRequest();
            //req.setHandled(true);
        }
    }

参考にしたブログは7.3.1、使おうとしてるのは7.6.1
いくらバージョンが違うとはいえ7.X.Xなのに・・・と思って、7.6.1のjavadoc見てたら
あったよ、↓の三つが・・・!
AbstructHttpConnection
AsyncHttpConnection
BlockingHttpConnection

なるほど、HttpConnectionが非同期通信のために二つに分かれたのか・・・。
というわけで、早速修正。

    private static class RequestHandlerImpl extends AbstractHandler {

        @Override
        public void handle(String target, Request baseRequest,
                HttpServletRequest request, HttpServletResponse response)
                throws IOException, ServletException {
            
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json");
            response.setStatus(HttpServletResponse.SC_OK);
            
            Writer writer = response.getWriter();
            writer.write(TEST_RESULT);
            
            AbstructHttpConnection connection =
                BlockingHttpConnection.getCurrentConnection();
            Request req = connection.getRequest();
            req.setHandled(true);
        }
    }

めでたく、レスポンスに設定した値も取れるようになりましたとさ。

参考にしたブログの記事は1年も立ってないのに、変わってしまうもんだねぇ・・・。

簡単なサーブレットのテスト

たとえばこんな、すごく単純なサーブレット

public void doGet(HttpServletRequest request, HttpServletResponse response) {
    response.setStatus(200);
}

200 OKを返すだけの、本当に単純なサーブレット
流石にここまで単純なのはないけど、あくまで例として。

こんな、単純なものでも試験はせにゃならないわけで
そのためだけに、EasyMockとかわざわざ入れるのもどうかと思った。
じゃあ、自前で実装すればいいじゃない
やりたいことは、setStatus(int)だけなのだから

public MockHttpServletResponse implements HttpServletResponse {

    private int status;

    @Override
    public void setStatus(int i) {
        this.status = i;
    }

    // 試験用(ステータスコードを取得)
    public int getStatus() {
        return status;
    }

    //以下盛大に省略
}

これだけの実装をしたモックさんを用意して

    MockHttpServletRequest mockResponse = new MockHttpServletRequest();
    testServlet.doGet(null, mockResponse);
    int status = mockResponse.getStatus();

こうやってやれば、statusに何セットしたかどうかわかるよねっ!

これだけなら、非常に簡単なお話だったのだけど、
テストしたかったサーブレットはこんなこともやってた。

public void doGet(HttpServletRequest request, HttpServletResponse response) {
    PrintWriter out = response.getWriter();
    out.print("<html></html>");
    // 中略
    response.setStatus(200);
}

PrintWriter#printとかちょっと詰んだ気しかしない。
でも、ちょっと頑張ってみたら、意外と簡単に何とかなった。
というわけで、実装追加したモックさん。

public MockHttpServletResponse implements HttpServletResponse {

    private int status;
    StringWriter stringWriter = new StringWriter();

    @Override
    public void setStatus(int i) {
        this.status = i;
    }

    // 試験用(ステータスコードを取得)
    public int getStatus() {
        return status;
    }

    @Override
    public void getPrintWriter() {
        return new PrintWriter(stringWriter);
    }

    // 試験用(PrintWriterで出力した文字列を取得)
    public String getPrintWritedString() {
        return stringWriter.toString();
    }

    //以下やっぱり盛大に省略
}

これくらいの実装なら、自分で書いちゃったほうが早いよね。多分。
Eclipseなら必要なメソッドはreturn nullとかで適当に実装してくれちゃうし。
今回は初回だったんで時間かかったけど。

HTMLのお話

多分知っている人には、知ってて当然だけど
知らない人にとっては、結構面白いと思う
そして多分、Web系の開発するなら知ってなきゃいけないお話。

面白かったのでメモ。

発端
俺「<div></div>って何なの?javascriptとかでよく使うけど。」
答「ブロック要素を作るタグだよ」

あまりにもぴんと来なかった俺に丁寧に回答してくれました。

聞いた話を、自分の中で消化し切れてはいないので
突っ込みどころ満載な気がする。指摘おねぎしまう。

HTMLってタグ使って書くよね
<p></p>で囲めば段落をあらわすし
<a></a>で囲めばリンクを貼れる。

で、そもそもの前提として
HTMLのタグって大きく分けて2種類ある。
ブロックを作るか、作らないか、だ。

<h1>とかのタイトルを表すタグや、前述