Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

RxJavaを使ってCallback Hellから脱出する( Vert.x がいいね!第5回 )

id:KenichiroMurata(@ )です。

本記事はJava Advent Calendar 2013 - Adventarの6日目の記事です。

f:id:acro-engineer:20131206014754j:plain

皆さん、RxJava 使っていますか?

RxJava はNetflixが開発する Reactive ExtensionsJava版です。Reactive Extensions (Rx)はReactive Programmingを可能にするライブラリです。

私はReactive Programmingとはなんぞや?と語れるほどには詳しくないので、ここでは asynchronous で event-based なプログラムを書くのに便利なライブラリというレベルの紹介とさせて頂きます。:-)

さて、なぜ RxJava なのか?というと Vert.x を使って、asynchronous で eventbusベースのプログラムを書こうとすると、どうしてもコールバックを多段に書くことなり、ネストの深いコード(Callback Hell)になってしまうのを改善したいからです。

そこで本記事では、Vert.xとVert.xにてRxJavaを使えるようにするためのモジュールである mod-rxvertx を使って、Callback Hellからいかに脱出できるのか?を試したいと思います。

ちなみに、mod-rxvertxはVert.xのAPIをObservableを返すようにラップしてくれるライブラリで、現在では以下のAPIに対応しています。

  • EventBus
  • HttpServer/HttpClient
  • NetServer/NetClient
  • Timer

また、RxJavaそのものについては、Wikiページが充実していますので、ぜひご覧下さい。


目次は以下の通りです。

  1. 環境
  2. BusModとIntegration Testを"Rx-ify"してみる
  3. Callback Hellはいかに解決されたか
  4. RxJavaを使うとできること
  5. まとめ

1. 環境

本記事で試した環境について説明します。

  • Vert.x 2.1M1
  • mod-rxvertx 1.0.0-beta2
  • RxJava 0.15.1

また、今回の全ソースはこちらにありますのでcloneしてお試しください。git clone したら以下を実行することで、eclipseからもJUnitテストを動かして試すことができます。

ken@vertx-test $ ./gradlew test
ken@vertx-test $ ./gradlew copyModJson
ken@vertx-test $ ./gradlew eclipse

2. BusModとIntegration Testを"Rx-ify"してみる

それでは、さっそくコードを書いてみましょう。題材は前回のBusModとそのIntegration Testです。

まずはVert.xで通常に書いた場合のコードです。

BusModVerticle.java

public class BusModVerticle extends Verticle {

  public void start() {
    container.logger().info("BusModVerticle start.");

    vertx.eventBus().registerHandler("muraken720.vertx.mod.testexample",
        new Handler<Message<JsonObject>>() {
          @Override
          public void handle(Message<JsonObject> message) {
            ConcurrentMap<String, String> map = vertx.sharedData().getMap(
                "muraken720.testexample");

            JsonObject json = message.body();

            if ("add".equals(json.getString("action"))) {
              String key = json.getString("key");
              String value = json.getString("value");
              map.put(key, value);
            } else {
              message.reply(new JsonObject().putString("status", "error")
                  .putString("message", "unknown action."));
            }

            message.reply(new JsonObject().putString("status", "ok"));
          }
        });
  }
}

これをRxJava(mod-rxvertx)を使って書くと次のようになります。

RxBusModVerticle.java

public class RxBusModVerticle extends Verticle {

  public void start() {
    container.logger().info("RxBusModVerticle start.");

    RxEventBus rxEventBus = new RxEventBus(vertx.eventBus());

    Observable<RxMessage<JsonObject>> obs = rxEventBus
        .<JsonObject> registerHandler("muraken720.vertx.mod.testexample");

    obs.subscribe(new Action1<RxMessage<JsonObject>>() {
      @Override
      public void call(RxMessage<JsonObject> message) {
        ConcurrentMap<String, String> map = vertx.sharedData().getMap(
            "muraken720.testexample");

        JsonObject json = message.body();

        if ("add".equals(json.getString("action"))) {
          String key = json.getString("key");
          String value = json.getString("value");
          map.put(key, value);
        } else {
          message.reply(new JsonObject().putString("status", "error")
              .putString("message", "unknown action."));
        }

        message.reply(new JsonObject().putString("status", "ok"));
      }
    });
  }
}

EventBusをラップしたRxEventBusを使います。Integration Test側のコードも見てみましょう。

BusModVerticleTest.testAddAction

  @Test
  public void testAddAction() {
    container.logger().info("in testAddAction()");

    JsonObject request = new JsonObject().putString("action", "add")
        .putString("key", "name").putString("value", "@muraken720");

    container.logger().info("request message: " + request);

    vertx.eventBus().send("muraken720.vertx.mod.testexample", request,
        new Handler<Message<JsonObject>>() {
          @Override
          public void handle(Message<JsonObject> reply) {
            JsonObject json = reply.body();
            container.logger().info("response message: " + json);

            assertEquals("ok", json.getString("status"));

            ConcurrentMap<String, String> map = vertx.sharedData().getMap(
                "muraken720.testexample");

            assertEquals("@muraken720", map.get("name"));

            testComplete();
          }
        });
  }

これをRxJava(mod-rxvertx)を使って書くと次のようになります。

RxBusModVerticleTest.testAddAction

  @Test
  public void testAddAction() {
    container.logger().info("in testAddAction()");

    JsonObject request = new JsonObject().putString("action", "add")
        .putString("key", "name").putString("value", "@muraken720");

    container.logger().info("request message: " + request);

    RxEventBus rxEventBus = new RxEventBus(vertx.eventBus());

    Observable<RxMessage<JsonObject>> obs = rxEventBus.send(
        "muraken720.vertx.mod.testexample", request);

    obs.subscribe(new Action1<RxMessage<JsonObject>>() {
      @Override
      public void call(RxMessage<JsonObject> reply) {
        JsonObject json = reply.body();
        container.logger().info("response message: " + json);

        assertEquals("ok", json.getString("status"));

        ConcurrentMap<String, String> map = vertx.sharedData().getMap(
            "muraken720.testexample");

        assertEquals("@muraken720", map.get("name"));

        testComplete();
      }
    });
  }

いかがでしょうか?あれ、あまり変わらない?

そうなんですよね。最初にRxJavaを使った例を見たとき、言うほどCallback Hellは解消してなくない?と私は思ってしまいました。まぁ、ただこの例があまりにも単純な例なので、その効果もよく分からない、むしろ複雑になっているではないか?とすら思います。

ただ、実際にコードを書いてみると、ネストが1段減るだけでも、書きやすくなったと感じます。

この例ではあまりにも単純なので、もう少し複雑なコードを書いてみましょう。

3. Callback Hellはいかに解決されたか

それでは、今回のBusModに対してシーケンシャルにリクエストを投げるコードを書いてみます。つまり、リクエストを投げて、レスポンスを受けたら次のリクエストを投げる。さらにレスポンスを受けたら次のリクエストを投げるという例です。

まずは、通常のVert.xで書いた場合のコードは次のようになります。

BusModVerticleTest.testSerialAction

  @Test
  public void testSerialAction() {
    container.logger().info("in testSerialAction()");

    final EventBus eventBus = vertx.eventBus();
    final ConcurrentMap<String, String> map = vertx.sharedData().getMap(
        "muraken720.testexample");

    // リクエスト1
    eventBus.send("muraken720.vertx.mod.testexample",
        new JsonObject().putString("action", "add").putString("key", "name")
            .putString("value", "@muraken720"),
        new Handler<Message<JsonObject>>() {
          @Override
          public void handle(Message<JsonObject> reply) {
            assertEquals("ok", reply.body().getString("status"));
            assertEquals("@muraken720", map.get("name"));

            // リクエスト2
            eventBus.send(
                "muraken720.vertx.mod.testexample",
                new JsonObject().putString("action", "add")
                    .putString("key", "name").putString("value", "Kenichiro"),
                new Handler<Message<JsonObject>>() {
                  @Override
                  public void handle(Message<JsonObject> reply) {
                    assertEquals("ok", reply.body().getString("status"));
                    assertEquals("Kenichiro", map.get("name"));

                    // リクエスト3
                    eventBus.send(
                        "muraken720.vertx.mod.testexample",
                        new JsonObject().putString("action", "add")
                            .putString("key", "name")
                            .putString("value", "Murata"),
                        new Handler<Message<JsonObject>>() {
                          @Override
                          public void handle(Message<JsonObject> reply) {
                            assertEquals("ok", reply.body().getString("status"));
                            assertEquals("Murata", map.get("name"));

                            testComplete();
                          }
                        });
                  }
                });
          }
        });
  }

