Hadoop 2.4をインストールしてみました

Hadoop 2.3より、大幅ブラッシュアップしてました。色々変わりましたので、最新のV2.4をインストールしてみました。

まずはdocs

http://hadoop.apache.org/docs/r2.4.0/hadoop-project-dist/hadoop-common/ClusterSetup.html

簡単な2台構成で行きましょう。

  1. hostname: hadoop01( master + slave )
  2. hostname: hadoop02( slave )

masterとslaveが同じ設定ファイルを使えますので、まずhadoop01でインストールし、その後scpすればよいはず。

事前準備

  1. 公式サイトからDLできるのは32bit版しかないので、32bitのjavaを使いましょう。
  2. Networkに関して、2台サーバーがお互いにssh接続できるようにする必要があります。

インストール

  1. DL & 解凍
    DLリンクはこちら:http://www.apache.org/dyn/closer.cgi/hadoop/common/

    tar -zxf hadoop-2.4.0.tar.gz
    cd hadoop-2.4.0
    
  2. 環境変数の設定。設定ファイル:etc/hadoop/hadoop-env.shとetc/hadoop/yarn-env.sh
    export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-i386/
    export HADOOP_HOME=/home/lulin/hadoop-2.4.0/
    export HADOOP_OPTS=-Djava.net.preferIPv4Stack=true
    
  3. etc/hadoop/core-site.xmlの設定まずはhadoop用tmpフォルダー作成
    mkdir /home/lulin/hadoop.tmp
    

    次はetc/hadoop/core-site.xmlの<configuration>と</configuration>の間下記の設定を追加する

    <property>
    	<name>fs.defaultFS</name>
    	<value>hdfs://hadoop01:9000</value>
    </property>
    <property>
    	<name>hadoop.tmp.dir</name>
    	<value>file:///home/lulin/hadoop.tmp</value>
    	<description>A base for other temporary directories.</description>
    </property>
    
  4. etc/hadoop/hdfs-site.xmlの設定まずはhafs用フォルダー作成
    mkdir -p /home/lulin/hadoop.dfs/name/
    mkdir -p /home/lulin/hadoop.dfs/data/
    

    etc/hadoop/hdfs-site.xmlに下記の設定を追加する。

     <property>
       <name>dfs.namenode.name.dir</name>
       <value>file:///home/lulin/hadoop.dfs/name/</value>
     </property>
     <property>
       <name>dfs.datanode.data.dir</name>
       <value>file:///home/lulin/hadoop.dfs/data/</value>
     </property>
     <property>
       <name>dfs.namenode.http-address</name>
       <value>hadoop01:10700</value>
     </property>
     <property>
       <name>dfs.datanode.address</name>
       <value>0.0.0.0:10010</value>
     </property>
    
     <property>
       <name>dfs.datanode.ipc.address</name>
       <value>0.0.0.0:10020</value>
     </property>
    
     <property>
       <name>dfs.datanode.ipc.address</name>
       <value>0.0.0.0:10030</value>
     </property>
    
     <property>
       <name>dfs.datanode.http.address</name>
       <value>0.0.0.0:10040</value>
     </property>
    
     <property>
       <name>dfs.namenode.secondary.http-address</name>
       <value>0.0.0.0:10050</value>
     </property>
    
  5. etc/hadoop/slavesの設定
    hadoop01
    hadoop02
    
  6. etc/hadoop/yarn-site.xmlの設定
     <property>
       <name>yarn.resourcemanager.address</name>
       <value>hadoop01:10110</value>
     </property>
     <property>
       <name>yarn.resourcemanager.scheduler.address</name>
       <value>hadoop01:10120</value>
     </property>
     <property>
       <name>yarn.resourcemanager.resource-tracker.address</name>
       <value>hadoop01:10130</value>
     </property>
     <property>
       <name>yarn.resourcemanager.admin.address</name>
       <value>hadoop01:10140</value>
     </property>
     <property>
       <name>yarn.resourcemanager.webapp.address</name>
       <value>hadoop01:10150</value>
     </property>
    
     <property>
       <name>yarn.nodemanager.localizer.address</name>
       <value>${yarn.nodemanager.hostname}:10160</value>
     </property>
    
     <property>
       <name>yarn.nodemanager.webapp.address</name>
       <value>${yarn.nodemanager.hostname}:10170</value>
     </property>
    
  7. etc/hadoop/mapred-site.xmlの設定
     <property>
       <name>mapreduce.jobhistory.address</name>
       <value>hadoop01:10210</value>
     </property>
    
     <property>
       <name>mapreduce.jobhistory.admin.address</name>
       <value>hadoop01:10220</value>
     </property>
    
     <property>
       <name>mapreduce.jobhistory.webapp.address</name>
       <value>hadoop01:10230</value>
     </property>
    
  8. hadoop、 tmpフォルダー、dfsフォルダーを全ての
    scp -r hadoop-2.4.0 hadoop.dfs hadoop.tmp hadoop02:~/
    
  9. hdfs作成
    bin/hdfs namenode -format
    
  10. ここまで準備完了です。hadoopスタート!masterであるhadoop01で、下記のコマンドを実行する。
    sbin/start-dfs.sh
    sbin/start-yarn.sh
    sbin/mr-jobhistory-daemon.sh start historyserver
    
  11. WEB UIでシステムステータスを確認できます。
    NameNode: http://hadoop01:10700/
    ResourceManager: http://hadoop01:10150/
    JobHistory: http://hadoop01:10230/
  12. 最後に、シャットダウンコマンドです。
    sbin/stop-dfs.sh
    sbin/stop-yarn.sh
    sbin/mr-jobhistory-daemon.sh stop historyserver
    

