ストリーム処理で文章内にある単語の出現頻度をカウントする(Apache Apex)

[ストリーム処理で文章内にある単語の出現頻度をカウントする\(Apache Apex\) \- 駄文型](http://koheikimura.hatenablog.com/entry/2017/06/07/190000)より

## 元ネタ

[Webinar: Building Your First Apache Apex Application - YouTube](https://www.youtube.com/watch?v=LwRWBudOjg4&feature=youtu.be)

<iframe width="560" height="315" src="https://www.youtube.com/embed/LwRWBudOjg4" frameborder="0" allowfullscreen></iframe>

## 必要なもの

- git
- Java SDK
- Apache Maven
- Hadoop

## 環境

### OS
```shell-session
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.1 LTS"
```

### Java
```shell-session
$ java -version
openjdk version "1.8.0_131"
OpenJDK Runtime Environment (build 1.8.0_131-8u131-b11-0ubuntu1.16.04.2-b11)
OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)
$ javac -version
javac 1.8.0_131
```

### git
```shell-session
$ git version
git version 2.7.4
```

### Maven
```shell-session
$ mvn --version
Apache Maven 3.3.9
Maven home: /usr/share/maven
Java version: 1.8.0_131, vendor: Oracle Corporation
Java home: /usr/lib/jvm/java-8-openjdk-amd64/jre
Default locale: en_US, platform encoding: ISO-8859-1
OS name: "linux", version: "4.4.0-57-generic", arch: "amd64", family: "unix"
```

## Hadoop

```shell-session
$ hadoop version
Hadoop 2.8.0
Subversion https://git-wip-us.apache.org/repos/asf/hadoop.git -r 91f2b7a13d1e97be65db92ddabc627cc29ac0009
Compiled by jdu on 2017-03-17T04:12Z
Compiled with protoc 2.5.0
From source with checksum 60125541c2b3e266cbf3becc5bda666
This command was run using /usr/local/hadoop/share/hadoop/common/hadoop-common-2.8.0.jar
```

## git clone

まずは必要なリポジトリをクローンする。使用するのは `apex-core` , `apex-malhar` , `DataTorrent/examples` の3つ。ホームに `apex` ディレクトリを作ってそこで作業する。

```shell-session:~
$ mkdir apex
$ cd apex
```

```shell-session:~/apex
$ git clone git@github.com:apache/apex-core.git
$ git clone git@github.com:apache/apex-malhar.git
$ git clone git@github.com:DataTorrent/examples.git
```

## Apex インストール

maven で `apex-core` と `apex-malhar` をビルド･インストールします。 `-DskipTests` でテストの実行をスキップすれば(少し)早く完了する。時間がかかるので、しばらく待つ :coffee: 

```shell-session:~/apex
$ ls              
apex-core   apex-malhar examples
$ cd apex-core
```

```shell-session:~/apex/apex-core
$ mvn clean install -DskipTests
```

```shell-session:~/apex
$ cd apex-malhar
```

```shell-session:~/apex/apex-malhar
$ mvn clean install -DskipTests
```

## 便利ツール

ビルドの完了を待っている間、ツールを準備しておく。 `DataTorrent/examples` の中に `aliases` というスクリプトがあるので、それを読み込んで使えるようにする。

```shell-session:~/apex
$ source examples/tutorials/topnwords/scripts/aliases
```

`aliases` の中身は以下の通り。

```bash:~/apex/examples/tutorials/topnwords/scripts/aliases
# bash aliases and functions useful for working on input and out directories
#

# input and output directories
in=/tmp/test/input-dir out=/tmp/test/output-dir

# list files in input directory
alias ls-input="hdfs dfs -ls $in"

# list files in output directory
alias ls-output="hdfs dfs -ls $out"

# clean input directory
alias clean-input="hdfs dfs -rm $in/*"

# clean output directory
alias clean-output="hdfs dfs -rm $out/*"

# convenient alias to run dtcli from code repository
alias dtcli3="$HOME/src/incubator-apex-core/engine/src/main/scripts/dtcli"

# copy local file (argument) to input directory
function put-file ( ) {
    hdfs dfs -put "$1" "$in"
}

# make local copy of output file (argument) from output directory
function get-file ( ) {
    hdfs dfs -get "$out/$1" "$1".out
}
```

`newapp` も `~/apex` にコピー呼び出しやすくしておく。

```shell-session:~/apex
$ cp examples/tutorials/topnwords/scripts/newapp .
```
中身はただの `mvn` コマンドで、必要に応じて `myapexapp` のところを書き換えて使うことができる。

```bash:~/apex/examples/tutorials/topnwords/scripts/newapp

#!/bin/bash
# script to create a new project
 
# change project name and archetype version as needed
name=myapexapp
version=3.3.0-incubating

mvn -B archetype:generate \
  -DarchetypeGroupId=org.apache.apex \
  -DarchetypeArtifactId=apex-app-archetype \
  -DarchetypeVersion=$version  \
  -DgroupId=com.example \
  -Dpackage=com.example.$name \
  -DartifactId=$name \
  -Dversion=1.0-SNAPSHOT
```

## プロジェクト作成

`newapp` を実行してプロジェクトを作成。 ` Y: : ` が出てきたら、そこでエンター(ッターン！)。

```shell-session:~/apex
$ bash newapp
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO] 
...
$ ls
apex-core   apex-malhar examples myapexapp newapp
$ cd myapexapp
```

プロジェクトの作成が完了したら、ソースコードを確認する。

```shell-session:~/apex/myapexapp
$ find src -name "*.java"
src/main/java/com/example/myapexapp/Application.java
src/main/java/com/example/myapexapp/RandomNumberGenerator.java
src/test/java/com/example/myapexapp/ApplicationTest.java
```

今回ユニットテストは不要なのでポイしておく。こうすることでビルド時に `-DskipTests` を忘れても、テストが実行されない。今回は `/tmp` に退避させたが、もちろん削除してもいい。

```shell-session:~/apex/myapexapp
$ mv src/test/java/com/example/myapexapp/ApplicationTest.java /tmp
```

## サンプルコードを導入

`DataTorrent/examples` のコードを導入する。まずはソースのディレクトリに移動する。

```shell-session:~/apex/myapexapp
$ cd src/main/java/com/example/myapexapp
```

`tutorial/topnwords` のソースコードを持ってくる。入力された文章内にある各単語の出現頻度を出力する。詳細は割愛。

```shell-session
$ ls   
Application.java           RandomNumberGenerator.java
$ ls ~/apex/examples/tutorials/topnwords/webinar/
ApplicationWordCount.java      WCPair.java                    WordReader.java
FileWordCount.java             WindowWordCount.java           properties-SortedWordCount.xml
LineReader.java                WordCountWriter.java
$ cp ~/apex/examples/tutorials/topnwords/webinar/*.java .
$ ls
Application.java           LineReader.java            WindowWordCount.java
ApplicationWordCount.java  RandomNumberGenerator.java WordCountWriter.java
FileWordCount.java         WCPair.java                WordReader.java
```

プロパティファイルも必要なので、それも `resources` にコピーする。

```shell-session
$ cd ../../../../resources/META-INF 
$ cp ~/apex/examples/tutorials/topnwords/webinar/properties-SortedWordCount.xml .
$ ls
properties-SortedWordCount.xml properties.xml
```



## アプリケーションのビルド

アプリケーションのトップディレクトリに移動してビルドする。ビルド後、 `target` ディレクトリに `myapexapp-1.0-SNAPSHOT.apa` が作成される。

```shell-session
$ cd ~/apex/myapexapp
```

```shell-session:~/apex/myapexapp
$ mvn clean package -DskipTests
$ ls target 
antrun                     generated-resources        maven-archiver             site
archive-tmp                generated-sources          maven-status               test-classes
classes                    generated-test-sources     myapexapp-1.0-SNAPSHOT.apa
deps                       javadoc-bundle-options     myapexapp-1.0-SNAPSHOT.jar
```


## アプリケーションの実行

ビルドしたアプリケーションの実行には [Apex の CLI ツール](https://apex.apache.org/docs/apex/apex_cli/#apex-cli-commands) を使う。 `apex-core` の下のあるので、 `myapexapp` ディレクトリ内で実行する。

```shell-session:~/apex/myapexapp
$ ../apex-core/engine/src/main/scripts/apex 
Apex CLI 3.7.0-SNAPSHOT 02.06.2017 @ 09:02:30 JST rev: 22feeed branch: master
apex> 
```

そこで `launch target/myapexapp-1.0-SNAPSHOT.apa` と打つと実行される。 `MyFirstApplication` と `SortedWordCount` のどちらを実行するか聞かれるので、2を選ぶとアプリケーションが実行され、 `appId` が発行される。

```bash
apex> launch target/myapexapp-1.0-SNAPSHOT.apa
  1. MyFirstApplication
  2. SortedWordCount
Choose application: 2
{"appId": "application_1496704660177_0001"}
apex (application_1496704660177_0001) > 
```

## 動作確認

まず `hdfs dfs` で入出力ディレクトリを作成する。

```shell-session
$ hdfs dfs -mkdir -p /tmp/test/input-dir
$ hdfs dfs -mkdir -p /tmp/test/output-dir
```

この時点ではまだなにもない。

```shell-session
$ ls-input
... 何も表示されない
$ ls-output
... 何も表示されない
```

`aliaces` で定義されている `put-file` を使って入力ディレクトリにテキストファイルを put する。今回は https://www.dummytextgenerator.com で英文を作成して `dummy.txt` として保存した。

```shell-session
$ ls -lh ~/*.txt
/home/user/dummy.txt
$ put-file ~/dummy.txt
```

`ls-input` すると、先ほどのテキストファイルと同じものがみつかる。

```shell-session
$ ls-input
Found 1 items
-rw-r--r--   1 vagrant supergroup      15167 2017-06-06 04:34 /tmp/test/input-dir/dummy.txt
```

`ls-output` すると、出力ファイルができているので、中身を確認する。

```shell-session
$ ls-output
Found 1 items
-rwxrwxrwx   1 vagrant supergroup       3028 2017-06-06 04:34 /tmp/test/output-dir/dummy.txt
$ hdfs dfs -cat /tmp/test/output-dir/dummy.txt | head
t : 75
you : 43
re : 29
sixth : 22
seasons : 22
creature : 22
every : 22
moved : 20
together : 20
subdue : 20
```

ちゃんと単語の出現頻度が出力されました！