ご覧の通り、最初にリクエストを投げてレスポンスを受けるHandlerの中で、次のリクエスト投げる。さらにそのレスポンスを受けるHandlerの中で、次のリクエストを投げる・・・という具合で、どんどんネストが深くなっています。これぞ Callback Hell です。

それではこれをRxJavaを使ってコードを書くと次のようになります。

RxBusModVerticleTest.testSerialAction

  @Test
  public void testSerialAction() {
    container.logger().info("in testSerialAction()");

    final RxEventBus rxEventBus = new RxEventBus(vertx.eventBus());
    final ConcurrentMap<String, String> map = vertx.sharedData().getMap(
        "muraken720.testexample");

    // リクエスト1
    Observable<RxMessage<JsonObject>> obs1 = rxEventBus.send(
        "muraken720.vertx.mod.testexample",
        new JsonObject().putString("action", "add").putString("key", "name")
            .putString("value", "@muraken720"));

    Observable<RxMessage<JsonObject>> obs2 = obs1
        .flatMap(new Func1<RxMessage<JsonObject>, Observable<RxMessage<JsonObject>>>() {
          @Override
          public Observable<RxMessage<JsonObject>> call(
              RxMessage<JsonObject> reply) {
            assertEquals("ok", reply.body().getString("status"));
            assertEquals("@muraken720", map.get("name"));

            // リクエスト2
            return rxEventBus.send(
                "muraken720.vertx.mod.testexample",
                new JsonObject().putString("action", "add")
                    .putString("key", "name").putString("value", "Kenichiro"));
          }
        });

    Observable<RxMessage<JsonObject>> obs3 = obs2
        .flatMap(new Func1<RxMessage<JsonObject>, Observable<RxMessage<JsonObject>>>() {
          @Override
          public Observable<RxMessage<JsonObject>> call(
              RxMessage<JsonObject> reply) {
            assertEquals("ok", reply.body().getString("status"));
            assertEquals("Kenichiro", map.get("name"));

            // リクエスト3
            return rxEventBus.send(
                "muraken720.vertx.mod.testexample",
                new JsonObject().putString("action", "add")
                    .putString("key", "name").putString("value", "Murata"));
          }
        });

    obs3.subscribe(new Action1<RxMessage<JsonObject>>() {
      @Override
      public void call(RxMessage<JsonObject> reply) {
        assertEquals("ok", reply.body().getString("status"));
        assertEquals("Murata", map.get("name"));

        testComplete();
      }
    });
  }

いかがでしょうか?深くなっていたネストが縦方向に展開されて、ネストが1段で収まっています。少しコードがゴテゴテしてますが、Java8のラムダに対応すれば、もっとスッキリとして見通しがよいコードになりそうです。

4. RxJavaを使うとできること

RxJava にはもっと便利な機能がたくさんあります。ここでmod-rxvertxのテストコード(EventBusIntegrationTest)からいくつか例を紹介します。

Observable.concat

  @Test
  public void testSimpleSerial() {
    final RxEventBus rxEventBus = new RxEventBus(vertx.eventBus());
    final AtomicInteger totalReqs = new AtomicInteger(3);
    final AtomicInteger activeReqs = new AtomicInteger(0);
    
    rxEventBus.<String>registerHandler("foo").subscribe(new Action1<RxMessage<String>>() {
      @Override
      public void call(RxMessage<String> message) {
        System.out.println("serial-foo["+message.body()+"]");
        message.reply("pong!");
        activeReqs.incrementAndGet();
      }
    });

    Observable<RxMessage<String>> obs1 = rxEventBus.observeSend("foo", "ping!A");
    Observable<RxMessage<String>> obs2 = rxEventBus.observeSend("foo", "ping!B");
    Observable<RxMessage<String>> obs3 = rxEventBus.observeSend("foo", "ping!C");

    Observable<RxMessage<String>> concatenated = Observable.concat(obs1, obs2, obs3);

    concatenated.subscribe(new Action1<RxMessage<String>>() {
      @Override
      public void call(RxMessage<String> message) {
        System.out.println("serial-resp["+message.body()+"]");
        assertEquals("pong!", message.body());
        assertEquals(0,activeReqs.decrementAndGet());
        if (totalReqs.decrementAndGet()==0)
          testComplete();
      }
    });
  }

Observable.concatは2つ以上のObservableをシーケンシャルに実行するように連結してくれます。

ここまで見てくると気づくと思いますが、Observableで処理を書く場合、宣言した処理は直ぐには実行されず、subscribeを呼び出した段階になって実行(遅延実行)されます。コーディングスタイルとしては、Observableに対する処理を宣言的に記述して行き、最後にsubscribeを呼んで処理を実行、という流れになります。

Observable.merge

  @Test
  public void testGather() {

    final RxEventBus rxEventBus = new RxEventBus(vertx.eventBus());

    rxEventBus.<String>registerHandler("foo").subscribe(new Action1<RxMessage<String>>() {
      @Override
      public void call(RxMessage<String> msg) {
        System.out.println("receive");
        msg.reply("pong"+msg.body());
      }
    });

    Observable<RxMessage<String>> obs1 = rxEventBus.send("foo", "A");
    Observable<RxMessage<String>> obs2 = rxEventBus.send("foo", "B");
    Observable<RxMessage<String>> obs3 = rxEventBus.send("foo", "C");
    Observable<RxMessage<String>> merged = Observable.merge(obs1, obs2, obs3);
    
    merged.takeLast(1).subscribe(new Action1<RxMessage<String>>() {
      @Override
      public void call(RxMessage<String> message) {
        assertEquals("pongC", message.body());
        testComplete();
      }
    });
  }

Observable.mergeはパラレルに処理を実行し、全ての処理が完了するまで待ちます。

Observable.reduce

  @Test
  public void testConcatResults() {
    final RxEventBus rxEventBus = new RxEventBus(vertx.eventBus());

    rxEventBus.<String>registerHandler("foo").subscribe(new Action1<RxMessage<String>>() {
      @Override
      public void call(RxMessage<String> msg) {
        System.out.println("receive: " + msg.body());
        msg.reply("pong"+msg.body());
      }
    });

    Observable<RxMessage<String>> obs1 = rxEventBus.send("foo", "A");
    Observable<RxMessage<String>> obs2 = rxEventBus.send("foo", "B");
    Observable<RxMessage<String>> obs3 = rxEventBus.send("foo", "C");
    Observable<RxMessage<String>> merged = Observable.merge(obs1, obs2, obs3);
    Observable<String> result = merged.reduce("", new Func2<String, RxMessage<String>, String>() {
      @Override
      public String call(String accum, RxMessage<String> reply) {
        return accum + reply.body();
      }
    });

    result.takeFirst().subscribe(new Action1<String>() {
      @Override
      public void call(String value) {
        assertEquals("pongApongBpongC", value);
        testComplete();
      }
    });
  }

先のObservable.mergeで一括りにしたパラレル処理の処理結果をrudeceで処理してまとめる、なんてこともできます。

ObservableにはTransforming、Filtering、Combiningといった様々な機能がありますので、ぜひWikiページをご覧下さい。

5 まとめ