Read More

けんかをやめて

竹内まりや版でも河合奈保子版でもいいので、感情混めてまずは歌いましょう…

さあ浮かんできませんか? “ごめんなさいね 私のせいよ” そうです、SwiftさんとObjective-Cさんがけんかをしてしまうのは、あなたのせいです!

では仲直りさせましょう!!

SwiftからObjective-Cを利用する場合は、

1. SwiftのプロジェクトからObjective-Cのファイルを作成したとき
2. Objective-CプロジェクトからSwiftのファイルを作成したとき

その際に以下のようなダイアログが表示されますので’YES’を選択します。

bridgingheader_2x

するとプロジェクトネーム-Bridging-Header.hというファイルが作成されます。このファイル内で必要なObjective-Cのヘッダーファイルを管理します。

In CustomObject.h

#import <Foundation/Foundation.h>

@interface CustomObject : NSObject

@property (strong, nonatomic) id someProperty;

- (void) someMethod;

@end

In CustomObject.m

#import "CustomObject.h"

@implementation CustomObject 

- (void) someMethod {
    NSLog(@"SomeMethod Ran");
}

@end

とりあえず用意したObjective-Cファイルがこのようなものだった場合、Swift側では以下のようにします。

1. プロジェクトネーム-Bridging-Header.hに、#import “CustomObject.h”を追加
2. 例として、以下のように使う

var instanceOfCustomObject: CustomObject = CustomObject()
instanceOfCustomObject.someProperty = "Hello World"
println(instanceOfCustomObject.someProperty)
instanceOfCustomObject.someMethod()

なおGitHubなどの外部Frameworkを使う場合でも、ダミーでファイルを作成する必要があります。プロジェクトネーム-Bridging-Header.hが作成されたら削除して構いません。

Objective-CからSwiftを利用する場合は、

たとえば以下のような内容で、MySwiftObject.swiftを作成します。

import Foundation

class MySwiftObject : NSObject {

    var someProperty: AnyObject = "Some Initializer Val"

    init() {}