いかかでしょうか?Vert.xによるプログラミングは asynchronous で eventbusベースなためにネストが深くなり、Callback Hell に陥りがちです。この Callback Hell から脱出する方法の一つとして、ぜひRxJava( mod-rxvertx )を試してみてください。私もまださわり始めたばかりなのですが、いかんせん日本語の情報はまだ少ないので、こんなこともできる!という情報などある方は、このブログのコメントや、tweetで教え頂けると嬉しいです。

See also

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

Apache Camel入門(その1 OSSをつなぐOSS)

こんにちは、ツカノ(@)です。
twitterのタイムラインを見ていると、新しいOSSが登場した、という情報が頻繁に流れてきます。何年か前と比べると「仕事で有用なOSS」の増加速度が早まっているように思います。その分、話題だけれどあまり知らないOSSもたくさんあると感じています。今回は、OSS時代の今にフィットしたOSSの紹介です。

様々なOSSを組み合わせて開発する時代

ここ数年、システム開発をする際に複数のOSSを組合せて利用することが一般的になってきています。このブログの最近1年くらいの記事を振り返ってみても、次のようなOSSについて取り上げています。

  • アプリケーション基盤となるフレークワークではSpringやVert.x
  • ビッグデータ関連ではHadoopやStorm
  • 通信関連ではNettyやZeroMQ
  • NoSQLのミドルウェアではInfinispan

例えば、大量のデータをリアルタイムに処理し、さらにスケールさせる必要があるシステムを開発するケースを考えてみます。
何年も前なら、頑張って独自プロダクトで構築していたかもしれません。一例ですが、現在であればOSSを組み合わせて、

  • Nettyで受信する
  • NoSQLプロダクトを参照しつつ、独自ロジックでメッセージ変換
  • Stormに流し込む

という流れで処理することができます。自分でゼロからすべてを開発せずに実現できます。
重要な事なのでもう一回言いますが、複数のOSSを組合せて利用することが一般的になっています。

一方で、各OSS毎にAPIや利用している通信プロトコルが異なっているため、それなりに学習コストもかかります。また、開発期間も短いため、処理の流れを変えることになったり、特定箇所を分散させたりといったケースでも柔軟に対応できる必要があります。

こういった課題を解決してくれるのが、Apache発のOSSであるCamelです。
f:id:acro-engineer:20131125064348p:plain

という訳で、今回は、強力なパワーを持ったプロダクトであるにも関わらず、日本ではあまり利用されていないように見えるCamelを紹介します。

Camelとは?

システム統合のベストプラクティスをパターン化したものが、Enterprise Integration Patternsとして定義されており、書籍も出版されています。

Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions (Addison-Wesley Signature Series (Fowler))

Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions (Addison-Wesley Signature Series (Fowler))

Camelはここで定義された処理のパターンをシンプルに実現するために開発されました。

分厚くて、お固い雰囲気の本ですが、ご心配なく。Camelで実現できるパターンは、以下のページに図と共にまとまっています(^^)
Apache Camel: Enterprise Integration Patterns

Camelを使うと、「別システムからのリクエストを受け付け、一旦キューに入れて、取り出した所で並列実行する」といったことが簡単にできます。個々のパーツを組み合わせて処理の流れを作るため、リクエストを受け付ける部分を、定期的に情報を取得しに行く処理に変更することもできます。また、並列実行する部分をラウンドロビンで割り当てたり、キーのハッシュ値で振り分けたり、なんてこともできます。よく利用する処理の流れは大抵サポートしています。

先ほど「別システムからのリクエスト」と書いたような、OSSのプロダクトにアクセスしたり通信を行う処理を、Camelでは「コンポーネント」という概念で実現しています。
Camelの素晴らしいところは、豊富なコンポーネントが用意されており、現在も日々増加中であるところです。Camelのコンポーネント一覧を見ると、100以上のコンポーネントが用意されており、冒頭で紹介したOSSに関するコンポーネントも、ほとんどは既に存在するか開発予定になっています。
FTPSSH、HTTP(S)といった昔からある通信プロトコルや、AWSヘのアクセス、ビッグデータやNoSQL関連プロダクトとの通信、FacebookTwitterへのアクセス等、様々なコンポーネントが用意されています。

Camelでは、これら処理のパターンやコンポーネントを組み合わせることで、処理の流れを簡単に定義することができます。

Hello, World!

百聞は一見にしかず。さっそく実際にCamelを使ってみましょう。
mavenをインストールしてあれば、アプリケーション開発のひな型を生成できますので、これを利用します。以下のコマンドを実行してください(groupId, artifactId, versionは作成するアプリケーションに合わせて読み換えてください)。

mvn archetype:generate -DgroupId=snuffkingit \
    -DartifactId=camel-example \
    -Dversion=1.0.0-SNAPSHOT \
    -DarchetypeGroupId=org.apache.camel.archetypes \
    -DarchetypeArtifactId=camel-archetype-java \
    -DarchetypeVersion=2.12.1

これで、camel-exampleディレクトリ配下にCamelを利用するためのpom.xml等が作成されました。
ちなみに、Camel関連で用意されているarchetypeは以下のページに一覧があります。
Apache Camel: Camel Maven Archetypes

src/main/java配下に、ひな型のjavaのコードが作られますが、それは置いといて以下のコードを書いてみましょう。

package snuffkingit.camel.example;

import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;

public class HelloWorld {

    public static void main(String... args) throws Exception {
        CamelContext context = new DefaultCamelContext(); // (1)
        context.addRoutes(new RouteBuilder() { // (2)
            @Override
            public void configure() {
                from("direct:foo") // (3)
                .to("log:MyCategory"); // (4)
            }
        });
        context.start();
        context.createProducerTemplate().sendBody("direct:foo", "Hello, World!"); // (5)
    }
}

「Hello, Worldで、どうしてこんなに書かせるんだ!」と思われるかもしれませんが、怒らず最後まで聞いてください^^; もっと実際的な話は次回以降紹介しますが、まずは基本の理解です。
(1) まずは、Camelのコンテキストを初期化します。
(2) 処理の流れをCamelでは「ルート(route)」として定義し、その中を流れる「メッセージ」を処理します。この行では、コンテキストにルートを追加しています。
(3) fromとして、ルートの開始部分を指定します。ここでは開始部分に「direct:foo」という名前を付けています。(この意味の詳しい説明は次回以降行います)
(4) toとして、ルートの行き先を指定します。ここでは、メッセージをログ出力しています。
(5) ルートの開始部分「direct:foo」に"Hello, World!"というメッセージを送ります。

このクラスを実行すると、以下のログが出力されます(デフォルトのログフォーマットなので、ちょっと見づらいかもしれません)。

[                          main] DefaultCamelContext            INFO  Apache Camel 2.12.1 (CamelContext: camel-1) is starting
[                          main] ManagedManagementStrategy      INFO  JMX is enabled
[                          main] DefaultTypeConverter           INFO  Loaded 175 type converters
[                          main] DefaultCamelContext            INFO  StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
[                          main] DefaultCamelContext            INFO  Route: route1 started and consuming from: Endpoint[direct://foo]
[                          main] DefaultCamelContext            INFO  Total 1 routes, of which 1 is started.
[                          main] DefaultCamelContext            INFO  Apache Camel 2.12.1 (CamelContext: camel-1) started in 1.027 seconds
[                          main] MyCategory                     INFO  Exchange[ExchangePattern: InOnly, BodyType: String, Body: Hello, World!]
[                          main] DefaultCamelContext            INFO  Apache Camel 2.12.1 (CamelContext: camel-1) is shutting down
[                          main] DefaultShutdownStrategy        INFO  Starting to graceful shutdown 1 routes (timeout 300 seconds)
[el-1) thread #1 - ShutdownTask] DefaultShutdownStrategy        INFO  Route: route1 shutdown complete, was consuming from: Endpoint[direct://foo]
[                          main] DefaultShutdownStrategy        INFO  Graceful shutdown of 1 routes completed in 0 seconds
[                          main] DefaultCamelContext            INFO  Apache Camel 2.12.1 (CamelContext: camel-1) uptime 1.089 seconds
[                          main] DefaultCamelContext            INFO  Apache Camel 2.12.1 (CamelContext: camel-1) is shutdown in 0.017 seconds

横長になっていますので、スクロールしないと見えないかもしれませんが、下から7行目(MyCategoryのところ)の右の方に「Hello, World!」と出ていますね。
「direct:foo」からメッセージが流れ、そのメッセージがログ出力されました。

実は「direct」や「log」というのがCamelのコンポーネントです。「direct」コンポーネントの中をメッセージが流れて行き、「log」コンポーネントがログ出力していたのです。
fromやtoに指定した文字列を替えるだけで様々な処理を行えるのがCamelの大きな魅力です。このあたりは次回のお楽しみです(^^)

参考情報

日本語で読めるCamelの情報はあまり多くありませんが、以下のサイトが参考になると思います。

「Camel in Action」はManning Publicationsから発売されています。

Camel in Action

Camel in Action

  • 作者: Claus Ibsen,Jonathan Anstey,Gregor Hohpe,James Strachan
  • 出版社/メーカー: Manning Pubns Co
  • 発売日: 2010/11/28
  • メディア: ペーパーバック
  • この商品を含むブログを見る


さて、どうでしたか。次回はCamelのコンポーネント周りについて、紹介します。それではまた~
 

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

日経ソフトウエアでJava8の連載を始めました!

娘に「スーパーマリオ3Dワールド」をねだられ、断るに断れず買ってしまった @ です。
っていうかむしろ自分から積極的に買いました。


3Dマリオは初体験だったのですが、フィールドを縦横無尽に走り抜ける感覚は、これまでの2Dマリオにはない斬新さ。
しゃがもうとしてカーソルキーの下を押したら、マリオが手前に走ってくることに当惑しつつも、3Dという新しい要素を取り入れながら、世界観も、キャラクターも、そして動かしている感覚も全て「マリオ」であることを保っている任天堂には感服しますね。

しかも今回のマリオにはネコマr(以下、3000字略)

先取り! Java SE 8

さて、前置きが長くなりましたが「日経ソフトウエア2014年1月号」より「先取り! Java SE 8」という連載を開始しました。

http://itpro.nikkeibp.co.jp/NSW/
http://itpro.nikkeibp.co.jp/NSW/

Acroquestの社員が、Java8の新機能をこれから数ヶ月にわたって紹介する短期集中連載です。

まだ正式リリースされていないJava8を解説するという野心的な連載なわけですが、特に学生や初学者に伝えたいという想いで書きましたので、ぜひ手に取ってみてください。

なお、Acroquestのサイトから2ページ分のPDFを閲覧できますので、こちらも参考にしてください。
http://www.acroquest.co.jp/company/press/2013/1122p1.html

第一回目は「ラムダ式

第一回目の内容は、Java8のアップデート全般とラムダ式について、私が執筆しました。
既にWeb媒体や勉強会などでは紹介されているラムダ式ですが、これまできちんと学ぶ機会がなかったという方や、Webで学んではみたけど紙媒体でも読んでみたい方に、ぜひ読んでもらいたいです。


ソースコード中に出てくる -> という見慣れない表記に当惑しつつも、ラムダ式という新しい要素を取り入れながら、文法も、クラスライブラリも、そして書いている感覚も全て「Java」であることを保っているJavaのスペックリードには感服しますね。

しかもJava8のラムダには型推r(以下、3000字略)


新しいパラダイムを取り入れながらも、古い感覚を保って進化していくところは、マリオもJavaも変わらないということですね。

・・・おあとがよろしいようで m(_ _)m
 

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

Vert.x がいいね!(第4回:テストする)

id:KenichiroMurata(@ )です。

f:id:acro-engineer:20131221154226j:plain:w200

皆さん、Vert.x 使っていますか?

私は先日(11/9)開催された JJUG CCC 2013 Fallに参加し、「Over the Node.js. An Introduction to Vert.x」というタイトルで発表させて頂きました。

内容はこの「Vert.x がいいね!」の第1回〜第3回で紹介した内容をベースとして、現在の最新バージョンであるVert.x 2.1Mの新機能の紹介、node.jsで開発したfront-endとvert.xアプリケーションをeventbusでつなぐためのnpmモジュール、vertx-eventbus-clientなどを紹介しています。スライドは以下にありますので、興味のある方はぜひご覧下さい。



さて、少し間が空いてしましいましたが、第4回目となる今回は「テストする」と題して、Vert.xのテストのやり方について書きます。

目次は以下の通りです。

  1. Vert.x's Tests
  2. BusMod Common Practices
  3. Integration Tests
  4. Gradle Jacoco Plugin (Code coverage)
  5. まとめ

1. Vert.x's Tests

Vert.xのテストについては公式ドキュメントの開発ガイドGradle Project Templateを見ると情報があります。

Vert.xのテストにはunitテストとintegrationテストという2種類のテストがあります。

unitテスト

unitテストは特別なものではなく、JUnitを使った通常のunitテストです。Vert.xアプリケーションはVerticleを作成し、そのVerticleをVert.x Instance上で動作させるのですが、このunitテストは、ただ単純に対象のクラスをテストするだけです。

integrationテスト

では、Vert.x 上でテスト対象のVerticleを実際に動作させたテストをしたい場合はどのようにすれば良いでしょうか?例えばeventbusを使ったメッセージによってRequestを受け、なにか処理をしてからResponseを返すVerticleを作ってテストしたい場合です。

Vert.xではintegrationテストという位置づけのテストを定義しており、このintgrationテストがテスト対象のVerticleをVert.x上で動作させてテストを行うものです。Vert.xではその実現のために testtoolsを提供しており、このtesttoolsの中に、JUnitを拡張したTestVerticleというテストクラスを用意しています。

このTestVerticleを継承したテストクラスを作成することで、Vert.xのAPIが使えるようになるため、テスト対象のVerticleをVert.x上で動かしたテストを簡単に作成することができます。

2. BusMod Common Practices

それではここから、eventbusを使ったモジュールを作成し、そのintegrationテストを書いていきます。

Vert.xでモジュールを作成する場合、基本のインタフェースはeventbusによるReq/Res、またはPub/Subメッセージになります。eventbusをインタフェースとしたモジュールのことをVert.xでは慣例的にBusModと呼んでいるようです。

このBusModを作成するにあたり、公式モジュールやその他モジュールをいくつか調べると、メッセージインタフェースの共通プラクティスと言えるものがあるようですので、今回のモジュールもそれに従いたいと思います。

2.1. メッセージにはJSONメッセージを使う

Vert.xの特長としてPolyglotがあります。Java, Groovy, JavaScript, Ruby, Python, Scala, Clojureなど様々なJVM上で動作する言語を使うことができます。言語間の差分を意識せずにメッセージをやり取りするには、メッセージにJSONを使うことが推奨されいます。

2.2. Req/Resのメッセージにはstatusプロパティを使う

Req/Res型のメッセージを使う場合、Requestに対する結果が正常だったのか、異常だったのかを知る必要があります。この正常/異常の処理結果を知るためのフィールドとして、JSONメッセージにstatusプロパティを設けて、正常の場合は"ok"、異常だった場合は"error"を返すようにします。

  • Responseのstatusプロパティでok/errorを表現する
  {"status":"ok"}

2.3. statusがerrorの場合はmessageプロパティでエラーメッセージを返す

上記のstatusが"error"だった場合、何がエラーだったのか原因を知るためにエラーメッセージが必要です。statusが"error"であった場合は、messageプロパティを設けて、そのエラーメッセージを設定して返すようにします。

  • Responseのstatusがerror時にはmessageプロパティを使う
  {"status":"error","message":"unknown action."}

2.4. メッセージの種類を分けるにはactionプロパティを使う

eventbusによってメッセージを受けるためには、eventbusに対して受信するためにドメイン(イベントパス)を指定してHandlerを登録します。1つのドメインに対するメッセージの中で、メッセージの種別によって処理を切り替えるような実装をする際には、actionプロパティを設けて、その値によって、例えば"add"、"update"、"delete"、"search"などを使います。

  • Requestのactionプロパティでメッセージの種別を分ける
  {"action":"add","key":"name","value":"@muraken720"}

3. Integration Tests

それでは実際にBusModを作成し、そのintegrationテストを書いてみましょう。まずは、テスト対象となるBusModです。

BusModVerticle

public class BusModVerticle extends Verticle {

  public void start() {

    vertx.eventBus().registerHandler("muraken720.vertx.mod.testexample",
        new Handler<Message<JsonObject>>() {
          @Override
          public void handle(Message<JsonObject> message) {
            ConcurrentMap<String, String> map = vertx.sharedData().getMap(
                "muraken720.testexample");

            JsonObject json = message.body();

            if ("add".equals(json.getString("action"))) {
              String key = json.getString("key");
              String value = json.getString("value");
              map.put(key, value);
            } else {
              message.reply(new JsonObject().putString("status", "error")
                  .putString("message", "unknown action."));
            }

            message.reply(new JsonObject().putString("status", "ok"));
          }
        });
  }
}

このBusModの仕様を簡単に列挙します。

  • イベントの受信ドメインは「muraken720.vertx.mod.testexample」とする
  • JSONメッセージを使う
  • Requestメッセージにて、action="add" を指定した場合、指定されたkeyとvalueによってSharedDataのmapにデータを登録する
  • 正常に処理した場合は status="ok" のResponseメッセージを返す
  • Requestメッセージにて、action="add"以外の値が指定された場合、status="error" のResponseメッセージを返す

BusModVerticleTest

上記のBusModをテストするためのテストクラス、BusModVerticleTestを作成します。
モジュールのintegrationテストをするには、TestVerticleを継承します。また、startメソッドにて初期化が必要になります。

public class BusModVerticleTest extends TestVerticle {

  @Override
  public void start() {
    // Make sure we call initialize() - this sets up the assert stuff so assert
    // functionality works correctly
    initialize();
    // Deploy the module - the System property `vertx.modulename` will contain
    // the name of the module so you
    // don't have to hardecode it in your tests
    container.deployModule(System.getProperty("vertx.modulename"),
        new AsyncResultHandler<String>() {
          @Override
          public void handle(AsyncResult<String> asyncResult) {
            // Deployment is asynchronous and this this handler will be called
            // when it's complete (or failed)
            if (asyncResult.failed()) {
              container.logger().error(asyncResult.cause());
            }
            assertTrue(asyncResult.succeeded());
            assertNotNull("deploymentID should not be null",
                asyncResult.result());
            // If deployed correctly then start the tests!
            startTests();
          }
        });
  }
}

ここはGradle Project Templateの実装をそのまま利用します。やっていることは、モジュールをVert.xのcontainerにdeployし、deployが完了したらテストを開始するというものです。

BusModVerticleTest.testAddAction

それでは次に正常系となる1つ目のテストケース、「testAddAction」を作成します。

  @Test
  public void testAddAction() {
    container.logger().info("in testAddAction()");

    JsonObject request = new JsonObject().putString("action", "add")
        .putString("key", "name").putString("value", "@muraken720");

    container.logger().info("request message: " + request);

    vertx.eventBus().send("muraken720.vertx.mod.testexample", request,
        new Handler<Message<JsonObject>>() {
          @Override
          public void handle(Message<JsonObject> reply) {
            JsonObject json = reply.body();
            container.logger().info("response message: " + json);

            assertEquals("ok", json.getString("status"));

            ConcurrentMap<String, String> map = vertx.sharedData().getMap(
                "muraken720.testexample");

            assertEquals("@muraken720", map.get("name"));

            testComplete();
          }
        });
  }

Request用のメッセージを生成し、eventbusに対してメッセージを送信します。Responseメッセージを受信したら、statusが"ok"であること、さらに、SharedDataのmapを取得し、要求したkeyとvalueが保存されていることを確認します。

TestVerticleでテストケースを書く際には1つ決まりごとがあります。それはテストケースの最後に「testComplete()」を呼び出してテストケースの終了を通知するというものです。これは非同期APIによって処理が行われるため、テストを終わりを通知しなければ完了したことが分からないためです。

BusModVerticleTest.testUnknownAction

それでは異常系となる2つ目のテストケース、「testUnknownAction」を作成します。

  @Test
  public void testUnknownAction() {
    container.logger().info("in testUnknownAction()");

    JsonObject request = new JsonObject().putString("action", "unknown")
        .putString("key", "name").putString("value", "@muraken720");

    container.logger().info("request message: " + request);

    vertx.eventBus().send("muraken720.vertx.mod.testexample", request,
        new Handler<Message<JsonObject>>() {
          @Override
          public void handle(Message<JsonObject> reply) {
            JsonObject json = reply.body();
            container.logger().info("response message: " + json);

            assertEquals("error", json.getString("status"));
            assertEquals("unknown action.", json.getString("message"));

            testComplete();
          }
        });
  }

TestVerticleによるテストケースはVert.xのAPIを使う以外は通常のJUnitにと同じですから、特に問題なく書けると思います。

テストを実行する

繰り返しになりますが、integrationテストに利用するTestVerticleはJUnitを拡張したものですので、テストの実行はEclipseからテストクラスを選択し、右クリックから「Run As > JUnit Test」で実行することができます。テストはEclipseから実行しますので、デバッグも同様にできます。

また、Gradle Project Templateを使っているので、gradleを使ってのテストの実行も可能です。

ken@vertx-test $ ./gradlew test

上記で実行することができます。

4. Gradle Jacoco Plugin (Code coverage)

今回のテストプロジェクトはgithubのこちらにありますので、全体を見たい方は参照してください。
このプロジェクトには、gradleのjacoco pluginを設定し、カバレッジを計測するできるようにしていますので、テストのカバレッジレポートを見ることができます。

ken@vertx-test $ ./gradlew test

上記のテスト実行で、build/reports/tests/index.htmlにテストレポートが、

f:id:acro-engineer:20131119155504p:plain

ken@vertx-test $ ./gradlew jacocoTestReport

上記のタスク実行で、build/reports/jacoco/index.htmlにカバレッジレポートが生成されます。

f:id:acro-engineer:20131119155632p:plain

テストをする上で、カバレッジを知りたいことがありますが、gradleのjacoco pluginを使えば簡単に確認できます。

5 まとめ

いかかでしょうか?Vert.xでアプリケーションを開発する上で一番開発が多いのがこのBusModです。非常に簡単なテストの例ではありましたが、そのテストをintegrationテストを使うことで簡単に実施できることが分かったと思います。ノンブロッキングで非同期なアプリケーションを開発する場合でも、このようにテストが書ければ、開発が捗りますね。

次回は?

Vert.xのテストの続きかコールバックヘルにどう対応するか辺りを取り上げたいと思います。

See also

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ


11/24(日) DODA転職フェアに参加します!ぜひブースにお立ち寄りください。

出展企業を探す【総合エリア】 | 転職フェア・適職フェア(DODA)

Javaのマイクロベンチマークツール「JMH」

本ブログの読者の皆様方におかれましては、JavaArrayListとLinkedListの
実装の違いにより性能に差があることは、当然のように熟知のことと存じあげます。

しかし!

実際にいかほどの差があるのか、それを数値で説明できるという方はどれほどいらっしゃるでしょうか。
いきなり丁寧語の煽りでスタートしました @ です。

f:id:acro-engineer:20131107105733p:plain

そう、今日のテーマはマイクロベンチマークです。

たとえば、

  • 文字列を + で結合すると遅い。
  • ArrayListの初期化時にはサイズを指定したほうが良い。
  • ArrayListはループ処理が得意、LinkedListは途中の追加が得意。

など、よくあるパフォーマンスのプラクティスについては既に知っているという方も多いと思うのですが
実際に何倍ぐらいの差なのか(どれぐらいのオーダーの差なのか)を数値で話すことができるという方は、
あまり多くないように思います。というか私もできません。

そこで登場するのがベンチマークなわけです。
ただ、このような小さな処理のベンチマークというものは一般的に計測が難しく、
特にJavaの場合、JITコンパイルやGCの影響があるため、
安定したベンチマークを提示することが難しいと言われています。

そこで登場するのが、OpenJDKのサイトで公開されている「JMH」というツールです。

1. JMHとは

JMHは単位時間あたり(1秒、1ミリ秒、1マイクロ秒・・・)の処理回数を計測することに特化したツールです。
http://openjdk.java.net/projects/code-tools/jmh/

JMHが何の略かはサイトには掲載されていませんが
Java harnessという記載があるので、Java Micro-benchmarking Harnessか何かだと推測します。

JMHはマイクロベンチマークに必要なこと、たとえば・・・

  1. ベンチマーク前のウォームアップ
  2. 複数回の計測実施
  3. 平均や平均誤差などの統計分析

などを行なってくれるほか、
ベンチマークの実行回数や時間などを簡単なパラメータ指定で変更できます。

2. JMHのビルド

残念なことにJMHはダウンロード可能なJARファイルなどは提供されておらず、自分でビルドする必要があります。
ビルドにはMavenが必要なので、事前にMavenのセットアップを済ませておいてください。

2-1. ソースコードのダウンロード

まず、OpenJDKのサイトよりJMHのソースコードをダウンロードします。
Mercurial (HG) をセットアップ済みの方は、公式サイトにもあるようにhgコマンドでチェックアウトができます。

hg clone http://hg.openjdk.java.net/code-tools/jmh/ jmh


また、Mercurialをセットアップせず、ブラウザで直接ダウンロードすることも可能です。
以下のリポジトリにブラウザでアクセスして、上部にある「zip」というリンクからダウンロードできます。
http://hg.openjdk.java.net/code-tools/jmh/

念のため、直リンクのURLも掲載しておきましょう。
http://hg.openjdk.java.net/code-tools/jmh/archive/tip.zip

これでソースコードの入手は完了です。

2-2. ソースコードのビルド

続いて、入手したソースコードをビルドします。
繰り返しになりますが、ビルドにはMavenが必要なので事前にセットアップしておいてください。
mvnコマンドにパスが通っていればOKです。

チェックアウトか解凍したJMHのディレクトリ(pom.xmlのあるディレクトリ)にて、以下のコマンドを実行します。

mvn clean install -DskipTests=true

これでMavenのローカルリポジトリにJMHのjarがインストールされます。

3. ベンチマークの作成

ビルドが完了したら、いよいよベンチマークの作成に移ります。

3-1. ベンチマーク用プロジェクトの作成

まずはベンチマーク用のJavaプロジェクトを作成します。

公式サイトにはmvnコマンドを使う方法と、pom.xmlに直接dependencyを指定する方法の
両方が提示されていますが、もちろんどちらでも構いません。

私の場合はIntelliJMaven Projectを作成してからpom.xmlにdependencyを記述しました。

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
3-2. ベンチマークの作成

さて、ようやくベンチマーク本体の作成を始められるようになりました。
今回はArrayListの初期サイズを指定した場合と、指定しなかった場合でどれぐらいの差になるのかを計測してみましょう。

package jp.co.acroquest.benchmark;

import org.openjdk.jmh.Main;
import org.openjdk.jmh.annotations.GenerateMicroBenchmark;

import java.util.ArrayList;
import java.util.List;

public class ArrayListBenchmark {
    private static final int size = 100;

    // ★1 ベンチマークメソッドにアノテーションをつける
    @GenerateMicroBenchmark
    public void withInitialSize() {
        List<Integer> sizedList = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            sizedList.add(i);
        }
    }

    @GenerateMicroBenchmark
    public void withoutInitialSize() {
        List<Integer> defaultList = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            defaultList.add(i);
        }
    }

    // ★2 Mainメソッドを直接呼び出して、ベンチマークを実行する
    public static void main(String[] args) {
        Main.main("-i 3 -wi 3 -f 1".split(" "));
    }
}