    func someFunction(someArg:AnyObject) -> String {
        var returnVal = "You sent me \(someArg)"
        return returnVal
    }

}

Objective-C側では、以下のようにします。

1. プロジェクト名が’Test’だった場合、利用したいObjective-Cファイル内に#import “Test-Swift.h”を追加
2. 例として、以下のように使う

#import "Test-Swift.h"

.....................

MySwiftObject * myOb = [MySwiftObject new];
NSLog(@"MyOb.someProperty: %@", myOb.someProperty);
myOb.someProperty = @"Hello World";
NSLog(@"MyOb.someProperty: %@", myOb.someProperty);
NSString * retString = [myOb someFunction:@"Arg"];
NSLog(@"RetString: %@", retString);

で、一件落着! 仲直りしましたょ!!

Read More

IE11のuserAgent対応javascriptによるIE判定式(全バージョン対応)

IE11は従来のIEと違い、userAgentの中にMSIEがありません。
よって良くある、userAgent内のMSIEindexOf等で探す方法ではIE11がうまく取れません。
jsでIE判定をかます際に色々調べたので情報共有。

javascriptによるIE判定式

var userAgent = window.navigator.userAgent.toLowerCase();
if( userAgent.match(/(msie|MSIE)/) || userAgent.match(/(T|t)rident/) ) {
    var isIE = true;
    var ieVersion = userAgent.match(/((msie|MSIE)\s|rv:)([\d\.]+)/)[3];
    ieVersion = parseInt(ieVersion);
} else {
    var isIE = false;
}

上記コードで
isIEにIEかどうかの判定(boolean型)
ieVersionにバージョン数(int型)
parseIntを挟むかどうかはケースバイケースで。

今後のIEはどうなるか分かりませんがとりあえず現在はこれでいけると思います。

Read More

Swiftさん、アレないっす

Swiftに興味津々のおヒトに言語仕様つらつら逝っちゃうのもいいかと思ったけどぉ…
本日は、入り口編

新規カラのProjectつくるとないんですょ、アレが… そお、main関数です !!

Objective-Cでいえば、こんなの

#import <UIKit/UIKit.h>

#import "AppDelegate.h"