簡単にソースコードのポイントを紹介します。

★1 ベンチマークメソッドにアノテーションをつける
ベンチマークを行なうメソッドには@GenerateMicroBenchmarkアノテーションをつけます。
ちょうどJUni4で言うところの@Testアノテーションのようなものです。

★2 Mainメソッドを直接呼び出して、ベンチマークを実行する
公式サイトにはビルドしてjarを生成してからベンチマークを行なう方法が記載されていますが
もちろんJMHのmainメソッドを直接呼び出して、ベンチマークを実行することもできます。

ちなみにこのソースコードの例では「ベンチマークのメソッド呼び出しを1秒間繰り返す」という処理を、
ウォームアップのために3回(-wi 3)、計測のために3回(-i 3)全体を1セット(-f 1)行なうような
オプションを指定して、mainメソッドを呼んでいます。

3-3. ベンチマークの実行(動作確認)

このソースコードをビルドして実行をすれば、ベンチマークを実施できます。
私の環境(MacBook Air Mid2011)では、このような結果が得られました。

Benchmark                                         Mode Thr    Cnt  Sec         Mean   Mean error    Units
j.c.a.b.ArrayListBenchmark.withInitialSize       thrpt   1      3    1     1895.013      220.400   ops/ms
j.c.a.b.ArrayListBenchmark.withoutInitialSize    thrpt   1      3    1     1220.345      110.302   ops/ms

初期サイズを指定した場合は1895回/ms、初期サイズを指定しない場合は1220回/msでした。

4. ベンチマークの実行

上で実行した方法は、あくまでもIDE上で実行する簡易的な動作確認のようなものでした。
より安定したベンチマークを行なうためには、IDE上で動作させるのではなく
ビルドしてJARファイルを生成し、java -jarコマンドで実行した方が良いでしょう。

4-1. ベンチマークのビルド&実行

ビルド時にJARファイルを作成するために、maven-shade-pluginの設定をpom.xmlに追加します。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <finalName>microbenchmarks</finalName>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>org.openjdk.jmh.Main</mainClass>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

これでビルド時にmicrobenchmarks.jarが生成されます。
実際にビルドを行なってみましょう。

mvn package

これでtargetディレクトリにmicrobenchmarks.jarが生成されるはずです。

4-2. ベンチマークの実行

ビルドしたJARファイルを用いて、ベンチマークを実行します。

java -jar target/microbenchmarks.jar -wi 5 -i 5 -f 2

オプションについては後で改めて紹介しますが、
この例ではウォームアップを5回、計測を5回、全体で2セットのベンチマークを実施しています。

実行した結果、このような統計が得られました。

Benchmark                                         Mode Thr    Cnt  Sec         Mean   Mean error    Units
j.c.a.b.ArrayListBenchmark.withInitialSize       thrpt   1     10    1     1781.790       34.361   ops/ms
j.c.a.b.ArrayListBenchmark.withoutInitialSize    thrpt   1     10    1     1129.782       14.142   ops/ms

なるほど、初期サイズを指定した方が1.5〜1.6倍ぐらいは高速なわけですね。
初期サイズを指定しなければ、配列のサイズを増やす際に
配列の作成とコピーが行なわれるわけですが、それがきちんと数字に表れました。

ちなみに要素数を100から100万に増やして同じ試験を実施してみたところ
スループットの差は1.3倍ぐらいまで縮まりました。