int main(int argc, char * argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

Objective-CはCの仲間なんで、main関数があるんですね。UIApplicationMainの前で行いたい処理、あるでしょ?!
Swiftではファイルごとありません。えぇぇぇ、ダメダメじゃん!!
あわてちゃいけませんよ、お客さん!! Swiftは、ちゃあんと隠し持っています。
それが、コレ@UIApplicationMain AppDelegate.swiftにあります。

でもコレじゃぁ、UIApplicationMainの前に処理入れられないって!!
だからあわてないでって言ってるじゃないですか。

import UIKit

UIApplicationMain(C_ARGC, C_ARGV, NSStringFromClass(UIApplication), NSStringFromClass(AppDelegate))

こんなかんじのmain.swiftをつくって、@UIApplicationMain をコメントアウトすればいいんです!!

Read More

LWP::Simple::get() のハマりポイント

昔から動いているサイトがアクセスしている外部サイトのAPIが今夏に新バージョンになるということで、対応作業をしていました。私達のサーバサイド言語はPerlです。

新旧API、どちらもJSONを返すのですが、構造が若干異なっているという感じ。各アイテムのキー名にそれほど違いはなかったので、テンプレート側(デザイナー側)の負担を最小限にするために、新APIで叩いて返ってきたJSONの構造を少々変更して、テンプレート側には旧APIで叩いて今まで渡していたようなデータ構造をそのまま模してしまうことにしました。

この部分はJSON::XSでJSONテキストをハッシュリファレンスにしたものをひたすら組み換えるだけなので、APIのパターンが複数あるものの、それぞれ含めても一日二日あればできる範囲です。

コードを書いて手元の開発環境で動かしてみました。旧APIでは表示ができる。それを新APIにアクセスするようにして、データ組み換えコードを通るようにしてみたら文字化けが起こりました。組み換え部分では、枝葉のデータの文字コードは一切いじっていないのにです。

文字化けのパターンを見たら、UTF−8へのバイト列への変換を多重で行ってしまうパターンでした。ここまでわかると、根本的な原因は分からなくても対処法はわかります。

ざっくりと概要を抜き出すと、旧APIでは

JSON::XS->new->decode(...)

となっていた部分、新APIでは

 

JSON::XS->new->utf8->decode(...)

としたら文字化けしなくなりました。

対症療法ではこれでいいのですが、原因がわからないと気持ちが悪いです。調査してみました。

旧APIのコードは古かったので、リファクタリングをしたり新APIへのアクセスモジュールを書いて疎結合的に対応したかったのですが、納期が限られていたので新APIへの対応コードは旧APIへの対応コードをベースに書くことにしました。お仕事的な都合ですね。

外部サイトのAPIにアクセスしてJSONを取ってくる部分は、以下の様なコードとなっていました。

my $json = LWP::Simple::get($url);

その後のコードは文字コードにタッチしていないので、いろいろ調べた結果、名前の通りシンプルなことをしていないここが何かしているだろうという話になりました。

LWP::Simpleのgetのコードを見てみます。手元の最新版6.00でのお話です。

sub get ($)
{
 my $response = $ua->get(shift);
 return $response->decoded_content if $response->is_success;
 return undef;
}

$uaはLWP::UserAgentオブジェクトです。よって$responseはHTTP::Responseオブジェクト。

素直な感想としては $response->content じゃなくて $response->decoded_content なんだというところ。以前deflateされたコンテンツを扱う際に使ったことがある程度の知識でした。

HTTP::Responseのソースコードを読んでもdecoded_contentが無かったので、HTTP::ResponseのスーパークラスであるHTTP::Messageを読んでみたらdecoded_contentがありました。長いので抜粋しながら解説します。

sub decoded_content
{
 my($self, %opt) = @_;
 my $content_ref;
 my $content_ref_iscopy;

 eval {
 $content_ref = $self->content_ref;
 die "Can't decode ref content" if ref($content_ref) ne "SCALAR";

 if (my $h = $self->header("Content-Encoding")) {

想像通り、Content-Encodingのハンドリングを最初に始めています。

 

さらに読み進めていきます。

        if ($self->content_is_text || (my $is_xml = $self->content_is_xml)) {

Content-Encoding を見るところが終わったら、次はこんな条件節が出てきました。Content-Encoding以外のハンドリングをしているとは知らなかった。

 

HTTP::Messageは何も親クラスに持っていないし、content_is_textも見つからないなと思っていたのでこれって抽象クラスなのかなと思っていた(実際HTTP::Messageはベースクラスとして使われることを想定しています)のですが、AUTOLOADでハンドリングしていて、HTTP::Headersに処理を委譲しているようです。というわけでHTTP::Headersを見てみました。

sub content_is_text {
 my $self = shift;
 return $self->content_type =~ m,^text/,;
}

確かに明朗快活。

実際に新旧APIのヘッダどうなのと思ってwget -S で見てみたら、図星でした。

  • 旧API → Content-Type: text/html;charset=UTF-8
  • 新API → Content-Type: application/json;charset=UTF-8

というか旧APIはtext/htmlでJSONを返してきていたことが驚き。さすがAPI黎明期(?)に生まれたAPIだけある。

新APIになって、この $self->content_is_text にひっからなくなって挙動が変わったんだなーということが分かります。

HTTP::Message の decoded_content に戻ると、以下の様な条件節があります。

 if ($self->content_is_text || (my $is_xml = $self->content_is_xml)) {
 my $charset = lc(
 $opt{charset} ||
 $self->content_type_charset ||
 $opt{default_charset} ||
 $self->content_charset ||
 "ISO-8859-1"
 );

$self->content_type_charset は Content-Type ヘッダの中から charset= を探し出すもののようでした。これでtext/* の Content-Type であれば UTF-8 が拾われます。その後こうしています。

 else {
 require Encode;
 eval {
 $content_ref = \Encode::decode($charset, $$content_ref,
 ($opt{charset_strict} ? Encode::FB_CROAK() : 0) | Encod
e::LEAVE_SRC());
 };

Content-Type が text/*;charset=UTF-8 の場合は、ここでPerlの内部文字列にアップグレードされます。

旧APIのレスポンスヘッダはContent-Type: text/html;charset=UTF-8 だったのでここで知らずとアップグレードされていました。

新APIのレスポンスヘッダは、ここの条件節にはひっかからなかったので、ここでは処理されること無くバイト列で処理されることになります。

当初の部分に話を戻しますが、my $json = LWP::Simple::get($url) したものを JSON::XS->new->decode($json) していましたが、旧APIのほうはこれで内部文字列が渡るので内部文字列を持ったデータ構造となっており、テンプレートに渡す都合でこのJSONテキストからデコードしたハッシュリファレンスの構造をData::Recursive::EncodeでUTF-8のバイト列で構成されたものにしていました。

新APIだと、JSON::XS->new->decode($json) に渡される $json が前述の LWP::Simple の暗黙のお世話機能で内部文字列ではなくなってしまったので、Data::Recursive::Encode でUTF-8のバイト列を内部文字とみなしてさらにUTF-8のバイト列にしようとして文字化けを起こしていたのでした。この文字化けの起こり方は予想の範囲内でした。

解決策の一つとしては、新しいAPIでは JSON::XS->new->decode($json) ではなく JSON::XS->new->utf8->decode($json) とすることです。

そもそも、外部サイトの旧APIがJSONなのにContent-Typeとしてtext/htmlを返してくること、そしてLWP::Simple::get($url) が内部で HTTP::Response#content ではなく HTTP::Response#decoded_content を呼んでしまって余計な処理が入って、それを旧APIの処理プログラムを書いた人が受け取った文字列通りにData::Recursive::Encode->encode してしまったとか、色々なな部分に罠があったのでした。

JSONやHTTPに関わる部分は以下のようなことを守っていればハマりを回避できると思います。

  • LWP::Simple::get() は使わずにLWP::UserAgent#get を使ってHTTP::Response#contentを使ってレスポンスボディを取得する。HTTP::Response#is_success などでエラーハンドリングも書けるから。HTTP::Response#decoded_content を使いたい場合も、LWP::Simple::get() で暗黙で使うのではなく、明示的に使ったほうがよさそう
  • JSON::XS を使う際は、基本的にPerlの内部文字列としてdecodeすることを意識して JSON::XS->new->decode(…) ではなく JSON::XS->new->utf8->decode(…) と書く。このあたりはJSON関連モジュールでインターフェースは似たようなものの、挙動が微妙に違ったりするので、他のモジュールを使っている場合は注意する必要があるし、JSON::XSも意識して使う必要がありそう

まぁ、JSON APIの作成者が Content-Type: text/html でJSONを返すのはちょっと反則ですよね。Content-Type に text/json というものは無いっぽいので、application/json で返してきてくれると各種JSON処理モジュールや周辺関連でハマらず済むのかもしれません。逆にハマったら、JSON APIのContent-Typeに注目して、自分が使用しているHTTPクライアントモジュールを疑ってみるのもひとつの解決策だと思いました。

私は既存の旧APIへのアクセスモジュールを読みながら新APIへの対応を書いていてハマったという話で、新API自体が返す Content-Type は application/json となっていたので、API開発側のAPI設計も良い方向に向かっているのかもしれません。そんな新旧APIの差異でハマるとは思いもよりませんでしたけど、LWP::Simple::get() からの内部処理やハマリポイントなどが分かってちょっと勉強になりました。

Read More