要素数が増えれば増えるほど、処理全体に占める配列の作成・コピーの割合が少なくなるため
スループットの差が小さくなるわけですね。そのようなことも簡単に計測ができます。

5. コマンド引数について

最後に、コマンド引数について紹介しましょう。
JMHでは、コマンドに引数を渡すことで、ウォームアップの回数や実行回数、実行時間などを変更することができます。
代表的なオプションについて、以下に表でまとめておきます。

オプション 概要 選択肢 初期値
-i 計測を繰り返す回数 (数値) 20
-wi ウォームアップ繰り返す回数 (数値) 20
-tu 結果を表記する際の単位 m, s, ms, us, ns ms
-f フォークの数。今回の例では計測全体の繰り返し数 (数値) 10
-r ベンチマークの実行時間 (時間: 10s, 200msなど) 1s

ひとまずはこの辺りを把握しておけば、問題なくベンチマークが行なえると思います。

6. まとめ&おまけ

「JMHはマイクロベンチマークを行なうための良いツールだ!」

簡単ですが、まとめは以上です。 #簡単すぎ


また、今回、私が作成したベンチマークやpom.xmlをgithubに置いておきました。
事前に「JMHのビルド」まで済ませたうえで、動かしてみてください。
https://github.com/cero-t/Benchmarks


今回は簡単な例のみ紹介しましたが、JMHは複数のJVMを利用したベンチマーク
マルチスレッド環境でのベンチマークなども行なうことができる、優秀なツールです。

もし今回のエントリーに反響があれば、より詳しい記事を書こうと思いますので
続きを読みたいという方は、ぜひツイートや、はてブなどでお知らせください。


それでは、レッツエンジョイ、マイクロベンチマーク


Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ


11/24(日) DODA転職フェアに参加します!ぜひブースにお立ち寄りください。

出展企業を探す【総合エリア】 | 転職フェア・適職フェア(DODA)

Storm0.9.0の新機能(Topology毎のログ出力)を試してみます

こんにちは。kimukimuです。

Storm0.9.0-rc系がリリースされてから正式版が出るのを待っている今日この頃ですが、
Storm0.9.0はApacheプロジェクトに入る前の最終リリースとなるということで、かなり大きなマイルストーンになりそうです。
そのため、検証のためまだ正式版が出るまでにはかかりそうです。

なので、正式版が出る前ですが、Storm0.9.0で追加/変更された機能を順に試してみることにします。
今回は、実際にStormを使っている方にとっては非常にありがたく感じる
「Topology毎のログ出力切り分け」について。

1.StormのTopologyログ出力はわかりにくい?

実際にStormを使ったことがある方だとわかるかと思いますが、StormTopologyのログ出力先はわかりにくいです。
理由は、以下の2点。

  1. どのTopologyがどのログに出力されているか」がファイル名からではわからない
  2. 同じファイルに複数のTopologyのログが混在して出力される

1点目の理由ですが、StormTopologyのログはログ出力先ディレクトリ配下に以下のようにworker-XXXX.logとして出力されます。
名前からではどのTopologyのログが出力されているかはわかりません。

> ls -l
-rw-r--r-- 1 root root     6966 10月 30 07:03 2013 drpc.log
-rw-r--r-- 1 root root    27424 10月 31 08:27 2013 nimbus.log
-rw-r--r-- 1 root root    36220 10月 31 07:43 2013 supervisor.log
-rw-r--r-- 1 root root     3348 10月 31 07:43 2013 ui.log
-rw-r--r-- 1 root root 42834753 10月 31 07:43 2013 worker-6701.log
-rw-r--r-- 1 root root 86293078 10月 31 07:43 2013 worker-6702.log
-rw-r--r-- 1 root root 86295346 10月 31 07:43 2013 worker-6703.log

Storm-UIのコンポーネント詳細画面の以下の部分を見て、どのログがどのTopologyのものかわかる・・・
という形になっています。
f:id:acro-engineer:20131101071248j:plain

2点目の理由ですが、StormではWorkerプロセスのIDは使いまわされます。
そのため、後で起動したTopologyのログが過去に起動したTopologyのログに追記されてしまい、
複数Topologyのログが1ファイル内に存在してしまう・・・という状態になります。

結果、Stormのログは見にくく、問題が発生した際に追うのが困難となってきます。

2.Storm0.9.0でのTopologyログ出力

同じようなことを考えていた方が他にもいたようで、Storm0.9.0からはログ出力先がTopology毎にログの出力先が切り分けられるようになっています。
但し、デフォルトでは有効になっていません。

そのため、実際に設定をして試してみますね。

ログ出力先の切り分け用設定

設定は簡単で、【Stormのインストール先】/logback/cluster.xmlの設定を以下のように修正するだけです。
■cluster.xml(修正前)

<!-- 省略 -->
<file>${storm.home}/logs/${logfile.name}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
  <fileNamePattern>${storm.home}/logs/${logfile.name}.%i</fileNamePattern>
  <minIndex>1</minIndex>
  <maxIndex>9</maxIndex>
</rollingPolicy>
<!-- 省略 -->


■cluster.xml(修正後)

<!-- 省略 -->
<file>${storm.home}/logs/${storm.id:-}${logfile.name}</file> <!-- storm.id追記 -->
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
  <fileNamePattern>${storm.home}/logs/${storm.id:-}${logfile.name}.%i</fileNamePattern> <!-- storm.id追記 -->
  <minIndex>1</minIndex>
  <maxIndex>9</maxIndex>
</rollingPolicy>
<!-- 省略 -->

Storm0.9.0からWorkerのJVMオプションにはTopologyのIDが"storm.id"として含まれるようになったため、それを利用しています。
Worker以外のプロセスで名称がおかしくならないよう、「デフォルト値は空」として設定しています。

動作確認

では、この状態でTopologyを実際に起動してみます。
Storm-UIで確認してみると、以下の画面の通り、「ExclamationTopology-2-1383257328」というTopologyが起動しているのがわかります。
f:id:acro-engineer:20131101072803j:plain
では、ログ出力先を確認してみると、以下のようになっています。
Workerのログ名称にTopologyのIDが付与され、どのTopologyのログかがすぐ分かるようになっていますね!
かつ、これなら複数のTopologyのログが1ファイル中に混在することもありません。

> ls -l
-rw-r--r-- 1 root root 10555253 11月  1 07:27 2013 ExclamationTopology-2-1383257328worker-6701.log
-rw-r--r-- 1 root root 21257836 11月  1 07:27 2013 ExclamationTopology-2-1383257328worker-6702.log
-rw-r--r-- 1 root root 21258738 11月  1 07:27 2013 ExclamationTopology-2-1383257328worker-6703.log
-rw-r--r-- 1 root root        0 10月 31 08:31 2013 access.log
-rw-r--r-- 1 root root     7046 10月 31 08:32 2013 drpc.log
-rw-r--r-- 1 root root     3897 10月 31 08:36 2013 logviewer.log
-rw-r--r-- 1 root root        0 10月 31 08:31 2013 metrics.log
-rw-r--r-- 1 root root    17817 11月  1 07:08 2013 nimbus.log
-rw-r--r-- 1 root root    35318 11月  1 07:08 2013 supervisor.log
-rw-r--r-- 1 root root     2022 11月  1 07:24 2013 ui.log

3.何が嬉しいの?

「StormTopologyのログ出力先はわかりにくい」の裏返しになりますが、以下の2点です。
実際にStormを使ったことがある方にはありがたさがわかっていただけると思います。

  1. どのTopologyがどのログに出力されているか」がファイル名からすぐわかるようになった
  2. 1ファイルには1Topologyのログが出力されるようになった

と、今回はこういった地味な機能でしたが、次回以降もStorm0.9.0で追加された機能について紹介していこうと思います。
それでは。
 
 

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

RealtimeConf 2013に参加してきました!

こんにちは。id:KenichiroMurata です。

少し間が空いてしまいましたが、先週私はRealtimeConfに参加してきました。今回から3回の予定でそのレポートをお届けします。

f:id:acro-engineer:20131018175504p:plain

RealtimeConf 2013とは?

RealtimeConf 2013はオレゴン州ポートランドにて10/18〜19の2日間で開催されました。ちなみに、ポートランドってどこだっけ?と同僚たちに聞かれたので、地図を貼っておきます。


アメリカ合衆国 オレゴン ポートランド

ポートランドはアメリカ北西部、サンフランシスコより北で、シアトルより少し南、バンクーバーにも近い所に位置しています。Delta航空の直行便が成田から出ていて、フライト時間は約9時間というところです。

RealtimeConfは&yetが総合プロデュースしているカンファレンスで今回の2013が3回目となります。その名と通りrealtime appに関する技術が中心で、中でもfront-end周りが主軸、node.js関連の内容が多いカンファレンスです。

キーワードで見るRealtimeConf 2013

今回のカンファレンスで出てきたキーワードを独断で分類すると以下のようになります。

  1. Indie Data / Indie Web
  2. WebRTC / HTTP 2.0
  3. WebScoket / XMPP FTW / Client-side cryptography
  4. Architecutre & Example
  5. User Experience / Leadership

もう既にほとんどのセッションがvimeoで公開されているので、興味がある方は、ぜひ以下からご覧下さい。

RealtimeConf Videos & Extras

ここからは、このキーワード別に各セッションを紹介したいと思います。

1. Indie Data / Indie Web

「Indie」とは、文字通り「独立」という意味ですが、このカンファレンスの最初の基調講演と2日目の最後のセッションがこのIndie Data、Indie Webというトピックに関する内容でした。

Aral Balkan 「Digital Feudalism and How to avoid it」

f:id:acro-engineer:20131029125150j:plain:w400

hardware, service, connectivityという3要素がそろい、いつでもどこでもスマートデバイスやPCを使って、サービスを利用できるという便利な世の中になっているが、例えば、GoogleFacebookAppleといった一企業が(closedに)その情報を管理している。

これまでは個人の情報(考えや行動)は体という物理的な境界によってその中身が守られてきたが、ネットワークに常につながったスマートデバイスを利用する世界では、その物理的な境界がスマートデバイスとそこで利用するサービスにまで範囲が広がり、便利になった一方で個人の情報に対する脅威が広がっている。

これからの方向性として、この脅威を理解し、一企業によるclosedな独占ではなく、自分のデータやサービスを利用者自身で守ることができる仕組みづくりが必要で、さらにそれをオープンに進めて行く必要がある。そこで今、コードネーム Prometheusというプロジェクトを開始していて注目して欲しい。

サマリするとこのような内容です。プレゼンの中では

google monetize your data

なんて言葉が出てきます。私も今回Google Mapsを使って、ポートランドの空港とかホテルとか調べましたが、自分でも忘れていた過去に検索した場所の履歴が出てきてちょっとビックしたばかりでした。Google検索した履歴、YouTubeの閲覧履歴などなど、全てがGoolge Accountに紐づいている訳ですから、便利な反面、怖さもあるというのは誰もが体感していることでしょう。サービスの利用者としては、どのように自分自身で守るのか、サービスの開発者としては、ユーザに提供するユーザ体験として、このIndie Dataというものをどう考えるかが、今後重要になると感じたセッションでした。

Indie Dataについてより詳しく知りたい方は、Aral Balkanさんのサイトにまとまった情報があるのでリンクを載せて置きます。

Amber Case 「The open web and the opportunity of now」

f:id:acro-engineer:20131029125405j:plain:w400

Indie WebはIndie Dataの中でもWeb上の自分のコンテンツに範囲を狭めた概念と理解しました。このコンテンツ(データ)をいかにして守るか、そのために今から個人でできることは何なのかを紹介するセッションです。

geocitiesに自分のコンテンツを載せていたら、サービスが終了してしまったら、そのデータがなくなってしまう。サービス提供側の都合に左右されずに、自身で自分のコンテンツを守れるようにしよう。そのためにはまずは個人でドメインを持ち、そこで情報発信することから始めようというのがサマリです(もうちょっと時系列に沿ったWebによる情報発信の変遷についての説明などありましたが)。

Indie Data / Indie Webのまとめ

このIndie Data, Indie Webという考え方は、これからのサービス開発をする上で避けては通れない重要なテーマだと感じました。「Coolな技術でこんなすごいサービスを作ったぜ!」という所に目が行きがちですが、そのサービスを利用者に提供する上で、これからのユーザ体験に必要不可欠な要素として、Indie Dataというものを考えないといけませんね。

このAral Balkanさんのセッションですが、プレゼンが非常に洗練されていて、素晴らしかったです。プレゼンが終わると会場はスタンディングオベーションで、カンファレンスを盛り上げていました。

ちなみに、宿泊したホテルが同じだったので、お会いしたときにプレゼンの感想を伝えて、一緒に写真を撮らせてもらいました。

f:id:acro-engineer:20131029131047j:plain:w400

2. WebRTC / HTTP 2.0

realtime appを開発する上での技術的なセッションでは、特にWebRTC、HTTP 2.0の注目度が高かったです。

WebRTCについては3つのセッションがありました。

Feross Aboukhadijeh 「WebRTC Data Black Magic」

f:id:acro-engineer:20131030023821j:plain:w400

Henrik Joreteg + Lance Stout 「Simple, silo-free WebRTC」

f:id:acro-engineer:20131030023837j:plain:w400

Eric Rescorla 「Simple, silo-free WebRTC」

f:id:acro-engineer:20131030023849j:plain:w400

WebRTCというとボイスチャット(audio)やビデオチャット(video)に使われる新しい技術という印象でしたが、それだけではありませんでした。注目すべきポイントは以下です。

  1. プラグインなしにブラウザの機能として利用可能
  2. ブラウザ間でのpeer-to-peer通信が可能
  3. DataChanelを使うとテキスト、バイナリ情報を通信可能
  4. 自動で128-bit AES encryptionしてくれる
  5. NAT越えの問題も解決してくれる

これはもう要注目です。

「WebRTC Data Black Magic」では、WebRTCの利用事例として、ブラウザをWebServerにしてしまうという+PeerServerや、サイトに訪れたユーザのブラウザをCDNとして活用できるようにするというPeerCDNが紹介されており、非常に興味深かったです。

「Simple, silo-free WebRTC」では、WebRTCをよりシンプルに使いやすくするためのライブラリであるSimpleWebRTCと、簡単にビデオチャット、スクリーン共有ができてしまうSimpleWebRTCを利用したデモサービスTalkyを紹介していました。

WebRTC (Web Real-Time Communication)という名が示す通り、これからのrealtime appに必要不可欠な技術になるという盛り上がりを感じることができました。

Ilya Grigorik 「Making HTTP realtime with HTTP 2.0 - dropping the hacks, reclaiming performance!」

f:id:acro-engineer:20131030033021j:plain:w600

HTTP2.0は最近よく目にするようになってきました。詳しい話を聞くのはこのセッションが初めてだったのですが、WebRTCに続き、HTTP2.0も要注目です。ポイントは以下です。

  1. HTTP1.1のセマンティックスを保つ
  2. マルチコネクションが不要(従来の1コネクション最大6並列までという制限がない)
  3. 1コネクションでStream通信(多重通信、優先度付き)
  4. 通信はバイナリフレーム
  5. HTTPヘッダの圧縮

従来のHTTP1.1のセマンティックスを壊さずに、効率の良い通信を行うことでパフォーマンスを向上させることができます。クライアント(ブラウザ)、サーバどちらもドラフト段階の実装が出てきているので、今から試しておきたい所です。

ちょっと長くなってきましたので、続きは次回!

f:id:acro-engineer:20131018174304p:plain:w200

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、Hadoop、Storm、NoSQL、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